## 弹窗组件

### 一. 背景及用途

* 当前问题：

  * 工程组织问题：
    
    * 项目中弹窗文件分布于各个业务代码中，很难实现复用，引用路径复杂。
    * 各弹窗的显隐控制逻辑混杂在业务代码中，各种业务props传递复杂
    * 父组件被隐藏时，弹窗也会被隐藏
    
  * 前端效率问题：

    针对弹窗这种可能一辈子也不会展示的组件：

    * ElementUI默认底层使用v-show实现，依然会绘制到dom树中
    * 理论上应该使用动态加载，不使用，不加载。减小最后打包的大小

  * 心智成本：

    * 个人风格及经验千差万别
    * 弹窗公共逻辑重复编写，且容易出错。
    * 新人接手旧项目，难以理解起外部逻辑及传入参数。

* 本组件主要解决的问题：
  
  * 工程组织问题：
    * 统一项目中所有弹窗文件位置至单一文件夹管理。
    * 弹窗由统一单独的dom元素负责展示,全自动。
    * 
  * 前端效率问题：
    * 使用webpack中require.context省去了手动引入大量弹窗文件的工作，同时默认支持动态加载。用不到的弹窗，永不加载。
    * 考虑到扩展性，提供四种优先级的公共参数配置
    * 提供公共样式，loading状态，确定及取消逻辑，确定/取消按钮的显隐控制等
  * 心智成本：
    * 无需手动引用及注册组件，减少代码量。
    * 所有业务相关参数在一个对象中传入，不用定义大量的props。
    * 显隐逻辑自治,无需手动管理相关逻辑。
    * 单一弹窗，单一逻辑。类似于纯函数，所有逻辑由传入的参数控制，便于不同的业务逻辑复用。
    * 默认提供loading状态，处理异步逻辑时，只用关心业务接口。



### 二. 安装

1.安装库

   ```
npm i @allo_shuang/modal@latest -S
   ```

2.在src目录下新建文件夹/modal

3.在main.js文件中引用

```
import Modal from '@allo_shuang/modal'

Vue.use(Modal,{参数放这里...});
```

> 实际项目中，在/modal文件夹中创建index.js文件并在文件中引入了组件库，及自定义的样式文件
>
> /modal
>
> ------index.js(在这里引入组件库及Vue.use传递统一配置参数，引入样式文件)
>
> ------style.scss
>
> 这样在main.js文件中只需引入@/modal即可。将配置及样式统一在modal文件夹中管理



###三. 使用

以新建弹窗ExampleModal为例

1. 在src/modal文件夹中新建<span style='color:red'>文件夹src/modal/ExampleModal并在其中创建index.vue文件</span>。这个组件为弹窗中间部分显示的内容

2. index.vue内部定义props：

   ```
    props: {params: Object},//params对应3中传入的参数
   ```

3. 在任意想要使用弹窗的地方，调用：

   ```
   this.$modal.show('ExampleModal', {组件内部需要的参数放这里，对应2的params})
   ```



### 四. 自定义选项

按照优先级，本组件有四个地方可以配置如下参数：

| 参数名            | 默认值 | 说明                                     |
| ----------------- | ------ | ---------------------------------------- |
| title             | null   | 弹窗标题                                 |
| width             | 640px  | 宽度                                     |
| cancelText        | 取消   | 取消按钮文字，设为空字符串''时,按钮隐藏  |
| okText            | 确定   | 确定按钮文字，设为空字符串''时,按钮隐藏  |
| showFooter        | true   | 确认/取消按钮所在的footer是否显示        |
| closeOnClickModal | false  | 同elementUI-Dialog的close-on-click-modal |
| showClose         | false  | 同elementUI-Dialog的show-close           |
| customClass       | ‘’     | 自定义的类名，用于个性化弹窗样式         |



#### 参数优先级:

<span style='color:red'>手动传入</span> > <span style='color:red'>组件定义</span> > <span style='color:red'>全局定义</span> > <span style='color:red'>默认值</span>

1. 唤起弹窗时传入的参数

   ```
   this.$modal.show('ExampleModal', {放这里的参数优先级最高})
   ```

2. 子组件内部定义的参数,与data,methods,生命周期等同级定义的参数

   ```
   export default {
       title: '我是标题',<-------这里的优先级第二高
       okText:'提交',<-------这里的优先级第二高
       ...
       ...
       data() {return {}},
       methods:{}
   }
   ```

3. 全局定义的参数。Vue.use传入的参数中：

   ```
   Vue.use(Modals, {
   title: '我是标题',<-------这里的优先级第三高
   okText:'提交',<-------这里的优先级第三高
   showFooter:false<-------这里的优先级第三高
   ...
   })

   ```

4. 默认值。如表中所列。代码在UniModal.vue中

   ```

     const defaultParams = {
       title: null,
       width: '640px',
       cancelText: '取消',
       okText: '确定',
       showFooter: true,
       closeOnClickModal: false,
       showClose: true,
       customClass: ''
     }
   ```



### 五. 内置方法

* 组件methods中

  * 如果定义了<span style='color:red'>onCancel()</span>方法，则会忽略默认的取消关闭操作
  * <span style='color:red'>onOK()</span>为点击确定按钮时触发的方法，需要自己实现

