# EditTable 可编辑表格组件

一个功能强大的可编辑表格组件，支持多种字段类型、分页、导入导出等企业级功能。

## ✨ 功能特性

- 🎯 **多种字段类型支持**：输入框、数字输入框、日期选择器、选择器、树形选择器、级联选择器等
- 📝 **表单验证**：支持必填、自定义验证规则
- 📄 **分页功能**：支持大数据量的分页显示，智能分页控制
- 📤 **导入导出**：支持模板下载、数据导入、数据导出
- 🔧 **高度可配置**：灵活的字段配置和表格配置
- 🌳 **树形数据支持**：支持树形选择器的各种模式
- 🔍 **远程搜索**：支持远程搜索和懒加载
- 👀 **只读模式**：支持表格的只读模式
- 📱 **响应式设计**：适配不同屏幕尺寸
- ⚡ **TypeScript 支持**：完整的类型定义

## 📦 安装

```bash
# 如果使用 npm
npm install element-plus

# 如果使用 pnpm
pnpm add element-plus
```

## 🚀 基础用法

```vue
<template>
  <EditTable
    v-model="tableData"
    :config="tableConfig"
    title="员工信息"
    addButtonText="添加员工"
    :empty-row="getEmptyRow"
    @add="handleAdd"
    @delete="handleDelete"
    @validate="handleValidate"
  />
</template>

<script setup>
import EditTable from '@/components/EditTable/index.vue'
import { createInputField, createNumberField, createSelectField } from '@/components/EditTable/helpers'

// 表格数据
const tableData = ref([])

// 表格配置
const tableConfig = {
  fields: [
    createInputField('name', '姓名'),
    createNumberField('age', '年龄'),
    createSelectField('gender', '性别', {
      options: [
        { label: '男', value: 'male' },
        { label: '女', value: 'female' }
      ]
    })
  ],
  showIndex: true,
  showAction: true,
  border: true
}

// 空行数据
const getEmptyRow = () => ({
  name: '',
  age: undefined,
  gender: ''
})

// 事件处理
const handleAdd = (params) => {
  console.log('添加行:', params)
}

const handleDelete = (params) => {
  console.log('删除行:', params)
}

const handleValidate = (valid) => {
  console.log('验证结果:', valid)
}
</script>
```

## 🎨 字段类型

### 1. 输入框 (INPUT)

```javascript
createInputField('username', '用户名', {
  placeholder: '请输入用户名',
  clearable: true,
  maxlength: 50,
  disabled: false,
  readonly: false
})
```

### 2. 数字输入框 (NUMBER)

```javascript
createNumberField('age', '年龄', {
  placeholder: '请输入年龄',
  precision: 0,          // 小数位数
  step: 1,              // 步长
  min: 0,               // 最小值
  max: 120              // 最大值
})
```

### 3. 日期选择器 (DATE)

```javascript
createDateField('birthday', '出生日期', {
  placeholder: '请选择出生日期',
  valueFormat: 'x',     // 时间戳格式
  format: 'YYYY-MM-DD'   // 显示格式
})
```

### 4. 日期范围选择器 (DATE_RANGE)

```javascript
createDateRangeField('dateRange', '日期范围', {
  startPlaceholder: '开始日期',
  endPlaceholder: '结束日期',
  valueFormat: 'x'
})
```

### 5. 普通选择器 (SELECT)

```javascript
createSelectField('gender', '性别', {
  placeholder: '请选择性别',
  options: [
    { label: '男', value: 'male' },
    { label: '女', value: 'female' }
  ],
  filterable: true,      // 可搜索
  clearable: true        // 可清空
})
```

### 6. 多选选择器 (MULTI_SELECT)

```javascript
createMultiSelectField('hobbies', '爱好', {
  placeholder: '请选择爱好',
  options: [
    { label: '读书', value: 'reading' },
    { label: '运动', value: 'sports' },
    { label: '音乐', value: 'music' }
  ],
  filterable: true
})
```

### 7. 树形选择器 (TREE_SELECT)

