表格底部增加一行合计功能的实现

javascript 复制代码
<template>
  <div class="table-summary-demo">
    <!-- 页面标题 -->
    <div class="demo-header">
      <h1>📊 Element Plus 表格合计功能</h1>
      <p>基于 Vue3 + TypeScript,支持接口返回合计数据的表格实现</p>
    </div>

    <!-- 操作按钮 -->
    <div class="demo-actions">
      <el-button 
        type="primary" 
        :loading="loading" 
        @click="fetchTableData"
        icon="Refresh"
      >
        刷新数据
      </el-button>
      <el-button 
        :type="showSummary ? 'success' : 'info'"
        @click="toggleSummary"
        :icon="showSummary ? 'View' : 'Hide'"
      >
        {{ showSummary ? '隐藏合计' : '显示合计' }}
      </el-button>
    </div>

    <!-- 表格组件 -->
    <el-table
      v-loading="loading"
      :data="tableData"
      :show-summary="showSummary"
      :summary-method="getSummaries"
      stripe
      border
      style="width: 100%"
      class="summary-table"
    >
      <el-table-column
        prop="productName"
        label="产品名称"
        width="200"
        show-overflow-tooltip
      />
      <el-table-column
        prop="category"
        label="产品分类"
        width="150"
      />
      <el-table-column
        prop="price"
        label="单价 (元)"
        width="120"
        align="right"
      >
        <template #default="{ row }">
          <span class="price-text">¥{{ formatNumber(row.price) }}</span>
        </template>
      </el-table-column>
      <el-table-column
        prop="quantity"
        label="数量"
        width="100"
        align="right"
      />
      <el-table-column
        prop="amount"
        label="金额 (元)"
        width="150"
        align="right"
      >
        <template #default="{ row }">
          <span class="amount-text">¥{{ formatNumber(row.amount) }}</span>
        </template>
      </el-table-column>
      <el-table-column
        prop="discount"
        label="折扣金额 (元)"
        width="150"
        align="right"
      >
        <template #default="{ row }">
          <span class="discount-text">-¥{{ formatNumber(row.discount) }}</span>
        </template>
      </el-table-column>
      <el-table-column
        prop="finalAmount"
        label="实付金额 (元)"
        width="150"
        align="right"
      >
        <template #default="{ row }">
          <span class="final-amount-text">¥{{ formatNumber(row.finalAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column
        prop="status"
        label="状态"
        width="120"
        align="center"
      >
        <template #default="{ row }">
          <el-tag 
            :type="getStatusType(row.status)"
            size="small"
          >
            {{ getStatusText(row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column
        prop="createTime"
        label="创建时间"
        width="180"
      />
    </el-table>

    <!-- 合计数据信息 -->
    <div v-if="summaryData" class="summary-info">
      <h3>📈 合计数据详情</h3>
      <div class="summary-grid">
        <div class="summary-item">
          <div class="summary-label">总记录数</div>
          <div class="summary-value">{{ summaryData.totalRecords }} 条</div>
        </div>
        <div class="summary-item">
          <div class="summary-label">总数量</div>
          <div class="summary-value">{{ formatNumber(summaryData.totalQuantity) }}</div>
        </div>
        <div class="summary-item">
          <div class="summary-label">总金额</div>
          <div class="summary-value price-highlight">¥{{ formatNumber(summaryData.totalAmount) }}</div>
        </div>
        <div class="summary-item">
          <div class="summary-label">总折扣</div>
          <div class="summary-value discount-highlight">-¥{{ formatNumber(summaryData.totalDiscount) }}</div>
        </div>
        <div class="summary-item">
          <div class="summary-label">实付总额</div>
          <div class="summary-value final-highlight">¥{{ formatNumber(summaryData.totalFinalAmount) }}</div>
        </div>
      </div>
    </div>

    <!-- API 响应示例 -->
    <div class="api-example">
      <h3>🔧 接口返回数据结构示例</h3>
      <el-collapse v-model="activeCollapse">
        <el-collapse-item title="查看 API 响应数据" name="api-response">
          <pre><code>{{ JSON.stringify(mockApiResponse, null, 2) }}</code></pre>
        </el-collapse-item>
      </el-collapse>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { TableColumnCtx } from 'element-plus'

// 类型定义
interface TableRowData {
  id: number
  productName: string
  category: string
  price: number
  quantity: number
  amount: number
  discount: number
  finalAmount: number
  status: 'active' | 'inactive' | 'pending'
  createTime: string
}

interface SummaryData {
  totalRecords: number
  totalQuantity: number
  totalAmount: number
  totalDiscount: number
  totalFinalAmount: number
}

interface ApiResponse {
  data: TableRowData[]
  summary: SummaryData
  success: boolean
  message: string
}

// 响应式数据
const loading = ref<boolean>(false)
const showSummary = ref<boolean>(true)
const activeCollapse = ref<string>('')
const tableData = ref<TableRowData[]>([])
const summaryData = ref<SummaryData | null>(null)

// 模拟 API 响应数据
const mockApiResponse = reactive<ApiResponse>({
  data: [
    {
      id: 1,
      productName: 'iPhone 15 Pro Max',
      category: '智能手机',
      price: 9999.00,
      quantity: 2,
      amount: 19998.00,
      discount: 1000.00,
      finalAmount: 18998.00,
      status: 'active',
      createTime: '2024-01-15 10:30:00'
    },
    {
      id: 2,
      productName: 'MacBook Pro 16"',
      category: '笔记本电脑',
      price: 25999.00,
      quantity: 1,
      amount: 25999.00,
      discount: 2000.00,
      finalAmount: 23999.00,
      status: 'active',
      createTime: '2024-01-16 14:20:00'
    },
    {
      id: 3,
      productName: 'iPad Air',
      category: '平板电脑',
      price: 4999.00,
      quantity: 3,
      amount: 14997.00,
      discount: 500.00,
      finalAmount: 14497.00,
      status: 'pending',
      createTime: '2024-01-17 09:15:00'
    },
    {
      id: 4,
      productName: 'AirPods Pro',
      category: '音频设备',
      price: 1999.00,
      quantity: 5,
      amount: 9995.00,
      discount: 200.00,
      finalAmount: 9795.00,
      status: 'active',
      createTime: '2024-01-18 16:45:00'
    },
    {
      id: 5,
      productName: 'Apple Watch Ultra',
      category: '智能手表',
      price: 6299.00,
      quantity: 1,
      amount: 6299.00,
      discount: 300.00,
      finalAmount: 5999.00,
      status: 'inactive',
      createTime: '2024-01-19 11:00:00'
    }
  ],
  summary: {
    totalRecords: 5,
    totalQuantity: 12,
    totalAmount: 77288.00,
    totalDiscount: 4000.00,
    totalFinalAmount: 73288.00
  },
  success: true,
  message: '数据获取成功'
})

// 模拟接口请求
const fetchTableData = async (): Promise<void> => {
  loading.value = true
  
  try {
    // 模拟网络延迟
    await new Promise(resolve => setTimeout(resolve, 1000))
    
    // 模拟接口调用
    const response: ApiResponse = {
      ...mockApiResponse,
      // 可以在这里添加随机数据变化逻辑
      data: mockApiResponse.data.map(item => ({
        ...item,
        // 模拟数据轻微变化
        quantity: item.quantity + Math.floor(Math.random() * 2),
      }))
    }
    
    // 重新计算金额
    response.data.forEach(item => {
      item.amount = item.price * item.quantity
      item.finalAmount = item.amount - item.discount
    })
    
    // 重新计算合计
    response.summary = {
      totalRecords: response.data.length,
      totalQuantity: response.data.reduce((sum, item) => sum + item.quantity, 0),
      totalAmount: response.data.reduce((sum, item) => sum + item.amount, 0),
      totalDiscount: response.data.reduce((sum, item) => sum + item.discount, 0),
      totalFinalAmount: response.data.reduce((sum, item) => sum + item.finalAmount, 0)
    }
    
    // 更新数据
    tableData.value = response.data
    summaryData.value = response.summary
    
  } catch (error) {
    console.error('获取数据失败:', error)
  } finally {
    loading.value = false
  }
}

// 自定义合计方法 - 核心功能
const getSummaries = (param: {
  columns: TableColumnCtx<TableRowData>[]
  data: TableRowData[]
}): string[] => {
  const { columns } = param
  const sums: string[] = []
  
  // 如果没有合计数据,返回空数组
  if (!summaryData.value) {
    return columns.map(() => '')
  }
  
  columns.forEach((column, index) => {
    if (index === 0) {
      // 第一列显示"合计"文字
      sums[index] = '合计'
    } else if (column.property === 'quantity') {
      // 数量列:显示总数量
      sums[index] = formatNumber(summaryData.value!.totalQuantity)
    } else if (column.property === 'amount') {
      // 金额列:显示总金额
      sums[index] = `¥${formatNumber(summaryData.value!.totalAmount)}`
    } else if (column.property === 'discount') {
      // 折扣列:显示总折扣
      sums[index] = `-¥${formatNumber(summaryData.value!.totalDiscount)}`
    } else if (column.property === 'finalAmount') {
      // 实付金额列:显示总实付金额
      sums[index] = `¥${formatNumber(summaryData.value!.totalFinalAmount)}`
    } else {
      // 其他列显示空值或横线
      sums[index] = '-'
    }
  })
  
  return sums
}

// 切换合计显示
const toggleSummary = (): void => {
  showSummary.value = !showSummary.value
}

// 格式化数字
const formatNumber = (num: number): string => {
  return num.toLocaleString('zh-CN', { 
    minimumFractionDigits: 2, 
    maximumFractionDigits: 2 
  })
}

// 获取状态类型
const getStatusType = (status: string): string => {
  const statusMap: Record<string, string> = {
    active: 'success',
    inactive: 'danger',
    pending: 'warning'
  }
  return statusMap[status] || 'info'
}

// 获取状态文本
const getStatusText = (status: string): string => {
  const statusMap: Record<string, string> = {
    active: '已激活',
    inactive: '已停用',
    pending: '待处理'
  }
  return statusMap[status] || '未知'
}

// 组件挂载时获取数据
onMounted(() => {
  fetchTableData()
})
</script>

<style scoped>
.table-summary-demo {
  padding: 20px;
  max-width: 1400px;
  margin: 0 auto;
}

.demo-header {
  text-align: center;
  margin-bottom: 30px;
  padding: 30px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 16px;
  color: white;
}

.demo-header h1 {
  font-size: 2rem;
  margin: 0 0 10px 0;
  font-weight: 700;
}

.demo-header p {
  font-size: 1.1rem;
  margin: 0;
  opacity: 0.9;
}

.demo-actions {
  margin-bottom: 20px;
  display: flex;
  gap: 12px;
}

/* 表格样式优化 */
.summary-table {
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

/* 数值样式 */
.price-text {
  color: #409EFF;
  font-weight: 600;
}

.amount-text {
  color: #67C23A;
  font-weight: 600;
}

.discount-text {
  color: #F56C6C;
  font-weight: 600;
}

.final-amount-text {
  color: #E6A23C;
  font-weight: 700;
  font-size: 14px;
}

/* 合计信息样式 */
.summary-info {
  margin-top: 30px;
  padding: 25px;
  background: #f8fafc;
  border-radius: 12px;
  border: 2px solid #e2e8f0;
}

.summary-info h3 {
  margin: 0 0 20px 0;
  color: #374151;
  font-size: 1.3rem;
  font-weight: 700;
}

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

.summary-item {
  background: white;
  padding: 20px;
  border-radius: 10px;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: transform 0.2s ease;
}

.summary-item:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.summary-label {
  font-size: 14px;
  color: #6b7280;
  margin-bottom: 8px;
  font-weight: 500;
}

.summary-value {
  font-size: 18px;
  font-weight: 700;
  color: #374151;
}

.price-highlight {
  color: #10b981;
}

.discount-highlight {
  color: #ef4444;
}

.final-highlight {
  color: #f59e0b;
  font-size: 20px;
}

/* API 示例样式 */
.api-example {
  margin-top: 30px;
  padding: 25px;
  background: white;
  border-radius: 12px;
  border: 2px solid #e5e7eb;
}

.api-example h3 {
  margin: 0 0 20px 0;
  color: #374151;
  font-size: 1.3rem;
  font-weight: 700;
}

.api-example pre {
  background: #1f2937;
  color: #e5e7eb;
  padding: 20px;
  border-radius: 8px;
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.5;
}

.api-example code {
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .table-summary-demo {
    padding: 10px;
  }
  
  .demo-header {
    padding: 20px;
  }
  
  .demo-header h1 {
    font-size: 1.5rem;
  }
  
  .summary-grid {
    grid-template-columns: 1fr;
  }
  
  .demo-actions {
    flex-direction: column;
  }
}

/* Element Plus 表格合计行样式自定义 */
:deep(.el-table__footer) {
  font-weight: 700;
}

:deep(.el-table__footer-wrapper tbody td) {
  background-color: #f8fafc !important;
  border-top: 2px solid #667eea !important;
  font-size: 14px;
  color: #374151;
}

:deep(.el-table__footer-wrapper tbody td:first-child) {
  color: #667eea;
  font-weight: 800;
  font-size: 15px;
}
</style>
相关推荐
猫林老师6 小时前
HarmonyOS 5分布式数据管理初探:实现跨设备数据同步
分布式·harmonyos
爱笑的眼睛119 小时前
HarmonyOS 应用开发深度解析:ArkUI 声明式 UI 与现代化状态管理最佳实践
华为·harmonyos
被开发耽误的大厨11 小时前
鸿蒙项目篇-22-项目功能结构说明-写子页面和导航页面
android·华为·harmonyos·鸿蒙
祥睿夫子1 天前
鸿蒙 ArkTS 类继承与多态实战:从语法到员工工资计算全指南
harmonyos
shenshizhong1 天前
看懂鸿蒙系统源码 比较重要的知识点
android·harmonyos
特立独行的猫a1 天前
强大的鸿蒙HarmonyOS网络调试工具PageSpy 介绍及使用
网络·华为·harmonyos
ChinaDragon1 天前
HarmonyOS:在NDK工程中使用预构建库
harmonyos
程序员潘Sir1 天前
鸿蒙应用开发从入门到实战(三):第一个鸿蒙应用
harmonyos·鸿蒙
安卓开发者1 天前
鸿蒙NEXT中SQLite数据库全面实战指南
数据库·sqlite·harmonyos