Skip to content

Table

Overview

The Table component is used to display row and column data, and is one of the core components for data display. Element Plus's Table component is powerful, supporting sorting, filtering, pagination, selection, expansion, and many other features.

Learning Objectives

  • Master the basic usage of the Table component
  • Understand table column configuration and data binding
  • Learn to use table sorting and filtering functions
  • Master table pagination and selection features
  • Understand advanced table features

Basic Usage

Basic Table

The simplest table display, just configure data and el-table-column:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

<script setup>
import { ref } from 'vue'

const tableData = ref([
  {
    date: '2016-05-03',
    name: 'Tom',
    address: '1518 Jinshajiang Road, Putuo District, Shanghai'
  },
  {
    date: '2016-05-02',
    name: 'Tom',
    address: '1518 Jinshajiang Road, Putuo District, Shanghai'
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: '1518 Jinshajiang Road, Putuo District, Shanghai'
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: '1518 Jinshajiang Road, Putuo District, Shanghai'
  }
])
</script>

Striped Table

Use the stripe attribute to create a striped table:

vue
<template>
  <el-table :data="tableData" stripe style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

Bordered Table

Use the border attribute to add borders to the table:

vue
<template>
  <el-table :data="tableData" border style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

Table Sorting

Default Sorting

Set the sortable attribute in a column to enable sorting based on that column:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" sortable width="180" />
    <el-table-column prop="name" label="Name" sortable width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

Custom Sorting

Use sort-method or sort-by to customize sorting rules:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column 
      prop="date" 
      label="Date" 
      sortable 
      :sort-method="sortByDate"
      width="180" 
    />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="score" label="Score" sortable width="180" />
  </el-table>
</template>

<script setup>
const sortByDate = (a, b) => {
  return new Date(a.date) - new Date(b.date)
}
</script>

Table Filtering

Column Filtering

Set the filters and filter-method attributes in a column to enable filtering:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column 
      prop="name" 
      label="Name" 
      width="180"
      :filters="[
        { text: 'Tom', value: 'Tom' },
        { text: 'Jerry', value: 'Jerry' },
        { text: 'Lucy', value: 'Lucy' }
      ]"
      :filter-method="filterHandler"
    />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

<script setup>
const filterHandler = (value, row, column) => {
  const property = column['property']
  return row[property] === value
}
</script>

Table Selection

Multiple Selection

Add a column with type="selection" to implement multiple selection:

vue
<template>
  <el-table 
    ref="multipleTableRef"
    :data="tableData" 
    style="width: 100%"
    @selection-change="handleSelectionChange"
  >
    <el-table-column type="selection" width="55" />
    <el-table-column prop="date" label="Date" width="120" />
    <el-table-column prop="name" label="Name" width="120" />
    <el-table-column prop="address" label="Address" />
  </el-table>
  
  <div style="margin-top: 20px">
    <el-button @click="toggleSelection()">Toggle Selection</el-button>
    <el-button @click="toggleSelection(tableData.slice(0, 2))">
      Toggle First Two Rows
    </el-button>
    <el-button @click="clearSelection">Clear Selection</el-button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const multipleTableRef = ref()
const multipleSelection = ref([])

const toggleSelection = (rows) => {
  if (rows) {
    rows.forEach((row) => {
      multipleTableRef.value!.toggleRowSelection(row, undefined)
    })
  } else {
    multipleTableRef.value!.clearSelection()
  }
}

const clearSelection = () => {
  multipleTableRef.value!.clearSelection()
}

const handleSelectionChange = (val) => {
  multipleSelection.value = val
}
</script>

Table Expandable Rows

Set type="expand" and use slots to enable expandable rows:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column type="expand">
      <template #default="props">
        <div style="padding: 20px">
          <p>Name: {{ props.row.name }}</p>
          <p>Address: {{ props.row.address }}</p>
          <p>Details: {{ props.row.detail }}</p>
        </div>
      </template>
    </el-table-column>
    <el-table-column label="Date" prop="date" />
    <el-table-column label="Name" prop="name" />
    <el-table-column label="Address" prop="address" />
  </el-table>