```javascript
createTreeSelectField('department', '部门', {
  placeholder: '请选择部门',
  data: departmentTree,    // 树形数据
  props: {
    label: 'name',
    children: 'children'
  },
  showCheckbox: false,    // 显示复选框
  checkStrictly: true     // 父子节点不互相关联
})
```

### 8. 级联选择器 (CASCADER)

```javascript
createCascaderField('region', '地区', {
  placeholder: '请选择地区',
  options: regionOptions,    // 级联数据
  cascaderProps: {
    expandTrigger: 'hover',  // 展开触发方式
    checkStrictly: false,   // 是否严格的父子不互相关联
    emitPath: true        // 是否返回由该节点所在的各级菜单的值组成的数组
  }
})
```

### 9. 弹窗选择器 (POPUP_SELECT) ⭐

弹窗选择器是最强大的选择器，支持复杂的查询、分页、自定义列等功能。

```javascript
import { createPopupSelectField } from '@/components/EditTable/helpers'

const bomSelectField = createPopupSelectField('bomNo', 'BOM编号', {
  // 弹窗配置
  title: '选择BOM',
  width: '900px',
  height: '600px',

  // 数据接口
  api: {
    getList: BomHeadApi.getBomHeadPage
  },

  // 默认查询参数（新增功能）
  queryParams: {
    status: 1,           // 只显示已审核的BOM
    fabsite: 'FAB1',     // 默认站点
    deptId: currentDeptId // 动态传递的参数
  },

  // 查询条件配置
  searchForm: {
    items: [
      {
        field: 'bomNo',
        label: '单据编号',
        type: 'input',
        placeholder: '请输入单据编号'
      },
      {
        field: 'status',
        label: '状态',
        type: 'select',
        placeholder: '请选择状态',
        options: [
          { label: '待审核', value: 0 },
          { label: '已审核', value: 1 },
          { label: '已发布', value: 2 }
        ]
      },
      {
        field: 'createDate',
        label: '创建日期',
        type: 'date-range',
        placeholder: '选择日期范围'
      }
    ]
  },

  // 表格列配置
  columns: [
    {
      prop: 'bomNo',
      label: '单据编号',
      width: 130,
      align: 'center'
    },
    {
      prop: 'status',
      label: '单据状态',
      width: 90,
      align: 'center',
      dictType: 'BOM_STATUS'
    },
    {
      prop: 'zbomType',
      label: '材料作业类型',
      minWidth: 110,
      align: 'center'
    }
  ],

  // 选择配置
  multiple: false,     // 是否多选
  selection: {
    labelKey: 'bomNo', // 显示字段
    valueKey: 'bomNo'  // 保存字段
  },

  // 分页配置
  pagination: {
    pageSize: 10,
    showSizeChanger: true,
    pageSizeOptions: [10, 20, 50]
  }
}, {
  // 字段列宽配置
  width: 150,
  align: 'center'
})
```

#### 弹窗选择器配置参数详解

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| title | string | 否 | '选择数据' | 弹窗标题 |
| width | string | 否 | '800px' | 弹窗宽度 |
| height | string | 否 | '600px' | 弹窗高度 |
| api.getList | Function | 是 | - | 获取列表数据的接口方法 |
| queryParams | Record<string, any> | 否 | {} | 调用接口时传递的额外查询参数 |
| searchForm.items | Array | 否 | [] | 查询表单项配置 |
| columns | Array | 是 | [] | 表格列配置 |
| multiple | boolean | 否 | false | 是否多选 |
| selection.labelKey | string | 否 | 'label' | 显示的字段名 |
| selection.valueKey | string | 否 | 'value' | 保存的字段名 |
| pagination | Object | 否 | { pageSize: 10 } | 分页配置 |

#### 查询表单项类型

