# plain-kit / 朴素工具包

## Features

- 管理后台生态圈
  - 推荐直接使用管理后台脚手架搭配代码生成器 可以做到开箱即用
  - 文档略长 是考虑到了开发中个性化的方方面面 尽可能实现动态可配 照顾到各种特殊情况
- 趁手小型组件
- 常用工具函数

<br/>

## Installation

![NPM](https://nodei.co/npm/plain-kit.png)

``` bash
$ yarn add plain-kit
```

**依赖项**：vue element-ui

如果你仅使用少量组件或函数 建议按需引入

babel7以上按需引入步骤：

1. ```$ yarn add babel-plugin-import --dev```
2. 在```babel.config.js```中增加如下配置：

```js
module.exports = {
  plugins: [
    ['import', {
      libraryName: 'plain-kit',
      libraryDirectory: 'lib',
      style: false,
      camel2DashComponentName: false
    }, 'plain-kit']
  ]
}
```

<br/>

**管理后台生态圈**

> main.js

```js
import request from '@/utils/request' // 你的axios封装
import { AdminRequisite, getMixins, getApiGenerator } from 'plain-kit'

// 全局注册 TimeRangePicker、AuthButton、Pagination、Selector、FormItemTip、FormDialog、Tag 等后台必备组件
Vue.use(AdminRequisite, {
  AuthButton: {
    // 可以在这里做全局配置
  }
})

let mixins = getMixins()
// 对mixins进行扩充
/*
import { mapGetters } from 'vuex'
mixins = {
  ...mixins,
  computed: {
    ...mixins.computed, 
    ...mapGetters([
      'dict',
    ]),
  }
}
*/

const apiGenerator = getApiGenerator({ request })

export { mixins, apiGenerator }
```

> somepage.vue

```js
import { mixins, apiGenerator } from '@/main'

export default {
  mixins: [mixins],
  data () {
    return {
      api: apiGenerator('/somepage')
    }
  }
}
```

<br/>

## Usage

### mixins

页面公共逻辑混入：

- 增查改删
- 常用dayjs插件
- 常用过滤器
- 常用校验
- 必备弹框
- 常用函数
- 页面销毁时终止尚未完成的查询请求（列表查询和单条查询）
- 单条删除时，如果删除的是当前页（非第一页）的最后一条，会将页码修改为上一页

  ...

全局配置默认值：

```js
getMixins({
  //  dayjs插件注册 已默认注册常用的
  dayjsExtends: ['isBetween', 'isSameOrAfter', 'isSameOrBefore', 'isToday', 'isTomorrow', 'isYesterday', 'objectSupport', 'relativeTime', 'minMax'],

  //  接口参数、返回值格式定制化
  normalizer: {
    // [列表查询接口]页码字段名
    pageNo: 'pageNo',

    // [列表查询接口]页容量字段名
    pageSize: 'pageSize',

    // [列表查询接口]返回值中列表字段所在位置
    // 考虑到分页与不分页的返回格式可能是不同的 所以支持传入一个数组 数组会被遍历 直到找到为止
    list: ['data', 'data.records'],

    // [列表查询接口] 返回值中总记录数字段所在位置
    total: 'data.total',

    // [0]指定列表查询返回值中单条的主键
    // [1]指定单条查询接口参数的主键
    // [2]指定单条查询接口返回值中数据所在位置
    row: ['id', 'id', 'data']
  }
})
```

配置方式：与默认值进行混入

normalizer具体配置方式请查看[getPropByPath](#getPropByPath)用法

<br/>

normalizer支持针对某个页面进行单独配置：

```js
export default {
  data () {
    return {
      normalizer__: {}
    }
  }
}
```

<br/>

getMixins()为你注册了一些常用的全局方法

- this._: lodash
- this.$dayjs: dayjs
  - 为什么不选用momentjs？
    1. momentjs已官宣停止开发进入维护状态，并建议使用其他库
    2. dayjs和momentjs的api一致，但体积小得多
- this.$isEmpty: 判空
- this.$notEmpty: 判非空
- this.$typeOf: 获取变量精确类型
- *弹框相关* → [Swal](#Swal)

<br/>

钩子函数

- dataGetter__(afterRetrieve, beforeRetrieve)

  - afterRetrieve

    > 查询单条记录之后

    > 参数：查询单条记录的返回值

    > 你可以在这个钩子中修改单条查询接口的返回值

  - beforeRetrieve

    > 查询单条记录之前

  ```html
  <FormDialog 
    :dataGetter="() => dataGetter__(
      resData => {
        // 在查询单条记录之后做点什么...
        // 比如将返回值中status的值修改为1：
  
        // 同步修改：
        resData.status = 1
        
        // 异步修改：
        this.$POST().then(res => {
          this.row__.data.status = 1
        })
      },
      () => {
        // 在查询单条记录之前做点什么...
      }
    )"
  />
  ```

- submit__(paramHandler)

  > 执行增删改（提交表单）时

  比如你想在新增时加一个参数：

  ```html
  <FormDialog
    :submit="() => submit__(
      // 参数可以是一个函数或一个对象
      // 函数会在表单校验通过后、接口调用前执行，对象会被用作接口参数
      () => {
        // 在提交之前做点什么...
        if (dialogStatus__ === 'c') {
          row__.data.status = 1
        }
      }).then(() => {
        // 在提交之后做点什么...
      }).catch(() => {
        return {
          close: false
        }
      })
    "
  />
  ```

- this.onQueryChange__(valid)

  > 列表筛选参数改变后（做了500毫秒的节流处理）

  > valid: 校验是否通过

<br/>

dialogStatus__

当前的表单状态（string）

可能的值：'c'（新增） / 'r'（查看） /  'u'（编辑） / ''（关闭）

<br/>

默认/初始值定义

```
data () {
  return {
    // 表单绑定对象
    row__: {
      data: {
        arr: [],
        num: 100
      }
    },
    // 列表查询参数
    list__: {
      query: {
        pageSize: 15, //覆盖默认值10
        status: 1 //新增的
      }
    }
  }
}
```

<br/>

this.getList__()

> 刷新列表

<br/>

init__

> 在页面初始化、查询参数改变、单条增删查改时getList__会被调用

> 你可以在methods中定义一个init__方法来取代getList__

  ```js
  methods: {
  init__(intention, res)
  {
    // intention可能的值：'init' 'pageNoChange' 'queryChange' 'c' 'r' 'u' 'd' 'enable' 'disable'
    // res：'c' 'r' 'u' 'd' 'enable' 'disable' 时的接口返回值

    // 在获取列表之前搞点事情...
    this.getList__(res => {
      // 在获取列表之后搞点事情...
      // res为列表接口返回值
    })
  }
}
  ```

filters（所有过滤器均可当作方法调用）

- id2name__

  > 数据字典转义

  ```html
  <el-table-column>
    <template slot-scope="{row}">
      {{row.type | id2name__(dict.type)}}
    </template>
  </el-table-column>
  ```

  > 当作方法调用：

  ```js
  /**
   * @param {String|Symbol} 需要查询的id
   * @param {Array} 由多个```{[id]: '', [name]: ''}```构成的数组
   * @param {String} 自定义id和name对应的属性名 默认为'dataValue/dataName'
   * @return {Any} id对应的name
   */
  this.id2name__('1', [
    { dataValue:'1', dataName: 'a' },
    { dataValue:'2', dataName: 'b' },
  ]) // 'a'
  
  this.id2name__('1', [
    { id:'1', name: 'a' },
    { id:'2', name: 'b' },
  ], 'id/name') // 'a'
  ```

<br/>

<a id='validator'>输入校验</a>

- required__: 必填

- phone__: 手机

- idCard__: 身份证

- email__: 邮件

- account__: 账号

- pwd__: 密码

- version__: 版本号

- len__: 字符长度限制 参数均为Number类型

  ```js
  len__(
    Number,  // 长度上限
    [Number] // 长度下限
  )
  ```

- tel__(): 座机

  > 允许多个：tel__()

  > 仅能单个：tel__(false)

- coord__(): 坐标

  > 经度：coord__('lng')

  > 纬度：coord__('lat')

- integer__: 整数

  ```js
  integer__({
    required: Boolean,   // 是否必填 默认false
    min: Number,         // 最小值（≥）
    greaterThan: Number, // 需大于（＞）
    max: Number,         // 最大值（≤）
    lessThan: Number,    // 需小于（＜）
    maxLen: Number,      // 最大长度（≤）
    minLen: Number,      // 最小长度（≥）
    len: Number,         // 长度（=）
  })
  ```

- decimal__(): 数字

  ```js
  decimal__({
    required: Boolean,        // 是否必填 默认false
    allowInt: Boolean,        // 是否允许整数 默认true 
    min: Number,              // 最小值（≥）
    greaterThan: Number,      // 需大于（＞）
    max: Number,              // 最大值（≤）
    lessThan: Number,         // 需小于（＜）
    decimalPlaces: Number,    // 小数位长度（=）
    minDecimalPlaces: Number, // 小数位长度下限（≥）
    maxDecimalPlaces: Number  // 小数位长度上限（≤）
  })
  ```

<br/>

### api生成器（根据接口前缀自动生成增删查改接口）

> 缩写说明：crud分别表示增查改删

getApiGenerator

```js
getApiGenerator({
  // axios封装 默认为空 必传
  request,

  // 接口后缀默认值
  url: {
    c: 'create',                  // 单条新增
    r: 'queryForDetail',          // 单条查询
    u: 'update',                  // 单条编辑
    d: 'delete',                  // 单条删除
    list: 'queryForPage',         // 列表查询
    updateStatus: 'updateStatus', // 单条状态变更
  },

  // 请求方式 默认全POST
  method: {
    c: 'POST',
    r: 'POST',
    u: 'POST',
    d: 'POST',
    list: 'POST',
    updateStatus: 'POST',
  },

  // 提交方式 默认空数组（全json） 可以在这里指定接口使用formData
  formData: ['c', 'u'],

  // 使用config你可以完全自定义每个接口的请求配置 甚至支持function（从而能够拿到data）
  config: {
    c: {},
    r (data) {
      return {}
    },
  }
})
```

<br/>

如果某个接口的前缀不是somepage，可以在后缀前添加斜线

```
data () {
  return {
    api: apiGenerator('/somepage', {
      url: {
        r: '/anotherpage/selectOne',
      },
    })
  }
}
```

这样会得到：

- /somepage/create
- /somepage/update
- /somepage/delete
- /somepage/queryForPage
- /somepage/updateStatus
- **/anotherpage/selectOne**

<br/>

如果不需要单条查询接口，想直接使用列表的数据

```
data () {
  return {
    api: apiGenerator('/somepage', {
      url: {
        r: false,
      },
    })
  }
}
```

<br/>

以上配置均支持全局统一配置（main.js）

```js
getApiGenerator({
  url: {
    ...
  },
  method: {
    ...
  }
})
```

<br/>

如果你的接口是动态拼接式的（RESTful风格），或者参数名不统一，可以自定义接口地址和参数：

```js
api: apiGenerator('/somepage', {
  // 方式1
  url: {
    r: data => 'module/' + data.id,
  },

  // 方式2
  config: {
    r (data) {
      return {
        url: 'module/' + data.id
      }
    },
  }
})
```

<br/>

将你传入的request绑定到了Vue原型上：

```this.$request(config)```

为方便起见，为所有支持的请求方法提供了别名：

> post请求示例：```this.$POST(url, data?, config?)```

> 支持$GET / $POST / $PATCH / $PUT / $DELETE / $HEAD

> 为避免重名 采用全大写

- 屏蔽了get和post请求参数属性不一致的差异（$request不变）

- 过滤掉参数中部分值为falsy值（null/NaN/undefined）的属性

- 有时候参数所绑定的对象中会存在一些临时属性 而这些属性是不应该提交到后端的 我们约定这些临时变量以双下划线__开头 __开头的属性会被过滤掉

<br/>

下载

- ajax请求方式下载

  ```js
  this.$download(url, data, {
      method: '', // 必传
      ...
  })
  ```

- 地址栏请求方式下载

  ```js
  this.$download(url, data?, config?)
  ```

<br/>

上传（请求体为multipart/form-data类型）

```this.$upload(url, data?, config?)```

> 默认POST请求

<br/>

**给$download/$upload添加全局回调**

> 由于$download/$upload本质上还是调用你的request 所以在request的响应拦截器中判断即可

```js
request.interceptors.response.use(
  response => {
    // $download
    if (response.config.responseType === 'blob') {
      succeed({
        titleText: '导出成功',
        timer: 5000
      })
    }
  },
)
```

<br/>

qs

```this.$qs```

<br/>

### 趁手小型组件

#### SvgIcon

``` bash
$ npm i svg-sprite-loader svgo --dev
```

- vue.config.js

```js
chainWebpack: config => {
  config.module
  .rule('svg')
  .exclude.add(resolve('src/assets/svg-sprite'))
  .end()
  config.module
  .rule('svg-sprite')
  .test(/\.svg$/)
  .include.add(resolve('src/assets/svg-sprite'))
  .end()
  .use('svg-sprite-loader')
  .loader('svg-sprite-loader')
  .options({
    symbolId: 'icon-[name]'
  })
  .end()
}
```

- main.js

```js
import { SvgIcon } from 'plain-kit'
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(require.context('@/assets/svg-sprite', false, /\.svg$/))
Vue.component('SvgIcon', SvgIcon)
```

- ```<SvgIcon icon-class=""/>```

<br/>

#### QR / 二维码 支持蒙层

```js
import { QR } from 'plain-kit'
components: { QR }
```

```html

<QR :value=""/>
```

如果value的值比较小 而size的值比较大 会导致图片模糊 此时可以增大scale解决：

```html

<QR :value="" :options="{scale:25}"/>
```

> scale指二维码每个黑点占用的px数量 可选值为二次方数值 如25 36 49 64 81

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| value | 二维码字符串（如果为base64编码 则不经过转换直接展示） | String | | |
| mask-text | 蒙层文案 | String | | |
| size | 宽高（单位px） | String | | 200 |
| options | 底层依赖qrcode的参数 | Object | https:// github.com/soldair/node-qrcode | {margin:0, scale:16, errorCorrectionLevel:'L'} |

<br/>

| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| load | 加载完成后触发 | |
| error | 加载出错后触发 | event |

<br/>

#### AuthButton / 权限按钮

```js
import { AuthButton } from 'plain-kit'

// 全局注册（未注册AdminRequisite时）
Vue.use(AuthButton, {
  // 全局配置
})

// 局部注册
components: {
  AuthButton
}
```

| Attribute | Description | Configuration Mode | Type |  Default |
| --- | --- | --- | --- | --- |
| name | 文案 | local | string | |
| show | 是否显示 | local, global | boolean, function | false |
| catalog | 目录 | global | object | |
| elPopconfirmProps | el-popconfirm的配置 未配置时默认不开启popconfirm | local, global | object | |
| elTooltipProps | el-tooltip的配置 默认circle为true时开启tooltip | local, global | object | |

> ...以及el-button所有props

**show**

show为function时支持返回boolean或者返回promise在promise内resolve一个boolean

**catalog**

如果同一个name的AuthButton需要多处使用 你可以在catalog中针对这个name进行全局配置（混入）

> elPopconfirmProps、elTooltipProps也支持在catalog中使用

默认值：

```
{
  新增: {
    type: 'primary',
    icon: 'el-icon-circle-plus-outline'
  },
  查看: {
    icon: 'el-icon-search',
    circle: true
  },
  编辑: {
    type: 'primary',
    icon: 'el-icon-edit',
    circle: true
  },
  删除: {
    type: 'danger',
    icon: 'el-icon-delete',
    circle: true,
    elPopconfirmProps: {}
  },
  停用: {
    type: 'warning',
    icon: 'el-icon-video-pause',
    circle: true,
    elPopconfirmProps: {}
  },
  启用: {
    type: 'success',
    icon: 'el-icon-video-play',
    circle: true,
    elPopconfirmProps: {}
  },
}
```

- 使用在catalog中定义过的AuthButton

```html

<AuthButton @click="" name="编辑"/>
```

- 未在catalog中定义的AuthButton

```html

<AuthButton @click="" name="" circle type="primary">
  <i class="el-icon-finished"/>
</AuthButton>
```

<br/>

#### Selector / 下拉框选择器

```js
import { Selector } from 'plain-kit'
```

> 已在AdminRequisite中全局注册

```html

<Selector v-model="list__.query.status"
          :options="['停用', '启用']"
          placeholder="状态"
/>
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| v-model / value | 绑定值 | | | |
| options | 选项 | Array | | |
| optionKey | options的key和label 用斜线分隔（options的元素为Object类型时有效） | String | | dataValue/dataName |
| rightLabel | 浮动在右侧的label属性名（options的元素为Object类型时有效） | String | | |
| ellipsis | 是否限宽并对超长的label作溢出省略处理（默认是超长撑开） | Boolean | | false |
| search | 搜索方法（即el-select的remote-method） | Function | | |

> 支持el-select全部参数（remote-method和value-key除外）

optionKey:

> 格式为key/label/rightLabel? 其中label支持模板字符串格式 如id/${startTime}-${endTime}

<br/>

#### Pagination / 分页

```js
import { Pagination } from 'plain-kit'
```

> 已在AdminRequisite中全局注册

支持el-pagination所有参数和事件

<br/>

#### FormItemTip / 表单项提示

用于表单项的填写规则说明

```js
import { FormItemTip } from 'plain-kit'
```

> 已在AdminRequisite中全局注册

```html

<el-form-item>
  <el-input/>
  <form-item-tip>xxx</form-item-tip>
</el-form-item>
```

<br/>

#### OnefoldTable / 一维表格

```js
import { OnefoldTable } from 'plain-kit'
components: { OnefoldTable }
```

```html

<OnefoldTable title="标题">
  <tr>
    <td>xxx</td>
    <td>xxx</td>
  </tr>
  <tr>
    <td>xxx</td>
    <td>xxx</td>
    <td>xxx</td>
    <td>xxx</td>
  </tr>
</OnefoldTable>
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| title | 标题 | String | | |

<br/>

#### CheckAllBox / 支持全选的复选框

```js
import { CheckAllBox } from 'plain-kit'
components: { CheckAllBox }
```

```html

<CheckAllBox v-model="date" :options="{
  周一: 1,
  周二: 2
}"/>
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| v-model / value | 绑定值 | String / Number / Boolean | | |
| options | 选项 key即label value即value | Object | | |

> 支持el-checkbox全部参数

<br/>

#### AuthTree / 权限编辑树

```js
import { AuthTree } from 'plain-kit'
components: { AuthTree }
```

```html

<AuthTree v-model="authTree"/>
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| v-model / value | 绑定值 | Array | | |

> 支持el-checkbox全部参数

<br/>

#### SmsButton / 短信验证码按钮

```js
import { SmsButton } from 'plain-kit'
components: { SmsButton }

// 如果发送短信前需要先校验手机号
methods: {
  sms(e)
  {
    this.$refs.formDialog.$refs.elForm.validateField('SmsButton', err => {
      if (err) {
        // 不开始计时
        e.stopPropagation()
      } else {
        // 调用短信接口
      }
    })
  }
}
```

```html

<SmsButton @click="sms($event)"/>
```

| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| click | 点击后触发（返回值需为Promise类型） | |

<br/>

#### AudioPlayer / 音乐播放器（原生audio元素封装）

```js
import { AudioPlayer } from 'plain-kit'
components: { AudioPlayer }
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| show.sync | 是否开启 | Boolean | | false |
| src | 音频文件链接 | String | | |
| *inline | 以行内元素的方式显示 | Boolean | | false |

> 支持audio标签全部参数

inline

- 默认false表示以弹框形式显示
- 开启后不再需要show参数

<br/>

#### VideoPlayer / 视频播放器（原生video元素封装）

```js
import { VideoPlayer } from 'plain-kit'
components: { VideoPlayer }
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| show.sync | 是否开启 | Boolean | | false |
| src | 视频文件链接 | String | | |
| poster | 视频封面图片链接 | String | | |
| inline | 以行内元素的方式显示 | Boolean | *see below* | false |

> 支持video标签全部参数

inline

- 默认false表示以弹框形式显示
- 开启后不再需要show参数

<br/>

#### Tag / 标签（el-tag封装）

使用场景：在预设的范围中根据某个动态的值显示其对应的标签

```js
import { Tag } from 'plain-kit'
components: { Tag }
```

> 已在AdminRequisite中全局注册

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| value | 值 | any | | |
| options | 选项 | string, array | *see below* | |

options

预设值：

- 'whether'：是/否
- 'being'：有/无
- 'status'：启用/停用
- 'required'：需要/不需要
- 'auth'：已授权/未授权

```html

<Tag :value="value" options="whether"/>
```

支持自定义

```html

<Tag :value="0" :options="[
  { 
    value: 0,         // 支持数组 数组以是否包含作为判断依据
    text: '0对应文案', // 即el-tag的innerText
    type: 'danger'    // 即el-tag的type
  },
  { 
    value: 1,
    text: '1对应文案',
    type: 'warning'
  },
]"/>
```

<br/>

#### FormDialog / 表单对话框

**Features**

- 支持el-dialog几乎所有参数、事件和slot
- 支持el-form几乎所有参数、事件（el-form没有slot）

```js
import { FormDialog } from 'plain-kit'
```

> 已在AdminRequisite中全局注册

```html

<FormDialog
        :show.sync="show"
        :dataGetter="dataGetter"
        :submit="submit"
        title=""
        v-model="form"
>
  <template #el-form>
    <el-form-item prop="input">
      <el-input v-model="form.input"/>
    </el-form-item>
  </template>
</FormDialog>
```

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| show.sync | 是否开启 | boolean | | false |
| title | 对话框标题 | string | | |
| readonly | 是否只读 | boolean | | false |
| v-model | 表单数据对象（即el-form的model参数） | any | *see below* | {} |
| elFormProps | el-form参数对象 | object | el-form绝大部分参数 | {} |
| dataGetter | 获取数据方法（返回值需为Promise类型） | function | | |
| submit | 提交方法 | Function | | |
| ... | el-dialog参数 | - | https://element.eleme.cn/#/zh-CN/component/dialog | |

<br/>

| name | description |
| --- | --- |
| el-form | el-form |
| title | el-dialog的slot |
| footer | el-dialog的slot |

> el-form插槽不是必须的 你可以传任意slot进去 只是提交时不会帮你校验罢了 你可以自行校验

<br/>

**v-model**

即使不使用el-form插槽 也建议传入 表单关闭时会将数据对象重置为初始状态（以避免二次打开时显示上次value）

<br/>

**dataGetter**

获取数据前后、提交前后的生命周期都是暴露出来的 如下所示

```js
dataGetter()
{
  // 表格打开之后、获取数据之前
  return request().then(() => {
    // 获取数据之后
  })
}
```

<br/>

**submit**

```js
submit()
{
  // 提交之前
  if (true) { //校验通过
    return this.$POST('').then(() => {
      // 提交之后
    })
  } else {
    this.$warn('校验失败')
  }
}
```

<br/>

> submit的返回值如果是一个Promise 则then时默认关闭弹框 而reject时不关闭

> 注意：如果catch了reject 则reject时也会关闭弹框 这是因为组件内部已无法获知被你捕获的reject

> 你可以在最后一个then/catch中```return { close: false }```来控制是否关闭弹框

> submit没有返回值或者返回值不是Promise时 则submit执行完毕后默认关闭弹框 你可以```return { close: false }```来控制该行为

内部el-form的ref获取方式：

1. 先给FormDialog添加一个ref 比如formDialog

2. ```this.$refs.formDialog.$refs.elForm```

<br/>

**footer slot 示例**

```html

<FormDialog
        :show.sync="form.show"
        ref="formDialog"
>
  <div slot="footer" class="footer">
    <el-button
            type="primary"
            :loading="$refs.formDialog && $refs.formDialog.submitting"
            @click="() => { $refs.makeOutFormDialog.doSubmit() }"
    >
      提 交
    </el-button>
    <el-button @click="() => { form.show = false }">
      取 消
    </el-button>
  </div>
</FormDialog>
```

<br/>

#### <a id="Swal">Swal / alert封装（基于sweetalert2）</a>

- this.$succeed
- this.$notice
- this.$warn
- this.$err
- this.$confirmation
- this.$Swal

```js
import { Swal } from 'plain-kit'

// 原型挂载（未注册AdminRequisite时）
Vue.use(Swal)

this.$succeed('成功弹框')
.then(() => {
  // 关闭时
})

this.$notice('提示弹框')
.then(() => {
  // 关闭时
})

this.$warn('警告弹框')
.then(() => {
  // 关闭时
})

this.$err('错误弹框')
.then(() => {
  // 关闭时
})

this.$confirmation('确认弹框')
.then(e => {
  // 点击确认
})
.catch(e => {
  if (e.isDenied) {
    // 点击拒绝
  } else if (e.isDismissed) {
    // 点击取消
  }
})

// 单独使用
Swal.succeed()
Swal.notice()
Swal.warn()
Swal.err()
Swal.confirmation()

// 强制确认
// 强制状态下无法取消（无取消按钮、点击弹框外部、按下esc键均无效果）
Swal.confirmation('同意以继续', true)

// 简单表单 + 三个按钮 + 异步确认的例子
Swal.confirmation({
  input: 'text',
  inputAttributes: {
    placeholder: '备注'
  },
  confirmButtonText: `同意`,
  showLoaderOnConfirm: true,
  preConfirm: input => {
    return new Promise(resolve => {
      setTimeout(resolve, 500)
    }).then(() => {
      alert('同意成功')
    }).catch(e => {
      alert('同意失败')
    })
  },
  // 拒绝按钮
  showDenyButton: true,
  denyButtonText: `拒绝`,
  returnInputValueOnDeny: true,
  preDeny: input => {
    if (input) {
      return new Promise((resolve, reject) => {
        setTimeout(reject, 500)
      }).then(() => {
        alert('拒绝成功')
      }).catch(e => {
        alert('拒绝失败')
      })
    } else {
      this.$Swal.showValidationMessage(`请填写备注`)
      return false
    }
  },
}).then(e => {
  alert('同意')
}).catch(e => {
  if (e.isDenied) {
    alert('拒绝')
  } else if (e.isDismissed) {
    alert('取消')
  }
})
```

Swal只是对最常用的五种场景做了封装 支持只传字符串作为title

完整配置参阅 https://sweetalert2.github.io/

<br/>

#### PullToRefresh / 下拉刷新上拉加载

```html

<PullToRefresh
        v-model="list"
        :listGetter="()=>$http.post('', query)"
        :param="query"
>
  <van-cell v-for="item in list" :key="item" :title="item"/>
</PullToRefresh>
```

<br/>

Props

| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| v-model | 双绑 | Array | | [] |
| listGetter | 列表接口（返回值需为Promise类型） | Function | | |
| param | 接口参数对象 | Object | | |
| normalizer | 接口参数/返回值格式定制 | Object | | *见下方说明 |
| vanPullRefreshProps | van-pull-refresh参数（混入） | Object | | |
| vanListProps | van-list参数（混入） | Object | | {finishedText: '没有更多了', immediateCheck: true} |

<br/><br/>

Slots

| 名称 | 说明 | SlotProps |
| --- | --- | --- |
| empty | 加载完成后数据为空的dom | |

> 支持van-pull-refresh所有具名插槽

> 支持van-list所有具名插槽

> 注意：由于van-pull-refresh和van-list都有名为loading的插槽 所以你传入的loading插槽会被两者共用

<br/>

normalizer

默认值：

```
{
  // 接口参数中页码的字段名
  "pageNo": "pageNo", 

  // 接口返回值中数组的字段路径                      
  "list": "data.list",                        

  // 判断是否为最后一页
  "isLastPage": res => res.data && !res.data.hasNextPage
}
```

> 配置方式：与默认值进行混入

<br/>

### 工具函数

#### getEnv / 获取运行环境（主要用于h5）

```js
import { getEnv } from 'plain-kit'
```

目前有三种可能的返回值（Promise类型）

- mp（小程序）
- wechat（微信内置浏览器）
- browser（其他浏览器）

<br/>

#### parseQueryString / 获取当前url某个查询参数的值

```js
import { parseQueryString } from 'plain-kit'

const code = parseQueryString('code')

//or

const { code } = parseQueryString()
```

```js
/**
 * 获取当前url某个查询参数的值
 * 支持微信公众号授权特例：授权后会重定向回来并在链接中加入一个code参数 但微信没有考虑hash路由的情况 所以获取这个code需要特殊处理
 * @param {Object}
 *        {String} key - 查询参数的key 如果为空 则返回查询参数映射的对象 可以解构使用
 *        {String} mode - router模式 可选值'history'/'hash' 默认'hash'
 *        {Boolean} del - 是否在url中删除该值（删除会引发页面刷新）
 * @return {String|Object} 查询参数中key对应的value / 如果key为空 则返回查询参数整个对象
 */
```

注意：如果search和hash中同时包含code 如```http://localhost:8080/?code=1#/?code=2``` 取的是search中的code 即返回1

<br/>

#### dynamicallyLoadScript / 动态加载js

```js
import { dynamicallyLoadScript } from 'plain-kit'

dynamicallyLoadScript(src)
```

```js
/**
 * @param {String} src - 脚本url
 * @return {Promise}
 */
```

<br/>

#### highlightFormError / 高亮提示校验失败的表单项

```js
import { highlightFormError } from 'plain-kit'
```

> 平滑滚动至校验失败的第一个表单项、并对所有校验失败的表单项产生震动效果

> FormDialog组件已内置 可用于其他的el-form

<br/>

#### <a id="getPropByPath">getPropByPath</a> / 支持多层的属性访问

```js
import { getPropByPath } from 'plain-kit'

const obj = {
  a: {
    b: 'c'
  }
}

getPropByPath(obj, 'a.b') // 'c'
getPropByPath(obj) // 返回obj本身
```

```js
/**
 * @param {Object} 对象
 * @param {String} 属性名 支持点运算符获取深层属性 可选
 * @return {Any} 属性值 如果第二个参数为空 则返回对象本身
 */
```

<br/>

#### validator / 输入校验

```js
import { validator } from 'plain-kit'

const { idCard } = validator

const errMsg = idCard.validator('xxx')

if (errMsg) {
  Toast(errMsg) // '格式不正确'
  return
}
```

```js
/**
 * @param {String} 需要校验的内容
 * @return {String} 校验失败提示信息 如果校验通过则返回''
 */
```

前文提到的[输入校验](#validator)都可以像这样独立使用 不过：

1. 不再需要后面的双下划线 这里不需要规避命名冲突

2. 需要多点一层validator 这也是为了兼容element所做的妥协

3. 该方法不是通过true和false来标识校验是否通过 因为错误往往需要一个友好的原因提示

> 如果仅仅想知道是否校验通过：

```js
!idCard.validator('xxx') // true通过 false失败
```

<br/>

#### isEllipsis / 判断dom是否触发溢出省略（text-overflow: ellipsis）

```js
import { isEllipsis } from 'plain-kit'

/**
 * @param {Element} 需要判断的元素
 * @return {Boolean} 是否触发溢出省略
 */
isEllipsis(document.querySelector('.text'))
```

<br/>

#### typeOf / 获取变量的精确类型

动机：原生js的typeof等类型检测手段都存在各种缺陷

```js
import { typeOf } from 'plain-kit'

/**
 * @param {any} 需要判断的变量
 * @return {string} 变量类型（全小写） 如string null undefined file...
 */
typeOf(1) // 'number'
```

<br/>

#### isBase64 / 判断是否为base64编码字符串

```js
import { isBase64 } from 'plain-kit'

/**
 * @param {string} 需要判断的字符串
 * @param {object}
 *        {string} mediaType - 媒体类型 可选值参考 https://en.wikipedia.org/wiki/Data_URI_scheme
 *        {boolean} scheme - 是否包含前缀 默认true
 * @return {boolean} 结果
 */
isBase64(1, {
  mediaType: 'image',
  scheme: false
}) // false
```

<br/>

#### isEmpty & notEmpty / 判空 & 判非空

notEmpty() 等同于 !isEmpty()

notEmpty作用：可读性更高

```js
import { isEmpty, notEmpty } from 'plain-kit'

/**
 * @param {any} 需要判断的变量
 * @return {boolean} 结果
 */
isEmpty(0)  // false
isEmpty({}) // true
isEmpty([]) // true
```

<br/>

#### jsonToFormData / json转formData

```js
import { jsonToFormData } from 'plain-kit'

/**
 * @param {object} 需要转换的对象
 * @return {formData} 结果
 */
```
