<div align="center">
[English](./README.md) | 简体中文
</div>
# Vue UI Kit 使用文档

基于 Ant Design Vue 的 Vue 3 UI 组件库，提供增强的表单、网格和实用组件。

## 安装

```bash
npm install @vue-ui-kit/ant
# 或
yarn add @vue-ui-kit/ant
# 或
pnpm add @vue-ui-kit/ant
```

```ts
/*main.ts*/
import { createApp } from 'vue';
import App from './App.vue';
import Antd from 'ant-design-vue';
import UIKit from '@vue-ui-kit/ant';
import '@vue-ui-kit/ant/scss';
/*创建渲染器(推荐使用tsx配置)*/
import { setupKit } from './setup/kit.tsx';

/*创建格式化*/
UIKit.addFormatter({
  test: ({ row, column, cellValue }) => 'test:...' + 'field:' + column.field + '...v:' + cellvalue,
});
createApp(App).use(Antd).use(UIKit).mount('#app');
```

```tsx
/*kit.tsx*/
import UIKit from '@vue-ui-kit/ant';
import { TypographyParagraph, Switch } from 'ant-design-vue';

export const setupKit = () => {
  /*注册$paragraph作为单元格渲染器*/
  UIKit.addRender('$paragraph', {
    renderDefault({ props = {} }: RenderOptions, { row, field }: RenderTableParams) {
      const content = props.getContent?.({ row, field }) ?? (valued(field) ? row[field!] : '');
      return valued(field) ? (
        <TypographyParagraph
          {...merge({}, defaultProps.$paragraph, omit(props, ['content', 'getContent']))}
          content={content}
        />
      ) : null;
    },
  });
  /*注册$switch 作为表单渲染器*/
  UIKit.addRender('$switch', {
    renderItemContent(
      { props = {}, events = {} }: RenderOptions,
      { data, field }: RenderFormParams,
    ) {
      return valued(field) ? (
        <Switch
          v-model:checked={data[field!]}
          {...props}
          onChange={(...arg) => {
            events.change?.({ data, field }, ...arg);
          }}
        />
      ) : null;
    },
  });
};

/*
* 已内置了渲染器
* 
  $input: Input,
  AInput: Input,
  $textarea: Textarea,
  Textarea: Textarea,
  $number: InputNumber,
  AInputNumber: InputNumber,
  $select: Select,
  ASelect: Select,
  $date: DatePicker,
  ADatePicker: DatePicker,
  $range: RangePicker,
  ARangePicker: RangePicker,
  AAutoComplete: AutoComplete,
  $Cascader: Cascader,
  ACascader: Cascader,
  ACheckbox: Checkbox,
  AMentions: Mentions,
  ARate: Rate,
  ASlider: Slider,
  $time: TimePicker,
  ATimePicker: TimePicker,
  ATreeSelect: TreeSelect,

* 
* */
```

## 环境要求

- Node.js >= 16
- Vue >= 3.2.0
- ant-design-vue >= 4.0.0
- @ant-design/icons-vue >= 7.0.0

## 组件

### PGrid

增强的数据表格组件，集成了查询表单、分页和工具栏功能。

#### 基础示例

```vue
<script setup lang="ts">
import { computed } from 'vue';
import { PGridProps, labelColDict } from '@vue-ui-kit/ant';

const gridSetting = computed<PGridProps<Student, { keyword?: string }>>(() => ({
  columns: [
    { field: 'name', title: '姓名', width: 200 },
    { field: 'email', title: '邮箱', width: 200 },
    { field: 'age', title: '年龄', width: 100 },
  ],
  formConfig: {
    searchItems: [
      {
        field: 'keyword',
        title: '关键字',
        labelCol: labelColDict[3],
        itemRender: {
          name: '$input',
          props: { placeholder: '请输入关键字...' },
        },
      },
    ],
  },
  proxyConfig: {
    ajax: {
      query: ({ form, page }) => api.getStudents({ ...form, ...page }),
    },
  },
}));
</script>

<template>
  <p-grid v-bind="gridSetting" />
</template>
```

### PForm

增强的表单组件，具有简化的配置和动态字段功能。

#### 属性

