### 开发指南

#### 一、快速上手

##### 演示
使用微信扫描下方的小程序码，在线查看组件的渲染效果。

[注：如果因为网络等原因，小程序码图片无法加载，也可以直接输入 `Meleon Uni-ui` 关键字查询]



![image-20231007221035897](https://github.com/floruitShow2/MeleonUI/raw/90bee9322594d979af8749c6c626bcbada93ffda/src/assets/app/mpweixin-code.jpg)

##### 安装

```shell
# npm
npm install @meleon/uni-ui
```

##### 引入

H5 端

```ts
import MeleonUI from "@meleon/uni-ui"

export function createApp() {
  const app = createSSRApp(App)
  app.use(MeleonUI)
    
  return { app }
}
```

微信小程序端

对比分别在项目入口文件 **main.ts** 和组件库的入口文件 **meleon-ui.ts** 里注册的组件，被编译到微信小程序上的结果：

```ts
// main.ts
import { createSSRApp } from 'vue'
import MeleonUI from "@meleon/uni-ui";
import MlButton from '@meleon/uni-ui/lib/ml-button/index.vue'
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)
  app.use(MeleonUI)

  app.component('MlButtonGlobal', MlButton)

  return {
    app
  }
}
// meleon-ui.ts
import type { App, Plugin } from 'vue'
import MlButton from './ml-button'

const components: Record<string, Plugin> = {
    MlButton,
}
const install = (app: App) => {
    for (const key in components) {
        if (Object.prototype.hasOwnProperty.call(components, key)) {
            app.use(components[key])
        }
    }
}

const MeleonUI = { install }

export default MeleonUI

// ml-button/index.ts
import type { App } from 'vue'
import _Button from './index.vue'

const install = async (app: App) => {
    app.component('MlButton', _Button)
}

export default { install }
```

虽然本质上都是使用 `app.component` 实现注册组件，但 uniapp 编译的结果却并不完全相同

在 `main.ts` 中注册 `MlButtonGroup` 组件被编译成函数

而 `meleon-ui.ts` 里的组件被编译成对象

在使用时也是前者可以被正常渲染成组件展示到页面上

因此，如果希望在微信小程序端实现批量注册组件，需要使用 uniapp 提供的 `easycom` 方法

> 详细内容可跳转：https://uniapp.dcloud.net.cn/collocation/pages.html#easycom

```json
// page.json
{
  "easycom": {
    "custom": {
      "^ml-(.*)": "@meleon/uni-ui/lib/ml-$1/index.vue"
    }
  }
}
```

#### 二、定制主题

MeleonUI 为用户提供了自定义主题的方法，可以通过向 `ml-config-provider` 组件传入 `themes` 属性，同时修改当前页面所有 ml- 组件的主题色

```html
<!-- template -->
<ml-config-provider :themes="themes">
  <!-- 消息提示 -->
  <ml-message ref="messageRef" />
  <!-- 导航栏 -->
  <ml-navigator
    :title="$t('home.navigation.title')"
    title-color="#FFFFFF"
    background="#7A98B3"
  ></ml-navigator>
  <ml-button
    type="primary"
    @click="onNavigate('/pages/componentList/index')"
  >
    {{ $t('home.actions.start') }}
    <ml-icon icon="ml-arrow-right--line" color="#FFFFFF" />
  </ml-button>
</ml-config-provider>
// script
import type { ConfigProviderProps } from '@meleon/uni-ui'

const themes: ConfigProviderProps['themes'] = {
    primary: '#D74B4B',
}
```

themes 支持以下属性，可根据项目的设计风格批量修改，当然，直接通过 CSS 修改样式也是可以的。

```ts
export type ColorType = 'primary' | 'info' | 'success' | 'warning' | 'danger'
```

#### 三、类型声明

在使用组件时，为了得到更好的类型提示，可以在 `tsconfig.json` 中注入 `MeleonUI` 的类型声明文件，如下所示。
如果你的编辑器中安装了 `Volar` 插件，注入成功后应该可以看到组件标签的颜色发生了变化，且当尝试传入组件 props 时，编辑器也会给出可选值的提示信息。

```json
{
  "types": ["@dcloudio/types", "@meleon/uni-ui"]
}
```

#### 四、更新日志

- 1.2.0
  - refactor: 打包结果调整
    - 去除初始的 .ts 文件
    - 去除用于 **重新导出** 的 `index.js` 和 `index.d.ts` 文件
    - 使用 terser 压缩代码，大幅降低组件库构建到小程序后的体积
    - `@meleon/uni-ui`小程序主包的构建体积从优化前的 **2.2MB** 降低到现在的 **958KB**
    - **注：**如果准备将 `1.1` 版本升级到 `1.2`版本，最好检查下之前对组件库内部方法、类型的引用路径是否存在问题，避免出现 bug
  - feat: 调整 `ml-form` 组件，添加下拉选框、多选、日期选择器、开关等表单项
- 1.1.13
  - feat: 新增 `ml-form` 组件【暂时仅支持 input 组件作为表单项】
  - feat: 新增 `ml-transition` 组件


- 1.1.12
  - style: `ml-drawer` 组件添加动效
  - fix: `ml-list` 组件修改虚拟列表插槽名称错误
  - feat: 新增 `ml-datetime-picker`、`ml-time-picker` 两个时间选择组件
- 1.1.11
  - feat: 调整 `ml-navigator` 组件的结构，完善功能
  - feat: 添加 `ml-switch` 组件
  - feat: `ml-cell` 组件添加 `SWITCH` 类型
  - fix: `ml-cell` 组件传入的 value 发生变化时未触发页面更新
  - feat: 新增 `ml-list` 组件，初步支持基础列表和虚拟列表功能
- 1.1.10
  - fix: `ml-checkbox` 设置 direction 失效的问题
  - fix: `ml-image` 预览层层级过低的问题
  - fix: `ml-select` 无激活样式的问题
  - fix: `ml-input-tag` 确认后未触发新增标签
  - docs: `@meleon/uni-ui`上线微信小程序，可体验各个组件的使用效果【微信扫描“演示”小程序码】
- 1.1.9
  - fix: `ml-loading` 图标缺失，`ml-button` 组件设置 loading 不生效
  - feat: 新增 `ml-image` 组件
  - feat: `ml-tree` 组件模块拆分，支持自定义 title 节点
  - feat: 新增 `ml-icon-switcher` 小组件
  - fix: `ml-button` 单图标按钮图标偏移的问题
  - feat: `ml-tree` 组件基本功能完善，支持文本选中、复选框选择、自定义标题节点等
  - fix: 去除 `ml-checkbox` 组件无文本时样式的偏移
  - feat: `ml-tree` 组件新增对外暴露的 expand、check 及 select 等方法
- 1.1.8
  - feat: 新增 `ml-cell`、`ml-cell-group` 组件
  - feat: 新增 `ml-uploader` 组件
- 1.1.7
  - feat: 新增 `ml-config-provider` 组件
  - docs: 调整项目接口，补全TS类型，新增 README 文档


### 组件

#### 头像 Avatar

###### 基本使用

如果头像内容是文字的话，会自动调节字体大小，来适应头像框

```html
<!-- 基础使用 -->
<ml-avatar>Meleon</ml-avatar>
<!-- 设置尺寸 -->
<ml-avatar :size="36">Meleon</ml-avatar>
<!-- 设置形状 -->
<ml-avatar :size="28" shape="circle">Meleon</ml-avatar>
<!-- 插入图片 -->
<ml-avatar shape="circle">
  <image src="@/assets/home/avatar.png" mode="widthFix" />
</ml-avatar>
```

###### APIs

| prop  | type             | default |
| ----- | ---------------- | ------- |
| size  | number           | 32      |
| shape | square \| circle | square  |

###### Slots

| name    | desc                     |
| ------- | ------------------------ |
| default | 默认插槽，传递文本或图片 |

#### 头像组 AvatarGroup

###### 基本使用

除了 maxCount 等属性，还可以传入 Avatar 的 size、shape 等属性实现批量设置

```html
<ml-avatar-group :size="36" :offset="10" shape="circle">
    <ml-avatar>Meleon</ml-avatar>
    <ml-avatar>Aliee</ml-avatar>
    <ml-avatar>Bob</ml-avatar>
</ml-avatar-group>
```

###### APIs

| prop     | type             | default | desc                         |
| -------- | ---------------- | ------- | ---------------------------- |
| maxCount | number           | 3       | 多出的部分会显示剩余的 count |
| offset   | number           | 0       | 头像组件之前的偏移量         |
| size     | number           | 32      | 头像组件的尺寸               |
| shape    | square \| circle | square  | 头像组件的形状               |

#### 按钮 Button

###### 基本使用

```html
<ml-button
  type="primary"
  size="mini"
  status="success"
>
    Primary
</ml-button>

<ml-button
  type="secondary"
  size="mini"
  shape="round"
  status="success"
>
    Secondary
</ml-button>

<ml-button
  type="outline"
  size="mini"
  status="success"
>
    Outline
</ml-button>

<ml-button
  type="text"
  size="mini"
  status="success"
  @click="handleClick"
>
    Text
</ml-button>
```

###### APIs

| prop     | type                                    | desc       |
| -------- | --------------------------------------- | ---------- |
| type     | primary \| secondary \| outline \| text | 按钮类型   |
| size     | mini \| small \| medium \| large        | 按钮尺寸   |
| shape    | square \| round \| circle               | 按钮形状   |
| status   | normal \| success \| warning \| danger  | 按钮状态   |
| loading  | boolean                                 | 是否加载中 |
| disabled | boolean                                 | 是否禁用   |

###### Slots

| name    | desc                         |
| ------- | ---------------------------- |
| default | 默认插槽                     |
| icon    | 图标插槽【位于默认插槽左侧】 |

###### Emits

| event | dessc    |
| ----- | -------- |
| click | 点击事件 |

#### 单元格 Cell

###### 基本使用

```html
<ml-cell-group title="测试分组" style="width: 100%">
    <ml-cell
        :type="CellTypeEnum.TEXT"
        label="测试标题"
        value="测试内容"
        description="测试描述"
        disabled
        allow-edit
        style="width: 100%"
        @change="handleCellChange"
    ></ml-cell>
    <ml-cell
        :type="CellTypeEnum.BUTTON"
        label="测试按钮"
        btn-status="danger"
        disabled
        style="width: 100%"
        @btn-click="handleClick"
    ></ml-cell>
    <ml-cell
        :type="CellTypeEnum.NAV"
        label="测试回到首页"
        value="回首页"
        url="/pages/home/index"
        :disabled="false"
        style="width: 100%"
    >
        <template #rightIcon>
          <ml-icon name="ml-arrow-right--line" color="var(--info-color-7)"></ml-icon>
        </template>
        <template #value>
          <text style="font-size: 14px; color: var(--info-color-7)">通过插槽展示内容</text>
        </template>
    </ml-cell>
</ml-cell-group>
```

###### APIs

| prop        | type                                   | default | desc                                                        |
| ----------- | -------------------------------------- | ------- | ----------------------------------------------------------- |
| label       | string                                 | ''      | 左侧标题                                                    |
| description | string                                 | ''      | 左侧描述                                                    |
| value       | string                                 | ''      | 右侧 值                                                     |
| type        | text \| button \| navigator            | text    | 单元格类型【推荐使用 `CellTypeEnum` 枚举变量赋值】          |
| disabled    | boolean                                | false   | 是否禁用                                                    |
| allowEdit   | boolean                                | false   | 文本单元格是否允许修改                                      |
| btnStatus   | normal \| success \| warning \| danger | normal  | 按钮单元格的按钮类型                                        |
| url         | string                                 | ''      | 导航单元格的跳转地址【小程序页面地址需要保留前面的 / 字符】 |

###### Slots

| name      | desc                                                   |
| --------- | ------------------------------------------------------ |
| label     | 左侧标题插槽【会覆盖默认的 label 和 description 区域】 |
| value     | 右侧内容插槽                                           |
| rightIcon | 导航单元格生效【覆盖右侧图标】                         |

###### Emits

| name     | desc                                                       |
| -------- | ---------------------------------------------------------- |
| change   | 文本单元格修改内容后触发                                   |
| btnClick | 按钮单元格点击触发                                         |
| navigate | 导航单元格点击跳转后触发【暂不暴露，考虑有没有必要放出来】 |

###### CellGroup APIs

| prop  | type   | default | desc     |
| ----- | ------ | ------- | -------- |
| title | string | ''      | 分组名称 |

###### CellGroup Slots

| name  | desc                     |
| ----- | ------------------------ |
| title | 标题插槽【覆盖标题内容】 |

#### 选择框 Checkbox

###### 基本使用

```html
<ml-checkbox v-model:checked="checked1">Radio</ml-checkbox>
<ml-checkbox indeterminate>Radio</ml-checkbox>
<ml-checkbox v-model:checked="checked1" disabled>Radio</ml-checkbox>
```

###### APIs

| props         | type             | default | desc                          |
| ------------- | ---------------- | ------- | ----------------------------- |
| checked       | boolean          | false   | 是否选中                      |
| indeterminate | boolean          | false   | 是否为半选状态                |
| disabled      | boolean          | false   | 是否禁用                      |
| value         | string \| number | ''      | CheckboxGroup 下的 value 标识 |

###### Slots

| name    | desc           |
| ------- | -------------- |
| default | 选择框右侧内容 |

###### Emits

| event          | desc     |
| -------------- | -------- |
| update:checked | 切换事件 |

#### 选择框分组 CheckboxGroup

###### 基本使用

```html
<ml-checkbox-group
  v-model:checked-list="checked3"
  mode="multi"
  :max="3"
  :min="1"
>
    <ml-checkbox value="A">RadioA</ml-checkbox>
    <ml-checkbox value="B">RadioB</ml-checkbox>
    <ml-checkbox value="C">RadioC</ml-checkbox>
    <ml-checkbox value="D">RadioD</ml-checkbox>
    <ml-checkbox value="E">RadioE</ml-checkbox>
</ml-checkbox-group>
```

###### APIs

| prop        | type                    | default    | desc           |
| ----------- | ----------------------- | ---------- | -------------- |
| checkedList | Array<string \| number> | 必填       | 选中的选项列表 |
| min         | number                  | 0          | 至少选中数量   |
| max         | max                     | Infinity   | 至多选中数量   |
| direction   | vertical \| horizontal  | horizontal | 纵向或横向排列 |
| mode        | single \| multi         | single     | 切换单选或多选 |

###### Emits

| event              | desc           |
| ------------------ | -------------- |
| update:checkedList | 更新选中的列表 |

#### 全局配置 ConfigProvider

###### 基本使用

设置全局主题色

```html
<ml-config-provider :themes="themes">
    <ml-button type="primary"></ml-button>
    <ml-button type="success"></ml-button>
    <ml-button type="warning"></ml-button>
    <ml-button type="danger"></ml-button>
</ml-config-provider>
import type { ConfigProviderProps } from '~/lib/ml-config-provider/index.interface'

const themes: ConfigProviderProps['themes'] = {
  primary: '#D74B4B'
}
```

###### APIs

| prop   | type                          | example                |
| ------ | ----------------------------- | ---------------------- |
| themes | ConfigProviderProps['themes'] | { primary: '#D74B4B' } |

#### 数值显示 CountTo

###### 基本使用

```html
<ml-count-to
    :from="0"
    :to="1250.44"
    animation
    :animation-duration="5000"
    :value-style="{ color: '#0fbf60' }"
>
    <template #suffix>
      <text>%</text>
    </template>
</ml-count-to>
```

###### APIs

| prop              | type                | default  | desc                   |
| ----------------- | ------------------- | -------- | ---------------------- |
| from              | number              | 0        | 起始值                 |
| to                | number              | required | 结束时展示的数据，必填 |
| animation         | boolean             | false    | 是否开启动画           |
| animationDuration | number              | 1000     | 动画持续时间           |
| showSeperator     | boolean             | false    | 是否显示千分位分隔符   |
| valueStyle        | Record<string, any> | {}       | 数据的样式             |

###### Slots

| name   | desc           |
| ------ | -------------- |
| prefix | 数值左侧的内容 |
| suffix | 数值右侧的内容 |

###### Expose

| name    | desc     |
| ------- | -------- |
| start   | 开启动画 |
| pause   | 暂停动画 |
| restart | 继续动画 |

#### 时间选择器 DatetimePicker

###### 基本使用

```html
<ml-datetime-picker
    v-model="value"
    mode="date"
    :disabled-date="isDateDisabled"
    style="width: 100%"
    @change="handleValueChange"
>
    <template #trigger>
        <ml-cell
            label="日期选择器"
            :value="formatToDateTime(value)"
            style="width: 100%"
        ></ml-cell>
    </template>
</ml-datetime-picker>
const value = ref(new Date())

const isDateDisabled = (current: Date) => {
    return current.getTime() < new Date().getTime() - 24 * 60 * 60 * 1000
}

const handleValueChange = (value: DatetimePickerProps['modelValue']) => {
    console.log('change', value)
}
```

###### APIs

| prop               | type                       | default      | desc                   |
| ------------------ | -------------------------- | ------------ | ---------------------- |
| modelValue         | Date, string, number       | ''           | 选择的日期时间         |
| defaultModelValue  | Date, string, number       | ''           | 默认展示的日期时间     |
| pickerValue        | Date, string, number       | ''           | 面板头部展示的时间     |
| defaultPickerValue | Date, string, number       | ''           | 面板头部默认展示的时间 |
|                    |                            |              |                        |
| locale             | Record<string, any>        | {}           | 自定义面板中展示的文本 |
| mode               | 'date', 'year', 'month'    | 'date'       | 选择器模式             |
| format             | string                     | 'YYYY-MM-DD' | 时间转换格式           |
|                    |                            |              |                        |
| disabledDate       | (current: Date) => boolean | undefined    | 判断日期是否禁用       |

###### Emits

| name              | type                  | desc                 |
| ----------------- | --------------------- | -------------------- |
| change            | (value: Date) => void | 选中值发生变化时触发 |
| update:modelValue | (value: Date) => void | 双向绑定选中日期     |

#### 抽屉 Drawer

###### 基本使用

```html
<ml-drawer
  v-model:visible="modelVisible"
  :placement="placement"
  :has-nav="hasNav"
>
    <template #title>自定义标题</template>
    <text>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Magni, dolor libero
        blanditiis distinctio commodi totam recusandae aliquam fugit officiis sequi
        perspiciatis non animi, eaque facilis sed reiciendis in aut vel.
    </text>
    <template #footer>
        <ml-button>自定义页脚</ml-button>
    </template>
</ml-drawer>
```

###### APIs

| prop       | type                           | default  | desc                                      |
| ---------- | ------------------------------ | -------- | ----------------------------------------- |
| hasNav     | boolean                        | true     | 当前页面有无导航栏，以控制 drawer 高度    |
| visible    | boolean                        | required | 是否显示                                  |
| placement  | left \| right \| top \| bottom | right    | 展开的方向                                |
| width      | number                         | 250      | placement 为 left \| right 时，抽屉的宽度 |
| height     | number                         | 250      | placement 为 top \| bottom 时，抽屉的高度 |
| radius     | number                         | 0        | 抽屉圆角                                  |
| okText     | string                         | 确定     | 底部确认按钮的文本                        |
| cancelText | string                         | 取消     | 底部取消按钮的文本                        |

###### Slots

| name    | desc     |
| ------- | -------- |
| default | 主体内容 |
| title   | 标题内容 |
| footer  | 底部内容 |

###### Emits

| name           | desc             |
| -------------- | ---------------- |
| update:visible | 显示状态变化触发 |
| close          | 点击取消按钮     |
| ok             | 点击确认按钮     |

#### 图标 Icon

###### 基本使用

```html
<ml-icon :name="icon" :size="24" color="#808080" />
export default {
  id: '4118979',
  name: 'MeleonUI',
  font_family: 'ml-icon',
  css_prefix_text: 'ml-',
  description: '',
  glyphs: [
    {
      icon_id: '8106223',
      name: 'cancel',
      font_class: 'close--circle',
      unicode: 'e698',
      unicode_decimal: 59032
    },
    {
      icon_id: '8106241',
      name: 'information',
      font_class: 'info--circle',
      unicode: 'e6a5',
      unicode_decimal: 59045
    },
    {
      icon_id: '8106248',
      name: 'selection',
      font_class: 'selection--circle',
      unicode: 'e6a6',
      unicode_decimal: 59046
    },
    {
      icon_id: '11992738',
      name: 'github',
      font_class: 'github',
      unicode: 'e6a9',
      unicode_decimal: 59049
    },
    {
      icon_id: '18531280',
      name: 'right arrow',
      font_class: 'arrow-right--line',
      unicode: 'e701',
      unicode_decimal: 59137
    },
    {
      icon_id: '38175316',
      name: 'right arrow',
      font_class: 'arrow-left--line',
      unicode: 'e87f',
      unicode_decimal: 59519
    },
    {
      icon_id: '12865674',
      name: 'night',
      font_class: 'night',
      unicode: 'e6ef',
      unicode_decimal: 59119
    },
    {
      icon_id: '12865690',
      name: 'day',
      font_class: 'day',
      unicode: 'e6f1',
      unicode_decimal: 59121
    },
    {
      icon_id: '702307',
      name: 'drag',
      font_class: 'drag',
      unicode: 'e7d0',
      unicode_decimal: 59344
    },
    {
      icon_id: '11488125',
      name: 'seleted',
      font_class: 'selected',
      unicode: 'e763',
      unicode_decimal: 59235
    },
    {
      icon_id: '10678428',
      name: 'mirror light ctrl',
      font_class: 'filter',
      unicode: 'e6e0',
      unicode_decimal: 59104
    },
    {
      icon_id: '5772874',
      name: '复制',
      font_class: 'copy',
      unicode: 'e617',
      unicode_decimal: 58903
    },
    {
      icon_id: '4936963',
      name: 'CodeSandbox',
      font_class: 'code-sandbox',
      unicode: 'e87e',
      unicode_decimal: 59518
    },
    {
      icon_id: '8106210',
      name: 'bluetoothon',
      font_class: 'bluetoothon',
      unicode: 'e697',
      unicode_decimal: 59031
    },
    {
      icon_id: '8106293',
      name: 'wifi',
      font_class: 'wifi',
      unicode: 'e6a4',
      unicode_decimal: 59044
    },
    {
      icon_id: '10678426',
      name: 'message',
      font_class: 'message',
      unicode: 'e6df',
      unicode_decimal: 59103
    },
    {
      icon_id: '10678434',
      name: 'search',
      font_class: 'search',
      unicode: 'e6e1',
      unicode_decimal: 59105
    },
    {
      icon_id: '10678439',
      name: 'scan QR',
      font_class: 'scan',
      unicode: 'e6e2',
      unicode_decimal: 59106
    },
    {
      icon_id: '10678401',
      name: 'back',
      font_class: 'arrow-left',
      unicode: 'e6db',
      unicode_decimal: 59099
    },
    {
      icon_id: '8106272',
      name: 'next',
      font_class: 'arrow-right',
      unicode: 'e6a3',
      unicode_decimal: 59043
    },
    {
      icon_id: '8106251',
      name: 'increase',
      font_class: 'plus',
      unicode: 'e69f',
      unicode_decimal: 59039
    },
    {
      icon_id: '8106217',
      name: 'delete',
      font_class: 'delete',
      unicode: 'e699',
      unicode_decimal: 59033
    },
    {
      icon_id: '8106245',
      name: 'close',
      font_class: 'close',
      unicode: 'e69e',
      unicode_decimal: 59038
    },
    {
      icon_id: '10670577',
      name: 'address',
      font_class: 'address',
      unicode: 'e6a7',
      unicode_decimal: 59047
    },
    {
      icon_id: '12865660',
      name: 'connect device',
      font_class: 'connect',
      unicode: 'e6e7',
      unicode_decimal: 59111
    },
    {
      icon_id: '12865665',
      name: 'done work',
      font_class: 'donework',
      unicode: 'e6e9',
      unicode_decimal: 59113
    },
    {
      icon_id: '12865672',
      name: 'like',
      font_class: 'like',
      unicode: 'e6eb',
      unicode_decimal: 59115
    },
    {
      icon_id: '12865673',
      name: 'mark',
      font_class: 'mark',
      unicode: 'e6ec',
      unicode_decimal: 59116
    },
    {
      icon_id: '12865685',
      name: 'share',
      font_class: 'share',
      unicode: 'e6ee',
      unicode_decimal: 59118
    },
    {
      icon_id: '12865693',
      name: 'setting',
      font_class: 'setting',
      unicode: 'e6f0',
      unicode_decimal: 59120
    },
    {
      icon_id: '8106224',
      name: 'down',
      font_class: 'arrow-down',
      unicode: 'e69b',
      unicode_decimal: 59035
    },
    {
      icon_id: '8106260',
      name: 'upward',
      font_class: 'arrow-up',
      unicode: 'e6a1',
      unicode_decimal: 59041
    },
    {
      icon_id: '8106276',
      name: 'visible',
      font_class: 'eye-close',
      unicode: 'e6a2',
      unicode_decimal: 59042
    },
    {
      icon_id: '8106285',
      name: 'eye_protection',
      font_class: 'eye',
      unicode: 'e6a0',
      unicode_decimal: 59040
    }
  ]
}
```

#### 图片 Image

###### 基本使用

| prop        | type          | default                     | desc                                                         |
| ----------- | ------------- | --------------------------- | ------------------------------------------------------------ |
| hasNav      | boolean       | true                        | 当前页面是否有导航栏，会计算系统导航栏高度，预览区域下移一段距离 |
| src         | string        | 必填项                      | 图片的资源路径                                               |
| mode        | ImageModeEnum | ImageModeEnum.ASPECT_FIT    | 图片的填充模式，可参照uniapp官方提供的 Image 组件的 mode 值  |
| width       | number        | 100                         | 图片宽度                                                     |
| height      | number        | 100                         | 图片高度                                                     |
| tools       | string        | zoomIn,zoomOut,rotate,reset | 由这四种工具随意组合，【,】拼接而成的字符串                  |
| showPreview | boolean       | true                        | 是否允许点击图片后展示预览                                   |
| showLoading | boolean       | false                       | 是否展示加载中标识                                           |
| showError   | boolean       | false                       | 是否展示加载失败提示                                         |

###### Slots

| name    | desc             |
| ------- | ---------------- |
| loading | 调整加载中提示   |
| error   | 覆盖加载失败提示 |

###### Emits

| name   | desc         |
| ------ | ------------ |
| loaded | 加载成功触发 |
| error  | 加载失败触发 |
| click  | 点击图片     |

#### 输入框 Input

###### 基本使用

```html
<ml-input v-model:model-value="modelValue" placeholder="提示文字可自定义" />

<ml-input v-model:model-value="modelValue" :size="inputSize" />

<ml-input v-model:model-value="modelValue" type="password" />

<ml-input v-model:model-value="modelValue" disabled :size="inputSize" />
```

#### 列表 List

###### 基本使用

基础列表使用示例

```html
<ml-list
    ref="listRef"
    v-model:error="error"
    :data="mockData"
    :loading="loading"
    :loading-text="'自定义的加载中文本...'"
    :finished="finished"
    :height="400"
    style="width: 100%"
    @load="handleLoad"
>
    <template #item="{ item }">
        <ml-cell
            :label="item.label"
            :description="item.description"
            :value="item.value"
            allow-edit
        />
    </template>
</ml-list>
import type { ListInstance, MessageInstance } from '@meleon/uni-ui'

const mockData = ref(
    new Array(20).fill(0).map((_, index) => {
        return {
            id: `id-${index}`,
            label: `标题${index}`,
            description: `描述${index}`,
            value: `值${index}`
        }
    })
)

const listRef = ref<ListInstance>()
const messageRef = ref<MessageInstance>()
const finished = ref(false)
const loading = ref(false)
const error = ref(true)
const handleLoad = () => {
    if (!messageRef.value) return
    if (mockData.value.length >= 60) {
        finished.value = true
    }

    if (finished.value) {
        messageRef.value.success({
            content: '所有数据加载完成',
            duration: 2000
        })
    } else {
        messageRef.value.primary({
            content: '触发 load 事件',
            duration: 2000
        })

        mockData.value = [
            ...mockData.value,
            ...new Array(20).fill(0).map((_, index) => {
                const idx = mockData.value.length + index
                return {
                    id: `id-${idx}`,
                    label: `标题${idx}`,
                    description: `描述${idx}`,
                    value: `值${idx}`
                }
            })
        ]
    }
}

onMounted(() => {
  if (listRef.value) listRef.value.scrollIntoView('id-10')
})
```

虚拟列表使用示例

区别于基础列表，虚拟列表不支持调用 load 加载新数据，使用时需要准备好完整的初始数据

默认情况下，页面上仅展示15条数据节点，可以通过传入 pageSize 自定义每页展示的数量

```html
<ml-list :data="mockData" :height="400" virtual-list style="width: 100%">
    <template #virtual="{ data }">
        <ml-cell
            v-for="item in data"
            :key="item.id"
            :label="item.label"
            :description="item.description"
            :value="item.value"
            allow-edit
        ></ml-cell>
    </template>
</ml-list>
const mockVirtualData = ref(
    new Array(100).fill(0).map((_, index) => {
        return {
            id: `id-${index}`,
            label: `标题${index}`,
            description: `描述${index}`,
            value: `值${index}`
        }
    })
)
```

###### APIs

| prop         | type     | default            | desc                                                     |
| ------------ | -------- | ------------------ | -------------------------------------------------------- |
| data         | WithId[] | []                 | 列表数据，需要提供 id 字段用以定位等操作                 |
| height       | number   | 必填               | 列表容器的高度                                           |
| itemHeight   | number   | 58                 | 列表项的高度，可控制提示文本的高度及滚动阈值             |
| loading      | boolean  | false              | 是否处于加载状态                                         |
| loadingText  | string   | 加载中...          | 加载状态底部的提示文本                                   |
| finished     | boolean  | false              | 是否加载完成                                             |
| finishedText | string   | 没有更多了         | 加载完成时，底部的提示文本                               |
| error        | boolean  | false              | 是否加载失败                                             |
| errorText    | string   | 加载失败，点击重试 | 加载失败时，底部的提示文本【可点击，重新触发 load 事件】 |
|              |          |                    |                                                          |
| virtualList  | boolean  | false              | 是否开启虚拟列表                                         |
| pageSize     | number   | 15                 | 虚拟列表中展示的数量                                     |

###### Slots

| name     | desc             |
| -------- | ---------------- |
| loading  | 加载中           |
| error    | 加载异常         |
| finished | 加载完成         |
| virtual  | 虚拟列表         |
| item     | 普通列表的列表项 |

###### Methods

| name           | type                                     | desc                             |
| -------------- | ---------------------------------------- | -------------------------------- |
| scrollToTop    | () => void                               | 滚动到顶部                       |
| scrollIntoView | (id: string \| number \| symbol) => void | 滚动到指定节点【仅支持普通列表】 |

###### Events

| name         | desc               |
| ------------ | ------------------ |
| update:error | 更新 error 属性    |
| load         | 列表滚动到底部触发 |

#### 选择器 Select

###### 基本使用

```html
<ml-select v-model:modelValue="modelValue" multiple :max-tag-count="1">
    <ml-option value="1" label="选项1" />
    <ml-option value="2" label="选项2" />
    <ml-option value="3" label="选项3" />
</ml-select>
```

#### 开关 Switch

###### 基本使用

```html
<ml-switch v-model="isActive"></ml-switch>

<ml-switch v-model="isActive" type="circle"></ml-switch>

<ml-switch v-model="isActive" type="line"></ml-switch>
```

###### APIs

| prop           | type                                             | default                | desc           |
| -------------- | ------------------------------------------------ | ---------------------- | -------------- |
| modelValue     | boolean                                          | false                  | 是否开启       |
| type           | circle \| square \|line                          | square                 | 开关类型       |
| checkedColor   | string                                           | var(--info-color-7)    | 激活状态颜色   |
| uncheckedColor | string                                           | var(--primary-color-6) | 未激活状态颜色 |
| disabled       | boolean                                          | false                  | 是否禁用       |
| beforeSwitch   | (newVal: boolean) => boolean \| Promise<boolean> | () => true             | 切换前触发     |

#### 表格 Table

###### 基本使用

```html
<ml-table
  :data="tableData"
  size="mini"
  stripe
  border
  :height="200"
  :loading="false"
  :refresher-enabled="true"
  :refresher-interval="2000"
  style="width: 100%"
  @row-click="onRowClick"
  @cell-click="onCellClick"
>
    <template #cell="{ column, row }">
        <block v-if="column.property === 'gender'">
            <ml-tag :model-value="row[column.property]" type="primary" size="mini"></ml-tag>
        </block>
        <block v-else-if="column.property === 'age'">
            <ml-count-to
                         :to="row[column.property]"
                         animation
                         :value-style="{ fontSize: '14px', color: 'var(--info-color-8)' }"
                         ></ml-count-to>
        </block>
        <text v-else>{{ column.property && row[column.property] }}</text>
    </template>
    <!-- 。。。这操作有点多余，后面改改 -->
    <ml-table-column type="index" fixed="left"></ml-table-column>
    <ml-table-column prop="name" label="姓名" fixed></ml-table-column>
    <ml-table-column prop="age" label="年龄"></ml-table-column>
    <ml-table-column prop="gender" label="性别"></ml-table-column>
</ml-table>
```

#### 消息提示 Message

###### 基本使用

```html
<ml-message ref="messageRef" />
import type { MessageInstance, MessageOptions } from '@meleon/uni-ui'

const messageRef = ref<MessageInstance>()

const showMessage = (type: MessageOptions['type']) => {
    if (!messageRef.value || !type) return
    messageRef.value[type]({
      content: type + idx.value++,
      duration: 2000
    })
}
```

#### 头部导航栏 Navigator

###### 基本使用

```html
<ml-navigator
  title="ml-navigator"
  title-color="#FFFFFF"
  has-back
  icon-color="#FFFFFF"
  background="#7A98B3"
/>
```

###### APIs

| prop       | type                       | default                | desc                             |
| ---------- | -------------------------- | ---------------------- | -------------------------------- |
| background | string                     | var(--primary-color-6) | 导航栏背景，支持纯色、渐变或图片 |
| title      | string                     | ''                     | 导航栏标题                       |
| titleColor | string                     | #FFFFFF                | 标题颜色                         |
| titleStyle | Object                     | {}                     | 标题样式                         |
| hasBack    | boolean                    | true                   | 是否有回退图标                   |
| iconColor  | string                     | #FFFFFF                | 图标颜色                         |
| iconStyle  | Object                     | {}                     | 图标样式                         |
| tools      | Array<NavigatorToolEntity> | []                     | 图标列表                         |

```
NavigatorToolEntity
```

| prop  | type                | desc                                         |
| ----- | ------------------- | -------------------------------------------- |
| icon  | string              | 图标名                                       |
| color | string              | 图标颜色                                     |
| type  | return \| navigator | 点击图标的事件的类型                         |
| delta | number[可选]        | type = return 时，delta 表示回退的页面数     |
| path  | string[可选]        | type = navigator 时，path 表示跳转的页面地址 |

###### Slots

| name    | desc         |
| ------- | ------------ |
| icon    | 图标插槽     |
| default | 主体内容插槽 |

#### 时间轴 TimeLine

###### 基本使用

```html
<ml-timeline :reverse="reverseCheckedList[0] === 'reverse'">
    <ml-timeline-item>
        <template #label>
          <text>2023-09-08</text>
        </template>
        <view>The first milestone content</view>
    </ml-timeline-item>
    <ml-timeline-item>
        <template #label>
          <text>2023-09-09</text>
        </template>
        <view>
            <view>The second milestone</view>
            <view>The second milestone</view>
            <view>The second milestone</view>
        </view>
    </ml-timeline-item>
    <ml-timeline-item>
        <template #label>
          <text> 2023-09-25 </text>
        </template>
        <view>The third milestone </view>
    </ml-timeline-item>
</ml-timeline>
```

#### 时间选择器 TimePicker

###### 基本使用

```html
<ml-time-picker v-model="value" style="width: 100%">
    <template #trigger>
        <ml-cell
            label="时间选择器"
            :value="value"
            style="width: 100%"
        ></ml-cell>
    </template>
</ml-time-picker>
```

#### 进度条 Progress

###### 基本使用

```html
<ml-progress :percent="percent" status="success" />

<ml-progress :percent="templateMap[0].percent">
  <template #text="{ percent, decimal }">
    <text>进度 {{ (percent * 100).toFixed(decimal) }}%</text>
  </template>
</ml-progress>

<ml-progress :percent="percent" type="circle" :size="curSize" status="primary" />
```

#### 标签页 Tabs

###### 基本使用

```html
<ml-tabs active="a">
    <ml-tab value="a" title="标签1" closable>内容1</ml-tab>
    <ml-tab value="b" title="标签2" disabled>内容2</ml-tab>
    <ml-tab value="c" title="标签3" disabled closable>内容3</ml-tab>
    <ml-tab value="d" title="标签4">内容4</ml-tab>
    <ml-tab value="e" title="标签5">内容5</ml-tab>
</ml-tabs>
```

#### 标签 Tag

###### 基本使用

```html
<ml-tag model-value="标签2" type="primary" plain />

<ml-tag model-value="标签3" size="medium" />

<view>closeable: 添加 ml-close 图标，点击可触发 close 事件</view>
<ml-tag model-value="标签1" closable @close="hanldeTagClose" />

<view>editable: 点击后标签会切换成 input 输入框，可修改标签内容 </view>
<ml-tag model-value="标签2" checkable @click="hanldeTagClick" />

<view>checkable: 添加点击样式，触发 click 事件</view>
<ml-tag v-model:model-value="tagValue" editable />
```

#### 树 Tree

###### 基本使用

```html
<ml-tree
  class="tree-wrapper"
  ref="treeRef"
  v-model:expanded-keys="expandedKeys"
  v-model:checked-keys="checkedKeys"
  v-model:selected-keys="selectedKeys"
  v-model:indeterminate-keys="indeterminateKeys"
  :data="treeData"
  checkable
  selectable
  multiple
  :auto-expand-parent="false"
  @check="handleCheck"
  @select="handleSelect"
  @expand="handleExpand"
></ml-tree>

<view class="btn-list">
    <ml-button type="primary" @click="handleExpandAll">
        {{ isExpandAll ? 'Close All' : 'Expand All' }}
    </ml-button>

    <ml-button type="primary" @click="handleCheckAll">
        {{ isCheckAll ? 'Unheck All' : 'Check All' }}
    </ml-button>

    <ml-button type="primary" @click="handleSelectAll">
        {{ isSelectAll ? 'Unselect All' : 'Select All' }}
    </ml-button>

    <ml-button type="primary" @click="handleExpandNode"> Expand Root </ml-button>

    <ml-button type="primary" @click="handleCheckNode"> Check Root </ml-button>

    <ml-button type="primary" @click="handleSelectNode"> Select Root </ml-button>
</view>
  import type {
    TreeDataEntity,
    TreeCheckPayload,
    TreeExpandPayload,
    TreeSelectPayload,
    TreeInstance
  } from '@meleon/uni-ui/index'

  const treeRef = ref<TreeInstance>()
  const rootKey = ref('0-0')
  const expandedKeys = ref<string[]>(['0-0-0'])
  const checkedKeys = ref<string[]>([])
  const selectedKeys = ref<string[]>([])
  const indeterminateKeys = ref<string[]>([])
  
  // 监听 tree 触发的事件
  const handleCheck = (val: string[], payload: TreeCheckPayload) => {
    console.log('onCheck', val, payload)
  }
  const handleExpand = (val: string[], payload: TreeExpandPayload) => {
    console.log('onExpand', val, payload)
  }
  const handleSelect = (val: string[], payload: TreeSelectPayload) => {
    console.log('onSelect', val, payload)
  }

  // 手动调用组件暴露的方法
  
  // 展开节点
  const isExpandAll = ref(false)
  const handleExpandAll = () => {
    if (!treeRef.value) return
    isExpandAll.value = !isExpandAll.value
    treeRef.value.expandAll(isExpandAll.value)
  }
  const handleExpandNode = () => {
    if (!treeRef.value) return
    treeRef.value.expandNode(rootKey.value, true)
  }

  // 选中节点复选框
  const isCheckAll = ref(false)
  const handleCheckAll = () => {
    if (!treeRef.value) return
    isCheckAll.value = !isCheckAll.value
    treeRef.value.checkAll(isCheckAll.value)
  }
  const handleCheckNode = () => {
    if (!treeRef.value) return
    treeRef.value.checkNode(rootKey.value, true)
  }

  // 选中节点
  const isSelectAll = ref(false)
  const handleSelectAll = () => {
    if (!treeRef.value) return
    isSelectAll.value = !isSelectAll.value
    treeRef.value.selectAll(isSelectAll.value)
  }
  const handleSelectNode = () => {
    if (!treeRef.value) return
    treeRef.value.selectNode(rootKey.value, true)
  }

  // 节点数据
  const treeData: TreeDataEntity[] = [
    {
      title: 'Trunk 0-0',
      key: '0-0',
      children: [
        {
          title: 'Branch 0-0-0',
          key: '0-0-0',
          disabled: false,
          children: [
            {
              title: 'Leaf',
              key: '0-0-0-0'
            },
            {
              title: 'Leaf',
              key: '0-0-0-1'
            }
          ]
        },
        {
          title: 'Branch 0-0-1',
          key: '0-0-1',
          children: [
            {
              title: 'Leaf',
              key: '0-0-1-0'
            }
          ]
        }
      ]
    }
  ]
```

###### APIs

| prop                | type             | default   | desc                           |
| ------------------- | ---------------- | --------- | ------------------------------ |
| data                | TreeDataEntity[] | 必填      | 树形数据                       |
| checkable           | boolean          | false     | 是否显示复选框                 |
| checkedKeys         | string[]         | undefined | 选中复选框的节点键值列表       |
| defaultCheckedKeys  | string[]         | []        | 默认选中复选框的节点键值列表   |
| indeterminateKeys   | string[]         | []        | 半选状态的节点键值列表         |
| expandedKeys        | string[]         | 必填      | 展开的节点键值列表             |
| defaultExpandedKeys | string[]         | []        | 默认展开的节点键值列表         |
| autoExpandParent    | boolean          | true      | 是否自动展开父级节点           |
| selectable          | boolean          | false     | 是否支持点击文本选中           |
| selectedKeys        | string[]         | undefined | 选中的文本节点键值列表         |
| defaultSelectedKeys | string[]         | []        | 默认选中的文本节点键值列表     |
| multiple            | boolean          | true      | 点击文本节点的选择是否支持多选 |

###### Slots

| name  | desc               | prop  |
| ----- | ------------------ | ----- |
| title | 自定义文本节点内容 | title |

###### Emits

| name                     | desc                              | params                                                       |
| ------------------------ | --------------------------------- | ------------------------------------------------------------ |
| update:expandedKeys      | 更新 expandedKeys【v-model】      | expandedKeys: string[]                                       |
| expand                   | 展开时触发                        | 1. expandedKeys: string[]<br />2. {<br />    expanded: boolean<br />    node: TreeNodeEntity<br />    nodeData: TreeDataEntity<br />} |
| update:selectedKeys      | 更新 selectedKeys【v-model】      | selectedKeys: string[]                                       |
| select                   | 点击文本节点时触发                | 1. selectedKeys: string[]<br />2. {<br />    selected: boolean<br />    node: TreeNodeEntity<br />    nodeData: TreeDataEntity<br />} |
| update:checkedKeys       | 更新 checkedKeys【v-model】       | checkedKeys: string[]                                        |
| check                    | 点击节点复选框时触发              | 1. checkedKeys: string[]<br />2. {<br />    checked: boolean<br />    checkedKeys: string[]<br />    indeterminateKeys: string[]<br />    node: TreeNodeEntity<br />    nodeData: TreeDataEntity<br />} |
| update:indeterminateKeys | 更新 indeterminateKeys【v-model】 | indeterminateKeys: string[]                                  |

###### Events

| name                  | desc                 | params                                               |
| --------------------- | -------------------- | ---------------------------------------------------- |
| getExpandedNodes      | 获取展开的节点       | () => TreeNodeEntity[]                               |
| expandNode            | 展开指定的节点       | (key: string \| string[], expand: boolean) => void   |
| expandAll             | 展开所有节点         | (expandAll: boolean = true) => void                  |
|                       |                      |                                                      |
| getSelectedNodes      | 获取选中的节点       | () => TreeNodeEntity[]                               |
| selectNode            | 选择指定的节点       | (key: string \| string[], selected: boolean) => void |
| selectAll             | 选择所有节点         | (selectAll: boolean = true) => void                  |
|                       |                      |                                                      |
| getCheckedNodes       | 获取选中复选框的节点 | () => TreeNodeEntity[]                               |
| getIndeterminateNodes | 获取半选状态的节点   | () => TreeNodeEntity[]                               |
| checkNode             | 选中指定节点的复选框 | (key: string \| string[], checked: boolean) => void  |
| checkAll              | 选中所有节点的复选框 | (checkAll: boolean = true) => void                   |



#### 上传组件 Uploader

###### 基本使用

```vue
<template>
    <ml-uploader
        ref="uploadRef"
        v-model:file-list="fileList"
        action="http://localhost:3000/api/file/upload"
        multiple
        show-file-list
        :disabled="false"
        :auto-upload="false"
        :on-change="handleOnChange"
        @delete="handleDelete"
    >
        <template #trigger>
          <ml-button type="primary">选择文件</ml-button>
        </template>
    </ml-uploader>

  <ml-button
    type="primary"
    status="success"
    @click="handleSubmit"
  >
        上传
  </ml-button>
</template>

<script lang="ts" setup>
    import type { FileItem, UploaderInstance } from '@meleon/uni-ui/ml-uploader'
    
    const fileList = ref<FileItem[]>([])
    const handleDelete = () => {
    console.log('a', fileList.value)
  }

  const handleOnChange = (files: FileItem[]) => {
    console.log('a', files)
  }

  const uploadRef = ref<UploaderInstance>()
  const handleSubmit = () => {
    if (!uploadRef.value) return
    uploadRef.value.submit()
  }
</script>
```

###### APIs

| props        | type                                               | default    | desc                         |
| ------------ | -------------------------------------------------- | ---------- | ---------------------------- |
| action       | string \| () => string                             | ''         | 文件上传的地址               |
| autoUpload   | boolean                                            | false      | 是否自动上传                 |
| fieldName    | string                                             | file       | 上传时文件对应的字段名       |
| headers      | Record<string, string>                             | {}         | 上传时携带的请求头           |
| data         | Record<string, any>                                | {}         | 上传时 FromData 里其他的数据 |
| fileList     | FileItem                                           | 必填项     | 展示的文件列表               |
| multiple     | boolean                                            | false      | 是否支持同时选择多个文件     |
| limit        | number                                             | 9          | 最多缓存的文件数量           |
| previewSize  | number                                             | 100        | 预览区域尺寸                 |
| disabled     | boolean                                            | false      | 是否禁用                     |
| showFileList | boolean                                            | false      | 是否展示文件列表             |
| sourceType   | UploaderSourceTypeEnum                             | ''         | 支持相机或相册，默认都支持   |
| beforeUpload | (files: FileItem[]) => boolean \| Promise<boolean> | () => true | 上传前的钩子函数             |
| beforeDelete | (file: FileItem) => boolean \| Promise<boolean>    | () => true | 删除前的钩子函数             |

###### Types

```
FileItem
```

| prop      | desc           |
| --------- | -------------- |
| path      | 图片路径       |
| id        | 图片id         |
| deletable | 是否可以被删除 |

```
UploaderSourceTypeEnum
```

| enum   | desc   |
| ------ | ------ |
| ALBUM  | album  |
| CAMERA | camera |

###### Emits

| name            | desc               |
| --------------- | ------------------ |
| delete          | 文件在缓存中被删除 |
| uploaded        | 文件上传成功       |
| update:fileList | 文件列表更新       |

###### Methods

| name       | type                                 | desc                       |
| ---------- | ------------------------------------ | -------------------------- |
| submit     | () => void                           | 手动上传当前展示的所有文件 |
| updateFile | (id: string, file: FileItem) => void | 指定id，更新文件对象       |
