Skip to content

扩展行

当表格内容较为复杂时,可以使用扩展行功能来显示更多详细信息。

基础扩展行

vue
<template>
  <VantTable 
    :headers="headers" 
    :data="data" 
    expandable
    @expand-change="handleExpandChange"
  >
    <template #expanded="{ row }">
      <div style="padding: 16px; background-color: #fafafa;">
        <h4>详细信息</h4>
        <p><strong>描述:</strong>{{ row.description }}</p>
        <p><strong>创建时间:</strong>{{ row.createTime }}</p>
        <p><strong>更新时间:</strong>{{ row.updateTime }}</p>
      </div>
    </template>
  </VantTable>
</template>

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

const headers = ref([
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: '项目名称', width: 180 },
  { key: 'leader', label: '负责人', width: 120 },
  { key: 'status', label: '状态', width: 100 }
])

const data = ref([
  { 
    id: 1, 
    name: '电商平台重构', 
    leader: '张三', 
    status: '进行中',
    description: '对现有电商平台进行全面重构,采用最新的技术栈,提升系统性能和用户体验。',
    createTime: '2024-01-01 09:00:00',
    updateTime: '2024-02-15 14:30:00'
  },
  { 
    id: 2, 
    name: '移动端优化', 
    leader: '李四', 
    status: '待开始',
    description: '优化移动端页面性能,减少页面加载时间,提升移动端用户体验。',
    createTime: '2024-01-15 10:00:00',
    updateTime: '2024-01-20 16:45:00'
  },
  { 
    id: 3, 
    name: '数据分析系统', 
    leader: '王五', 
    status: '已完成',
    description: '建设数据分析系统,支持实时数据监控和多维度数据分析。',
    createTime: '2023-10-01 08:30:00',
    updateTime: '2024-01-31 17:00:00'
  }
])

const handleExpandChange = (event) => {
  console.log('扩展状态变化:', event)
}
</script>

复杂扩展内容

vue
<template>
  <VantTable 
    :headers="headers" 
    :data="data" 
    expandable
  >
    <template #expanded="{ row }">
      <div style="padding: 20px; background-color: #f8f9fa;">
        <!-- 员工详细信息 -->
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
          <div>
            <h4 style="margin-bottom: 12px; color: #333;">基本信息</h4>
            <div style="display: grid; gap: 8px;">
              <div><strong>邮箱:</strong>{{ row.email }}</div>
              <div><strong>电话:</strong>{{ row.phone }}</div>
              <div><strong>地址:</strong>{{ row.address }}</div>
              <div><strong>入职日期:</strong>{{ row.joinDate }}</div>
            </div>
          </div>
          
          <div>
            <h4 style="margin-bottom: 12px; color: #333;">工作信息</h4>
            <div style="display: grid; gap: 8px;">
              <div><strong>直属上级:</strong>{{ row.manager }}</div>
              <div><strong>工作年限:</strong>{{ row.experience }} 年</div>
              <div><strong>绩效评级:</strong>{{ row.performance }}</div>
              <div><strong>上次考评:</strong>{{ row.lastReview }}</div>
            </div>
          </div>
        </div>
        
        <!-- 技能标签 -->
        <div>
          <h4 style="margin-bottom: 12px; color: #333;">专业技能</h4>
          <div style="display: flex; gap: 8px; flex-wrap: wrap;">
            <span 
              v-for="skill in row.skills" 
              :key="skill"
              style="
                padding: 4px 12px; 
                background-color: #e6f7ff; 
                color: #1890ff; 
                border-radius: 16px; 
                font-size: 12px;
                border: 1px solid #91d5ff;
              "
            >
              {{ skill }}
            </span>
          </div>
        </div>
        
        <!-- 项目经历 -->
        <div style="margin-top: 20px;">
          <h4 style="margin-bottom: 12px; color: #333;">项目经历</h4>
          <div style="display: grid; gap: 12px;">
            <div 
              v-for="project in row.projects" 
              :key="project.name"
              style="
                padding: 12px; 
                background-color: white; 
                border-radius: 8px; 
                border-left: 4px solid #52c41a;
              "
            >
              <div style="font-weight: bold; margin-bottom: 4px;">{{ project.name }}</div>
              <div style="font-size: 12px; color: #666; margin-bottom: 8px;">
                {{ project.period }} | 担任:{{ project.role }}
              </div>
              <div style="font-size: 14px;">{{ project.description }}</div>
            </div>
          </div>
        </div>
      </div>
    </template>
  </VantTable>
</template>

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

const headers = ref([
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: '姓名', width: 120 },
  { key: 'department', label: '部门', width: 120 },
  { key: 'position', label: '职位', width: 150 },
  { key: 'salary', label: '薪资', width: 120, type: 'currency' }
])