| 类型 | 说明 | 示例 |
|------|------|------|
| input | 输入框 | { field: 'name', label: '姓名', type: 'input', placeholder: '请输入姓名' } |
| select | 下拉选择 | { field: 'status', label: '状态', type: 'select', options: [...] } |
| date-range | 日期范围 | { field: 'date', label: '日期', type: 'date-range' } |
| custom | 自定义组件 | { field: 'custom', label: '自定义', type: 'custom', component: MyComponent } |
})
```

### 2. 数字输入框 (NUMBER)

```javascript
createNumberField('price', '价格', {
  precision: 2,        // 小数位数
  step: 0.1,           // 步长
  min: 0,              // 最小值
  max: 99999           // 最大值
})
```

### 3. 日期选择器 (DATE)

```javascript
createDateField('birthday', '生日', {
  type: 'date',                    // 日期类型
  format: 'YYYY-MM-DD',             // 显示格式
  valueFormat: 'YYYY-MM-DD',        // 值格式
  placeholder: '选择生日'
})
```

### 4. 日期范围选择器 (DATE_RANGE)

```javascript
createDateRangeField('dateRange', '日期范围', {
  startPlaceholder: '开始日期',
  endPlaceholder: '结束日期',
  valueFormat: 'YYYY-MM-DD'
})
```

### 5. 普通选择器 (SELECT)

```javascript
createSelectField('status', '状态', {
  options: [
    { label: '启用', value: 1 },
    { label: '禁用', value: 0 }
  ],
  filterable: true,     // 可搜索
  clearable: true,      // 可清空
  multiple: false       // 是否多选
})
```

### 6. 多选选择器 (MULTI_SELECT)

```javascript
createMultiSelectField('tags', '标签', {
  options: [
    { label: 'JavaScript', value: 'js' },
    { label: 'TypeScript', value: 'ts' },
    { label: 'Vue', value: 'vue' }
  ],
  filterable: true
})
```

### 7. 远程搜索选择器 (REMOTE_SELECT)

```javascript
createRemoteSelectField('user', '用户', async (query) => {
  const response = await api.searchUsers(query)
  return response.data.map(item => ({
    label: item.name,
    value: item.id
  }))
})
```

### 8. 树形选择器 (TREE_SELECT)

```javascript
createTreeSelectField('department', '部门', {
  options: departmentTree,
  checkStrictly: true,      // 是否严格选择
  showCheckbox: false,      // 是否显示复选框
  filterable: true,         // 可搜索
  defaultExpandAll: false    // 是否默认展开所有节点
})
```

### 9. 级联选择器 (CASCADER) 🆕

```javascript
createCascaderField('region', '地区', {
  options: [
    {
      label: '中国',
      value: 'china',
      children: [
        {
          label: '北京',
          value: 'beijing',
          children: [
            { label: '朝阳区', value: 'chaoyang' },
            { label: '海淀区', value: 'haidian' }
          ]
        }
      ]
    }
  ],
  cascaderProps: {
    expandTrigger: 'hover',    // 展开方式: click/hover
    checkStrictly: false,      // 是否严格选择
    emitPath: true,           // 是否返回完整路径
    multiple: false            // 是否多选
  },
  separator: '/'              // 分隔符
})
```

## ✅ 验证规则

### 必填验证

```javascript
import { setRequired } from '@/components/EditTable/helpers'