</template>

Fixed Columns and Headers

Fixed Header

Set the height attribute to fix the table header:

vue
<template>
  <el-table :data="tableData" height="250" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

Fixed Columns

Set the fixed attribute to fix columns:

vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column fixed prop="date" label="Date" width="150" />
    <el-table-column prop="name" label="Name" width="120" />
    <el-table-column prop="province" label="Province" width="120" />
    <el-table-column prop="city" label="City" width="120" />
    <el-table-column prop="address" label="Address" width="300" />
    <el-table-column prop="zip" label="Zip" width="120" />
    <el-table-column fixed="right" label="Operations" width="100">
      <template #default>
        <el-button link type="primary" size="small">View</el-button>
        <el-button link type="primary" size="small">Edit</el-button>
      </template>
    </el-table-column>
  </el-table>
</template>

Practical Application Examples

User Management Table

A complete user management table example:

vue
<template>
  <div class="user-table-container">
    <!-- Search Bar -->
    <div class="search-bar">
      <el-input
        v-model="searchText"
        placeholder="Search username or email"
        style="width: 300px; margin-right: 10px"
        clearable
        @input="handleSearch"
      />
      <el-button type="primary" @click="handleAdd">Add User</el-button>
    </div>

    <!-- Table -->
    <el-table
      v-loading="loading"
      :data="filteredTableData"
      style="width: 100%"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column prop="id" label="ID" width="80" sortable />
      <el-table-column prop="name" label="Username" width="120" sortable />
      <el-table-column prop="email" label="Email" width="200" />
      <el-table-column prop="role" label="Role" width="100">
        <template #default="{ row }">
          <el-tag :type="getRoleType(row.role)">{{ row.role }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="status" label="Status" width="100">
        <template #default="{ row }">
          <el-switch
            v-model="row.status"
            @change="handleStatusChange(row)"
          />
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="Created Time" width="180" sortable />
      <el-table-column fixed="right" label="Operations" width="200">
        <template #default="{ row }">
          <el-button link type="primary" size="small" @click="handleEdit(row)">
            Edit
          </el-button>
          <el-button link type="danger" size="small" @click="handleDelete(row)">
            Delete
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- Pagination -->
    <div class="pagination-container">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

// Reactive data
const loading = ref(false)
const searchText = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const tableData = ref([])
const multipleSelection = ref([])

// Computed properties
const filteredTableData = computed(() => {
  if (!searchText.value) return tableData.value
  return tableData.value.filter(item => 
    item.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
    item.email.toLowerCase().includes(searchText.value.toLowerCase())
  )
})

// Methods
const getRoleType = (role) => {
  const typeMap = {
    'admin': 'danger',
    'user': 'primary',
    'guest': 'info'
  }
  return typeMap[role] || 'info'
}

const handleSearch = () => {
  currentPage.value = 1
  fetchData()
}

const handleSelectionChange = (selection) => {
  multipleSelection.value = selection
}

const handleStatusChange = async (row) => {
  try {
    loading.value = true
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    ElMessage.success('Status updated successfully')
  } catch (error) {
    ElMessage.error('Status update failed')
    row.status = !row.status // Rollback status
  } finally {
    loading.value = false
  }
}

const handleAdd = () => {
  // Navigate to add page or open dialog
  console.log('Add user')
}

const handleEdit = (row) => {
  // Navigate to edit page or open dialog
  console.log('Edit user', row)
}

const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm(
      `Are you sure you want to delete user "${row.name}"?`,
      'Confirm Delete',
      {
        confirmButtonText: 'Confirm',
        cancelButtonText: 'Cancel',
        type: 'warning'
      }
    )
    
    loading.value = true
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    
    // Remove from table data
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      tableData.value.splice(index, 1)
      total.value--
    }
    
    ElMessage.success('Deleted successfully')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('Delete failed')
    }
  } finally {
    loading.value = false
  }
}