| 属性 | 类型 | 说明 | 默认值 |
| --- | --- | --- | --- |
| items | `PFormItemProps[]` | 表单项配置数组 | - |
| data | `T` | 表单数据对象 | - |
| customReset | `() => void` | 自定义重置函数 | - |
| labelCol | `ColProps` | 标签列布局 | `{ span: 6 }` |
| wrapperCol | `ColProps` | 包装列布局 | `{ span: 16 }` |
| ...其他 | `FormProps` | 所有 ant-design-vue Form 的属性 | - |

#### 事件

| 事件 | 说明 | 参数 |
| --- | --- | --- |
| apply | 表单提交时触发 | `(formData: T) => void` |
| reset | 表单重置时触发 | `() => void` |

#### PFormItemProps

| 属性 | 类型 | 说明 | 默认值 |
| --- | --- | --- | --- |
| field | `string` | 表单字段名 | - |
| title | `string` | 字段标签 | - |
| span | `number` | 栅格跨度 (1-24) | - |
| colon | `boolean` | 标签后显示冒号 | `true` |
| labelCol | `ColProps` | 标签列布局 | - |
| wrapperCol | `ColProps` | 包装列布局 | - |
| forceRequired | `boolean` | 显示必填标记（仅视觉效果） | `false` |
| align | `'left' \| 'right' \| 'center'` | 文本对齐方式 | `'left'` |
| col | `ColProps` | 栅格列属性 | - |
| rule | `Rule[]` | 验证规则 | - |
| itemRender | `ItemRender` | 字段渲染器配置 | - |
| tooltipConfig | `TooltipConfig` | 提示配置 | - |
| slots | `object` | 自定义插槽渲染器 | - |

#### 内置渲染器

- `$input`: 输入框
- `$textarea`: 文本域
- `$number`: 数字输入框
- `$select`: 下拉选择
- `$date`: 日期选择器
- `$range`: 日期范围选择器
- `$time`: 时间选择器
- `ASwitch`: 开关组件
- `ACheckbox`: 复选框
- `ARate`: 评分组件
- `ASlider`: 滑动输入条

#### 基础示例

```vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { PFormProps } from '@vue-ui-kit/ant';

interface UserForm {
  name: string;
  email: string;
  age?: number;
  gender: string;
  skills: string[];
}

const formData = ref<UserForm>({
  name: '',
  email: '',
  age: undefined,
  gender: '',
  skills: [],
});

const formSetting = computed<PFormProps<UserForm>>(() => ({
  items: [
    {
      field: 'name',
      title: '姓名',
      span: 12,
      rule: [{ required: true, message: '请输入姓名' }],
      itemRender: {
        name: '$input',
        props: { placeholder: '请输入姓名' },
      },
    },
    {
      field: 'email',
      title: '邮箱',
      span: 12,
      rule: [
        { required: true, message: '请输入邮箱' },
        { type: 'email', message: '邮箱格式不正确' },
      ],
      itemRender: {
        name: '$input',
        props: { placeholder: '请输入邮箱' },
      },
    },
    {
      field: 'gender',
      title: '性别',
      span: 12,
      rule: [{ required: true, message: '请选择性别' }],
      itemRender: {
        name: '$select',
        props: {
          placeholder: '请选择性别',
          options: [
            { label: '男', value: 'male' },
            { label: '女', value: 'female' },
          ],
        },
      },
    },
    {
      field: 'skills',
      title: '技能',
      span: 24,
      itemRender: {
        name: '$select',
        props: {
          mode: 'multiple',
          placeholder: '请选择技能',
          options: [
            { label: 'Vue.js', value: 'vue' },
            { label: 'React', value: 'react' },
            { label: 'Angular', value: 'angular' },
          ],
        },
      },
    },
  ],
}));

const handleSubmit = (data: UserForm) => {
  console.log('表单提交:', data);
};
</script>

<template>
  <p-form 
    v-bind="formSetting" 
    :data="formData"
    @apply="handleSubmit"
  />
</template>
```

#### 自定义插槽示例

```vue
<script setup lang="tsx">
import { ref, computed } from 'vue';

const formData = ref({
  name: '',
  isActive: false,
});

const formSetting = computed(() => ({
  items: [
    {
      field: 'isActive',
      title: '状态',
      span: 24,
      slots: {
        default: ({ data }) => (
          <a-switch
            v-model:checked={data.isActive}
            checkedChildren="启用"
            unCheckedChildren="禁用"
          />
        ),
      },
    },
  ],
}));
</script>
```

