npm package: @qingbing/ts-v3-xz-form
Description: 以 vue3 + element-plus 为基础封装的 form 表单组件
Author: qingbing
Latest version: 2.0.45

# XzForm 插件介绍

- [XzForm 插件介绍](#xzform-插件介绍)
  - [1. 概要说明](#1-概要说明)
    - [1.1 地址](#11-地址)
    - [1.2 插件描述](#12-插件描述)
    - [1.3 重要依赖](#13-重要依赖)
    - [1.4 插件安装](#14-插件安装)
  - [2. 包说明](#2-包说明)
    - [2.1 属性说明](#21-属性说明)
    - [2.2 事件说明](#22-事件说明)
    - [2.3 实例暴露说明](#23-实例暴露说明)
  - [3. 配置工具介绍](#3-配置工具介绍)
    - [3.1 组件工具的引入和基础使用](#31-组件工具的引入和基础使用)
    - [3.2 工具入口原型](#32-工具入口原型)
    - [3.3 工具使用主要掌握以下配置规则即可](#33-工具使用主要掌握以下配置规则即可)
      - [3.3.1 配置规则原型](#331-配置规则原型)
      - [3.4.1 TXzFormItem 属性说明](#341-txzformitem-属性说明)
      - [3.4.2 TXzFormItem.input\_type 说明](#342-txzformiteminput_type-说明)
      - [3.4.3 TXzFormItem.rules(TXzFormRuleItem\[\]) 说明](#343-txzformitemrulestxzformruleitem-说明)
      - [3.4.3 TXzFormItem.rules(TXzFormRuleItem\[\]).type 说明](#343-txzformitemrulestxzformruleitemtype-说明)
      - [3.4.4 TXzFormItem.exts 说明](#344-txzformitemexts-说明)
  - [4. item 生成工具](#4-item-生成工具)
  - [5. 示例](#5-示例)
    - [5.1 建议全局注册验证提示的中文包，不注册也不影响组件使用，那样每个规则最好都带上 message 就好](#51-建议全局注册验证提示的中文包不注册也不影响组件使用那样每个规则最好都带上-message-就好)
    - [5.2 全局注册使用](#52-全局注册使用)
    - [3.2 模板组件使用](#32-模板组件使用)

## 1. 概要说明
### 1.1 地址

- 源代码: [https://gitee.com/duqingbing/ts-v3-package/tree/ts-v3-xz-form](https://gitee.com/duqingbing/ts-v3-package/tree/ts-v3-xz-form)
- 参考网址: [https://element-plus.org/zh-CN/component/overview.html](https://element-plus.org/zh-CN/component/overview.html)

### 1.2 插件描述

以 vue3 + element-plus 为基础封装的 form 表单组件

### 1.3 重要依赖

- @element-plus/icons-vue: ^2.3.1
- @qingbing/ts-v3-utils: ^1.0.1
- @qingbing/ts-v3-xz-editor: ^1.0.1
- @qingbing/ts-v3-element-plus: ^2.1.3
- @qingbing/ts-v3-json-editor: ^2.1.6
- @qingbing/ts-v3-md-editor: ^2.0.4
- axios: ^1.6.8
- element-plus: ^2.6.3
- vue: ^3.4.21

### 1.4 插件安装

```bash
# yarn 安装
yarn add @qingbing/ts-v3-xz-form

# npm 安装
npm i @qingbing/ts-v3-xz-form
```

## 2. 包说明

### 2.1 属性说明

该包主要封装了 vue3 下表单组件的使用， 重要提供以下五个属性参数

| 属性名     | 类型                        | 是否必需 | 默认值 | 意义               |
| :--------- | :-------------------------- | :------- | :----- | :----------------- |
| isForm     | boolean                     | 否       | true   | 是否作为表单来处理 |
| items      | Record<string, TXzFormItem> | 是       | -      | 传递的字段结果集   |
| formData   | TRecord                     | 是       | -      | 表单操作数据       |
| viewFields | string[]                    | 是       | []     | 信息展示字段       |
| showFields | string[]                    | 是       | []     | form编辑字段       |

该组件的特殊之处， 在于不需要自己手动构建以上后续的四个参数， 组件提供了一个针对组件参数的工具，使用和理解该工具的配置，该组件也就没有难点了。

### 2.2 事件说明

| 事件名 | 类型 | 意义 |
| :----- | :--- | :--- |
| -      | 无   | -    |

### 2.3 实例暴露说明

| 属性名 | 类型 |
| :----- | :--- |
| -      | 无   |

## 3. 配置工具介绍

### 3.1 组件工具的引入和基础使用

```ts
import { XzFormItemParse } from '@qingbing/ts-v3-xz-form'

const { items, viewFields, showFields, formData, rules } = XzFormItemParse.instance([
    {
        input_type: "text-view",
        field: "uid",
        default: "11",
        label: "UID",
        rules: [{ type: "required" }],
    },
    {
        input_type: "input-text",
        field: "username",
        default: "",
        label: "用户名",
        rules: [{ required: true, type: "username" }],
    }
])
    .setViewFields(['username']) // 设置展示字段，设置后的字段，无论设置的 输入类型 如何， 得到的结果就是采取较好的方式提供用户浏览
    .setShowFields(['async_remote']) // 表单显示字段, 不设置显示全部
    .done() // 解析完毕得到结果
```

### 3.2 工具入口原型

> 如果向了解具体的 验证规则解析，参考 XzFormRuleParse 即可， 这里不赘述。

```ts
export declare class XzFormItemParse {
    /**
     * 获取字段处理实例
     * @param items 表单配置项目
     * @param formData 字段默认赋值
     * @returns XzFormItemParse
     */
    static instance(items: TXzFormItem[], formData?: TRecord): XzFormItemParse;
    /**
     * @param fields 展示字段集合
     * @returns XzFormItemParse
     */
    setViewFields(fields: string[]): XzFormItemParse;
    /**
     * @param fields 表单显示字段, 不设置显示全部
     * @returns XzFormItemParse
     */
    setShowFields(fields: string[]): XzFormItemParse;
    /**
     * 解析字段及规则信息并返回
     *
     * @returns { items, viewFields, showFields, formData, rules }
         */
    done(): {
         items: TXzFormItem[];
         viewFields: string[];
         showFields: string[];
         formData: TRecord;
         rules: Record<string, FormItemRule[]>;
    };
}
```

### 3.3 工具使用主要掌握以下配置规则即可

#### 3.3.1 配置规则原型

```ts
import type { TObject, TRecord } from '@qingbing/ts-v3-utils'
import type { FormItemRule, UploadRequestOptions } from 'element-plus'
import type { RuleType, SyncValidateResult, Value } from 'async-validator'

export type TXzFormOptions = {
    name: string
}

// 支持的表单输入方式
export type TXzFormInputType =
    | 'text-view' // 文本显示
    | 'input-text' // 文本框编辑
    | 'input-password' // 密码框编辑
    | 'input-area' // 文本域编辑
    | 'input-number' // 数字输入
    | 'input-radio' // 单选按钮组
    | 'input-checkbox' // 复选按钮组
    | 'input-select' // 选择框
    | 'switch' // 开关控制
    | 'cascader' // 级联选择
    | 'slider' // 滑块输入
    | 'rate' // 评分
    | 'color' // 取色器
    | 'uploader' // 上传图片
    | 'time-select' // 时间选择器
    | 'time-picker' // 时间选择器
    | 'date-picker' // 日期选择器
    | 'auto-complete' // 自动补全输入
    | 'json-editor' // json-editor
    | 'md-editor' // md-editor
    | 'content-editor' // text-editor

// 表单验证类型扩展
export type TXzFormRuleItem = Omit<FormItemRule, 'type'> & {
    type?:
    | RuleType
    | 'required'
    | 'id-card' // 身份证号
    | 'qq' // qq
    | 'username' // 用户名
    | 'password' // 密码
    | 'zipcode' // 邮编
    | 'mobile' // 手机
    | 'phone' // 座机
    | 'contact' // 联系人（手机 | 座机）
    | 'fax' // 传真
    | 'ipv4' // ipv4 地址
    | 'color' // 颜色值
    | 'rate' // 评分
    | 'slider' // 滑块
    | 'switch' // 开关
    | 'confirm' // 确认
    | 'callback' // 回调函数
    fullField?: string // bug for element-plus
    params?: TObject // 回调或后端请求时携带的额外参数
    callback?: TXzFormCallback // 同步回调函数，配合 type == callback 使用
    asyncCallback?: TXzFormAsyncCallback // 异步回调函数，配合 type == callback 使用, 异步和同步可以同时使用，不建议
}

// 表单项目的配置字段配置类型
export type TXzFormItem = {
    field: string // 操作字段
    label: string // 显示标签
    default?: any // 默认值
    input_type: TXzFormInputType // 表单输入类型
    exts?: TRecord // 额外参数
    rules?: TXzFormRuleItem[] // 规则
}

/**
 * 项目中的 option 选项类型
 */
export type TXzFormInputOption = {
    id?: any,
    value: PropertyKey,
    label: string
}

/**
 * 异步验证回调函数
 */
export type TXzFormAsyncCallback = (
    callback: (error?: string | Error) => void,
    value: unknown,
    formData: TRecord,
    params: TRecord
) => void | Promise<void>

/**
 * 同步验证回调函数
 */
export type TXzFormCallback = (
    callback: (error?: string | Error) => void,
    value: Value,
    formData: TRecord,
    params: TRecord
) => SyncValidateResult | void

/**
 * select 远端搜索并获取选项卡的函数类型
 */
export type TXzFormRemoteOption = (
    params: TRecord,
    formData: TRecord,
    callback: ((ops: TXzFormInputOption[]) => void)
) => void

/**
 * 自定义上传的函数类型
 */
export type TXzFormCustomUpload = (
    options: UploadRequestOptions,
    formData: TRecord,
    callback: {
        success: (fileUrl: string, message?: string) => void,
        failure: (message: string) => void,
    }
) => void
```

#### 3.4.1 TXzFormItem 属性说明

| key        | 类型              | 必须 | 意义         |
| :--------- | :---------------- | :--- | :----------- |
| field      | string            | 是   | 操作字段     |
| label      | string            | 是   | 显示标签     |
| default    | any               | 否   | 默认值       |
| input_type | TXzFormInputType  | 是   | 表单输入类型 |
| exts       | TRecord           | 否   | 额外参数     |
| rules      | TXzFormRuleItem[] | 否   | 规则         |

#### 3.4.2 TXzFormItem.input_type 说明

| 值             | 类型         |
| :------------- | :----------- |
| text-view      | 文本显示     |
| input-text     | 文本框编辑   |
| input-password | 密码框编辑   |
| input-area     | 文本域编辑   |
| input-number   | 数字输入     |
| input-radio    | 单选按钮组   |
| input-checkbox | 复选按钮组   |
| input-select   | 选择框       |
| switch         | 开关控制     |
| cascader       | 级联选择     |
| slider         | 滑块输入     |
| rate           | 评分         |
| color          | 取色器       |
| uploader       | 上传图片     |
| time-select    | 时间选择器   |
| time-picker    | 时间选择器   |
| date-picker    | 日期选择器   |
| auto-complete  | 自动补全输入 |
| json-editor    | json-editor  |
| md-editor      | md-editor    |
| content-editor | text-editor  |

#### 3.4.3 TXzFormItem.rules(TXzFormRuleItem[]) 说明

> 规则参考 [`async-validator.RuleItem`](https://github.com/yiminghe/async-validator), 这里介绍下增加或不同的内容

| 值            | 类型                 | 必填 | 说明                                                                                     |
| :------------ | :------------------- | :--- | :--------------------------------------------------------------------------------------- |
| type          | string               | 否   | **支持原有的所有类型，同时还提供几种扩展类型**                                           |
| fullField     | string               | 否   | 该字段不算新增，卡bug，验证不过时的字段替换位置，不设置使用 `TXzFormItem.label` 字段顶替 |
| params        | TObject              | 否   | 回调或后端请求时携带的额外参数                                                           |
| callback      | TXzFormCallback      | 否   | 同步回调函数，配合 type == callback 使用                                                 |
| asyncCallback | TXzFormAsyncCallback | 否   | 异步回调函数，配合 type == callback 使用                                                 |

> callback 和 asyncCallback 可以同时使用，不建议


#### 3.4.3 TXzFormItem.rules(TXzFormRuleItem[]).type 说明

> 组件支持所有原有的类型，同时扩展如下几种常用验证

| 值       | 类型                                            |
| :------- | :---------------------------------------------- |
| required | 必填，不必须的，常规可使用 `rule.required=true` |
| id-card  | 身份证号                                        |
| qq       | qq                                              |
| username | 用户名                                          |
| password | 密码                                            |
| zipcode  | 邮编                                            |
| mobile   | 手机                                            |
| phone    | 座机                                            |
| contact  | 联系人（手机，座机）                            |
| fax      | 传真                                            |
| ipv4     | ipv4 地址                                       |
| color    | 颜色值                                          |
| rate     | 评分                                            |
| slider   | 滑块                                            |
| switch   | 开关                                            |
| confirm  | 确认                                            |
| callback | 回调函数                                        |

#### 3.4.4 TXzFormItem.exts 说明

> 该参数主要提供了不同规则、不同输入类型的个性化属性，全部罗列在了该字段里面
> 对于输入参数，提供了 element-plus 官网组件属性中个人觉得有用的大部分属性
> 具体配置可参考底部示例， 提供几个组件参数配置地址参考:
> > json-editor: [@qingbing/ts-v3-json-editor](https://gitee.com/duqingbing/ts-v3-package/tree/ts-v3-json-editor/)
> > md-editor: [@qingbing/ts-v3-md-editor](https://gitee.com/duqingbing/ts-v3-package/tree/ts-v3-md-editor/)
> > content-editor: [@qingbing/ts-v3-xz-editor](https://gitee.com/duqingbing/ts-v3-package/tree/ts-v3-editor/)
> 
> 对于 element-plus 封装的表单项， 以 `switch` 为例: [属性参考网址](https://element-plus.org/zh-CN/component/switch.html#attributes)
> > 内部规则代码如下: (ps: 其他element-plus 组件的规则解析类似，不再赘述)

```ts
const binding: TRecord = {}

binding.size = getExtData<string>('size', 'default')
binding.activeText = getExtData<string>('active-text', '')
binding.inactiveText = getExtData<string>('inactive-text', '')

const fileType = typeof props.formData[props.item.field]
// active-value 处理
let activeValue = getExtData<string | number | boolean | undefined>('active-value', undefined)
if (activeValue === undefined) {
    switch (fileType) {
        case "string":
            activeValue = "1"
            break;
        case "number":
            activeValue = 1
            break;
        default:
            activeValue = true
            break;
    }
}
binding.activeValue = activeValue
// inactive-value 处理
let inactiveValue = getExtData<string | number | boolean | undefined>('inactive-value', undefined)
if (inactiveValue === undefined) {
    switch (fileType) {
        case "string":
            inactiveValue = "0"
            break;
        case "number":
            inactiveValue = 0
            break;
        default:
            inactiveValue = false
            break;
    }
}
binding.inactiveValue = inactiveValue

const width = getExtData<string | number | false>('width', false)
false !== width && (binding.width = width)

const activeIcon = getExtData<string | Component | false>('active-icon', false)
false !== activeIcon && (binding.activeIcon = activeIcon)

const inactiveIcon = getExtData<string | Component | false>('inactive-icon', false)
false !== inactiveIcon && (binding.inactiveIcon = inactiveIcon)

if (props.isText) {
    binding.disabled = true
} else {
    binding.disabled = getExtData<boolean>('disabled', false)
}
```

## 4. item 生成工具

提供 快速生成大多数 form-item 的工具 XzFormItemMachine, 基础的类型验证以包含, 额外的演奏需要自行 mixins. 使用示例如下

```ts
import { XzFormItemMachine } from '@qingbing/ts-v3-xz-form'

const items= {
    uid: mixins(XzFormItemMachine('textView', 'id', 'UID')),
    nickname: mixins(XzFormItemMachine('text', 'nickname', '用户昵称'), {
        rules: [{ type: "required" }],
    }),
}
```

## 5. 示例

### 5.1 建议全局注册验证提示的中文包，不注册也不影响组件使用，那样每个规则最好都带上 message 就好

```ts
// main.ts
import '@qingbing/ts-v3-xz-form/dist/messages'
```

### 5.2 全局注册使用

- 一旦注册， `XzForm` 作为组件全局通用 
- 使用方法参考 [4.2 模板组件使用](#32-模板组件使用), 去掉组件导入的语句即可
- 如果可以，建议全局安装并注册使用 @element-plus/icons-vue， 测试过程中组件手动引入时报错(或者全局注册下 Plus 组件即可，组件只在 uploader 时使用了该组件， 如果不使用 uploader 输入不注册也可)
- 对于样式的处理，md-editor 和 wangeditor 的样式比较大，最好按需导入， 如果不会使用到，建议不 import 到项目中

```ts
// main.ts

// icon 全导入并注册
// import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
//   app.component(key, component)
// }

// icon 导入并注册 Plus 插件
// import { Plus } from '@element-plus/icons-vue'
// app.component('Plus', Plus)

// 导入表单组件
import '@qingbing/ts-v3-xz-form/dist/style.css'
import { XzFormPlugin } from '@qingbing/ts-v3-xz-form'
app.use(XzFormPlugin, {
  name: 'XzForm',
  options: {}
})
```

### 3.2 模板组件使用

```vue
<template>
    <el-form ref="refForm" :model="formData" :rules="rules" label-width="160px">

        <XzForm :isForm="isForm" :items="items" :formData="formData" :viewFields="viewFields" :showFields="showFields" />

        <el-form-item>
            <el-button type="primary" @click="submitForm(refForm)">Create </el-button>
            <el-button @click="resetForm(refForm)">Reset</el-button>
        </el-form-item>
    </el-form>

    <el-row>
        <el-col :span="8" style="border-right: 1px solid #ccc;">
            <p>items</p>
            <pre><code>{{ items }}</code></pre>
        </el-col>
        <el-col :span="8" style="border-right: 1px solid #ccc;">
            <p>rules</p>
            <pre><code>{{ rules }}</code></pre>
        </el-col>
        <el-col :span="8">
            <p>formData</p>
            <pre><code>{{ formData }}</code></pre>
        </el-col>
    </el-row>
</template>

<script lang="ts" setup>
import 'md-editor-v3/lib/style.css'; // md-editor 编辑器样式
import 'md-editor-v3/lib/preview.css'; // md-editor 编辑器预览样式

import '@wangeditor/editor/dist/css/style.css' // wangeditor 样式表
import "@qingbing/ts-v3-xz-editor/dist/css/preview.css" // xz-editor 组件定义的一套编辑器基础样式

import '@qingbing/ts-v3-xz-form/dist/style.css' //  xz-form 组件样式(整合了 json-editor 和 xz-editor 组件样式)

import type { FormInstance, UploadRawFile } from 'element-plus'
import type { TRecord } from "@qingbing/ts-v3-utils";
import type { TXzFormRemoteOption, TXzFormCustomUpload, TXzFormItem } from "@qingbing/ts-v3-xz-form";
import { XzForm, XzFormItemParse, XzFormItemMachine } from "@qingbing/ts-v3-xz-form";
import { mixins } from "@qingbing/ts-v3-utils";
import { ref } from 'vue'
import request from "axios";

const isForm = ref(true)
const refForm = ref<FormInstance>()

const submitForm = (form: FormInstance | undefined) => {
    if (!form) { return }
    form.validate((valid) => {
        if (valid) {
            console.log('哦业，验证都通过了!')
        } else {
            console.log('哎呀，验证到有错误呢!')
            return false
        }
    })
}

const resetForm = (form: FormInstance | undefined) => {
    if (!form) { return }
    form.resetFields()
}

const fetchRemote = (data?: TRecord) => {
    return request.create({
        baseURL: 'http://mock.qiyezhu.net/mock/64e35b8e4a2b7b001dd2e4ec/example/ajax-test', // 默认就是 "/",
        timeout: 5000, // 5000毫秒，5秒
        headers: {
            'Content-Type': 'application/json;charset=utf-8'
        }
    }).post('/', data ?? {})
}

const remoteFunc: TXzFormRemoteOption = (params, _, cb) => {
    request.create({
        baseURL: 'http://mock.qiyezhu.net/mock/64e35b8e4a2b7b001dd2e4ec/example/select-options',
        timeout: 5000, // 5000毫秒，5秒
        headers: {
            'Content-Type': 'application/json;charset=utf-8'
        }
    })
        .post('/', params)
        .then(res => {
            cb(res.data.data)
        })
        .catch(err => err)
}

const customUpload: TXzFormCustomUpload = (options, data: TRecord, callback) => {
    const form = new FormData();
    form.append('file', options.file);
    form.append('name', 'qingbing');
    request.post('http://mock.qiyezhu.net/mock/64e35b8e4a2b7b001dd2e4ec/example/upload-avatar', form)
        .then(res => {
            callback.success(res.data.data.url, "ok")
        }).catch(err => callback.success(err.message))
}
const { items, viewFields, showFields, formData, rules } = XzFormItemParse.instance([
    {
        input_type: "text-view",
        field: "uid",
        default: "11",
        label: "UID",
        rules: [{ type: "required" }],
    },
    {
        input_type: "auto-complete",
        field: "suggestion",
        default: "",
        label: "建议输入",
        exts: {
            fetchSuggestions: (keyword: string, cb: (arg: any) => void) => {
                console.log(keyword);
                request.create({
                    baseURL: 'http://mock.qiyezhu.net/mock/64e35b8e4a2b7b001dd2e4ec/example/fetch-suggestion',
                    timeout: 5000, // 5000毫秒，5秒
                    headers: {
                        'Content-Type': 'application/json;charset=utf-8'
                    }
                })
                    .post('/', { keyword, })
                    .then(res => {
                        cb(res.data.data)
                    })
                    .catch(err => err)
            }
        },
    },
    {
        input_type: "input-select",
        field: "remote",
        default: "",
        label: "UID",
        exts: {
            remoteMethod: remoteFunc
        },
    },
    {
        input_type: "input-text",
        field: "username",
        default: "",
        label: "用户名",
        rules: [{ required: true, type: "username" }],
    },
    {
        input_type: "input-text",
        field: "nickname",
        default: "",
        label: "昵称",
        rules: [{ type: "required" }],
    },
    {
        input_type: "input-text",
        field: "email",
        default: "",
        label: "邮箱",
        rules: [{ required: true, type: "email" }],
    },
    {
        input_type: "input-text",
        field: "id_card",
        default: "",
        label: "身份证",
        rules: [{ required: true, type: "id-card" }],
    },
    {
        input_type: "input-text",
        field: "qq",
        default: "",
        label: "QQ",
        rules: [{ required: true, type: "qq" }],
    },
    {
        input_type: "input-text",
        field: "zipcode",
        default: "",
        label: "邮编",
        rules: [{ required: true, type: "zipcode" }],
    },
    {
        input_type: "input-text",
        field: "mobile",
        default: "",
        label: "手机",
        rules: [{ required: true, type: "mobile" }],
    },
    {
        input_type: "input-text",
        field: "phone",
        default: "",
        label: "座机",
        rules: [{ required: true, type: "phone" }],
    },
    {
        input_type: "input-text",
        field: "contact",
        default: "",
        label: "联系人",
        rules: [{ required: true, type: "contact" }],
    },
    {
        input_type: "input-text",
        field: "fax",
        default: "",
        label: "传真",
        rules: [{ required: true, type: "fax" }],
    },
    {
        input_type: "input-text",
        field: "ipv4",
        default: "",
        label: "ipv4 地址",
        rules: [{ required: true, type: "ipv4" }],
    },
    {
        input_type: "color",
        field: "color",
        default: "",
        label: "颜色",
        rules: [{ required: true, type: "color" }],
    },
    {
        input_type: "rate",
        field: "rate",
        default: 0,
        label: "评分",
        rules: [{ required: true, type: "rate" }],
    },
    {
        input_type: "slider",
        field: "slider",
        default: '',
        label: "滑块",
        exts: {
            min: 0,
            max: 10
        },
        rules: [{ required: true, type: "slider" }],
    },
    {
        input_type: "switch",
        field: "switch",
        default: 1,
        label: "开关",
        rules: [{ required: true, type: "switch" }],
        exts: {
            'inactiveValue': 0,
            'activeValue': 1,
        }
    },
    {
        input_type: "input-area",
        field: "info",
        default: "",
        label: "个人简介",
        rules: [{ type: "required" }],
    },
    {
        input_type: "input-number",
        field: "age",
        default: 0,
        label: "年龄",
        exts: {
            min: 0,
            max: 127
        },
        rules: [{ type: "number", required: true }],
    },
    {
        input_type: "input-radio",
        field: "fav_fruit",
        default: "banana",
        label: "最爱水果",
        exts: {
            options: [
                { value: "apple", label: "苹果" },
                { value: "pear", label: "梨子" },
                { value: "banana", label: "香蕉" },
                { value: "pomegranate", label: "石榴" },
                { value: "persimmon", label: "柿子" },
            ]
        },
        rules: [{ required: true, type: "enum" }],
    },
    {
        input_type: "input-checkbox",
        field: "like_fruit",
        default: ["apple", "banana"],
        label: "喜欢水果",
        exts: {
            min: 2,
            max: 4,
            options: [
                { value: "apple", label: "苹果" },
                { value: "pear", label: "梨子" },
                { value: "banana", label: "香蕉" },
                { value: "pomegranate", label: "石榴" },
                { value: "persimmon", label: "柿子" },
            ]
        },
        rules: [{ required: true, type: "array" }],
    },
    {
        input_type: "input-select",
        field: "sex",
        default: "2",
        label: "性别",
        exts: {
            min: 2,
            max: 4,
            options: [
                { value: "1", label: "秘密" },
                { value: "2", label: "男生" },
                { value: "3", label: "女生" },
            ]
        },
        rules: [{ required: true, type: "enum" }],
    },
    {
        input_type: "input-select",
        field: "hobby",
        default: ["sing", "run"],
        label: "爱好",
        exts: {
            min: 2,
            max: 3,
            multiple: true,
            options: [
                { value: "dance", label: "跳舞" },
                { value: "sing", label: "唱歌" },
                { value: "run", label: "跑步" },
                { value: "listen", label: "听歌" },
            ]
        },
        rules: [{ required: true, type: "array" }],
    },
    {
        input_type: "md-editor",
        field: "md-editor",
        default: "# hello\n```php\n$a = 5;\n$b = 5;\n$c = $a + $b\n```\n",
        label: "Md编辑",
        exts: {
            conf: {
                mode: "mini"
            }
        },
    },
    {
        input_type: "json-editor",
        field: "json-editor",
        default: '{"a":10, "b":10}',
        label: "Json Editor",
        exts: {
            options: {
                mode: "text"
            },
            height: 200
        },
    },
    {
        input_type: "content-editor",
        field: "information",
        default: '<p>我的爱好黑多</p><blockquote>test</blockquote>',
        label: "个人简介",
        exts: {
            showHtml: true,
            height: "300px"
        },
    },
    {
        input_type: "cascader",
        field: "cascader",
        default: ["510000", "511600", "511623"],
        label: "Content Editor",
        exts: {
            options: [
                {
                    value: '110000',
                    label: '北京市',
                    children: [
                        {
                            value: '110101',
                            label: '东城区'
                        },
                        {
                            value: '110102',
                            label: '西城区'
                        }
                    ]
                },
                {
                    value: '510000',
                    label: '四川省',
                    children: [
                        {
                            value: '510100',
                            label: '成都市',
                            children: [
                                {
                                    value: '510104',
                                    label: '锦江区'
                                },
                                {
                                    value: '510105',
                                    label: '青羊区'
                                }
                            ]
                        },
                        {
                            value: '510300',
                            label: '自贡市',
                            children: [
                                {
                                    value: '510302',
                                    label: '自流井区'
                                },
                                {
                                    value: '510303',
                                    label: '贡井区'
                                }
                            ]
                        },
                        {
                            value: '511600',
                            label: '广安市',
                            children: [
                                {
                                    value: '511602',
                                    label: '广安区'
                                },
                                {
                                    value: '511623',
                                    label: '邻水县'
                                },
                                {
                                    value: '511681',
                                    label: '华蓥市'
                                }
                            ]
                        },
                        {
                            value: '511700',
                            label: '达州市',
                            children: [
                                {
                                    value: '511702',
                                    label: '通川区'
                                },
                                {
                                    value: '511703',
                                    label: '达川区'
                                }
                            ]
                        }
                    ]
                },
                {
                    value: '130000',
                    label: '河北省',
                    children: [
                        {
                            value: '130100',
                            label: '石家庄市',
                            children: [
                                {
                                    value: '130102',
                                    label: '长安区'
                                },
                                {
                                    value: '130104',
                                    label: '桥西区'
                                }
                            ]
                        },
                        {
                            value: '130300',
                            label: '秦皇岛市',
                            children: [
                                {
                                    value: '130302',
                                    label: '海港区'
                                }
                            ]
                        }
                    ]
                }
            ],
        },
    },
    {
        input_type: "uploader",
        field: "avatar",
        default: '',
        label: "头像",
        exts: {
            class: "avatar-upload",
            action: "/personal/upload-avatar",
            name: "file", // 上传的文件字段名
            autoUpload: true, // 是否在选取文件后立即进行上传
            withCredentials: true, // 支持发送 cookie 凭证信息
            headers: {}, // 文件头
            data: {}, // 上传时附带的参数
            accept: "image/png, image/jpeg", // 接受上传的文件类型
            // 上传前检查
            beforeUpload: (rawFile: UploadRawFile) => {
                if (rawFile.type !== 'image/png' && rawFile.type !== 'image/jpeg') {
                    alert('上传头像图片只能是 JPG 或 PNG 格式!')
                    return false
                } else if (rawFile.size / 1024 / 1024 > 2) {
                    alert('上传头像图片大小不能超过 2MB!')
                    return false
                }
                return true;
            },
            customUpload: customUpload,
        },
    },
    {
        input_type: "date-picker",
        field: "year",
        default: '',
        label: "年份",
        exts: {
            type: 'year',
        },
    },
    {
        input_type: "date-picker",
        field: "years",
        default: '',
        label: "年份(多)",
        exts: {
            type: 'years',
        },
    },
    {
        input_type: "date-picker",
        field: "month",
        default: '',
        label: "月份",
        exts: {
            type: 'month',
        },
    },
    {
        input_type: "date-picker",
        field: "monthrange",
        default: '',
        label: "月份范围",
        exts: {
            type: 'monthrange',
        },
    },
    {
        input_type: "date-picker",
        field: "daterange",
        default: '',
        label: "日期范围",
        exts: {
            type: 'daterange',
        },
    },
    {
        input_type: "date-picker",
        field: "datetime",
        default: '',
        label: "时间",
        exts: {
            type: 'datetime',
        },
    },
    {
        input_type: "date-picker",
        field: "datetimerange",
        default: '',
        label: "时间范围",
        exts: {
            type: 'datetimerange',
        },
    },
    {
        input_type: "date-picker",
        field: "week",
        default: '',
        label: "周",
        exts: {
            type: 'week',
        },
    },
    {
        input_type: "date-picker",
        field: "dates",
        default: '',
        label: "日期(多)",
        exts: {
            type: 'dates',
        },
    },
    {
        input_type: "date-picker",
        field: "date",
        default: '',
        label: "日期",
        exts: {
            type: 'date',
        },
    },
    {
        input_type: "date-picker",
        field: "past_date",
        default: '',
        label: "以往日期",
        exts: {
            type: 'date',
            'disabledDate': 'future',
        },
    },
    {
        input_type: "date-picker",
        field: "future_date",
        default: '',
        label: "往后日期",
        exts: {
            type: 'date',
            'disabledDate': 'past',
        },
    },
    {
        input_type: "time-picker",
        field: "time",
        default: '',
        label: "选择时间1",
        exts: {},
    },
    {
        input_type: "time-picker",
        field: "timerange",
        default: '',
        label: "时间范围2",
        exts: {
            'is-range': true, // 是否时间范围
        },
    },
    {
        input_type: "time-select",
        field: "time_select1",
        default: '',
        label: "时间选择3",
        exts: {
        },
    },
    {
        input_type: "input-password",
        field: "new_password",
        default: "",
        label: "新密码",
        rules: [{ required: true, type: "password" }],
    },
    {
        input_type: "input-password",
        field: "confirm_password",
        default: "",
        label: "确认密码",
        rules: [
            // { required: true, type: "password" },
            { required: true, type: "confirm", message: "确认密码和上次密码不同" }
        ],
        exts: {
            field: 'new_password',
            value: 'admin' // field 存在时， field 优先
        }
    },
    {
        input_type: "input-text",
        field: "callback",
        default: "",
        label: "同步回调",
        exts: {
            params: {
                name: "qingbing"
            }
        },
        rules: [
            {
                required: true, type: "callback", trigger: 'blur', callback: <TXzFormCallback>(
                    callback: (error?: string | Error) => void,
                    value: unknown,
                    formData: TRecord,
                    params: TRecord
                ) => {
                    console.log(value);
                    console.log(formData);
                    console.log(params);
                    /**
                     * validator 为同步验证，验证失败方式
                     *  1. return false
                     *  2. 调用 callback(), 除了不给参数，其它都是失败
                     *      1. 如果设置了 message， 提示为 message
                     *      2. 没有message时， callback 参数不为空就以参数为提示，否则为 "{FullField} fails"
                     */
                    // 成功
                    // return true
                    // callback()
                    // 失败
                    callback("错误提示1")
                    // callback(new Error("错误提示2"))
                    // return false
                },
            }
        ],
    },
    {
        input_type: "input-text",
        field: "async_callback",
        default: "",
        label: "异步回调",
        rules: [
            {
                required: true, type: "callback", trigger: 'blur', asyncCallback: <TXzFormCallback>(
                    callback: (error?: string | Error) => void,
                    value: unknown,
                    formData: TRecord,
                    params: TRecord
                ) => {
                    console.log(value);
                    console.log(formData);
                    console.log(params);
                    /**
                     * 异步验证，返回方式只能使用 callback 调用方式
                     * 成功: callback()
                     * 失败: callback('error message'); callback(new Error('error message'))
                     */
                    // 成功
                    // callback() 
                    // 失败
                    // callback('error message')
                    callback(new Error('error - message'))
                },
            }
        ],
    },
    {
        input_type: "input-text",
        field: "custom_async",
        default: "",
        label: "自定义异步",
        rules: [
            {
                required: true, trigger: 'blur', asyncValidator(rule, value, callback, source, options) {
                    return new Promise((resolve, reject) => {
                        // resolve() // "哦业，验证成功了"
                        reject("哎呀，验证有误呢")
                    })
                },
            }
        ],
    },
    {
        input_type: "input-text",
        field: "async_remote",
        default: "",
        label: "Ajax 远端回调",
        rules: [
            {
                required: true, type: "callback", trigger: 'blur', asyncCallback: <TXzFormCallback>(
                    callback: (error?: string | Error) => void,
                    value: unknown,
                    formData: TRecord,
                    params: TRecord
                ) => {
                    fetchRemote(params)
                        .then((res) => {
                            if (res.data.code == 0) {
                                callback() // 成功
                            } else {
                                callback(res.data.msg) // 失败
                            }
                        })
                        .catch(err => callback(err));
                },
            }
        ],
    },
])
    .setViewFields(['username', 'nickname'])
    // .setShowFields(['async_remote', 'custom_async', 'time_select1'])
    .done()

const resetPasswordItems: Record<string, TXzFormItem> = {
    oldPassword: mixins(XzFormItemMachine('password', 'oldPassword', '原始密码'), {
        rules: [{ required: true }]
    }),
    newPassword: mixins(XzFormItemMachine('password', 'newPassword', '新 密 码'), {
        rules: [{ required: true }]
    }),
    confirmPassword: mixins(XzFormItemMachine('password', 'confirmPassword', '确认密码'), {
        rules: [
            { required: true },
            { type: "confirm", message: "两次秘密输入不一致" }
        ],
        exts: {
            field: 'newPassword'
        }
    }),
}

// const { items, viewFields, showFields, formData, rules } = XzFormItemParse.instance(resetPasswordItems)
//     .done()
</script>
```