const handleSizeChange = (size) => {
  pageSize.value = size
  currentPage.value = 1
  fetchData()
}

const handleCurrentChange = (page) => {
  currentPage.value = page
  fetchData()
}

const fetchData = async () => {
  try {
    loading.value = true
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    
    // Mock data
    const mockData = Array.from({ length: 100 }, (_, index) => ({
      id: index + 1,
      name: `User${index + 1}`,
      email: `user${index + 1}@example.com`,
      role: ['admin', 'user', 'guest'][index % 3],
      status: Math.random() > 0.3,
      createTime: new Date(Date.now() - Math.random() * 10000000000).toLocaleString()
    }))
    
    total.value = mockData.length
    const start = (currentPage.value - 1) * pageSize.value
    const end = start + pageSize.value
    tableData.value = mockData.slice(start, end)
  } catch (error) {
    ElMessage.error('Failed to load data')
  } finally {
    loading.value = false
  }
}

// Lifecycle
onMounted(() => {
  fetchData()
})
</script>

<style scoped>
.user-table-container {
  padding: 20px;
}

.search-bar {
  margin-bottom: 20px;
  display: flex;
  align-items: center;
}

.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: center;
}
</style>

API Documentation

Table Attributes

ParameterDescriptionTypeAccepted ValuesDefault
dataTable dataarray
heightTable heightstring/number
max-heightTable maximum heightstring/number
stripeWhether to apply stripe stylebooleanfalse
borderWhether to show vertical bordersbooleanfalse
sizeTable sizestringlarge/default/small
fitWhether column width automatically fits its containerbooleantrue
show-headerWhether to show table headerbooleantrue
highlight-current-rowWhether to highlight current rowbooleanfalse
current-row-keyKey of the current rowstring/number
row-class-nameFunction that returns custom class names for rowsfunction({row, rowIndex})/string
row-styleFunction that returns custom styles for rowsfunction({row, rowIndex})/object
cell-class-nameFunction that returns custom class names for cellsfunction({row, column, rowIndex, columnIndex})/string
cell-styleFunction that returns custom styles for cellsfunction({row, column, rowIndex, columnIndex})/object
header-row-class-nameFunction that returns custom class names for header rowsfunction({row, rowIndex})/string
header-row-styleFunction that returns custom styles for header rowsfunction({row, rowIndex})/object
header-cell-class-nameFunction that returns custom class names for header cellsfunction({row, column, rowIndex, columnIndex})/string
header-cell-styleFunction that returns custom styles for header cellsfunction({row, column, rowIndex, columnIndex})/object
row-keyKey of row datafunction(row)/string
empty-textText to show when data is emptystringNo Data
default-expand-allWhether to expand all rows by defaultbooleanfalse
expand-row-keysSet expanded rows by keysarray
default-sortDefault sort column and orderobjectorder: ascending, descending
tooltip-effectTooltip effect propertystringdark/lightdark
show-summaryWhether to show summary rowbooleanfalse
sum-textText for the first column of summary rowstringSum
summary-methodCustom summary methodfunction({columns, data})
span-methodMethod for merging cellsfunction({row, column, rowIndex, columnIndex})
select-on-indeterminateWhether to select all rows when clicking the header checkbox in indeterminate statebooleantrue
indentHorizontal indentation of tree datanumber16
lazyWhether to lazy load tree databoolean
loadMethod for loading child nodesfunction(row, treeNode, resolve)
tree-propsConfiguration for rendering nested dataobject
table-layoutSets the algorithm used for table layoutstringfixed/autofixed
scrollbar-always-onWhether to always show scrollbarsbooleanfalse
flexibleEnsures minimum size on the main axisbooleanfalse

Table Events