### PSearchForm

基于 `PForm` 的查询表单。只需传入查询字段，组件会自动在末尾添加查询、重置按钮，并在字段仅占一行时保持操作列同行。标签默认按文字内容自适应宽度，查询控件默认宽度为 `100%`；可通过 `itemRender.attrs.style.width` 覆盖。

```vue
<p-search-form
  :data="queryData"
  :items="searchItems"
  :loading="loading"
  @search="handleSearch"
  @reset="handleReset"
/>
```

`PGrid` 和 `PCanvasGrid` 可通过 `formConfig.searchItems` 使用相同能力；它优先于旧的 `formConfig.items`。旧配置仍按原样渲染，不会自动追加按钮。

| 属性 | 类型 | 说明 | 默认值 |
| --- | --- | --- | --- |
| items | `PFormItemProps[]` | 查询字段配置，不需要添加按钮项 | - |
| data | `T` | 查询数据对象 | - |
| loading | `boolean` | 查询按钮加载状态 | `false` |
| autoReset | `boolean` | 是否复用 PForm 原有 reset 流程重置 data | `true` |
| labelCol | `ColProps` | 标签布局 | `{ flex: 'none' }` |
| wrapperCol | `ColProps` | 控件布局 | `{ flex: 'auto' }` |
| ...其他 | `PFormProps` | 透传给 PForm 的属性 | - |

| 事件 | 说明 | 参数 |
| --- | --- | --- |
| search | 点击查询或提交表单时触发 | `(data: T) => void` |
| reset | 点击重置时触发 | `() => void` |

### PFormGroup

动态表单组组件，用于管理多个表单实例，支持添加/删除功能。

#### 属性

| 属性 | 类型 | 说明 | 默认值 |
| --- | --- | --- | --- |
| v-model | `Array<T & { __index: number }>` | 表单组数据数组 | `[]` |
| getFormSetting | `(data: T) => PFormProps<T>` | 获取表单配置的函数 | - |
| title | `string` | 组标题 | - |
| tabLabel | `string` | 自定义标签页标签模板 | - |
| editAble | `boolean` | 标签页是否可编辑 | `true` |
| showAdd | `boolean` | 显示添加按钮 | `true` |
| lazyErrorMark | `boolean` | 延迟错误标记 | `false` |
| forceRender | `boolean` | 强制渲染所有标签页 | `false` |
| keepSerial | `boolean` | 保持连续编号 | `false` |
| loading | `boolean` | 加载状态 | `false` |
| max | `number` | 最大项目数量 | `Infinity` |
| itemMenus | `GroupMenuItem[]` | 自定义菜单项 | 默认复制/删除 |
| createItem | `(opts: { list: T[] }) => Promise<T>` | 自定义项目创建器 | - |
| menuHandler | `GroupMenuItemHandler<T>` | 自定义菜单处理器 | - |

#### 暴露的方法

| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| validateAll | 验证所有表单实例 | - | `Promise<void>` |
| validate | 验证特定表单实例 | `__index: number` | `Promise<void>` |
| setActiveKey | 设置活动标签页 | `key: number` | `void` |

#### 基础示例

```vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { PFormGroupProps } from '@vue-ui-kit/ant';

interface Project {
  __index: number;
  name: string;
  startDate: string;
  endDate: string;
  budget?: number;
}

const projects = ref<Project[]>([
  {
    __index: 0,
    name: '项目A',
    startDate: '',
    endDate: '',
    budget: undefined,
  }
]);

const groupSetting = computed<PFormGroupProps<Project>>(() => ({
  title: '项目管理',
  showAdd: true,
  max: 10,
  getFormSetting: (data) => ({
    items: [
      {
        field: 'name',
        title: '项目名称',
        span: 24,
        rule: [{ required: true, message: '请输入项目名称' }],
        itemRender: {
          name: '$input',
          props: { placeholder: '请输入项目名称' },
        },
      },
      {
        field: 'startDate',
        title: '开始日期',
        span: 12,
        rule: [{ required: true, message: '请选择开始日期' }],
        itemRender: {
          name: '$date',
          props: { placeholder: '请选择开始日期' },
        },
      },
      {
        field: 'endDate',
        title: '结束日期',
        span: 12,
        rule: [{ required: true, message: '请选择结束日期' }],
        itemRender: {
          name: '$date',
          props: { placeholder: '请选择结束日期' },
        },
      },
      {
        field: 'budget',
        title: '预算',
        span: 24,
        itemRender: {
          name: '$number',
          props: {
            min: 0,
            placeholder: '请输入预算',
            formatter: (value) => `￥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','),
            parser: (value) => value.replace(/￥\s?|(,*)/g, ''),
          },
        },
      },
    ],
  }),
}));

