在开发页面时,经常会遇到这样的需求,在很多地方都会用到相同的弹框组件(比如 通用的选择商品功能、表单功能等 ),通常的实现方式是在每个页面上以标签的方式引入弹框组件(如 elementUIel-dialog 或是 ant-design-vuemodal ),然后处理 弹框 的各种事件交互,这样处理会产生很多重复的代码。所有想着能不能在组件内部直接调用方法来实现需求,解决办法就是模仿 UI框架的message组件的实现方式,以函数的方式来调用组件功能

实现步骤

1. 首先定义一个通用方法,主要的函数式的实现逻辑在这里

createCustomModal.ts


import { Component, createApp } from "vue";
import {
  Button,
  Input,
  Modal,
  Select,
  Tooltip,
  Form,
  Table,
} from "ant-design-vue";

/**
* modalConstructor 为传入的业务组件,
* options 为调用业务组件时可传入的参数
*/
export function createCustomModal(modalConstructor: Component, options?: any) {

// 这里使用promise的方式,使我们在以函数的方式调用组件时,可以异步处理

  return new Promise((resolve, reject) => {
    let app: any = null;
    let instance: any = null;
    const container = document.createElement("div");
    document.body.appendChild(container);

    // 销毁元素
    function destroyNodes() {
      instance = null;
      app?.unmount();
      document.body.removeChild(container);
    }
    
    // 定义close方法,通过props传递给组件
    function close() {
      destroyNodes();
      reject();
    }

    // 定义ok方法,通过props传递给组件
    function ok(val?: any) {
      destroyNodes();
      resolve(val);
    }
    
    // 使用createAPP方法生成vue实例,第一个参数modalConstructor为component类型,
    // 第二个参数为传递给modalConstructor组件的参数props

    app = createApp(modalConstructor, {
      close,
      ok,
      ...options
    });

    instance = app
      .use(Select)
      .use(Input)
      .use(Tooltip)
      .use(Button)
      .use(Modal)
      .use(Table)
      .mount(container); // 渲染到创建的div节点上
  });
}


上面使用 use 的方式引入Button, Input, Modal, Select, Tooltip, Form, Table这些组件是因为使用 createApp 生成的 app实例main.ts 中的 app实例 是完全不同的两个 app实例,不 use 的话这些组件不能正常显示运行,而且一个个引入是因为使用了 按需加载ant-design-vue 组件 的方式 ,如果不考虑 按需加载 的话全部引入 ant-design-vue 即可。

2. 然后给出一个商品选择业务的demo页面


index.vue


<template>
  <a-modal
    :closeOnClickModal="false"
    width="960px"
    :title="modalTitle"
    :visible="visible"
    :row-selection="rowSelection"
    @cancel="handleModalCancel"
    @ok="handleModalOk"
    :ok-text="'确定'"
    :calcel-text="'取消'"
    class="select-goods-modal"
    >

    <a-table
      :columns="columns"
      :data-source="tableData.list"
      :loading="tableData.loading"
      rowKey="id"
        >
        <template #action="{text, record, index}">
          <a-button @click="handleSelectItem(text, record, index)">选择</a-button>
        </template>
    </a-table>
  </a-modal>
</template>
<script lang="ts">
import {
  defineComponent,
  computed,
  ref,
  PropType,
  onMounted,
  onBeforeUnmount,
  reactive,
  unref,
  toRaw
} from "vue";
export default defineComponent({
  name: "SelectGoodsModal",
  props: {
    close: {
      type: Function,
      required: true
    },
    ok: {
      type: Function,
      required: true
    },
  },
  setup(props) {
    const modalTitle = ref("选择商品");
    const visible = ref(false);
    const tableData: any = reactive({
      list: [] as Record<any, any>[],
      total: 0
    });

    const columns = [
        {
           title: "商品",
           key: "skuName",
           dataIndex: "skuName",
           width: 250
        },
        {
           title: "售价",
           key: "price",
           dataIndex: "price",
        },
        {
           title: "操作",
           dataIndex: "",
           key: "x",
           slots: { customRender: "action" },
           width: 100
        }
    ];

    const selectedRowKeys = ref<Key[]>([]);
    const selectedRows = ref<Record<any, any>[]>([]);

    const rowSelection = {
       selectedRowKeys: unref(selectedRowKeys),
       onChange: (changableRowKeys: (string | number)[], changableRows: any[]) => {
          selectedRowKeys.value = changableRowKeys;
          selectedRows.value = changableRows;
       }
    };

    function handleModalCancel() {
      visible.value = false;
      props.close(); // 调用传入的close方法销毁节点
    }

    function handleModalOk() {
      props.ok(unref(selectedRows)); // 调用传入的ok方法传入已选的“商品”,会走到promise的resolve
    }


    onMounted(() => {
      visible.value = true;
    });

    return {
      modalTitle,
      columns,
      tableData,
      visible,
      rowSelection,
      handleModalOk,
      handleModalCancel
    }
   }
});

3. 最后定义一个向外部暴露的可引用文件


index.ts

import type { App } from 'vue'
import {createCustomModal} from "./createCustomModal";
import SelectGoodsModalConstructor from "./index.vue";
type SFCWithInstall<T> = T & { install(app: App): void; }


const SelectGoodsModal = function(option?: Record<any, any>) {
     // 在这里使用通用方法生成弹框
    return createCustomModal(SelectGoodsModalConstructor, option); 
} as Function;

const _SelectGoodsModal: SFCWithInstall<typeof SelectGoodsModal> = SelectGoodsModal as SFCWithInstall<typeof SelectGoodsModal>

_SelectGoodsModal.install = (app: App) => { 
    // 可以注册到全局的globalProperties属性上
    app.config.globalProperties.$selectGoodsModal = _SelectGoodsModal
}

export default _SelectGoodsModal // 导出方法


如何在项目中使用

第一种方法


 1. 在项目的main.ts中先引入

    import SelectGoodsModal from "@/components/selectGoodsModal"; // 默认导入的index.ts文件

    app.use(SelectGoodsModal).mount("#app");

 2. 在业务里使用

    const internalInstance = getCurrentInstance();

    function selectGoods() {
      return internalInstance!.appContext.config.globalProperties.$selectGoodsModal({
        selectedList: []
      });
    }
    
    selectGoods().then(res => {
      console.log(res); // 选择到的商品
    }, err => {
      console.log("取消选择")
    })

第二种,当然也可以不挂载在全局,单独导入直接使用

import {createCustomModal} from "./createCustomModal"; // 导入createCustomModal方法
import SelectGoodsModalConstructor from "./index.vue";

 createCustomModal(SelectGoodsModalConstructor, {selectedList: []})
    .then(res => {
      console.log(res); // 选择到的商品
    }, err => {
      console.log("取消选择")
    })

版权属于:xigua

本文链接:https://www.xianh5.com/archives/63/

转载时须注明出处及本声明

最后修改:2021 年 06 月 07 日
如果觉得我的文章对你有用,请随意赞赏或留下你的评论~