const data = ref([
  { 
    id: 1, 
    name: '张三', 
    department: '技术部', 
    position: '前端工程师',
    salary: 25000,
    email: 'zhangsan@example.com',
    phone: '138-8888-8888',
    address: '北京市朝阳区xxx街道xxx号',
    joinDate: '2022-03-15',
    manager: '技术总监-陈总',
    experience: 5,
    performance: 'A',
    lastReview: '2024-01-15',
    skills: ['Vue.js', 'React', 'TypeScript', 'Node.js', 'Docker'],
    projects: [
      {
        name: '电商平台前端重构',
        period: '2023.06 - 2024.01',
        role: '技术负责人',
        description: '负责电商平台前端架构设计和核心功能开发,提升了页面性能30%。'
      },
      {
        name: '移动端H5开发',
        period: '2023.01 - 2023.05',
        role: '主要开发者',
        description: '开发移动端商城H5页面,实现了完整的购物流程和支付功能。'
      }
    ]
  },
  { 
    id: 2, 
    name: '李四', 
    department: '产品部', 
    position: '产品经理',
    salary: 28000,
    email: 'lisi@example.com',
    phone: '139-9999-9999',
    address: '上海市浦东新区xxx路xxx号',
    joinDate: '2021-08-20',
    manager: '产品总监-王总',
    experience: 8,
    performance: 'A+',
    lastReview: '2024-02-01',
    skills: ['产品设计', '需求分析', '用户研究', 'Axure', 'Figma'],
    projects: [
      {
        name: '用户增长策略制定',
        period: '2023.09 - 至今',
        role: '产品负责人',
        description: '制定并执行用户增长策略,通过数据分析和A/B测试提升用户留存率。'
      },
      {
        name: '新用户引导流程优化',
        period: '2023.03 - 2023.08',
        role: '产品经理',
        description: '重新设计新用户引导流程,将注册转化率提升了25%。'
      }
    ]
  }
])
</script>

表格与扩展行组合