const field = createInputField('name', '姓名')
setRequired(field, '姓名不能为空')
```

### 自定义验证

```javascript
const field = createInputField('email', '邮箱', {
  rules: [
    { required: true, message: '邮箱不能为空', trigger: 'blur' },
    { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' },
    { 
      validator: (rule, value, callback) => {
        if (value && value.length < 3) {
          callback(new Error('邮箱长度至少3位'))
        } else {
          callback()
        }
      }, 
      trigger: 'blur' 
    }
  ]
})
```

## 📄 分页功能

```javascript
const tableConfig = {
  fields: [...],
  
  // 分页配置
  pagination: {
    enabled: true,              // 启用分页
    pageSize: 10,               // 每页条数
    showSizeChanger: true,       // 显示每页条数选择
    pageSizeOptions: [10, 20, 50, 100], // 可选每页条数
    showQuickJumper: true,       // 显示快速跳转
    showTotal: true             // 显示总数
  }
}
```

## 📤 导入导出功能

### 配置导入导出

```javascript
const tableConfig = {
  fields: [...],
  
  // 导入导出配置
  importExport: {
    showImport: true,           // 显示导入按钮
    showExport: true,           // 显示导出按钮
    importButtonText: '导入数据', // 导入按钮文本
    exportButtonText: '导出数据', // 导出按钮文本
    templateFileName: '数据导入模板.xlsx', // 模板文件名
    acceptFileTypes: ['.xlsx', '.xls', '.csv'] // 支持的文件类型
  }
}
```

### 实现导入导出方法

```javascript
// 下载模板
const handleDownloadTemplate = async () => {
  // 创建模板数据
  const templateData = [
    { '姓名': '张三', '年龄': 25, '邮箱': 'zhangsan@example.com' }
  ]
  
  // 下载模板文件
  await downloadTemplateFile(templateData)
}

// 导入数据
const handleImport = async (params) => {
  const { file, fileName } = params
  
  try {
    // 解析文件
    const data = await parseFile(file)
    
    // 数据验证和转换
    const validData = data.map(item => ({
      ...getEmptyRow(),
      ...item,
      id: undefined
    }))
    
    message.success(`成功导入 ${validData.length} 条数据`)
    return validData
  } catch (error) {
    message.error('导入失败，请检查文件格式')
    throw error
  }
}

// 导出数据
const handleExport = async (params) => {
  const { data, fields } = params
  
  try {
    // 处理导出数据
    const exportData = data.map(item => ({
      ...item,
      // 根据需要处理数据格式
    }))
    
    // 导出文件
    await exportToFile(exportData, '导出数据.xlsx')
    
    message.success('导出成功')
  } catch (error) {
    message.error('导出失败')
    throw error
  }
}
```

### 传递方法给组件

```vue
<EditTable
  v-model="tableData"
  :config="tableConfig"
  :on-import="handleImport"
  :on-export="handleExport"
  :on-download-template="handleDownloadTemplate"
/>
```

## 👀 只读模式

```vue
<EditTable
  v-model="tableData"
  :config="tableConfig"
  :readonly="isReadonly"
/>

<script setup>
const isReadonly = ref(true) // 设置为只读模式
</script>
```

在只读模式下：
- 所有表单字段变为不可编辑
- 操作按钮（复制、删除）自动隐藏
- 导入按钮自动隐藏，导出按钮显示

## 🎛️ 高级配置

### 表格配置

```javascript
const tableConfig = {
  fields: [...],
  
  // 表格样式
  border: true,          // 边框
  stripe: true,          // 斑马纹
  size: 'default',       // 尺寸: large/default/small
  maxHeight: 400,        // 最大高度
  
  // 列配置
  showIndex: true,       // 显示序号列
  indexLabel: '序号',    // 序号列标题
  indexWidth: 60,        // 序号列宽度
  
  showAction: true,      // 显示操作列
  actionWidth: 100,      // 操作列宽度
  actionLabel: '操作',    // 操作列标题
  actionFixed: 'right',  // 操作列固定位置
  
  // 分页配置
  pagination: { ... },
  
  // 导入导出配置
  importExport: { ... }
}
```

### 字段高级配置

```javascript
const field = createSelectField('status', '状态', {
  // 基础配置
  prop: 'status',
  label: '状态',
  type: FieldType.SELECT,
  required: true,
  placeholder: '请选择状态',
  
  // 选择器配置
  options: statusOptions,
  multiple: false,
  filterable: true,
  clearable: true,
  remote: false,
  remoteMethod: undefined,
  
  // 树形选择器配置
  nodeKey: undefined,
  props: { label: 'label', children: 'children' },
  showCheckbox: false,
  checkStrictly: true,
  defaultExpandAll: false,
  lazy: false,
  load: undefined,
  
  // 级联选择器配置
  cascaderProps: {
    expandTrigger: 'click',
    checkStrictly: false,
    emitPath: true,
    multiple: false
  },
  separator: '/',
  
  // 显示配置
  width: 120,
  minWidth: 100,
  align: 'left',
  
  // 验证配置
  rules: [
    { required: true, message: '状态不能为空', trigger: 'change' }
  ],
  
  // 状态配置
  disabled: false,
  readonly: false,
  
  // 样式配置
  size: 'default',
  clearable: true
})
```

## 📋 事件

### 组件事件

```vue
<EditTable
  v-model="tableData"
  :config="tableConfig"
  @add="handleAdd"
  @delete="handleDelete"
  @copy="handleCopy"
  @validate="handleValidate"
/>
```

- `add`: 添加行时触发
- `delete`: 删除行时触发  
- `copy`: 复制行时触发
- `validate`: 表单验证时触发

### 事件参数

```javascript
const handleAdd = (params) => {
  console.log('添加的行索引:', params.index)
  console.log('添加的行数据:', params.row)
  console.log('操作类型:', params.type) // 'add' | 'delete' | 'copy'
}
```

## 🔧 方法调用

```vue
<template>
  <EditTable ref="editTableRef" v-model="tableData" :config="tableConfig" />
</template>

<script setup>
const editTableRef = ref()

// 验证整个表单
const validateForm = async () => {
  const valid = await editTableRef.value.validate()
  if (valid) {
    console.log('验证通过')
  } else {
    console.log('验证失败')
  }
}

// 验证单个字段
const validateField = async (index, prop) => {
  const valid = await editTableRef.value.validateField(index, prop)
  return valid
}

// 添加行
const addRow = () => {
  editTableRef.value.addRow()
}

// 删除行
const deleteRow = (index) => {
  editTableRef.value.deleteRow(index)
}

// 复制行
const copyRow = (index) => {
  editTableRef.value.copyRow(index)
}

// 导入数据
const importData = () => {
  editTableRef.value.importData()
}

// 导出数据
const exportData = () => {
  editTableRef.value.exportData()
}
</script>
```

## 🎨 自定义渲染

如果内置的字段类型不满足需求，可以使用自定义渲染：

```javascript
import { h } from 'vue'

const field = createInputField('custom', '自定义字段')
field.render = (row, column, index) => {
  return h('div', { class: 'custom-field' }, [
    h('span', '自定义内容: '),
    h('strong', row[column.prop] || '空'),
    h('button', {
      onClick: () => console.log('点击了自定义内容', row)
    }, '操作')
  ])
}
```

## 🎨 样式定制

组件提供了基础的样式类名，可以通过 CSS 进行定制：

```css
/* 表格容器 */
.edit-table {
  width: 100%;
}

/* 表格头部 */
.edit-table__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

/* 表格标题 */
.edit-table__title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: #000;
}