Event NameDescriptionParameters
selectTriggered when user clicks the checkbox in a rowselection, row
select-allTriggered when user clicks the checkbox in table headerselection
selection-changeTriggered when selection changesselection
cell-mouse-enterTriggered when hovering into a cellrow, column, cell, event
cell-mouse-leaveTriggered when hovering out of a cellrow, column, cell, event
cell-clickTriggered when clicking a cellrow, column, cell, event
cell-dblclickTriggered when double-clicking a cellrow, column, cell, event
row-clickTriggered when clicking a rowrow, column, event
row-contextmenuTriggered when right-clicking a rowrow, column, event
row-dblclickTriggered when double-clicking a rowrow, column, event
header-clickTriggered when clicking a column headercolumn, event
header-contextmenuTriggered when right-clicking a column headercolumn, event
sort-changeTriggered when table's sorting changes
filter-changeTriggered when table's filters changefilters
current-changeTriggered when current row changescurrentRow, oldCurrentRow
header-dragendTriggered after a column is dragged to a new widthnewWidth, oldWidth, column, event
expand-changeTriggered when a row is expanded or collapsedrow, expandedRows

Table Methods

MethodDescriptionParameters
clearSelectionClears selection
toggleRowSelectionToggles selection state of a rowrow, selected
toggleAllSelectionToggles selection state of all rows
setCurrentRowSets a row as the current rowrow
clearSortClears sorting
clearFilterClears filterscolumnKeys
doLayoutRefreshes the layout of the table
sortSorts the table manuallyprop, order
scrollToScrolls to a specific position(options: ScrollToOptions | number, yCoord?: number)
setScrollTopSets vertical scroll positiontop
setScrollLeftSets horizontal scroll positionleft

Table-column Attributes

ParameterDescriptionTypeAccepted ValuesDefault
typeType of the columnstringselection/index/expand
indexCustomize indices when using type=indexnumber/function(index)
labelColumn labelstring
column-keyColumn's keystring
propField name corresponding to column's datastring
widthColumn widthstring/number
min-widthColumn minimum widthstring/number
fixedWhether column is fixed (true means fixed left)string/booleantrue/left/right
render-headerFunction for rendering column headerfunction(h, { column, $index })
sortableWhether column can be sortedboolean/stringtrue/false/'custom'false
sort-methodSorting methodfunction(a, b)
sort-bySpecifies which property to sort bystring/array/function(row, index)
sort-ordersThe order of the sorting strategies used when sorting the dataarrayElements in array need to be one of: ascending, descending, null['ascending', 'descending', null]
resizableWhether column width can be resizedbooleantrue
formatterFunction that formats cell contentfunction(row, column, cellValue, index)
show-overflow-tooltipWhether to show tooltip when content overflowsboolean/objectundefined
alignAlignmentstringleft/center/rightleft
header-alignAlignment of the table headerstringleft/center/rightSame as align
class-nameClass name of cells in the columnstring
label-class-nameClass name of the label of this columnstring
filtersFilter optionsarray[{ text, value }]
filter-placementPlacement of the filter dropdownstringSame as Tooltip's placement
filter-multipleWhether data filtering supports multiple optionsbooleantrue
filter-methodData filtering methodfunction(value, row, column)
filtered-valueFilter value for selected dataarray

Practice Exercises

Exercise 1: Product Management Table

Create a product management table with the following features:

  • Product list display (name, price, stock, status)
  • Sort by price
  • Filter by status
  • Batch operations (list/delist)
  • Pagination
