# SelectTable 组件使用文档

# 组件概述

SelectTable 是一个基于 Vue2 + Element UI 的高级表格选择器组件,支持本地数据筛选和远程 API 搜索两种模式,提供丰富的交互体验和性能优化。

# 特性

  • 🔍 智能搜索:支持本地数据筛选和远程 API 搜索
  • 📊 表格展示:以表格形式展示选项,支持多列显示
  • 单选/多选:支持单选和多选模式
  • 🎯 高亮搜索:搜索关键词高亮显示
  • 📄 分页支持:支持前端和后端分页
  • 💾 智能缓存:API 模式下支持请求缓存,提升性能
  • 🔄 数据回显:支持通过回显接口快速加载选中数据
  • 🎨 自定义配置:支持自定义列配置、空状态文案等
  • 性能优化:防抖搜索、虚拟滚动等性能优化

# 基础用法

# 本地数据模式-单选

选中值: 选中数据:
<template>
  <div>
    <jp-select-table
      v-model="selectedValue"
      :data="localData"
      :columns="columns"
      :prop="prop"
      use-local-data
      show-pagination
      @change="handleChange"
    />
    <span>选中值: {{ selectedValue }}</span>
    <span>选中数据: {{ selectedData }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedValue: '',
      selectedData: null,
      prop: {
        id: 'id',
        label: 'name',
        value: 'code'
      },
      localData: this.generateMockData(),
      columns: [
        { field: 'name', title: '姓名', minWidth: 120 },
        { field: 'code', title: '编码', minWidth: 100 },
        { field: 'department', title: '部门', minWidth: 100 },
      ]
    }
  },
  methods: {
    generateMockData() {
      const data = []
      const names = ['张三', '李四', '王五', '赵六', '陈七', '刘八', '杨九', '黄十']
      const departments = ['技术部', '销售部', '财务部', '人事部', '市场部', '运营部']
      
      for (let i = 1; i <= 200; i++) {
        const nameIndex = (i - 1) % names.length
        const deptIndex = (i - 1) % departments.length
        data.push({
          id: i,
          name: `${names[nameIndex]}${Math.floor((i - 1) / names.length) + 1}`,
          code: `USER${String(i).padStart(3, '0')}`,
          department: departments[deptIndex],
          age: 20 + (i % 40),
          phone: `138${String(i).padStart(8, '0')}`,
          email: `user${i}@example.com`
        })
      }
      return data
    },
    handleChange(value, data) {
      console.log('选中值:', value)
      console.log('选中数据:', data)
      this.selectedData = data
    }
  }
}
</script>
显示代码

# 本地数据模式-多选

选中值: 选中数据:
<template>
  <div>
    <jp-select-table
      v-model="selectedValue"
      :data="localData"
      :columns="columns"
      :prop="prop"
      :multiple="true"
      use-local-data
      show-pagination
      @change="handleChange"
    />
    <span>选中值: {{ selectedValue }}</span>
    <span>选中数据: {{ selectedData }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedValue: '',
      selectedData: null,
      prop: {
        id: 'id',
        label: 'name',
        value: 'code'
      },
      localData: this.generateMockData(),
      columns: [
        { field: 'name', title: '姓名', minWidth: 120 },
        { field: 'code', title: '编码', minWidth: 100 },
        { field: 'department', title: '部门', minWidth: 100 }
      ]
    }
  },
  methods: {
    generateMockData() {
      const data = []
      const names = ['张三', '李四', '王五', '赵六', '陈七', '刘八', '杨九', '黄十']
      const departments = ['技术部', '销售部', '财务部', '人事部', '市场部', '运营部']

      for (let i = 1; i <= 200; i++) {
        const nameIndex = (i - 1) % names.length
        const deptIndex = (i - 1) % departments.length
        data.push({
          id: i,
          name: `${names[nameIndex]}${Math.floor((i - 1) / names.length) + 1}`,
          code: `USER${String(i).padStart(3, '0')}`,
          department: departments[deptIndex],
          age: 20 + (i % 40),
          phone: `138${String(i).padStart(8, '0')}`,
          email: `user${i}@example.com`
        })
      }
      return data
    },
    handleChange(value, data) {
      console.log('选中值:', value)
      console.log('选中数据:', data)
      this.selectedData = data
    }
  }
}
</script>
显示代码

# API模式(带缓存)