* 关闭当前弹窗

  ```
  this.$modal.close()
  ```

* loading状态：loading状态下显示loading动画，屏蔽弹窗上所有交互操作，确认/取消按钮均不可点击。

  * 常用于点击确定按钮后，发出网络请求。

  ```
  this.$emit('updateLoading', true/false)
  ```

* 内置的确认弹窗：

  *   this.$modal.text(params,callback)：纯文本内容，无图标

  *  this.$modal.success(params,callback)：绿色✓图标+文本

  *  this.$modal.warning(params,callback)：黄色!图标+文本

  *  this.$modal.error(params,callback)：红色x图标+文本

  *  this.$modal.info(params,callback)：蓝色i图标+文本

  * 上述五个方法的参数：

    * params：除自定义参数与上述内置参数外，额外增加三个参数：

      * content：文本内容
      * elementIcon：elementUI中Icon的名字，例如：'el-icon-warning',
      * iconColor：图标的颜色

    * callback：点击确认的回调。形如(params, closeModal, setLoading) => {}

      * params：与第一个参数params相同，用于回传自定义参数

      * closeModal：关闭弹窗的方法。closeModal()

      * setLoading：控制弹窗的loading状态

```
//例如，依据companyId删除信息
this.$modal.warning(
	{title:'警告'，content:'确认删除公司信息吗?',width:'200px',companyId:'com-oiie-9839'},
  (params, closeModal, setLoading) => {
    setLoading(true)
    api.deleteCompanyInfo(params.companyId).then(()=>{
        setLoading(false)
        closeModal()
      })          
  })
)
```



### 六. 样式

除了手动传入的customClass类名外，默认提供类：modal-inner-class

可以在.modal-inner-class类下定义公共样式，在自定义类名下定义弹窗特异样式。

### 七. Q & A

* 文件夹是否只能叫/modal?

  * 不是的，如果使用的是vue-cli创建的项目，可以在.env文件中自定义常量：

    ```
    VUE_APP_MODAL_PATH = '自定义路径'
    ```

    同时在引入组件库时，使用

    ```
    import Modals from '@allo_shuang/modal/cli'
    ```

    原理：require.context编译时，第一个参数为字符串，不接受变量及运算...

* 是否只支持Element-UI?

  * 算是吧。因为默认使用的是el-dialog。理论上使用任何UI组件库都是可以的。这个组件最开始是React+AntD版的,现在使用VUE技术栈，顺便迁移过来的。

* 为什么看网络请求，发现弹窗对应的js文件请求了两次？
  * 这是因为webpack对动态加载的文件做了prefetch以提高性能。
  * 第一个请求可以在header中看到*Purpose*: *prefetch*
  * 第二个请求可以看到size一栏目显示：(prefetch cache)
  * 可以在webpack或者vue.config.js中禁用prefetch。这是另外的话题。
  
* 既然使用了Element-UI,为什么不直接使用this.$confirm方法而要提供默认的success等方法？
  * 首先是为了将默认的这些方法统一归到默认dom下进行管理
  * 使其支持普通弹窗接受的任何参数，减少心智负担
  * 提供了loading等高级功能
  * 默认的confirm不太支持高级定制
  * 默认方法也是动态加载的，不使用不加载
  
* 为什么生成key要使用random方法？

  * 即使是同名的弹窗，也要使用不同的key来保证单个弹窗的独立性
  * 不排除将来同时打开两个同名的弹窗

* 代码中的overlay参数是干什么的？

  * 你居然发现了隐藏参数!!!
  * 默认同一时间只有一个弹窗存在。如果打开弹窗时，传入overlay:true,则弹窗会同时最在。所以弹窗在容器中使用栈来保存的。同时closeAll方法也是因为同样的原因而存在。
  * 规划中多弹窗的情况有很多功能及场景，预计在后续版本中放出，所以文档中暂时隐藏该参数。

* 为什么要自己写一个遮罩？

  * 多个弹窗切换时，如果使用element-ui自带的遮罩，会出现闪屏的现象。所以自己实现一个遮罩在最下方，同时默认遮罩全部隐藏。

### 八. 相关技术点

* 如何自定义组件，Vue.use()的使用

* require.context的使用及与动态加载的结合

* Object.create(null)

* 实例化单文件组件及单独挂载：new + Vue.extend + .$mount() + document.body.appendChild

* Vue挂载全局方法，Vue.prototype.xxx=

* 使用key属性来强制刷新/更新组件

* 获取动态加载组件内定义的值与方法：

  ```
  component().then(module => {
          if (module && module.default) {
          module.default.xxxxxx
  }})
  ```

* 动态组件的使用<component  :is='xxxx'/>

* Vue-cli项目环境变量的规则：在.env文件中以VUE_APP_开头

### 九. TODO:

- [ ] 增加element-loading-text，element-loading-spinner，element-loading-background的支持
- [ ] 多弹窗展示时的遮盖，显隐，参数传递等
- [ ] 增加默认的样式代码以适配常见场景，例如弹窗中，form的input长度改为100%等