vue
<template>
  <div class="product-management">
    <div class="toolbar">
      <el-button type="primary" @click="handleBatchOnline" :disabled="!hasSelection">
        Batch List
      </el-button>
      <el-button @click="handleBatchOffline" :disabled="!hasSelection">
        Batch Delist
      </el-button>
      <el-button type="success" @click="handleAdd">Add Product</el-button>
    </div>

    <el-table
      ref="tableRef"
      :data="tableData"
      @selection-change="handleSelectionChange"
      style="width: 100%"
    >
      <el-table-column type="selection" width="55" />
      <el-table-column prop="name" label="Product Name" width="200" />
      <el-table-column 
        prop="price" 
        label="Price" 
        width="120" 
        sortable
        :formatter="priceFormatter"
      />
      <el-table-column prop="stock" label="Stock" width="100" sortable />
      <el-table-column 
        prop="status" 
        label="Status" 
        width="120"
        :filters="[
          { text: 'Listed', value: 'online' },
          { text: 'Delisted', value: 'offline' },
          { text: 'Out of Stock', value: 'out_of_stock' }
        ]"
        :filter-method="filterStatus"
      >
        <template #default="{ row }">
          <el-tag :type="getStatusType(row.status)">
            {{ getStatusText(row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="Created Time" width="180" />
      <el-table-column fixed="right" label="Operations" width="200">
        <template #default="{ row }">
          <el-button link type="primary" size="small" @click="handleEdit(row)">
            Edit
          </el-button>
          <el-button 
            link 
            :type="row.status === 'online' ? 'warning' : 'success'" 
            size="small" 
            @click="handleToggleStatus(row)"
          >
            {{ row.status === 'online' ? 'Delist' : 'List' }}
          </el-button>
          <el-button link type="danger" size="small" @click="handleDelete(row)">
            Delete
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

const tableRef = ref()
const tableData = ref([])
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const multipleSelection = ref([])

const hasSelection = computed(() => multipleSelection.value.length > 0)

const priceFormatter = (row, column, cellValue) => {
  return `$${cellValue.toFixed(2)}`
}

const getStatusType = (status) => {
  const typeMap = {
    'online': 'success',
    'offline': 'info',
    'out_of_stock': 'warning'
  }
  return typeMap[status] || 'info'
}

const getStatusText = (status) => {
  const textMap = {
    'online': 'Listed',
    'offline': 'Delisted',
    'out_of_stock': 'Out of Stock'
  }
  return textMap[status] || 'Unknown'
}

const filterStatus = (value, row) => {
  return row.status === value
}

const handleSelectionChange = (selection) => {
  multipleSelection.value = selection
}

const handleBatchOnline = async () => {
  try {
    await ElMessageBox.confirm(
      `Are you sure you want to list ${multipleSelection.value.length} selected products?`,
      'Batch List',
      { type: 'warning' }
    )
    
    // Simulate batch listing operation
    multipleSelection.value.forEach(item => {
      item.status = 'online'
    })
    
    ElMessage.success('Batch listing successful')
    tableRef.value.clearSelection()
  } catch (error) {
    // User canceled operation
  }
}

const handleBatchOffline = async () => {
  try {
    await ElMessageBox.confirm(
      `Are you sure you want to delist ${multipleSelection.value.length} selected products?`,
      'Batch Delist',
      { type: 'warning' }
    )
    
    // Simulate batch delisting operation
    multipleSelection.value.forEach(item => {
      item.status = 'offline'
    })
    
    ElMessage.success('Batch delisting successful')
    tableRef.value.clearSelection()
  } catch (error) {
    // User canceled operation
  }
}

const handleAdd = () => {
  console.log('Add product')
}

const handleEdit = (row) => {
  console.log('Edit product', row)
}

const handleToggleStatus = (row) => {
  const newStatus = row.status === 'online' ? 'offline' : 'online'
  const action = newStatus === 'online' ? 'list' : 'delist'
  
  ElMessageBox.confirm(
    `Are you sure you want to ${action} product "${row.name}"?`,
    `Confirm ${action}`,
    { type: 'warning' }
  ).then(() => {
    row.status = newStatus
    ElMessage.success(`${action} successful`)
  }).catch(() => {
    // User canceled operation
  })
}

const handleDelete = (row) => {
  ElMessageBox.confirm(
    `Are you sure you want to delete product "${row.name}"?`,
    'Confirm Delete',
    { type: 'warning' }
  ).then(() => {
    const index = tableData.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      tableData.value.splice(index, 1)
      total.value--
    }
    ElMessage.success('Delete successful')
  }).catch(() => {
    // User canceled operation
  })
}

const handleSizeChange = (size) => {
  pageSize.value = size
  currentPage.value = 1
  fetchData()
}

const handleCurrentChange = (page) => {
  currentPage.value = page
  fetchData()
}

const fetchData = () => {
  // Simulate data loading
  loading.value = true
  
  setTimeout(() => {
    // Generate mock product data
    const mockProducts = Array.from({ length: 100 }, (_, index) => ({
      id: index + 1,
      name: `Product ${index + 1}`,
      price: Math.round(Math.random() * 1000) / 10,
      stock: Math.floor(Math.random() * 1000),
      status: ['online', 'offline', 'out_of_stock'][Math.floor(Math.random() * 3)],
      createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toLocaleString()
    }))
    
    total.value = mockProducts.length
    const start = (currentPage.value - 1) * pageSize.value
    const end = start + pageSize.value
    tableData.value = mockProducts.slice(start, end)
    
    loading.value = false
  }, 500)
}

// Initialize data
onMounted(() => {
  fetchData()
})
</script>

<style scoped>
.product-management {
  padding: 20px;
}

.toolbar {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
}

.pagination {
  margin-top: 20px;
  display: flex;
  justify-content: center;
}
</style>

Exercise 2: Data Analysis Table

Create a data analysis table with the following features:

  • Summary row
  • Cell merging
  • Custom cell styles
  • Conditional formatting
vue
<template>
  <div class="data-analysis">
    <h3>Sales Data Analysis</h3>
    
    <el-table
      :data="salesData"
      :span-method="objectSpanMethod"
      :cell-style="cellStyle"
      border
      show-summary
      :summary-method="getSummaries"
      style="width: 100%"
    >
      <el-table-column prop="region" label="Region" width="120" />
      <el-table-column prop="product" label="Product" width="180" />
      <el-table-column prop="quarter" label="Quarter" width="100" />
      <el-table-column prop="sales" label="Sales" width="120" sortable>
        <template #default="{ row }">
          {{ formatCurrency(row.sales) }}
        </template>
      </el-table-column>
      <el-table-column prop="cost" label="Cost" width="120" sortable>
        <template #default="{ row }">
          {{ formatCurrency(row.cost) }}
        </template>
      </el-table-column>
      <el-table-column prop="profit" label="Profit" width="120" sortable>
        <template #default="{ row }">
          <span :class="getProfitClass(row.profit)">
            {{ formatCurrency(row.profit) }}
          </span>
        </template>
      </el-table-column>
      <el-table-column prop="growth" label="Growth" width="120">
        <template #default="{ row }">
          <div class="growth-indicator">
            <span :class="getGrowthClass(row.growth)">
              {{ formatPercentage(row.growth) }}
            </span>
            <el-icon v-if="row.growth > 0"><CaretTop /></el-icon>
            <el-icon v-else-if="row.growth < 0"><CaretBottom /></el-icon>
          </div>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { CaretTop, CaretBottom } from '@element-plus/icons-vue'

// Sample data
const salesData = ref([
  {
    region: 'North',
    product: 'Laptop',
    quarter: 'Q1',
    sales: 125000,
    cost: 87500,
    profit: 37500,
    growth: 0.12
  },
  {
    region: 'North',
    product: 'Laptop',
    quarter: 'Q2',
    sales: 140000,
    cost: 95000,
    profit: 45000,
    growth: 0.2
  },
  {
    region: 'North',
    product: 'Smartphone',
    quarter: 'Q1',
    sales: 85000,
    cost: 51000,
    profit: 34000,
    growth: 0.05
  },
  {
    region: 'North',
    product: 'Smartphone',
    quarter: 'Q2',
    sales: 95000,
    cost: 57000,
    profit: 38000,
    growth: 0.12
  },
  {
    region: 'South',
    product: 'Laptop',
    quarter: 'Q1',
    sales: 115000,
    cost: 86250,
    profit: 28750,
    growth: -0.05
  },
  {
    region: 'South',
    product: 'Laptop',
    quarter: 'Q2',
    sales: 125000,
    cost: 93750,
    profit: 31250,
    growth: 0.09
  },
  {
    region: 'South',
    product: 'Smartphone',
    quarter: 'Q1',
    sales: 75000,
    cost: 48750,
    profit: 26250,
    growth: -0.08
  },
  {
    region: 'South',
    product: 'Smartphone',
    quarter: 'Q2',
    sales: 82000,
    cost: 51250,
    profit: 30750,
    growth: 0.17
  }
])

// Cell merging
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  if (columnIndex === 0) {
    if (rowIndex % 4 === 0) {
      return {
        rowspan: 4,
        colspan: 1
      }
    } else {
      return {
        rowspan: 0,
        colspan: 0
      }
    }
  }
  
  if (columnIndex === 1) {
    if (rowIndex % 2 === 0) {
      return {
        rowspan: 2,
        colspan: 1
      }
    } else {
      return {
        rowspan: 0,
        colspan: 0
      }
    }
  }
}

// Cell styling
const cellStyle = ({ row, column, rowIndex, columnIndex }) => {
  if (column.property === 'profit') {
    if (row.profit > 40000) {
      return { backgroundColor: '#f0f9eb', color: '#67c23a' }
    } else if (row.profit < 30000) {
      return { backgroundColor: '#fef0f0', color: '#f56c6c' }
    }
  }
  
  if (column.property === 'growth') {
    if (row.growth > 0.15) {
      return { backgroundColor: '#f0f9eb' }
    } else if (row.growth < 0) {
      return { backgroundColor: '#fef0f0' }
    }
  }
}

// Summary method
const getSummaries = (param) => {
  const { columns, data } = param
  const sums = []
  
  columns.forEach((column, index) => {
    if (index === 0) {
      sums[index] = 'Total'
      return
    }
    
    if (index === 1 || index === 2) {
      sums[index] = ''
      return
    }
    
    const values = data.map(item => Number(item[column.property]))
    
    if (!values.every(value => isNaN(value))) {
      const sum = values.reduce((prev, curr) => {
        const value = Number(curr)
        if (!isNaN(value)) {
          return prev + value
        } else {
          return prev
        }
      }, 0)
      
      if (column.property === 'growth') {
        // Calculate average growth
        const avg = sum / values.length
        sums[index] = `${(avg * 100).toFixed(2)}%`
      } else {
        sums[index] = formatCurrency(sum)
      }
    } else {
      sums[index] = 'N/A'
    }
  })
  
  return sums
}

// Formatting functions
const formatCurrency = (value) => {
  return `$${value.toLocaleString()}`
}

const formatPercentage = (value) => {
  return `${(value * 100).toFixed(2)}%`
}

// Style classes
const getProfitClass = (profit) => {
  if (profit > 40000) return 'profit-high'
  if (profit < 30000) return 'profit-low'
  return ''
}

const getGrowthClass = (growth) => {
  if (growth > 0) return 'growth-positive'
  if (growth < 0) return 'growth-negative'
  return ''
}
</script>

<style scoped>
.data-analysis {
  padding: 20px;
}

.data-analysis h3 {
  margin-bottom: 20px;
}

.profit-high {
  color: #67c23a;
  font-weight: bold;
}

.profit-low {
  color: #f56c6c;
}

.growth-indicator {
  display: flex;
  align-items: center;
  gap: 5px;
}

.growth-positive {
  color: #67c23a;
}

.growth-negative {
  color: #f56c6c;
}
</style>

Summary

The Table component is one of the most important data display components in Element Plus. Through this document, you should be able to:

  1. Master the basic usage of the Table component
  2. Understand how to configure table columns and bind data
  3. Learn to use table sorting and filtering functions
  4. Master table pagination and selection features
  5. Understand advanced table features like fixed columns, expandable rows, and cell merging

In actual development, you can combine these features according to business requirements to create powerful data display interfaces.

References

Element Plus Study Guide