# SearchHeader、TableLayout、CTable、IconPark、CFieldList、CnUpload 组件使用文档

## 目录

- [概述](#概述)
- [CTable 组件](#ctable-组件)
- [SearchHeader 组件](#searchheader-组件)
- [TableLayout 组件](#tablelayout-组件)
- [IconPark 组件](#iconpark-组件)
- [CFieldList 组件](#cfieldlist-组件)
- [CnUpload 组件](#cnupload-组件)
- [常见问题](#常见问题)
- [更新日志](#更新日志)

---

## 概述

这些组件是基于 Element Plus 封装的业务组件，用于快速开发业务页面：

- **CTable**：纯表格组件，封装了复选框、序号列、操作列、动态列选择、高度自适应等功能
- **SearchHeader**：搜索表单组件，支持多种输入类型、字段联动、展开收起
- **TableLayout**：完整的表格页面布局组件，集成了上述两个组件及分页、批量操作、高度自适应等
- **IconPark**：IconPark 图标组件封装，支持 hover 变色、点击事件、多种主题等
- **CFieldList**：详情信息展示组件，支持多种数据类型自动渲染、Grid 布局、文本溢出处理、自定义插槽等
- **CnUpload**：文件上传组件，支持多文件上传、拖拽上传、拖拽排序、上传进度、上传中断等功能

---

## CTable 组件

### 功能特性

- 支持复选框多选
- 自动序号列
- 自定义操作列
- 自定义渲染列
- 完全自定义列
- 行点击事件
- **动态列选择**：顶部多选下拉框，支持选择显示哪些列
- **高度自适应**：根据父容器高度自动调整
- **滚动条优化**：内容超出时滚动条一直显示
- 支持 Element Plus el-table 所有原生属性透传

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| data | 表格数据 | Array | [] | 是 |
| columns | 列配置数组 | Array | [] | 否 |
| hasCheck | 是否显示复选框列 | Boolean | false | 否 |
| customHeight | 额外的高度偏移（px） | Number | 0 | 否 |
| columnsList | 选中的列 key 数组 | Array | [] | 否 |
| hasTableHandler | 是否有表格操作栏 | Boolean | false | 否 |

### Columns 配置项

| 属性 | 说明 | 类型 | 必填 |
|------|------|------|------|
| key | 字段名 | String | 是 |
| label | 列标题 | String | 是 |
| width | 列宽度 | String/Number | 否 |
| minWidth | 最小宽度 | String/Number | 否 |
| align | 对齐方式：'left'\|'center'\|'right' | String | 否 |
| fixed | 固定列：'left'\|'right' | String | 否 |
| resizable | 是否可调整列宽 | Boolean | 否 |
| formatter | 格式化函数 | Function | 否 |
| **render** | **渲染函数，优先级最高** | **Function(h, row, index, column)** | **否** |
| **component** | **动态组件配置** | **Object** | **否** |
| **slotName** | **自定义插槽名称，配合同名插槽使用** | **String** | **否** |
| **default** | **是否默认显示该列，默认 true** | **Boolean** | **否** |

### 特殊 key 值

| key | 说明 |
|-----|------|
| 'serialIndex' | 序号列，自动显示 1,2,3... |
| 'action' | 操作列，配合 #action 插槽使用 |
| 'customRender' | 自定义渲染列，配合 #customRender 插槽使用 |

### 动态列选择说明

- 顶部提供多选下拉框，支持选择显示哪些列
- 通过列配置的 `default` 属性控制默认显示状态
  - `default: true` 或未配置：默认显示
  - `default: false`：默认不显示
- 数据可从后台接口获取

### Events 事件

| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| row-click | 行点击事件 | (row, column, event, extraParam) |
| update:modelValue | 选中数据变化 | (checkedArray) |

### Slots 插槽

| 插槽名 | 说明 | 作用域参数 |
|--------|------|------------|
| action | 操作列 | { row, $index, column } |
| customRender | 自定义渲染列 | { row, $index, column } |
| customColumn | 完全自定义列 | - |
| empty | 空数据状态 | - |
| [slotName] | **自定义列插槽，插槽名为 columns 配置中的 slotName** | { row, $index, column } |

### Exposed Methods 暴露方法

通过 `ref` 调用：

```javascript
const tableRef = ref(null);

// 获取选中的行数据
tableRef.value.getCheckedColumn();

// 重置选中状态
tableRef.value.resetCheckedColumn();

// 重新计算表格高度
tableRef.value.calcTableHeight();
```

### 基础示例

```vue
<template>
  <CTable 
    ref="tableRef"
    :columns="columns" 
    :data="tData" 
    :hasCheck="true"
    :customHeight="20"
    @row-click="handleRowClick"
    @update:modelValue="updateCheckedColumn">
    <template #action="scope">
      <el-button @click="tapEdit(scope)" size="small">编辑</el-button>
    </template>
  </CTable>
</template>

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

const tableRef = ref(null);
const tData = ref([
  { id: 1, name: '张三', age: 18, amount: 100 },
  { id: 2, name: '李四', age: 20, amount: 200 },
]);

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem', default: true },
  { key: 'name', label: '姓名', default: true },
  { key: 'age', label: '年龄', default: true },
  { key: 'amount', label: '金额', default: false }, // 默认不显示
  { key: 'action', label: '操作', width: 160, align: 'center', default: true },
];

const handleRowClick = (row, column, event) => {
  console.log(row);
};

const tapEdit = (scope) => {
  console.log(scope.row);
};

const updateCheckedColumn = (val) => {
  console.log('选中数据:', val);
};
</script>
```

### 使用 render 函数自定义渲染

```vue
<template>
  <CTable 
    ref="tableRef"
    :columns="columns" 
    :data="tableData" 
    :hasCheck="true"
  />
</template>

<script setup>
import { ref, h } from 'vue';
import { ElTag } from 'element-plus';

const tableRef = ref(null);
const tableData = ref([
  { id: 1, name: '张三', status: 1, createTime: '2024-01-15' },
  { id: 2, name: '李四', status: 0, createTime: '2024-01-16' },
]);

const columns = [
  { key: 'name', label: '姓名' },
  { 
    key: 'status', 
    label: '状态',
    render: (h, row) => {
      return h(ElTag, { 
        type: row.status === 1 ? 'success' : 'danger' 
      }, {
        default: () => row.status === 1 ? '启用' : '禁用'
      });
    }
  },
  { key: 'createTime', label: '创建时间' },
];
</script>
```

### 使用 slotName 自定义插槽

```vue
<template>
  <CTable 
    ref="tableRef"
    :columns="columns" 
    :data="tableData" 
    :hasCheck="true"
  >
    <!-- statusSlot 插槽，对应 columns 配置中的 slotName -->
    <template #statusSlot="{ row }">
      <el-tag :type="row.status === 1 ? 'success' : 'danger'">
        {{ row.status === 1 ? '启用' : '禁用' }}
      </el-tag>
    </template>
  </CTable>
</template>

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

const tableRef = ref(null);
const tableData = ref([
  { id: 1, name: '张三', status: 1, createTime: '2024-01-15' },
  { id: 2, name: '李四', status: 0, createTime: '2024-01-16' },
]);

const columns = [
  { key: 'name', label: '姓名' },
  { key: 'status', label: '状态', slotName: 'statusSlot' },
  { key: 'createTime', label: '创建时间' },
];
</script>
```

---

## SearchHeader 组件

### 功能特性

- 支持输入框、下拉选择框、日期选择、日期范围
- 支持字段联动（显示/隐藏、启用/禁用）
- **展开收起功能**：默认显示 1 行，超出部分隐藏，支持展开/收起
- 支持多个插槽自定义
- 支持控件原生属性透传

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| gutter | 栅格间隔 | Number | 20 | 否 |
| searchConfig | 搜索配置项数组 | Array | [] | 是 |
| modelValue | v-model 绑定的搜索参数 | Object | {} | 否 |
| defaultRows | 默认显示行数 | Number | 1 | 否 |
| buttonSpan | 按钮占用的栅格宽度 | Number | 6 | 否 |
| foldableSlots | 可折叠的插槽列表 | Array | ['searchLeft', 'searchMiddle'] | 否 |

### searchConfig 配置项

#### 通用属性

| 属性 | 说明 | 类型 | 必填 |
|------|------|------|------|
| type | 控件类型：'input'\|'select'\|'date'\|'daterange'\|'treeSelect'\|'radioGroup'\|'inputTag'\|'inputNumber'\|'checkbox' | String | 是 |
| label | 标签文本 | String | 是 |
| prop | 字段名 | String | 是 |
| span | 栅格宽度 | Number | 6 | 否 |
| placeholder | 占位文本 | String | 否 |
| clearable | 是否可清空 | Boolean | true | 否 |
| disabled | 是否禁用 | Boolean | false | 否 |
| bindProps | 统一的原生属性绑定（优先使用） | Object | 否 |
| inputProps | el-input 原生属性（兼容旧写法） | Object | 否 |
| selectProps | el-select 原生属性（兼容旧写法） | Object | 否 |
| dateProps | el-date-picker 原生属性（单个日期，兼容旧写法） | Object | 否 |
| dateRangeProps | el-date-picker 原生属性（日期范围，兼容旧写法） | Object | 否 |
| treeSelectProps | el-tree-select 原生属性（兼容旧写法） | Object | 否 |
| treeData | 树形数据（treeSelect 类型） | Array | 否 |
| radioGroupProps | el-radio-group 原生属性（兼容旧写法） | Object | 否 |
| inputTagProps | el-input 原生属性（textarea，兼容旧写法） | Object | 否 |
| inputNumberProps | el-input-number 原生属性（兼容旧写法） | Object | 否 |
| checkboxProps | el-checkbox 原生属性（兼容旧写法） | Object | 否 |
| checkboxLabel | checkbox 标签文本 | String | 否 |
| options | 选项数据（select、radioGroup 类型） | Array | 否 |
| enter | 回车事件（仅 input） | Function | 否 |
| change | 值变化事件 | Function | 否 |
| linkage | 字段联动配置 | Object | 否 |

#### type 类型详解

**关于 bindProps**
- 推荐使用 `bindProps` 作为统一的属性绑定方式
- 如果同时设置了 `bindProps` 和特定类型的 props（如 `inputProps`），优先使用 `bindProps`
- 旧的特定类型 props（如 `inputProps`、`selectProps` 等）仍然保留兼容

**type: 'input' 输入框**

```javascript
{
  type: 'input',
  label: '用户名称',
  prop: 'userName',
  span: 6,
  placeholder: '请输入用户名称',
  inputProps: {
    maxlength: 20
  }
}
```

**使用 bindProps 统一写法（推荐）：**

```javascript
{
  type: 'input',
  label: '用户名称',
  prop: 'userName',
  span: 6,
  placeholder: '请输入用户名称',
  bindProps: {
    maxlength: 20,
    showWordLimit: true
  }
}
```

**type: 'select' 下拉选择框**

```javascript
{
  type: 'select',
  label: '用户状态',
  prop: 'status',
  span: 6,
  options: [
    { label: '正常', value: 1 },
    { label: '禁用', value: 0 }
  ],
  selectProps: {
    clearable: true
  }
}
```

**type: 'date' 单个日期**

```javascript
{
  type: 'date',
  label: '创建日期',
  prop: 'createTime',
  span: 6,
  dateProps: {
    valueFormat: 'YYYY-MM-DD'
  }
}
```

**type: 'daterange' 日期范围**

```javascript
{
  type: 'daterange',
  label: '日期范围',
  prop: 'dateRange',
  span: 12,
  rangeSeparator: '至',
  startPlaceholder: '开始日期',
  endPlaceholder: '结束日期',
  dateRangeProps: {
    valueFormat: 'YYYY-MM-DD'
  }
}
```

**type: 'treeSelect' 树形选择器**

```javascript
{
  type: 'treeSelect',
  label: '部门选择',
  prop: 'deptId',
  span: 6,
  treeData: [
    {
      label: '研发部',
      value: 1,
      children: [
        { label: '前端组', value: 11 },
        { label: '后端组', value: 12 }
      ]
    },
    {
      label: '市场部',
      value: 2,
      children: [
        { label: '销售组', value: 21 }
      ]
    }
  ],
  treeSelectProps: {
    multiple: false,
    checkStrictly: false
  }
}
```

**type: 'radioGroup' 单选框组**

```javascript
{
  type: 'radioGroup',
  label: '性别',
  prop: 'gender',
  span: 6,
  options: [
    { label: '男', value: 1 },
    { label: '女', value: 2 }
  ],
  radioGroupProps: {
    size: 'default'
  }
}
```

**type: 'inputTag' 输入标签**

```javascript
{
  type: 'inputTag',
  label: '备注信息',
  prop: 'remark',
  span: 12,
  placeholder: '请输入备注信息',
  inputTagProps: {
    rows: 3,
    maxlength: 200,
    showWordLimit: true
  }
}
```

**type: 'inputNumber' 数字输入框**

```javascript
{
  type: 'inputNumber',
  label: '数量',
  prop: 'count',
  span: 6,
  placeholder: '请输入数量',
  inputNumberProps: {
    min: 0,
    max: 1000,
    step: 1
  }
}
```

**type: 'checkbox' 复选框**

```javascript
{
  type: 'checkbox',
  label: '是否启用',
  prop: 'enabled',
  span: 6,
  checkboxLabel: '启用状态',
  checkboxProps: {
    size: 'default'
  }
}
```

#### linkage 字段联动配置

| 属性 | 说明 | 类型 | 必填 |
|------|------|------|------|
| dependProp | 依赖字段名 | String | 是 |
| dependValue | 依赖值，支持单个值或数组 | Any/Array | 是 |
| hide | 满足条件时是否隐藏 | Boolean | 否 |
| show | 满足条件时是否显示（与 hide 相反） | Boolean | 否 |
| disabled | 满足条件时是否禁用 | Boolean | 否 |
| enable | 满足条件时是否启用（与 disabled 相反） | Boolean | 否 |

**联动示例**

```javascript
// 示例1：满足条件时隐藏
{
  type: 'date',
  label: '创建日期',
  prop: 'createTime',
  linkage: {
    dependProp: 'status',
    dependValue: 0,
    hide: true  // 当 status=0 时隐藏
  }
}

// 示例2：满足条件时显示
{
  type: 'date',
  label: '创建日期',
  prop: 'createTime',
  linkage: {
    dependProp: 'status',zl
    dependValue: 1,
    show: true  // 当 status=1 时显示（其他值隐藏）
  }
}

// 示例3：满足条件时禁用
{
  type: 'date',
  label: '创建日期',
  prop: 'createTime',
  linkage: {
    dependProp: 'status',
    dependValue: 0,
    disabled: true  // 当 status=0 时禁用
  }
}

// 示例4：多个依赖值
{
  type: 'date',
  label: '创建日期',
  prop: 'createTime',
  linkage: {
    dependProp: 'status',
    dependValue: [0, 2],  // status=0 或 status=2 都触发
    hide: true
  }
}
```

### Events 事件

| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| search | 搜索事件 | (searchForm) |
| reset | 重置事件 | (searchForm) |
| update:modelValue | 搜索参数变化 | (searchForm) |
| fieldChange | 表单项变化 | (searchForm) |
| toggleExpand | 展开/收起事件 | (isExpanded) |

### Slots 插槽

| 插槽名 | 说明 |
|--------|------|
| searchLeft | 搜索栏左侧（可折叠） |
| searchMiddle | 搜索表单右侧，搜索按钮之前（可折叠） |
| searchBtnAfter | 搜索按钮之后 |
| searchFooter | 搜索栏底部（单独一行，不受折叠控制，始终显示） |

### 完整示例

```vue
<template>
  <SearchHeader 
    :searchConfig="searchConfig" 
    :defaultRows="2"
    v-model="searchForm"
    @search="handleSearch"
    @reset="handleReset">
    <template #searchLeft>
      <el-button type="primary">新增</el-button>
    </template>
  </SearchHeader>
</template>

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

const searchForm = ref({});

const searchConfig = [
  { type: 'input', label: '用户名称', prop: 'userName', span: 6 },
  { 
    type: 'select', 
    label: '用户状态', 
    prop: 'status', 
    span: 6,
    options: [
      { label: '正常', value: 1 },
      { label: '禁用', value: 0 }
    ]
  },
  { 
    type: 'date', 
    label: '创建日期', 
    prop: 'createTime', 
    span: 6,
    linkage: {
      dependProp: 'status',
      dependValue: 0,
      disabled: true
    }
  },
  { type: 'input', label: '手机号', prop: 'phone', span: 6 },
  { type: 'input', label: '邮箱', prop: 'email', span: 6 },
  { type: 'input', label: '地址', prop: 'address', span: 6 },
];

const handleSearch = (form) => {
  console.log('搜索:', form);
};

const handleReset = (form) => {
  console.log('重置:', form);
};
</script>
```

---

## TableLayout 组件

### 功能特性

- 集成 SearchHeader、CTable、分页、批量操作
- 支持静态数据和接口数据两种模式
- 支持参数格式化
- **高度自适应**：整个页面高度自适应，表格区域自动填充剩余空间，表格内部滚动
- **表格操作栏**：支持 tableHandler 和 tableHandlerRight 两个插槽自定义操作按钮
- **列选择器**：内置列多选下拉框，支持动态选择显示/隐藏哪些列
- **统计数据**：可选显示统计数据卡片
- 完整的事件回调

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| data | 静态表格数据 | Array | [] | 否 |
| tableData | 同 data（兼容旧版本） | Array | [] | 否 |
| tableUrl | 动态数据接口地址 | String | '' | 否 |
| columns | 列配置（同 CTable） | Array | [] | 否 |
| hasCheck | 是否显示复选框 | Boolean | false | 否 |
| searchOptions | 搜索配置（同 SearchHeader），设为 false/null/undefined  | Array/Boolean | [] | 否 |
| formatParams | 参数格式化函数 | Function | - | 否 |
| resetPage | 分页配置 | Object | - | 否 |
| multiTip | 批量操作配置 | Object | - | 否 |
| customHeight | 自定义高度调整（正数增加表格高度，负数减少） | Number | 0 | 否 |
| showCustomRenderColumns | 是否显示自定义列选择器 | Boolean | true | 否 |
| gutter | SearchHeader 栅格间隔 | Number | 12 | 否 |
| showSumData | 是否显示统计数据复选框 | Boolean | false | 否 |
| showMultiTip | 是否显示批量操作组件 | Boolean | true | 否 |
| **v-bind="$attrs"** | **Element Plus el-table 所有原生属性透传** | - | - | - |

### resetPage 分页配置

```javascript
{
  pageIndex: 1,              // 初始页码
  jumpSize: [30, 40, 50],    // 每页条数选项
  pageSize: 30,               // 默认每页条数
  total: 0                    // 总条数（可选）
}
```

### multiTip 批量操作配置

```javascript
{
  showOptBtn: true,          // 是否显示操作按钮
  emptyOptMessage: "至少选择一项",  // 未选择时的提示
  optText: "批量操作",         // 按钮文本
  tapOpt: (checked) => {}    // 点击按钮回调
}
```

### Events 事件

| 事件名 | 说明 | 回调参数 |
|--------|------|----------| rowClick | 行点击 | (row, column, event, extraParam) |
| search | 搜索 | (searchForm, pageInfo) |
| reset | 重置 | (searchForm, pageInfo) |
| loadSuccess | 数据加载成功 | (data) |
| loadData | 加载数据（仅在传入 data 时触发） | (pageInfo, searchForm) |
| tapOpt | 批量操作按钮点击 | (checkedArray) |
| update:modelValue | 选中数据变化 | (checkedArray) |
| changeColumns | 列选择变化 | (selectedKeys) |

### v-model 双向绑定

```vue
<TableLayout v-model="checkedColumn" ... />
```

### Slots 插槽

**搜索区域插槽：**

| 插槽名 | 说明 |
|--------|------|
| searchLeft | 搜索栏左侧 |
| searchMiddle | 搜索表单右侧 |
| searchBtnAfter | 搜索按钮之后 |
| searchFooter | 搜索栏底部 |

**表格操作栏插槽：**

| 插槽名 | 说明 |
|--------|------|
| tableHandler | 表格操作栏左侧，放置新增等按钮 |
| tableHandlerRight | 表格操作栏右侧，放置导入导出等按钮 |

**统计数据插槽：**

| 插槽名 | 说明 |
|--------|------|
| chartCard | 统计数据卡片内容（需要配合 showSumData 属性使用） |

**表格区域插槽（同 CTable）：**

| 插槽名 | 说明 |
|--------|------|
| action | 操作列 |
| customRender | 自定义渲染列 |
| customColumn | 完全自定义列 |
| empty | 空数据状态 |
| [slotName] | **自定义列插槽，插槽名为 columns 配置中的 slotName** |

**批量操作区域插槽：**

| 插槽名 | 说明 |
|--------|------|
| opt | MultiTip 中自定义操作按钮区域，替换默认的按钮 |

### Exposed Methods 暴露方法

```javascript
const tableRef = ref(null);

// 获取选中的行数据
tableRef.value.getCheckedColumn();

// 重置选中状态
tableRef.value.resetCheckedColumn();

// 手动触发搜索
tableRef.value.search();

// 手动触发重置
tableRef.value.reset();

// 手动加载列表
tableRef.value.loadList();

// 获取当前搜索参数
tableRef.value.getParams();

// 手动更新列选择
tableRef.value.changeColumns(selectedKeys);

// 获取分页信息
console.log(tableRef.value.pageInfo); // { pageIndex, pageSize, total }
```

### 完整示例 - 静态数据

```vue
<template>
  <div style="height: 100%;">
    <TableLayout
      ref="tableRef"
      :data="tData"
      :columns="columns"
      :hasCheck="true"
      :searchOptions="searchOptions"
      :showSumData="true"
      v-model="checkedColumn"
      @rowClick="rowClick"
      @search="handleSearch"
      @reset="handleReset"
      @changeColumns="handleChangeColumns">
      
      <!-- 搜索栏左侧按钮 -->
      <template #searchLeft>
          <el-input :span="6" placeholder="请输入用户名" v-model="searchParams.username" />
        <el-button type="primary">新增</el-button>
      </template>
      
      <!-- 表格操作栏左侧按钮 -->
      <template #tableHandler>
        <el-button type="success">批量导入</el-button>
      </template>
      
      <!-- 表格操作栏右侧按钮 -->
      <template #tableHandlerRight>
        <el-button type="info">导出</el-button>
      </template>
      
      <!-- 统计数据卡片 -->
      <template #chartCard>
        <el-card style="margin-top: .75rem;">
          <div style="display: flex; gap: 1.5rem;">
            <div>
              <div style="color: #999; font-size: .75rem;">总记录数</div>
              <div style="font-size: 1.5rem; font-weight: bold;">{{ tData.length }}</div>
            </div>
            <div>
              <div style="color: #999; font-size: .75rem;">平均年龄</div>
              <div style="font-size: 1.5rem; font-weight: bold;">{{ avgAge }}</div>
            </div>
          </div>
        </el-card>
      </template>
      
      <!-- 批量操作自定义按钮 -->
      <template #opt>
        <el-button type="primary" @click="handleBatchDelete">批量删除</el-button>
        <el-button type="success" @click="handleBatchExport">批量导出</el-button>
      </template>
      
      <!-- 表格操作列 -->
      <template #action="scope">
        <el-button @click="tapEdit(scope)" type="primary" size="small">编辑</el-button>
        <el-button @click="tapDelete(scope)" type="danger" size="small">删除</el-button>
      </template>
    </TableLayout>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const tableRef = ref(null);
const checkedColumn = ref([]);

const tData = ref([
  { id: 1, name: '张三', age: 18 },
  { id: 2, name: '李四', age: 20 },
  { id: 3, name: '王五', age: 25 },
]);

const avgAge = computed(() => {
  const sum = tData.value.reduce((acc, cur) => acc + cur.age, 0);
  return tData.value.length ? (sum / tData.value.length).toFixed(1) : 0;
});

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem', show: true },
  { key: 'name', label: '姓名', show: true },
  { key: 'age', label: '年龄', show: true },
  { key: 'action', label: '操作', width: 200, show: true },
];

const searchOptions = [
  { type: 'input', label: '用户名称', prop: 'userName', span: 6 },
];

const rowClick = (row) => {
  console.log('点击行:', row);
};

const tapEdit = (scope) => {
  console.log('编辑:', scope.row);
};

const tapDelete = (scope) => {
  console.log('删除:', scope.row);
};

const handleSearch = (form, pageInfo) => {
  console.log('搜索:', form, pageInfo);
  // 请求列表数据时需要带上 pageInfo
  fetchList({ ...form, pageInfo });
};

const handleReset = (form, pageInfo) => {
  console.log('重置:', form, pageInfo);
  // 请求列表数据时需要带上 pageInfo
  fetchList({ ...form, pageInfo });
};

const handleChangeColumns = (selectedKeys) => {
  console.log('列选择变化:', selectedKeys);
};

const handleBatchDelete = () => {
  console.log('批量删除:', checkedColumn.value);
};

const handleBatchExport = () => {
  console.log('批量导出:', checkedColumn.value);
};
</script>
```

### 完整示例 - 接口数据

```vue
<template>
  <div style="height: 100%;">
    <TableLayout
      ref="tableRef"
      tableUrl="/api/user/list"
      :columns="columns"
      :hasCheck="true"
      :searchOptions="searchOptions"
      :formatParams="formatParams"
      :resetPage="resetPage"
      :multiTip="multiTipOpt"
      :showSumData="true"
      v-model="checkedColumn"
      @loadSuccess="loadSuccess"
      @tapOpt="tapOptBtn">
      
      <!-- 搜索栏左侧按钮 -->
      <template #searchLeft>
          <el-input :span="6" placeholder="请输入用户名" v-model="searchParams.username" />
        <el-button type="primary">新增</el-button>
      </template>
      
      <!-- 表格操作栏按钮 -->
      <template #tableHandler>
        <el-button type="success">批量导入</el-button>
      </template>
      
      <!-- 统计数据 -->
      <template #chartCard>
        <el-card style="margin-top: .75rem;">
          <div style="display: flex; gap: 1.5rem;">
            <div>
              <div style="color: #999; font-size: .75rem;">总记录数</div>
              <div style="font-size: 1.5rem; font-weight: bold;">{{ totalCount }}</div>
            </div>
          </div>
        </el-card>
      </template>
      
      <!-- 批量操作自定义按钮 -->
      <template #opt>
        <el-button type="primary" @click="handleBatchDelete">批量删除</el-button>
        <el-button type="success" @click="handleBatchExport">批量导出</el-button>
      </template>
      
      <!-- 表格操作列 -->
      <template #action="scope">
        <el-button @click="tapEdit(scope)" type="primary" size="small">编辑</el-button>
        <el-button @click="tapDelete(scope)" type="danger" size="small">删除</el-button>
      </template>
    </TableLayout>
  </div>
</template>

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

const tableRef = ref(null);
const checkedColumn = ref([]);
const totalCount = ref(0);

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem', show: true },
  { key: 'name', label: '姓名', show: true },
  { key: 'age', label: '年龄', show: true },
  { key: 'action', label: '操作', width: 200, show: true },
];

const searchOptions = [
  { type: 'input', label: '用户名称', prop: 'userName', span: 6 },
  { 
    type: 'select', 
    label: '状态', 
    prop: 'status', 
    span: 6,
    options: [
      { label: '正常', value: 1 },
      { label: '禁用', value: 0 }
    ]
  }
];

const resetPage = {
  pageIndex: 1,
  jumpSize: [30, 40, 50],
  pageSize: 30,
  total: 0
};

const multiTipOpt = {
  showOptBtn: true,
  emptyOptMessage: '至少选择一项',
  optText: '批量删除',
  tapOpt: (checked) => {
    console.log('批量操作:', checked);
  }
};

const formatParams = (params) => {
  params.extraParam = 'value';
  return params;
};

const loadSuccess = (data) => {
  console.log('加载成功:', data);
  totalCount.value = data?.length || 0;
};

const tapEdit = (scope) => {
  console.log('编辑:', scope.row);
};

const tapDelete = (scope) => {
  console.log('删除:', scope.row);
};

const tapOptBtn = (checked) => {
  console.log('批量操作', checked);
};

const handleBatchDelete = () => {
  console.log('批量删除', checkedColumn.value);
};

const handleBatchExport = () => {
  console.log('批量导出', checkedColumn.value);
};
</script>
```

### 不显示搜索区域

```vue
<template>
  <div style="height: 100%;">
    <TableLayout
      ref="tableRef"
      :data="tData"
      :columns="columns"
      :hasCheck="true"
      :searchOptions="false"
      v-model="checkedColumn">
      
      <template #action="scope">
        <el-button @click="tapEdit(scope)" type="primary" size="small">编辑</el-button>
        <el-button @click="tapDelete(scope)" type="danger" size="small">删除</el-button>
      </template>
    </TableLayout>
  </div>
</template>

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

const tableRef = ref(null);
const checkedColumn = ref([]);

const tData = ref([
  { id: 1, name: '张三', age: 18 },
  { id: 2, name: '李四', age: 20 },
]);

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem', show: true },
  { key: 'name', label: '姓名', show: true },
  { key: 'age', label: '年龄', show: true },
  { key: 'action', label: '操作', width: 200, show: true },
];

const tapEdit = (scope) => {
  console.log('编辑:', scope.row);
};

const tapDelete = (scope) => {
  console.log('删除:', scope.row);
};
</script>
```

### 使用 opt 插槽自定义批量操作按钮

```vue
<template>
  <PageLayout>
    <template #body>
      <TableLayout
        ref="tableRef"
        tableUrl="/api/user/list"
        :columns="columns"
        :hasCheck="true"
        :searchOptions="searchOptions"
        v-model="checkedColumn"
        @loadSuccess="loadSuccess">
        <!-- 使用 opt 插槽自定义批量操作按钮 -->
        <template #opt>
          <el-button type="primary" @click="handleBatchDelete">批量删除</el-button>
          <el-button type="success" @click="handleBatchExport">批量导出</el-button>
        </template>
        <template #action="scope">
          <el-button @click="tapEdit(scope)" size="small">编辑</el-button>
        </template>
      </TableLayout>
    </template>
  </PageLayout>
</template>

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

const tableRef = ref(null);
const checkedColumn = ref([]);

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem' },
  { key: 'name', label: '姓名' },
  { key: 'age', label: '年龄' },
  { key: 'action', label: '操作', width: 160 },
];

const searchOptions = [
  { type: 'input', label: '用户名称', prop: 'userName', span: 6 },
];

const loadSuccess = (data) => {
  console.log('加载成功', data);
};

const tapEdit = (scope) => {
  console.log('编辑', scope.row);
};

const handleBatchDelete = () => {
  console.log('批量删除', checkedColumn.value);
};

const handleBatchExport = () => {
  console.log('批量导出', checkedColumn.value);
};
</script>
```

### TableLayout 中使用 render 和 slotName

```vue
<template>
  <div style="height: 100%;">
    <TableLayout
      ref="tableRef"
      :data="tableData"
      :columns="columns"
      :hasCheck="true"
      :searchOptions="searchOptions"
      v-model="checkedColumn"
    >
      <!-- statusSlot 插槽，对应 columns 配置中的 slotName -->
      <template #statusSlot="{ row }">
        <el-tag :type="row.status === 1 ? 'success' : 'danger'">
          {{ row.status === 1 ? '启用' : '禁用' }}
        </el-tag>
      </template>
      
      <template #action="scope">
        <el-button @click="tapEdit(scope)" type="primary" size="small">编辑</el-button>
        <el-button @click="tapDelete(scope)" type="danger" size="small">删除</el-button>
      </template>
    </TableLayout>
  </div>
</template>

<script setup>
import { ref, h } from 'vue';
import { ElTag } from 'element-plus';

const tableRef = ref(null);
const checkedColumn = ref([]);

const tableData = ref([
  { id: 1, name: '张三', status: 1, createTime: '2024-01-15' },
  { id: 2, name: '李四', status: 0, createTime: '2024-01-16' },
]);

const columns = [
  { key: 'serialIndex', label: '序号', width: '5rem', show: true },
  { key: 'name', label: '姓名', show: true },
  { 
    key: 'status', 
    label: '状态',
    show: true,
    render: (h, row) => {
      return h(ElTag, { 
        type: row.status === 1 ? 'success' : 'danger' 
      }, {
        default: () => row.status === 1 ? '启用' : '禁用'
      });
    }
  },
  { key: 'createTime', label: '创建时间', show: true },
  { key: 'customSlotCol', label: '自定义插槽列', slotName: 'statusSlot', show: true },
  { key: 'action', label: '操作', width: 200, show: true },
];

const searchOptions = [
  { type: 'input', label: '用户名称', prop: 'userName', span: 6 },
];

const tapEdit = (scope) => {
  console.log('编辑:', scope.row);
};

const tapDelete = (scope) => {
  console.log('删除:', scope.row);
};
</script>
```

---

## 常见问题

### Q: 如何在点击复选框时不触发行点击？

A: 在 rowClick 事件中添加判断：

```javascript
const rowClick = (row, column, event, extraParam) => {
  // 阻止复选框、下拉菜单等冒泡
  if (event.target.closest('.el-dropdown,.el-button--link,.el-checkbox,.el-checkbox__input,.el-checkbox__label')) {
    return;
  }
  // 复选框列特殊处理
  if (column && column.width === 40) {
    return;
  }
  console.log('点击行:', row);
};
```

### Q: 如何使用 formatter 返回带样式的内容？

A: 使用 Vue 的 h 函数：

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

const columns = [
  {
    label: '状态',
    key: 'status',
    formatter: (row) => {
      if (row.status === 1) {
        return h('span', { style: 'color: green;' }, '正常');
      } else {
        return h('span', { style: 'color: red;' }, '禁用');
      }
    }
  }
];
```

### Q: 如何完全自定义表格列？

A: 使用 #customColumn 插槽：

```vue
<CTable :data="tData">
  <template #customColumn>
    <el-table-column prop="name" label="姓名" width="180" />
    <el-table-column prop="age" label="年龄" width="180" />
    <el-table-column label="操作" width="160">
      <template #default="scope">
        <el-button @click="tapEdit(scope.row)">编辑</el-button>
      </template>
    </el-table-column>
  </template>
</CTable>
```

### Q: 如何实现页面高度自适应，表格内部滚动？

A: 使用 PageLayout + TableLayout 组合：

```vue
<template>
  <PageLayout>
    <template #body>
      <TableLayout
        :columns="columns"
        :data="tableData"
        :hasCheck="true"
        :searchOptions="searchOptions">
      </TableLayout>
    </template>
  </PageLayout>
</template>
```

### Q: CTable 动态列选择如何从后台获取配置？

A: 直接把后台返回的 columns 数据传给组件即可：

```javascript
// 从后台获取 columns 配置
const loadColumns = async () => {
  const res = await api.getColumns();
  // res.data 格式: [
  //   { key: 'name', label: '姓名', default: true },
  //   { key: 'age', label: '年龄', default: false }
  // ]
  columns.value = res.data;
};
```

---

## IconPark 组件

### 功能特性

- 基于 @icon-park/vue-next 封装
- 支持 hover 变色效果
- 支持点击、鼠标进入、鼠标离开事件
- 支持四种主题：outline、filled、two-tone、multi-color
- 支持自定义尺寸、颜色、描边样式等
- 支持自定义类名和样式
- **新增 tooltip 功能**：设置 tooltip 内容后 hover 显示提示
- 支持自定义 tooltip 位置
- **新增 tooltip 自定义内容 slot**：支持通过插槽自定义 tooltip 显示内容

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| name | 图标名称（必传） | String | - | 是 |
| size | 图标尺寸 | String/Number | 24 | 否 |
| color | 默认颜色 | String | '#666' | 否 |
| hoverColor | hover 时的颜色 | String | '#1890ff' | 否 |
| strokeWidth | 描边宽度 | Number | 4 | 否 |
| strokeLinecap | 描边端点样式 | String | 'round' | 否 |
| strokeLinejoin | 描边连接样式 | String | 'round' | 否 |
| theme | 主题 | String | 'outline' | 否 |
| disableHover | 是否禁用 hover 效果 | Boolean | false | 否 |
| customClass | 自定义类名 | String | '' | 否 |
| customStyle | 自定义样式 | Object | {} | 否 |
| bordered | 是否显示边框 | Boolean | false | 否 |
| borderWidth | 边框宽度 | String/Number | 1 | 否 |
| borderRadius | 边框圆角 | String/Number | 4 | 否 |
| borderColor | 边框颜色（默认同 color） | String | '' | 否 |
| borderHoverColor | hover 时的边框颜色（默认同 hoverColor） | String | '' | 否 |
| padding | 内边距 | String/Number | 4 | 否 |
| tooltip | tooltip 内容，设置后 hover 会显示 | String | '' | 否 |
| tooltipPlacement | tooltip 位置 | String | 'top' | 否 |

#### strokeLinecap 可选值

| 值 | 说明 |
|----|------|
| butt | 平端 |
| round | 圆端（默认） |
| square | 方端 |

#### strokeLinejoin 可选值

| 值 | 说明 |
|----|------|
| miter | 斜接 |
| round | 圆角（默认） |
| bevel | 斜角 |

#### theme 可选值

| 值 | 说明 |
|----|------|
| outline | 线性图标（默认） |
| filled | 填充图标 |
| two-tone | 双色图标 |
| multi-color | 多色图标 |

#### tooltipPlacement 可选值

| 值 | 说明 |
|----|------|
| top | 顶部（默认） |
| top-start | 顶部偏左 |
| top-end | 顶部偏右 |
| bottom | 底部 |
| bottom-start | 底部偏左 |
| bottom-end | 底部偏右 |
| left | 左侧 |
| left-start | 左侧偏上 |
| left-end | 左侧偏下 |
| right | 右侧 |
| right-start | 右侧偏上 |
| right-end | 右侧偏下 |

### Events 事件

| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| click | 点击事件 | (event) |
| mouseenter | 鼠标进入事件 | (event) |
| mouseleave | 鼠标离开事件 | (event) |

### Slots 插槽

| 插槽名 | 说明 |
|--------|------|
| tooltip-content | 自定义 tooltip 内容插槽 |

### 基础示例

```vue
<template>
  <div style="padding: 1.25rem;">
    <h3>基础用法</h3>
    <IconPark name="Home" />
    
    <h3>设置尺寸</h3>
    <IconPark name="Home" :size="32" />
    <IconPark name="Home" :size="48" />
    
    <h3>设置颜色</h3>
    <IconPark name="Home" color="#ff4d4f" />
    <IconPark name="Home" color="#52c41a" />
    
    <h3>hover 变色</h3>
    <IconPark name="Home" color="#666" hoverColor="#1890ff" />
    
    <h3>禁用 hover</h3>
    <IconPark name="Home" disableHover />
    
    <h3>不同主题</h3>
    <IconPark name="Home" theme="outline" />
    <IconPark name="Home" theme="filled" />
    <IconPark name="Home" theme="two-tone" />
    <IconPark name="Home" theme="multi-color" />
    
    <h3>点击事件</h3>
    <IconPark name="Home" @click="handleClick" />
    
    <h3>自定义样式</h3>
    <IconPark 
      name="Home" 
      :customStyle="{ marginRight: '.625rem', opacity: 0.8 }" 
    />
    
    <h3>带边框</h3>
    <IconPark name="Home" bordered />
    <IconPark name="Home" bordered color="#1890ff" borderColor="#d9d9d9" borderHoverColor="#1890ff" />
    
    <h3>自定义边框样式</h3>
    <IconPark name="Home" bordered :borderWidth="2" :borderRadius="8" :padding="8" />
    <IconPark name="Home" bordered borderColor="#ff4d4f" borderHoverColor="#ff7875" color="#ff4d4f" hoverColor="#ff7875" />
    
    <h3>Tooltip 提示</h3>
    <IconPark name="Home" tooltip="首页" />
    <IconPark name="Add" tooltip="新增" tooltipPlacement="right" />
    <IconPark name="Edit" tooltip="编辑" tooltipPlacement="bottom" />
    <IconPark name="Delete" tooltip="删除" tooltipPlacement="left" />
    
    <h3>自定义 Tooltip 内容</h3>
    <IconPark name="More">
      <template #tooltip-content>
        <div style="padding: 8px;">
          <p style="margin: 0; font-weight: 600;">更多操作</p>
          <p style="margin: 4px 0 0 0; color: #999; font-size: 12px;">点击查看更多选项</p>
        </div>
      </template>
    </IconPark>
  </div>
</template>

<script setup>
import { IconPark } from 'cimos7-vue3-common';

const handleClick = (event) => {
  console.log('点击了图标', event);
};
</script>
```

### 在按钮中使用

```vue
<template>
  <el-button type="primary">
    <IconPark name="Add" style="margin-right: .25rem;" />
    新增
  </el-button>
  
  <el-button>
    <IconPark name="Edit" style="margin-right: .25rem;" />
    编辑
  </el-button>
  
  <el-button type="danger">
    <IconPark name="Delete" style="margin-right: .25rem;" />
    删除
  </el-button>
</template>

<script setup>
import { IconPark } from 'cimos7-vue3-common';
</script>
```

### 在表格操作列中使用

```vue
<template>
  <CTable :data="tableData" :columns="columns">
    <template #action="scope">
      <span style="display: flex; gap: .5rem;">
        <IconPark 
          name="Edit" 
          color="#1890ff" 
          size="18"
          @click="handleEdit(scope.row)"
          style="cursor: pointer;"
        />
        <IconPark 
          name="Delete" 
          color="#ff4d4f" 
          size="18"
          @click="handleDelete(scope.row)"
          style="cursor: pointer;"
        />
      </span>
    </template>
  </CTable>
</template>

<script setup>
import { ref } from 'vue';
import { CTable, IconPark } from 'cimos7-vue3-common';

const tableData = ref([]);

const handleEdit = (row) => {
  console.log('编辑', row);
};

const handleDelete = (row) => {
  console.log('删除', row);
};
</script>
```

### 图标名称参考

图标名称可参考 [IconPark 官方文档](https://iconpark.oceanengine.com/official)

---

## CFieldList 组件

### 功能特性

- 详情信息展示组件，支持多种数据类型自动渲染
- 支持 Grid 布局，可自定义列数和间距
- **文本溢出处理**：超出时显示省略号，hover 时通过 tooltip 显示完整内容（仅在溢出时显示 tooltip）
- 支持隐藏特定字段（hide=1）
- 支持自定义 tooltip 属性
- 支持自定义插槽渲染特定字段
- 内置日期、日期时间、数字、布尔值等类型格式化
- 支持 options 配置下拉选项映射
- 支持 _text 后缀字段优先显示文本值

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| fields | 字段配置数组 | Array | [] | 是 |
| data | 数据对象 | Object | {} | 是 |
| title | 标题文本 | String | '详情信息' | 否 |
| columnsCount | 列数 | Number | 3 | 否 |
| gap | 网格间距 | String | '.75rem 1rem' | 否 |
| tooltipProps | Tooltip 配置对象，支持 Element Plus el-tooltip 所有属性 | Object | { placement: 'top', effect: 'dark' } | 否 |

### fields 配置项

| 属性 | 说明 | 类型 | 必填 |
|------|------|------|------|
| id | 字段 id（对应 data 中的 key） | String | 是 |
| cnName | 字段中文名称（显示的标签） | String | 是 |
| type | 字段类型（date/datetime/number/boolean） | String | 否 |
| hide | 是否隐藏（1=隐藏） | Number | 否 |
| options | 选项数组，用于 value 到 label 的映射 | Array | 否 |

#### type 可选值说明

| 值 | 说明 |
|----|------|
| date | 日期类型，格式化为 YYYY-MM-DD |
| datetime | 日期时间类型，格式化为 YYYY-MM-DD HH:mm:ss |
| number | 数字类型 |
| boolean | 布尔类型，显示"是"或"否" |

#### options 配置格式

```javascript
[
  { value: 1, label: '正常' },
  { value: 0, label: '禁用' }
]
```

### Slots 插槽

| 插槽名 | 说明 | 作用域参数 |
|--------|------|------------|
| [field.id] | 自定义字段内容，插槽名为字段 id | { field, index } |

### 基础示例

```vue
<template>
  <div style="padding: 1.25rem;">
    <CFieldList
      :fields="fields"
      :data="data"
      title="用户详情"
      :columnsCount="2"
      gap="1rem 1.5rem"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CFieldList } from 'cimos7-vue3-common';

const data = ref({
  id: 1,
  name: '张三',
  age: 28,
  phone: '13800138000',
  email: 'zhangsan@example.com',
  status: 1,
  createTime: '2024-01-15T08:30:00.000Z',
  updateTime: '2024-01-20T14:45:30.000Z'
});

const fields = ref([
  { id: 'id', cnName: '用户ID', type: 'number' },
  { id: 'name', cnName: '姓名' },
  { id: 'age', cnName: '年龄', type: 'number' },
  { id: 'phone', cnName: '手机号' },
  { id: 'email', cnName: '邮箱' },
  { 
    id: 'status', 
    cnName: '状态', 
    options: [
      { value: 1, label: '正常' },
      { value: 0, label: '禁用' }
    ]
  },
  { id: 'createTime', cnName: '创建时间', type: 'datetime' },
  { id: 'updateTime', cnName: '更新时间', type: 'datetime' }
]);
</script>
```

### 使用自定义插槽

```vue
<template>
  <div style="padding: 1.25rem;">
    <CFieldList
      :fields="fields"
      :data="data"
      title="资产详情"
      :columnsCount="3"
      :tooltipProps="tooltipProps"
    >
      <!-- 自定义 status 字段显示 -->
      <template #status="{ field, index }">
        <el-tag :type="data.status === 1 ? 'success' : 'danger'">
          {{ field.options?.find(opt => opt.value === data.status)?.label || '-' }}
        </el-tag>
      </template>
      
      <!-- 自定义 amount 字段显示 -->
      <template #amount="{ field, index }">
        <span style="color: #ff4d4f; font-weight: bold;">
          ¥{{ data.amount.toLocaleString() }}
        </span>
      </template>
    </CFieldList>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CFieldList } from 'cimos7-vue3-common';

const data = ref({
  id: 1001,
  name: '办公电脑',
  type: '电子设备',
  status: 1,
  amount: 50000,
  purchaseDate: '2023-06-15'
});

const fields = ref([
  { id: 'id', cnName: '资产编号', type: 'number' },
  { id: 'name', cnName: '资产名称' },
  { id: 'type', cnName: '资产类型' },
  { id: 'status', cnName: '资产状态' },
  { id: 'amount', cnName: '资产金额', type: 'number' },
  { id: 'purchaseDate', cnName: '购置日期', type: 'date' }
]);

const tooltipProps = ref({
  placement: 'bottom',
  effect: 'light',
  showAfter: 300
});
</script>
```

### 隐藏特定字段

```vue
<template>
  <div style="padding: 1.25rem;">
    <CFieldList
      :fields="fields"
      :data="data"
      title="订单详情"
      :columnsCount="2"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CFieldList } from 'cimos7-vue3-common';

const data = ref({
  orderNo: 'ORD202401150001',
  productName: '测试产品',
  price: 99.99,
  quantity: 2,
  totalAmount: 199.98,
  createTime: '2024-01-15T10:30:00.000Z'
});

const fields = ref([
  { id: 'orderNo', cnName: '订单编号' },
  { id: 'productName', cnName: '产品名称' },
  { id: 'price', cnName: '单价', type: 'number' },
  { id: 'quantity', cnName: '数量', type: 'number' },
  { id: 'totalAmount', cnName: '总金额', type: 'number' },
]);
</script>
```

---

## CnUpload 组件

### 功能特性

- 支持单文件/多文件上传
- 文件大小限制
- 文件类型限制（accept）
- 支持上传前拦截（beforeUpload）
- 拖拽上传支持
- 文件列表拖拽排序
- 上传进度显示
- 上传中断支持
- 文件预览功能
- 文件删除功能
- 文件下载功能
- 支持 Element Plus 原生属性透传
- 内置多种文件类型图标（图片、视频、音频、PDF、Word、Excel、压缩包等）

### Props 参数

| 参数 | 说明 | 类型 | 默认值 | 必填 |
|------|------|------|--------|------|
| modelValue | 已上传的文件列表（双向绑定） | Array | [] | 否 |
| multiple | 是否多选 | Boolean | true | 否 |
| accept | 接受的文件类型 | String | '*' | 否 |
| limit | 最大文件数量 | Number | 10 | 否 |
| maxSize | 单个文件最大大小（字节），默认 300MB | Number | 300 * 1024 * 1024 | 否 |
| uploadPath | 上传接口地址 | String | - | 是 |
| downloadPath | 下载接口地址（可选） | String | '' | 否 |
| moduleId | 模块ID（可选，默认从 sessionStorage 获取） | String | sessionStorage.getItem('moduleId') \|\| '' | 否 |
| beforeUpload | 上传前拦截函数，返回 false 可阻止上传 | Function | () => true | 否 |
| dragDrag | 是否开启拖拽上传功能 | Boolean | false | 否 |

### Events 事件

| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| update:modelValue | 文件列表变化 | (fileList) |
| change | 文件列表变化（同 update:modelValue） | (fileList) |
| remove | 删除文件 | (file) |
| preview | 预览文件 | (file) |
| success | 上传成功 | (file) |
| error | 上传失败 | (file) |
| abort | 上传中断 | (file) |
| progress | 上传进度 | (file) |

### Slots 插槽

CnUpload 组件主要通过配置使用，暂无额外插槽。

### Exposed Methods 暴露方法

| 方法名 | 说明 | 参数 |
|--------|------|------|
| abortSingleFile | 中断单个文件上传 | (file) |

### 基础示例

```vue
<template>
  <div style="padding: 1.25rem;">
    <CnUpload
      v-model="fileList"
      :uploadPath="uploadPath"
      :downloadPath="downloadPath"
      :multiple="true"
      :limit="5"
      :maxSize="100 * 1024 * 1024"
      accept="image/*,video/*,application/pdf"
      @success="handleSuccess"
      @error="handleError"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CnUpload } from 'cimos7-vue3-common';

const fileList = ref([]);
const uploadPath = '/api/file/upload';
const downloadPath = '/api/file/download';

const handleSuccess = (file) => {
  console.log('上传成功:', file);
};

const handleError = (file) => {
  console.log('上传失败:', file);
};
</script>
```

### 使用 beforeUpload 拦截

```vue
<template>
  <div style="padding: 1.25rem;">
    <CnUpload
      v-model="fileList"
      :uploadPath="uploadPath"
      :beforeUpload="beforeUpload"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { CnUpload } from 'cimos7-vue3-common';

const fileList = ref([]);
const uploadPath = '/api/file/upload';

const beforeUpload = (files) => {
  // files 是即将上传的文件数组
  const valid = files.every(file => {
    if (file.size > 50 * 1024 * 1024) {
      ElMessage.error('单个文件大小不能超过 50MB');
      return false;
    }
    return true;
  });
  
  return valid;
};
</script>
```

### 开启拖拽上传和排序

```vue
<template>
  <div style="padding: 1.25rem;">
    <CnUpload
      v-model="fileList"
      :uploadPath="uploadPath"
      :dragDrag="true"
      @change="handleChange"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CnUpload } from 'cimos7-vue3-common';

const fileList = ref([]);
const uploadPath = '/api/file/upload';

const handleChange = (fileList) => {
  console.log('文件列表变化:', fileList);
};
</script>
```

### 单文件上传

```vue
<template>
  <div style="padding: 1.25rem;">
    <CnUpload
      v-model="fileList"
      :uploadPath="uploadPath"
      :multiple="false"
      :limit="1"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { CnUpload } from 'cimos7-vue3-common';

const fileList = ref([]);
const uploadPath = '/api/file/upload';
</script>
```

### 监听所有事件

```vue
<template>
  <div style="padding: 1.25rem;">
    <CnUpload
      v-model="fileList"
      :uploadPath="uploadPath"
      @success="handleSuccess"
      @error="handleError"
      @remove="handleRemove"
      @preview="handlePreview"
      @progress="handleProgress"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { CnUpload } from 'cimos7-vue3-common';

const fileList = ref([]);
const uploadPath = '/api/file/upload';

const handleSuccess = (file) => {
  ElMessage.success(`${file.filename} 上传成功`);
};

const handleError = (file) => {
  ElMessage.error(`${file.filename} 上传失败`);
};

const handleRemove = (file) => {
  console.log('删除文件:', file);
};

const handlePreview = (file) => {
  console.log('预览文件:', file);
};

const handleProgress = (file) => {
  console.log('上传进度:', file.filename, file._temp?.percent + '%');
};
</script>
```

### 上传后数据格式说明

上传成功后，fileList 中每个文件对象通常包含以下字段：

```javascript
{
  id: '文件ID',
  filename: '文件名',
  type: '文件MIME类型',
  requestPath: '文件访问路径',
  // 其他后端返回的字段
}
```

---

## 更新日志

### v1.8.0
- CTable: 新增 render 函数支持，优先级最高
- CTable: 新增 component 动态组件配置支持
- CTable: 新增 slotName 配置项，配合自定义插槽使用
- CTable: 渲染优先级：slotName → component → render → formatter → 默认
- CTable: 更新文档，添加 render 和 slotName 使用说明和示例
- TableLayout: 支持传递自定义插槽到内部 CTable（通过列配置的 slotName）
- TableLayout: 更新文档，添加 TableLayout 中使用 render 和 slotName 的示例

### v1.7.0
- IconPark: 新增 tooltip-content slot，支持自定义 tooltip 内容
- IconPark: 更新文档，添加 tooltip-content slot 使用说明和示例

### v1.6.0
- TableLayout: search 和 reset 事件新增 pageInfo 参数传递
- TableLayout: 父组件请求列表数据时需要带上 pageInfo（包含 pageIndex、pageSize、total）
- TableLayout: 更新文档，说明 search/reset 事件新的参数格式和使用方法

### v1.5.0
- 新增 CnUpload 组件：文件上传组件，支持多文件上传、拖拽上传、拖拽排序、上传进度、上传中断等功能
- CnUpload: 内置多种文件类型图标（图片、视频、音频、PDF、Word、Excel、压缩包等）
- CnUpload: 支持 beforeUpload 拦截、文件大小限制、文件类型限制
- 更新文档，添加 CnUpload 组件使用说明

### v1.4.0
- SearchHeader: 新增 bindProps 统一属性绑定方式，兼容旧的特定类型 props
- SearchHeader: 优先级：bindProps > inputProps/selectProps/dateProps 等
- SearchHeader: 更新文档，添加 bindProps 使用说明和示例

### v1.3.0
- SearchHeader: 新增 treeSelect、radioGroup、inputTag、inputNumber、checkbox 类型支持
- SearchHeader: 优化重置逻辑，根据不同类型设置正确的默认值
- SearchHeader: 更新文档，添加新类型的使用说明和示例

### v1.2.0
- SearchHeader: 重构插槽的显示逻辑，插槽内容作为 el-col 参与 24 栅格布局
- SearchHeader: 折叠时，插槽内容与搜索配置项一起计算显示（按顺序：searchLeft → 搜索项 → searchMiddle → searchBtnAfter）
- 更新组件使用文档，说明新的插槽折叠逻辑

### v1.1.0
- SearchHeader: 考虑插槽内容占用的 span，优化展开/收起逻辑
- SearchHeader: 修复插槽内容影响行数计算的问题
- 更新组件使用文档，添加插槽预留 span 的使用说明和示例

### v1.0.0
- 新增 CFieldList 详情信息展示组件
- CFieldList: 支持多种数据类型自动渲染（date/datetime/number/boolean）
- CFieldList: 支持 Grid 布局，可自定义列数和间距
- CFieldList: 文本溢出时显示省略号，仅在溢出时显示 tooltip
- CFieldList: 支持自定义 tooltip 属性
- CFieldList: 支持自定义插槽渲染特定字段
- CFieldList: 支持隐藏特定字段（hide=1）
- CFieldList: 支持 options 配置下拉选项映射
- CFieldList: 支持 _text 后缀字段优先显示文本值
- 更新组件使用文档，添加 CFieldList 组件详细说明和示例

### v0.9.0
- TableLayout: searchOptions 设为 false/null/undefined 或空数组时不显示搜索区域
- TableLayout: 更新文档，添加不显示搜索区域的示例

### v0.8.0
- CTable: 新增 columnsList 属性（选中的列 key 数组）
- CTable: 新增 hasTableHandler 属性（是否有表格操作栏）
- CTable: 通过 columnsList 控制显示哪些列
- TableLayout: 移除 hasSearchCard 属性
- TableLayout: 内部自动处理表格操作栏显示逻辑
- SearchHeader: 移除 hasSearchCard 属性
- SearchHeader: 移除 toggleChartCard 事件
- SearchHeader: 移除 chartCard 插槽
- 更新组件使用文档

### v0.7.0
- TableLayout: 重构组件结构，移除 PageLayout 包裹，组件独立使用
- TableLayout: 新增 tableHandler 和 tableHandlerRight 插槽（表格操作栏）
- TableLayout: 新增 showSumData 属性（是否显示统计数据）
- TableLayout: 新增 showMultiTip 属性（是否显示批量操作组件）
- TableLayout: 新增 changeColumns 事件（列选择变化）
- TableLayout: 新增 changeColumns 暴露方法
- TableLayout: 内置列选择器功能
- TableLayout: 优化统计数据卡片展示
- TableLayout: 更新完整示例，展示所有新插槽使用
- 更新组件使用文档

### v0.6.0
- TableLayout: 新增 gutter 属性（SearchHeader 栅格间隔）
- TableLayout: 新增 total 属性（静态数据模式下的总条数）
- TableLayout: 新增 loadData 事件（静态数据模式下的加载事件）
- TableLayout: 新增 search、reset、loadList、getParams、pageInfo 暴露方法
- TableLayout: 支持 Element Plus el-table 所有原生属性透传（v-bind="$attrs"）
- TableLayout: 使用 PageLayout 作为外层布局容器
- IconPark: 新增 tooltip 功能
- IconPark: 新增 tooltip、tooltipPlacement 属性
- FormItem: 重写为标准 Vue 单文件组件
- FormItem: 支持 v-bind="$attrs"，修复表单校验问题
- 更新使用文档，添加新功能说明

### v0.5.1
- IconPark: 新增边框功能
- IconPark: 边框颜色随 hover 变化
- IconPark: 新增 bordered、borderWidth、borderRadius、borderColor、borderHoverColor、padding 属性
- 更新使用文档，添加边框功能说明

### v0.5.0
- 新增 IconPark 图标组件
- IconPark: 支持 hover 变色、点击事件、多种主题
- 更新使用文档，添加 IconPark 组件说明

### v0.4.0
- CTable: 新增动态列选择功能
- CTable: 新增高度自适应功能
- CTable: 新增 customHeight 属性
- CTable: 新增 calcTableHeight 暴露方法
- SearchHeader: 新增展开/收起功能
- SearchHeader: 新增 defaultRows 属性
- TableLayout: 新增高度自适应布局
- 更新使用文档

### v0.3.0
- SearchHeader: 完善 linkage 联动功能，支持 show、enable 配置，支持数组 dependValue
- 新增完整的组件使用文档
- 丰富 C1.vue、C2.vue 示例文件的注释
