# Modal 弹窗

对 antv 的 modal 组件进行封装，扩展拖拽，全屏，自适应高度等功能

代码路径 [src/components/Modal](https://github.com/vbenjs/vue-vben-admin/tree/main/src/components/Modal)

## 代码演示

<CodePreview src="/doc-comp/modal" :height="780">
<details>
<summary>展开查看</summary>

```vue
<template>
  <PageWrapper title="modal组件使用示例">
    <Alert
      message="使用 useModal 进行弹窗操作，默认可以拖动，可以通过 draggable
    参数进行控制是否可以拖动/全屏，并演示了在Modal内动态加载内容并自动调整高度"
      show-icon
    />
    <a-button type="primary" class="my-4" @click="openModalLoading">
      打开弹窗,加载动态数据并自动调整高度(默认可以拖动/全屏)
    </a-button>

    <Alert message="内外同时同时显示隐藏" show-icon />
    <a-button type="primary" class="my-4" @click="openModal2"> 打开弹窗 </a-button>
    <Alert message="自适应高度" show-icon />
    <a-button type="primary" class="my-4" @click="openModal3"> 打开弹窗 </a-button>

    <Alert message="内外数据交互" show-icon />
    <a-button type="primary" class="my-4" @click="send"> 打开弹窗并传递数据 </a-button>

    <Alert message="使用动态组件的方式在页面内使用多个弹窗" show-icon />
    <a-space>
      <a-button type="primary" class="my-4" @click="openTargetModal(1)"> 打开弹窗1 </a-button>
      <a-button type="primary" class="my-4" @click="openTargetModal(2)"> 打开弹窗2 </a-button>
      <a-button type="primary" class="my-4" @click="openTargetModal(3)"> 打开弹窗3 </a-button>
      <a-button type="primary" class="my-4" @click="openTargetModal(4)"> 打开弹窗4 </a-button>
    </a-space>

    <Alert
      message="使用函数方式创建Prompt，适合较为简单的表单内容，如果需要弹出较为复杂的内容，请使用 Modal."
      show-icon
    />
    <a-button type="primary" class="my-4" @click="handleCreatePrompt"> Prompt </a-button>

    <component :is="currentModal" v-model:open="modalOpen" :userData="userData" />

    <Modal1 :minHeight="100" @register="register1" />
    <Modal2 @register="register2" />
    <Modal3 @register="register3" />
    <Modal4 @register="register4" />
  </PageWrapper>
</template>
<script lang="ts" setup>
  import { createPrompt, PageWrapper, useModal } from '@eciol/ant-ui';
  import { type Nullable } from '@eciol/types';
  import { Alert, message } from 'ant-design-vue';

  import Modal1 from './Modal1.vue';
  import Modal2 from './Modal2.vue';
  import Modal3 from './Modal3.vue';
  import Modal4 from './Modal4.vue';

  const currentModal = shallowRef<Nullable<ComponentOptions>>(null);
  const [register1, { openModal: openModal1 }] = useModal();
  const [register2, { openModal: openModal2 }] = useModal();
  const [register3, { openModal: openModal3 }] = useModal();
  const [register4, { openModal: openModal4 }] = useModal();
  const modalOpen = ref<boolean>(false);
  const userData = ref<any>(null);

  function send() {
    openModal4(true, {
      data: 'content',
      info: 'Info',
    });
  }
  function openModalLoading() {
    openModal1(true);
    // setModalProps({ loading: true });
    // setTimeout(() => {
    //   setModalProps({ loading: false });
    // }, 2000);
  }

  function openTargetModal(index) {
    switch (index) {
      case 1:
        currentModal.value = Modal1;
        break;
      case 2:
        currentModal.value = Modal2;
        break;
      case 3:
        currentModal.value = Modal3;
        break;
      default:
        currentModal.value = Modal4;
        break;
    }
    nextTick(() => {
      // `useModal` not working with dynamic component
      // passing data through `userData` prop
      userData.value = { data: Math.random(), info: 'Info222' };
      // open the target modal
      modalOpen.value = true;
    });
  }

  function handleCreatePrompt() {
    createPrompt({
      title: '请输入邮箱',
      required: true,
      label: '邮箱',
      defaultValue: '默认邮箱',
      onOK: async (email: string) => {
        message.success('填写的邮箱地址为' + email);
      },
      inputType: 'Input',
    });
  }
</script>

```

```vue
<template>
  <BasicModal
    v-bind="$attrs"
    destroyOnClose
    title="Modal Title"
    :helpMessage="['提示1', '提示2']"
    @register="register"
    @open-change="handleShow"
  >
    <template #insertFooter>
      <a-button type="primary" danger :disabled="loading" @click="setLines">点我更新内容</a-button>
    </template>
    <template v-if="loading">
      <div class="empty-tips">加载中，稍等3秒……</div>
    </template>
    <template v-if="!loading">
      <ul>
        <li v-for="index in lines" :key="index">加载完成{{ index }}！</li>
      </ul>
    </template>
  </BasicModal>
</template>
<script lang="ts" setup>
  import { BasicModal, useModalInner } from '@eciol/ant-ui';

  const loading = ref(true);
  const lines = ref(10);
  const [register, { setModalProps, redoModalHeight }] = useModalInner();

  watch(
    () => lines.value,
    () => {
      redoModalHeight();
    },
  );

  function handleShow(open: boolean) {
    if (open) {
      loading.value = true;
      setModalProps({ loading: true, confirmLoading: true });
      setTimeout(() => {
        lines.value = Math.round(Math.random() * 30 + 5);
        loading.value = false;
        setModalProps({ loading: false, confirmLoading: false });
      }, 3000);
    }
  }

  function setLines() {
    lines.value = Math.round(Math.random() * 20 + 10);
  }
</script>
<style scoped>
  .empty-tips {
    height: 100px;
    line-height: 100px;
    text-align: center;
  }
</style>

```
</details>
</CodePreview>

## Usage

**由于弹窗内代码一般作为单文件组件存在，也推荐这样做，所以示例都为单文件组件形式**

::: tip

注意 `v-bind="$attrs"`记得写，用于将弹窗组件的 `attribute` 传入 `BasicModal` 组件

:::

```vue
// Modal.vue
<template>
  <BasicModal v-bind="$attrs" title="Modal Title" :helpMessage="['提示1', '提示2']">
    Modal Info.
  </BasicModal>
</template>
<script lang="ts" setup>
  import { BasicModal } from '@eciol/ant-ui';
</script>
```

**页面引用弹窗**

```vue
// Page.vue
<template>
  <div class="px-10">
    <Modal @register="register" />
  </div>
</template>
<script lang="ts" setup>
  import { useModal } from '@eciol/ant-ui';
  import Modal from './Modal.vue';
  const [register, { openModal }] = useModal();
</script>
```

## useModal

用于外部组件调用

**useModal** 用于操作组件

```ts
const [register, { openModal, setModalProps }] = useModal();
```

**register**

register 用于注册 `useModal`，如果需要使用 `useModal` 提供的 api，必须将 `register` 传入组件的 `onRegister`。

原理其实很简单，就是 vue 的组件子传父通信，内部通过 `emit("register"，instance)` 实现。

同时独立出去的组件需要将 `attrs` 绑定到 `BasicModal` 上面。

```vue
<template>
  <BasicModal v-bind="$attrs"></BasicModal>
</template>
```

**openModal**

用于打开/关闭弹窗

```tsx
// true/false: 打开关闭弹窗
// data: 传递到子组件的数据
openModal(true, data);
```

**closeModal**

用于关闭弹窗

```ts
closeModal();
```

**setModalProps**

用于更改 modal 的 props 参数因为 modal 内容独立成组件，如果在外部页面需要更改 props 可能比较麻烦，所以提供 **setModalProps** 方便更改内部 modal 的 props

[Props](#Props) 内容可以见下方

```ts
setModalProps(props);
```

## useModalInner

用于独立的 Modal 内部调用

### Usage

```vue
<template>
  <BasicModal
    v-bind="$attrs"
    @register="register"
    title="Modal Title"
    :helpMessage="['提示1', '提示2']"
  >
    <a-button type="primary" @click="closeModal" class="mr-2">从内部关闭弹窗</a-button>

    <a-button type="primary" @click="_setModalProps">从内部修改title</a-button>
  </BasicModal>
</template>
<script lang="ts" setup>
  import { BasicModal, useModalInner } from '@eciol/ant-ui';
  const [register, { closeModal, setModalProps }] = useModalInner();
  const _setModalProps = () => {
    setModalProps({ title: 'Modal New Title' });
  };
</script>
```

**useModalInner**用于操作独立组件

```ts
const [register, { closeModal, setModalProps }] = useModalInner(callback);
```

**callback**

type: `(data:any)=>void`

回调函数用于接收 openModal 第二个参数传递的值

```ts
useModal((data: any) => {
  console.log(data);
});
```

**closeModal**

用于关闭弹窗

```ts
closeModal();
```

**changeOkLoading**

用于修改确认按钮的 loading 状态

```ts
changeOkLoading(true);
```

**changeLoading**

用于修改 modal 的 loading 状态

```tsx
// true or false
changeLoading(true);
```

**setModalProps**

用于更改 modal 的 props 参数因为 modal 内容独立成组件，如果在外部页面需要更改 props 可能比较麻烦，所以提供 **setModalProps** 方便更改内部 modal 的 props

[Props](#Props) 内容可以见下方

## Props

::: tip

除以下参数外，组件库文档内的 props 也都支持，具体可以参考 [antv modal](https://www.antdv.com/components/modal-cn/#API)

:::

| 属性 | 类型 | 默认值 | 可选值 | 说明 |
| --- | --- | --- | --- | --- |
| title | `string` | - | - | modal 标题 |
| height | `number` | - | - | 固定 modal 的高度 |
| minHeight | `number` | - | - | 设置 modal 的最小高度 |
| draggable | `boolean` | true | true/false | 是否开启拖拽 |
| useWrapper | `boolean` | true | true/false | 是否开启自适应高度，开启后会跟随屏幕变化自适应内容，并出现滚动条 |
| wrapperFooterOffset | `number` | 0 | - | 开启是适应高度后，如果超过屏幕高度，底部和顶部会保持一样的间距，该参数可以用来缩小底部的间距 |
| canFullscreen | `boolean` | true | true/false | 是否可以进行全屏 |
| defaultFullscreen | `boolean` | false | true/false | 默认全屏 |
| loading | `boolean` | false | true/false | loading 状态 |
| loadingTip | `string` | - | - | loading 文本 |
| showCancelBtn | `boolean` | true | true/false | 显示关闭按钮 |
| showOkBtn | `boolean` | true | true/false | 显示确认按钮 |
| helpMessage | `string , string[]` | - | - | 标题右侧提示文本 |
| centered | `boolean` | false | true/false | 是否居中弹窗 |
| cancelText | `string` | '关闭' | - | 关闭按钮文本 |
| okText | `string` | '保存' | - | 确认按钮文本 |
| closeFunc | `() => Promise<boolean>` | 关闭函数 | - | 关闭前执行，返回 true 则关闭，否则不关闭 |

## Events

| 事件           | 回调参数                | 说明             |
| -------------- | ----------------------- | ---------------- |
| ok             | `function(e)`           | 点击确定回调     |
| cancel         | `function(e)`           | 点击取消回调     |
| open-change | `(open:boolean)=>{}` | 打开或者关闭触发 |

## Slots

| 名称    | 说明     |
| ------- | -------- |
| default | 默认区域 |
| footer | 底部区域(会替换掉默认的按钮) |
|insertFooter|关闭按钮的左边(不使用footer插槽时有效) |
|centerFooter| 关闭按钮和确认按钮的中间(不使用footer插槽时有效) |
|appendFooter| 确认按钮的右边(不使用footer插槽时有效) |