const handleSave = async () => {
  try {
    await groupRef.value?.validateAll();
    console.log('所有项目已保存:', projects.value);
  } catch (error) {
    console.error('验证失败:', error);
  }
};

const groupRef = ref();
</script>

<template>
  <div>
    <p-form-group 
      ref="groupRef"
      v-model="projects"
      v-bind="groupSetting"
    />
    <a-button type="primary" @click="handleSave">
      保存所有项目
    </a-button>
  </div>
</template>
```

#### 自定义菜单示例

```vue
<script setup lang="ts">
import { ref, computed } from 'vue';

const projects = ref([]);

const groupSetting = computed(() => ({
  title: '高级项目管理',
  itemMenus: [
    { content: '复制', code: 'copy' },
    { content: '删除', code: 'delete' },
    { content: '导出', code: 'export' },
    { 
      content: '归档', 
      code: 'archive',
      visibleMethod: ({ data }) => data.status !== 'archived'
    },
  ],
  createItem: async ({ list }) => {
    return {
      name: `项目 ${list.length + 1}`,
      status: 'active',
      startDate: new Date().toISOString().split('T')[0],
    };
  },
  menuHandler: ({ code, data, index }) => {
    switch (code) {
      case 'export':
        exportProject(data);
        break;
      case 'archive':
        archiveProject(data, index);
        break;
    }
  },
  getFormSetting: (data) => ({
    // ... 表单配置
  }),
}));
</script>
```

## 工具函数

### labelColDict

为不同文本长度预定义的标签列配置：

```ts
import { labelColDict } from '@vue-ui-kit/ant';

// 在表单项中使用
{
  field: 'shortField',
  title: '姓名', // 2个字符
  labelCol: labelColDict[2],
}

{
  field: 'longerField', 
  title: '项目描述', // 4+个字符
  labelCol: labelColDict[4],
}
```

### 自定义渲染器

注册自定义表单字段渲染器：

```tsx
import UIKit from '@vue-ui-kit/ant';
import { Switch } from 'ant-design-vue';

UIKit.addRender('$switch', {
  renderItemContent(
    { props = {}, events = {} },
    { data, field }
  ) {
    return (
      <Switch
        v-model:checked={data[field]}
        {...props}
        onChange={(...args) => {
          events.change?.({ data, field }, ...args);
        }}
      />
    );
  },
});
```

### 自定义格式化器

为 PGrid 注册自定义单元格格式化器：

```ts
UIKit.addFormatter({
  currency: ({ cellValue }) => `￥${cellValue?.toLocaleString() || '0'}`,
  percentage: ({ cellValue }) => `${(cellValue * 100).toFixed(2)}%`,
});
```

## 常见问题

### 1. 使用 computed + v-bind 生成动态表格+表单时，表单选项更新导致表格重新渲染，如何解决？

答案：将 formConfig 分离为另一个计算属性

```vue
<p-grid v-bind="gridSetting" :form-config="computedFormConfig" />
```

### 2. 表格 ajax 目前只支持 multiDelete 和表格数据获取吗？

答案：编辑详情在大多数情况下无法与表格一一对应，所以没有开发意义。多行删除需要配合使用：

```ts
selectConfig: {
  multiple: true,
},
toolbarConfig: {
  buttons: {
    code: "multipleDelete"
  }
}
```

### 3. 为什么推荐 computed 方式？

答案：它简化了所有动态逻辑。对于复杂场景，您可以尝试使用 reactive 并自己维护，但在大多数情况下，computed 更方便。

## TypeScript 支持

该库完全使用 TypeScript 编写，并提供全面的类型定义：

```ts
import type { 
  PFormProps, 
  PFormItemProps,
  PFormGroupProps,
  PGridProps,
  ColumnProps 
} from '@vue-ui-kit/ant';
```

## 许可证

MIT