选中值: 选中数据:
回显数据
多选
回显数据
<template>
  <div>
    <jp-select-table v-model="selectedValue" :request-api="searchCustomers" :columns="columns" :prop="prop" show-pagination @change="handleChange" />
    <span>选中值: {{ selectedValue }}</span>
    <span>选中数据: {{ selectedData }}</span>
    <div style="margin-top: 20px">回显数据</div>
    <jp-select-table v-model="selectedValue2" :request-api="searchCustomers" :columns="columns" :prop="prop" show-pagination />

    <div style="margin-top: 20px">多选</div>
    <jp-select-table v-model="selectedValue3" :request-api="searchCustomers" :columns="columns" :prop="prop" show-pagination multiple />
    <div style="margin-top: 20px">回显数据</div>
    <jp-select-table v-model="selectedValue4" :request-api="searchCustomers" :columns="columns" :prop="prop" show-pagination multiple />
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedValue: '',
      selectedValue2: 'USER001',
      selectedValue3: [],
      selectedValue4: ['USER001'],
      selectedData: null,
      prop: {
        id: 'id',
        label: 'name',
        value: 'code'
      },
      columns: [
        { field: 'name', title: '姓名', minWidth: 120 },
        { field: 'code', title: '编码', minWidth: 100 },
        { field: 'department', title: '部门', minWidth: 100 }
      ]
    }
  },
  methods: {
    async searchCustomers(data) {
      const { currentPage = 1, pageSize = 10, value = '', ...param } = data

      // 使用 Promise 模拟 API 请求
      return new Promise((resolve) => {
        // 模拟网络延迟
        setTimeout(() => {
          const allData = this.generateMockData()

          // 根据搜索关键字过滤数据
          let filteredData = allData
          if (value) {
            filteredData = allData.filter((item) => item.name.includes(value) || item.code.includes(value) || item.department.includes(value))
          }

          // 计算分页
          const total = filteredData.length
          const startIndex = (currentPage - 1) * pageSize
          const endIndex = startIndex + pageSize
          const pageData = filteredData.slice(startIndex, endIndex)

          // 返回模拟的响应数据
          resolve({
            data: pageData,
            total: total
          })
        }, 500) // 模拟500ms的网络延迟
      })
    },

    generateMockData() {
      const data = []
      const names = ['张三', '李四', '王五', '赵六', '陈七', '刘八', '杨九', '黄十']
      const departments = ['技术部', '销售部', '财务部', '人事部', '市场部', '运营部']

      for (let i = 1; i <= 200; i++) {
        const nameIndex = (i - 1) % names.length
        const deptIndex = (i - 1) % departments.length
        data.push({
          id: i,
          name: `${names[nameIndex]}${Math.floor((i - 1) / names.length) + 1}`,
          code: `USER${String(i).padStart(3, '0')}`,
          department: departments[deptIndex],
          age: 20 + (i % 40),
          phone: `138${String(i).padStart(8, '0')}`,
          email: `user${i}@example.com`
        })
      }
      return data
    },
    handleChange(value, data) {
      console.log('选中值:', value)
      console.log('选中数据:', data)
      this.selectedData = data
    }
  }
}
</script>
显示代码

# Attributes

参数 说明 类型 可选值 默认值
value / v-model 绑定值 string / number / array
data 静态数据源(本地模式) array []
columns 表格列配置 array []
search-props 搜索字段数组 array []
table-max-height 表格最大高度 string / number 300
page-size 分页大小 number 100
highlight-search 是否高亮搜索词 boolean true
multiple 是否多选 boolean false
prop 字段映射配置 object {id: 'id', label: '', value: ''}
collapse-tags 多选时是否折叠标签 boolean true
use-local-data 是否使用本地数据模式 boolean false
request-api 远程搜索API函数 function null
external-params 额外的请求参数 object {}
show-pagination 是否显示分页 boolean false
enable-virtual-scroll 是否启用虚拟滚动 boolean false
virtual-scroll-threshold 虚拟滚动触发阈值 number 500
cache-enabled 是否启用请求缓存 boolean true
cache-duration 缓存时长(毫秒) number 60000
empty-text 自定义空状态文案 object 见下方说明
error-handler 自定义错误处理函数 function null
echo-api 回显数据接口 function null
disabled 是否禁用 boolean false

# prop 对象说明

属性 说明 类型 默认值
id 唯一标识字段名 string 'id'
label 显示标签字段名 string ''
value 值字段名 string ''

# columns 数组说明

属性 说明 类型 默认值
field 字段名 string
title 列标题 string
minWidth 最小宽度 number 120

# empty-text 对象说明

属性 说明 类型 默认值
initial 初始状态文案 string '请输入关键词搜索'
noResult 无结果状态文案 string '未找到相关数据'
loadError 加载错误状态文案 string '加载失败,请重试'

# Events

事件名称 说明 回调参数
input 值改变时触发 (value)
change 选择改变时触发 (value, selectedData)
search 搜索时触发 (keyword)
selection-change 多选模式下选择改变时触发 (selectedRows)
current-change 单选模式下当前行改变时触发 (currentRow)
clear 清空选择时触发
load-success API模式下数据加载成功时触发 (data)
load-error API模式下数据加载失败时触发 (error)

# API 接口规范

# request-api 函数参数

{
  value: '',        // 搜索关键词
  currentPage: 1,   // 当前页码
  pageSize: 100,    // 每页数量
  ...externalParams // 额外参数
}

# request-api 返回格式

{
  data: [],    // 数据数组
  total: 0     // 总数量
}

# 使用注意事项

# 数据模式选择

  1. 本地数据模式 (use-local-data="true")

    • 适用于数据量较小(建议 < 1000 条)的场景
    • 支持前端分页和搜索
    • 性能更好,用户体验更流畅
  2. API 模式 (use-local-data="false")

    • 适用于大数据量场景
    • 支持后端分页和搜索
    • 需要配置 request-api 函数

# 性能优化建议

  1. 启用缓存:API 模式下建议启用缓存以减少重复请求
  2. 合理设置分页大小:根据数据量和网络情况调整 page-size
  3. 使用防抖搜索:组件内置防抖机制,无需额外处理
  4. 虚拟滚动:大数据量时可启用虚拟滚动提升性能

# 常见问题

# 1. 数据回显问题

确保 prop 配置正确,特别是 value 字段要与实际数据字段对应:

// 正确配置
prop: {
  id: 'id',
  label: 'name',    // 显示字段
  value: 'code'     // 值字段,要与 v-model 绑定的值对应
}

# 2. API 接口调用失败

检查 request-api 函数的返回格式是否符合规范:

// 正确的返回格式
{
  data: [...],  // 必须是数组
  total: 100    // 总数量(可选)
}

# 3. 搜索不生效

检查 search-props 配置是否正确:

// 指定搜索字段
:search-props="['name', 'code', 'department']"

# 最佳实践

  1. 字段映射配置:始终明确配置 prop 对象,避免使用默认值
  2. 错误处理:生产环境建议配置 error-handler 进行统一错误处理
  3. 空状态文案:根据业务场景自定义 empty-text 提升用户体验