/* 操作按钮区域 */
.edit-table__actions {
  display: flex;
  gap: 8px;
}

/* 表单项 */
.edit-table__form-item :deep(.el-form-item) {
  margin-bottom: 0;
}

/* 分页器 */
.edit-table__pagination {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}

/* 导入弹窗 */
.import-dialog-content {
  padding: 0;
}

.import-section {
  margin-bottom: 24px;
}

.import-section h4 {
  margin: 0 0 12px 0;
  font-size: 14px;
  font-weight: 600;
  color: #303133;
}

.template-tip {
  margin-top: 8px;
  font-size: 12px;
  color: #909399;
}
```

## 🚀 完整示例

查看 `examples/CompleteExample.vue` 获取完整的使用示例，包含：

- 所有字段类型的演示
- 分页功能
- 导入导出功能
- 只读模式
- 事件处理
- 样式定制

## 💡 最佳实践

### 1. 数据管理

```javascript
// 使用 ref 管理表格数据
const tableData = ref([])

// 提供空行构造函数
const getEmptyRow = () => ({
  id: undefined,
  name: '',
  age: undefined,
  // ... 其他字段
})
```

### 2. 配置分离

```javascript
// 将配置抽离为独立的函数或文件
const createTableConfig = () => ({
  fields: [...],
  pagination: { ... },
  importExport: { ... }
})

const tableConfig = computed(() => createTableConfig())
```

### 3. 错误处理

```javascript
// 导入导出时的错误处理
const handleImport = async (params) => {
  try {
    const data = await parseFile(params.file)
    return data
  } catch (error) {
    message.error('导入失败：' + error.message)
    throw error
  }
}
```

### 4. 性能优化

```javascript
// 对于大数据量，使用分页
const tableConfig = {
  pagination: {
    enabled: true,
    pageSize: 20
  }
}