vue
<template>
  <VantTable 
    :headers="headers" 
    :data="data" 
    expandable
    selectable
    v-model:selected-keys="selectedKeys"
  >
    <template #expanded="{ row, rowIndex }">
      <div style="padding: 16px; background: linear-gradient(90deg, #f0f9ff 0%, #f8fafc 100%);">
        <!-- 订单详情表格 -->
        <h4 style="margin-bottom: 16px; color: #1890ff;">订单详情 #{{ row.orderNo }}</h4>
        
        <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 20px;">
          <div>
            <div style="font-size: 12px; color: #666; margin-bottom: 4px;">订单金额</div>
            <div style="font-size: 18px; font-weight: bold; color: #52c41a;">¥{{ row.amount.toLocaleString() }}</div>
          </div>
          <div>
            <div style="font-size: 12px; color: #666; margin-bottom: 4px;">商品数量</div>
            <div style="font-size: 18px; font-weight: bold; color: #1890ff;">{{ row.itemCount }} 件</div>
          </div>
          <div>
            <div style="font-size: 12px; color: #666; margin-bottom: 4px;">配送地址</div>
            <div style="font-size: 14px;">{{ row.address }}</div>
          </div>
        </div>
        
        <!-- 商品列表 -->
        <table style="width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden;">
          <thead>
            <tr style="background: #fafafa;">
              <th style="padding: 12px; text-align: left; border-bottom: 1px solid #e8e8e8;">商品</th>
              <th style="padding: 12px; text-align: center; border-bottom: 1px solid #e8e8e8;">数量</th>
              <th style="padding: 12px; text-align: right; border-bottom: 1px solid #e8e8e8;">单价</th>
              <th style="padding: 12px; text-align: right; border-bottom: 1px solid #e8e8e8;">小计</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="item in row.items" :key="item.id">
              <td style="padding: 12px; border-bottom: 1px solid #f0f0f0;">
                <div style="display: flex; align-items: center; gap: 12px;">
                  <img :src="item.image" style="width: 40px; height: 40px; border-radius: 4px;" />
                  <div>
                    <div style="font-weight: bold; margin-bottom: 4px;">{{ item.name }}</div>
                    <div style="font-size: 12px; color: #666;">{{ item.specs }}</div>
                  </div>
                </div>
              </td>
              <td style="padding: 12px; text-align: center; border-bottom: 1px solid #f0f0f0;">
                {{ item.quantity }}
              </td>
              <td style="padding: 12px; text-align: right; border-bottom: 1px solid #f0f0f0;">
                ¥{{ item.price }}
              </td>
              <td style="padding: 12px; text-align: right; border-bottom: 1px solid #f0f0f0; font-weight: bold;">
                ¥{{ (item.price * item.quantity).toLocaleString() }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </template>
  </VantTable>
  
  <div style="margin-top: 16px;">
    <p>已选择订单: {{ selectedKeys.join(', ') }}</p>
  </div>
</template>

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

const selectedKeys = ref([])

const headers = ref([
  { key: 'orderNo', label: '订单号', width: 120 },
  { key: 'customer', label: '客户', width: 120 },
  { key: 'amount', label: '订单金额', width: 120, type: 'currency' },
  { key: 'status', label: '状态', width: 100 },
  { key: 'createTime', label: '下单时间', width: 150 }
])

const data = ref([
  { 
    id: 1,
    orderNo: 'ORD001', 
    customer: '张三', 
    amount: 1299,
    status: '已发货',
    createTime: '2024-01-15 14:30',
    itemCount: 2,
    address: '北京市朝阳区xxx街道xxx号',
    items: [
      {
        id: 1,
        name: 'iPhone 15 手机壳',
        specs: '透明硅胶 | 6.1英寸',
        quantity: 1,
        price: 99,
        image: 'https://images.unsplash.com/photo-1574944985070-8f3ebc6b79d2?w=100'
      },
      {
        id: 2,
        name: 'AirPods Pro 2',
        specs: '主动降噪 | 白色',
        quantity: 1,
        price: 1200,
        image: 'https://images.unsplash.com/photo-1588423771073-b8903fbb85b5?w=100'
      }
    ]
  },
  { 
    id: 2,
    orderNo: 'ORD002', 
    customer: '李四', 
    amount: 2599,
    status: '待发货',
    createTime: '2024-01-16 09:15',
    itemCount: 1,
    address: '上海市浦东新区xxx路xxx号',
    items: [
      {
        id: 3,
        name: 'MacBook Air M2',
        specs: '13英寸 | 256GB | 午夜色',
        quantity: 1,
        price: 2599,
        image: 'https://images.unsplash.com/photo-1541807084-5c52b6b3adef?w=100'
      }
    ]
  }
])
</script>

扩展行状态控制

vue
<template>
  <div>
    <div style="margin-bottom: 16px;">
      <button @click="expandAll" style="margin-right: 8px;">全部展开</button>
      <button @click="collapseAll">全部收起</button>
    </div>
    
    <VantTable 
      ref="tableRef"
      :headers="headers" 
      :data="data" 
      expandable
      v-model:expanded-keys="expandedKeys"
    >
      <template #expanded="{ row }">
        <div style="padding: 16px; background-color: #f0f9ff;">
          <p><strong>备注:</strong>{{ row.remark }}</p>
          <p><strong>标签:</strong>{{ row.tags.join(', ') }}</p>
        </div>
      </template>
    </VantTable>
    
    <div style="margin-top: 16px;">
      <p>当前展开的行: {{ expandedKeys.join(', ') }}</p>
    </div>
  </div>
</template>

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

const tableRef = ref()
const expandedKeys = ref(['2']) // 默认展开第2行

const headers = ref([
  { key: 'id', label: 'ID', width: 80 },
  { key: 'title', label: '标题', width: 200 },
  { key: 'author', label: '作者', width: 120 },
  { key: 'category', label: '分类', width: 100 }
])

const data = ref([
  { 
    id: 1, 
    title: 'Vue.js 3.0 新特性详解', 
    author: '张三',
    category: '前端',
    remark: '这是一篇关于Vue.js 3.0新特性的详细介绍文章,包含Composition API、Teleport等重要特性。',
    tags: ['Vue.js', '前端框架', 'JavaScript']
  },
  { 
    id: 2, 
    title: 'TypeScript 实战指南', 
    author: '李四',
    category: '后端',
    remark: '从基础语法到高级应用,全面介绍TypeScript在实际项目中的应用。',
    tags: ['TypeScript', '类型系统', '编程语言']
  },
  { 
    id: 3, 
    title: 'React Hooks 深入浅出', 
    author: '王五',
    category: '前端',
    remark: 'React Hooks的原理分析和最佳实践,帮助开发者更好地理解和使用Hooks。',
    tags: ['React', 'Hooks', '状态管理']
  }
])

const expandAll = () => {
  expandedKeys.value = data.value.map(item => item.id.toString())
}

const collapseAll = () => {
  expandedKeys.value = []
}
</script>

扩展行最佳实践

1. 内容设计

  • 将详细信息放在扩展行中,保持主表格的简洁
  • 使用合理的信息层次结构
  • 考虑内容的可读性和视觉层次

2. 性能优化

  • 避免在扩展行中放置过于复杂的组件
  • 使用懒加载机制,只在展开时加载详细数据
  • 合理控制同时展开的行数

3. 用户体验

  • 提供清晰的展开/收起视觉反馈
  • 考虑添加展开/收起动画效果
  • 在移动端确保扩展内容的可用性

4. 数据管理

  • 合理设计数据结构,分离主要信息和详细信息
  • 考虑扩展行状态的持久化需求
  • 提供批量展开/收起功能