// 避免频繁的验证
const validateOnAdd = false // 新增时不立即验证
```

### 5. 弹窗选择器最佳实践 🆕

```javascript
// 1. 接口标准化
const productApi = {
  getBomList: (params: any) => request('/api/bom/list', params)
}

// 2. 参数类型化
interface QueryParams {
  productName?: string
  category?: string
  status?: number
  [key: string]: any
}

// 3. 返回数据标准化
interface ApiResponse {
  list: any[]
  total: number
}

// 4. 动态查询参数
const bomSelectField = createPopupSelectField('bomNo', 'BOM编号', {
  api: {
    getList: async (params: QueryParams) => {
      // 合并默认参数和查询参数
      const requestParams = {
        ...popupConfig.queryParams,
        ...params,
        // 动态参数，比如当前用户、部门等
        currentUserId: getCurrentUserId(),
        deptId: userStore.deptId,
        pageSize: params.pageSize || 10
      }
      
      const response = await productApi.getBomList(requestParams)
      return response.data
    }
  },
  
  queryParams: {
    status: 1,          // 默认只显示激活状态
    deptId: userStore.deptId  // 传递部门参数
  }
})

// 5. 数据预处理
const handlePopupSelect = (rowIndex: number, field: FieldConfig) => {
  // 打开弹窗前可以预处理数据
  const currentRow = dataList.value[rowIndex]
  
  if (currentRow.category) {
    // 如果已选择分类，可以设置默认查询参数
    field.popupSelect.queryParams = {
      ...field.popupSelect.queryParams,
      category: currentRow.category
    }
  }
  
  // 正常打开弹窗
  currentPopupField.value = field
  currentRowIndex.value = rowIndex
  showPopupDialog.value = true
}
```

### 6. 错误处理最佳实践

```javascript
const handleImport = async (params: ImportParams) => {
  try {
    const importedData = await parseImportFile(params.file)
    
    // 数据验证
    const validData = importedData.filter(item => {
      return item.name && item.price > 0
    })
    
    if (validData.length === 0) {
      ElMessage.warning('导入数据为空或格式不正确')
      return []
    }
    
    // 数据转换
    const transformedData = validData.map(item => ({
      ...getEmptyRow(),
      ...item,
      id: undefined,
      createTime: Date.now()
    }))
    
    ElMessage.success(`成功导入 ${transformedData.length} 条有效数据`)
    return transformedData
    
  } catch (error) {
    console.error('导入失败:', error)
    
    // 友好的错误提示
    let errorMessage = '导入失败'
    if (error.code === 'FILE_FORMAT_ERROR') {
      errorMessage = '文件格式不支持，请下载模板文件'
    } else if (error.code === 'DATA_VALIDATION_ERROR') {
      errorMessage = '数据格式不正确，请检查必填字段'
    }
    
    ElMessage.error(errorMessage)
    throw error
  }
}
```

## ⚠️ 注意事项

1. **数据绑定**：使用 `v-model`，组件内部会维护数据的响应性
2. **远程搜索**：需要实现 `remoteMethod` 方法并返回 Promise
3. **树形数据**：数据格式需要遵循 `SelectOption` 接口
4. **验证规则**：支持 Element Plus 的所有验证格式
5. **导入导出**：需要实现对应的方法，组件只提供界面和参数传递
6. **分页智能**：新增和删除操作会智能处理分页，避免频繁跳转
7. **只读模式**：会自动隐藏编辑相关按钮和功能
8. **TypeScript**：完整的类型支持，建议开启严格模式

## 📝 更新日志

### v2.0.0
- ✨ 新增级联选择器字段类型
- ✨ 新增分页功能
- ✨ 新增导入导出功能
- ✨ 新增只读模式
- 🐛 优化分页逻辑，新增删除时智能保持当前页
- 🐛 修复验证规则的一些边界问题

### v1.0.0
- 🎉 初始版本发布
- 📝 支持基础字段类型
- 📝 支持表单验证
- 📝 支持树形选择器
- 📝 支持远程搜索