vue3 encapsulates public pop-up functions

Foreword:

The blogger encapsulates a public pop-up window function that receives four parameters, (title: pop-up window title, ContentComponent: component content displayed in the pop-up window, opt: receives the properties and props of the pop-up window itself, beforeSure: click OK to do the operation ( Request backend interface))

Encapsulated public functions:

import { defineComponent, h, ref, getCurrentInstance } from "vue-demi";
import Vue from "vue";
import { isFunction, isUndefined, noop, isPlainObject } from "lodash";

const WRAPPED = "wrapped";

function generateDialogComponent(wrapped, opt, listeners = {}) {
  return defineComponent({
    setup() {
      const loading = ref(false);
      const vm = getCurrentInstance();
      const visible = opt.visible; // Ref<boolean>
      const closeable = opt.closeable; // Ref<boolean>
      const showCancelButton = isUndefined(opt.dialog.showCancelButton)
        ? true
        : opt.dialog.showCancelButton;
      const showSureBtn =
        isUndefined(opt.dialog.showSureBtn) &&
        isUndefined(opt.dialog.showSureButton)
          ? true
          : opt.dialog.showSureBtn || opt.dialog.showSureButton;
      const showFooter = isUndefined(opt.dialog.showFooter)
        ? true
        : opt.dialog.showFooter;
      const confirmButtonText = opt.dialog.confirmButtonText || "确定";
      const cancelButtonText = opt.dialog.cancelButtonText || "取消";

      return () => {
        const sure = listeners.sure || (() => Promise.resolve());
        const cancel = listeners.cancel || noop;
        const destroy = listeners.destroy || noop;
        // footer
        const sureBtn = h(
          "el-button",
          {
            props: {
              size: "mini",
              type: "primary",
              loading: loading.value,
            },
            on: {
              click: () => {
                loading.value = true;
                const wrappedVm = vm.proxy.$refs[WRAPPED];
                return sure(wrappedVm)
                  .then(() => {
                    visible.value = false;
                  })
                  .finally(() => (loading.value = false));
              },
            },
          },
          confirmButtonText
        );
        const cancelBtn = h(
          "el-button",
          {
            props: {
              size: "mini",
            },
            on: {
              click: () => {
                visible.value = false;
              },
            },
          },
          cancelButtonText
        );
        const footer = h(
          "div",
          {
            slot: "footer",
            style: {
              display: "flex",
              justifyContent: "space-between",
            },
          },
          [
            h("div", [opt.dialog?.leftFooter?.()]),
            h("div", [
              closeable.value && showCancelButton && cancelBtn,
              showSureBtn && sureBtn,
            ]),
          ]
        );
        return h(
          "el-dialog",
          {
            props: {
              closeOnClickModal: false,
              visible: visible.value,
              ...opt.dialog,
              closeOnClickModal: closeable.value,
              closeOnPressEscape: closeable.value,
              showClose: closeable.value,
            },
            on: {
              "update:visible": (val) => {
                visible.value = val;
              },
              closed: () => {
                cancel();
                destroy();
              },
            },
          },
          [
            h("div", { style: { padding: "20px" } }, [
              h(wrapped, {
                ref: WRAPPED,
                attrs: Object.assign({}, opt.props),
                on: {
                  close: sure, // 组件内部可以通过 emit('close') 来关闭弹窗
                },
              }),
            ]),
            showFooter && footer,
          ]
        );
      };
    },
  });
}

function openDialog(title, ContentComponent, opt, beforeSure) {
  const defaultOpt = {
    dialog: {},
    props: {},
  };
  // 参数格式化
  if (isUndefined(opt)) {
    opt = defaultOpt;
  }
  if (isFunction(opt)) {
    opt = defaultOpt;
    beforeSure = opt;
  }
  if (!isFunction(beforeSure)) {
    beforeSure = (vm) => vm.submit?.();
  }

  if (isPlainObject(opt)) {
    if (isUndefined(opt.props)) {
      opt = {
        ...opt,
        props: opt,
      };
    }
  }

  opt.dialog = opt.dialog || opt || {};
  opt.dialog.title = title;

  const mountComponent = ($vueconfig) => {
    const vm = new Vue($vueconfig);
    const anchor = document.createElement("div");
    document.body.appendChild(anchor);
    vm.$mount(anchor);
    return () => {
      vm.$destroy();
      document.body.removeChild(vm.$el);
    };
  };

  // 控制 dialog 显隐
  const visible = ref(false);
  const closeDialog = () => {
    visible.value = false;
  };

  // 控制是否可以关闭
  const closeable = ref(true);
  // 不可关闭弹窗
  const freeze = () => {
    closeable.value = false;
  };
  // 可关闭弹窗
  const unfreeze = () => {
    closeable.value = true;
  };

  const wait = new Promise((resolve, reject) => {
    let disposer = null;
    const destroy = () => isFunction(disposer) && disposer();
    const cancel = () => {
      reject(new Error("cancel"));
    };
    const sure = async (wrappedComp) => {
      const promise = await beforeSure(wrappedComp);
      resolve(promise);
    };
    disposer = mountComponent(
      generateDialogComponent(
        ContentComponent,
        { ...opt, visible, closeable },
        { sure, cancel, destroy }
      )
    );
    // 打开弹窗
    setTimeout(() => (visible.value = true), 20);
  });

  return {
    close: closeDialog,
    promise: wait,
    freeze,
    unfreeze,
  };
}

export function pickByDialog(...args) {
  const { promise } = openDialog(...args);
  return promise;
}

/**
 * 让 pickByDialog 静默失败, 并且提供一个手动关闭弹窗的函数
 * @Returns { close }
 */
export const openByDialog = (...args) => {
  const { close, freeze, unfreeze, promise } = openDialog(...args);
  promise.catch(() => {
    // no throw error
  });
  return {
    close,
    freeze,
    unfreeze,
  };
};

Partial code explanation:

generateDialogComponentFunction explanation:

  1. generateDialogComponentThe function defines a component and returns that component.
  2. In the component's setupfunction, some variables and constants are first defined, including:
    • loading: A reactive variable (Ref) used to represent the loading status.
    • vm: A reference to the current component instance.
    • visible: A responsive variable indicating the visibility of the dialog box.
    • closeable: A responsive variable indicating whether the dialog box can be closed.
    • showCancelButton: A Boolean value indicating whether to display the cancel button, the default is true.
    • showSureBtn: A Boolean value indicating whether to display the OK button, the default is true.
    • showFooter: A Boolean value indicating whether to display the bottom content, the default is true.
    • confirmButtonText: The text of the OK button, the default is "OK".
    • cancelButtonText: The text of the cancel button, the default is "Cancel".
  1. Returns a function that uses Vue 3's Composition API syntax as the component's render function.
  2. The render function returns a el-dialogcomponent, which is a dialog component based on the Element UI library.
  3. el-dialogComponent properties include:
    • closeOnClickModal: Controls whether to close the dialog box when the modal box is clicked, closeableset according to the value.
    • visible: Control the visibility of the dialog box, visibleset according to the value.
    • ...opt.dialog: Treat opt.dialogall properties in the object as el-dialogproperties of the component.
    • closeOnClickModal: Controls whether to close the dialog box when the modal box is clicked, closeableset according to the value.
    • closeOnPressEscape: Controls whether to close the dialog box when the Esc key is pressed, closeableset according to the value.
    • showClose: Control whether to display the close button, closeableset according to the value.
  1. el-dialogComponent events include:
    • update:visible: The value that is updated when the visibility of the dialog box changes visible.
    • closedcancel: Trigger and function when the dialog box is closed destroy.
  1. el-dialogComponent slots include:
    • Default slot: Contains a styled { padding: "20px" }element divthat contains wrappedthe component.
    • showFooterLasttrue , bottom slot: Contains a styled { display: "flex", justifyContent: "space-between" }element divthat contains the bottom content.
  1. Returns the generated component.

openDialogFunction explanation:

  1. openDialogThe function accepts four parameters: title(dialog title), ContentComponent(dialog content component), opt(optional configuration object), and beforeSure(optional callback function before the OK button is clicked).
  2. defaultOptA default configuration object named is defined , containing two properties dialog: and .props
  3. Format the incoming optsum :beforeSure
    • If optit is undefined, set it to defaultOptthe value.
    • If opta function, it is set to defaultOptthe value of , and will beforeSurebe set to the function.
    • If beforeSurenot a function, sets it to a default function that calls submitthe method of the component instance passed in, if one exists.
  1. For optfurther processing:
    • If optit is a normal object and has no propsproperties, optthe value of is copied to opt.props.
  1. will opt.dialogbe set to the value of optor opt.dialog, and will titlebe set to opt.dialog.title.
  2. A function named is defined mountComponent, which is used to mount the component on the DOM and returns a destruction function.
    • Inside the function, a new Vue instance is created and mounted on a newly created divelement.
    • Add this divelement to document.body.
    • Returns a function that, when called, destroys the Vue instance document.bodyand removes the divelement from it.
  1. Created a responsive variable visibleto control the display and hiding of the dialog box.
  2. Defines a closeDialogfunction to close the dialog box.
  3. Created a responsive variable closeableto control whether the dialog box can be closed.
  4. freezeA function is defined that will closeablebe set falseto make the dialog box unclosable.
  5. unfreezeA function is defined that will closeablebe set trueto make the dialog box closeable.
  6. A Promise object is created waitto wait for the dialog box operation to complete.
  7. In waitthe execution function, some internal functions and variables are defined:

  • disposer: Used to store a reference to the destruction function.
  • destroyFunction: used to execute the destruction function.
  • cancelFunction: Used to reject a Promise and throw a cancellation error.
  • sureFunction: The callback function that is executed when the button is clicked, calls beforeSurethe function and passes it wrappedCompin as a parameter, and resolves the returned Promise to resolvethe value.
  • Set disposerto call mountComponentthe function and pass in generateDialogComponentthe dialog component generated by the function.
  • After using setTimeouta delay of 20 milliseconds, which will visiblebe set to true, the dialog opens.

  1. Returns an object containing the following properties and methods:

  • close: Method to close the dialog box.
  • promise: Returns the Promise object waiting for the dialog box operation to complete.
  • freeze: Method to make the dialog box unclosable.
  • unfreeze: Method to make the dialog box closeable.

Examples of usage are as follows

Example 1

const { close } = openByDialog(
        "自定义列表",
        Setting2,
        {
          props: menuProps,
          dialog: {
            width: "980px",
            confirmButtonText: "保存",
            leftFooter: () =>
              h(
                "el-button",
                {
                  style: { color: "#2783fe" },
                  props: {
                    size: "mini",
                    type: "text",
                    loading: reseting.value,
                  },
                  on: {
                    click: () => doReset(),
                  },
                },
                "恢复系统默认设置"
              ),
          },
        },
        async (componentWrapper) => {
          const updatedColumns = await componentWrapper?.updateColumnSetting();
          this.emitChangeColumns2(updatedColumns);
        }
      );

Example 2

//组件弹窗测试
      async doImportLog() {
        const [cancel, blob] = await to(
          pickByDialog(
            "弹窗导出测试",
            getLogComp(this), //这个是组件
            {
              dialog: { width: "35%" },
              // props: { value: this.value },
              // on: {
              //   input: (val) => {
              //     this.value = val
              //   },
              // },
            },
            async (vm) => {
              const [changeDateBt, changeDateEt] = vm.date;
              console.log("changeDateBt", changeDateBt);
              console.log("changeDateEt", changeDateEt);
            }
          )
        );
        if (cancel) {
          this.$message.info(this.$tof("cancel"));
          return;
        }
        const curDate = dayjs().format("YYYYMMDD");
        console.log("curDate", curDate);
        saveAs(blob.data, this.$tof("file_name", { curDate }));
      },


  function getLogComp(vm) {
    return {
      data() {
        return {
          date: [],
        };
      },
      render(h) {
        return h("el-row", { style: { margin: "100px 50px" } }, [
          h(
            "el-col",
            { style: { lineHeight: "36px" }, attrs: { span: 8 } },
            "导出范围时间"
          ),
          h("el-col", { attrs: { span: 16 } }, [
            h("el-date-picker", {
              attrs: {
                type: "daterange",
                rangeSeparator: "-",
                startPlaceholder: "开始日期",
                endPlaceholder: "结束日期",
                value: this.date,
                valueFormat: "yyyy-MM-dd",
              },
              style: { width: "auto !important" },
              on: {
                input: (val) => {
                  this.date = val;
                },
              },
            }),
          ]),
        ]);
      },
    };
  }

Example three

//组件弹窗测试222
      async doAdd() {
        const [cancel] = await to(
          pickByDialog(
            "新增客户",
            GroupCreate, //组件
            {
              dialog: {
                width: "80%",
                confirmButtonText: "保存",
                leftFooter: () =>
                  h(
                    "el-button",
                    {
                      style: { color: "#2783fe" },
                      props: {
                        size: "mini",
                        type: "text",
                        loading: false,
                      },
                      on: {
                        click: () => doReset(),
                      },
                    },
                    "恢复系统默认设置"
                  ),
              },
              props: {},
            },
            async (vm) => {
              console.log("测试点击确定", vm);
            }
          )
        );
        if (cancel) {
          this.$message.info("已取消");
          return;
        }
        function doReset() {
          console.log("测试左边操作");
        }
      },


    GroupCreate组件:

    
    <template>
  <div>
    <CollapsePanel :show-header="true">
      <template #panel-button>
        <el-button
          size="small"
          type="primary"
          :loading="loading"
          @click="doInquiry"
        >
          搜索
        </el-button>
      </template>
      <template #panel-main>
        <el-form
          ref="filterForm"
          :inline="true"
          :model="schemaModel"
          :max-height="200"
          class="list-schema-form"
          label-position="top"
        >
          <el-row
            v-for="(row, index) in schemaList"
            :key="index"
            v-bind="layout"
          >
            <el-col
              v-for="{ rules, label, prop, component, ...attrs } in row"
              :key="prop"
              :span="12"
            >
              <el-form-item v-if="prop" v-bind="{ label, rules, prop }">
                {
   
   { component }}
                <component
                  :is="`el-${component}`"
                  v-model="schemaModel[prop]"
                  :placeholder="component + '_placeholder'"
                  v-bind="attrs"
                >
                  <el-option
                    v-for="ops in schemaOptions[prop]"
                    :key="ops.value"
                    v-bind="ops"
                  />
                </component>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </template>
    </CollapsePanel>

    <CollapsePanel :auto-height="true">
      <template #panel-main>
        <el-table
          ref="tableRef"
          v-loading="loading"
          :data="list"
          border
          stripe
          max-height="550"
        >
          <el-table-column type="selection" fixed="left" width="40" />
          <el-table-column
            type="index"
            width="60"
            fixed="left"
            :label="'序号'"
          />
          <el-table-column
            v-for="{ field, render, ...attrs } in columns"
            :key="field"
            v-bind="attrs"
          >
            <template #default="scope">
              <field-render :render="render" :scope="scope" :field="field" />
            </template>
          </el-table-column>
        </el-table>
        <div class="table-pagination">
          <pagination
            :limit="pageSize"
            :page="pageNum"
            :total="totalPages"
            @pagination="(pageNum = $event.page) && (pageSize = $event.limit)"
          />
        </div>
      </template>
    </CollapsePanel>
  </div>
</template>

<script>
  // import { customerinfoFindCustByCustNameOrCustCodeToCommodity } from '@/api/commodity/price'

  import to from "await-to-js";
  import { snakeCase } from "lodash";

  export default {
    name: "GroupCreate",
    components: {
      FieldRender: {
        functional: true,
        props: {
          scope: {
            type: Object,
            default: () => ({}),
          },
          render: Function,
          field: String,
        },
        render: (h, { props }) => {
          const { render, scope, field } = props;
          return render
            ? render(h, { ...scope, field })
            : h("span", null, scope.row[field]);
        },
      },
    },
    data(vm) {
      return {
        ...getSchemaList(vm),
        list: [],
        columns: getColumList(vm),
        pageNum: 1,
        pageSize: 10,
        totalPages: 0,
        loading: false,
        layout: {
          gutter: 60,
          justify: "start",
        },
      };
    },
    watch: {
      pageNum: {
        handler() {
          this.doInquiry();
        },
      },
      pageSize: {
        handler() {
          this.doInquiry();
        },
      },
    },
    async created() {
      // const options = await this.$ops({});
      // Object.assign(this.schemaOptions, options);

      this.doInquiry();
    },
    methods: {
      async doInquiry() {
        console.log("测试");
        // this.loading = true
        // const [err, data] = await to(
        //   customerinfoFindCustByCustNameOrCustCodeToCommodity(
        //     Object.assign(this.schemaModel, {
        //       page: this.pageNum - 1,
        //       size: this.pageSize,
        //     })
        //   )
        // ).finally(() => (this.loading = false))
        // if (err) {
        //   return
        // }

        // const { content, totalElements = 5 } = data
        // this.totalPages = +totalElements
        // this.list = content.map((i) => ({
        //   custCode: i.customerCode,
        //   custName: i.customerName,
        // }))
      },
      doReset() {
        this.schemaModel = {};
      },
      doDelete() {},
      doAdd() {},
      async doSave() {
        const [err] = await to(this.$refs["filterForm"].validate());
        if (err) {
          return;
        }
      },
      doBack() {
        this.$store.dispatch("tabsBar/delVisitedRoute", this.$route.fullPath);
        this.$router.back();
      },
    },
  };

  function getColumList(vm) {
    const COLUM_FIELDS = ["custCode", "custName"];
    const FORM_FIELDS_NAME = {
      custName: "客户名称",
      custCode: "客户编码",
    };

    const colProperties = {};
    return COLUM_FIELDS.map((prop) =>
      Object.assign(
        { prop, label: FORM_FIELDS_NAME[prop], field: prop },
        colProperties[prop] || {
          sortable: true,
        }
      )
    );
  }

  function getSchemaList(vm) {
    const FORM_FIELDS = ["custCode", "custName"];
    const FORM_FIELDS_NAME = {
      custName: "客户名称",
      custCode: "客户编码",
    };
    let properties = {
      custCode: {},
      custName: {},
    };
    const array = FORM_FIELDS.map((prop) => {
      const label = FORM_FIELDS_NAME[prop];
      const attrs = properties[prop];

      return {
        prop,
        label,
        clearable: true,
        filterable: true,
        component: "input",
        ...attrs,
      };
    });

    const schemaList = [array];
    return {
      schemaList,
      schemaModel: { custStatus: ["20"], custCode: "", custNameList: [] },
      schemaOptions: { status: [] },
    };
  }
</script>

<style lang="scss" scoped>
  ::v-deep .el-button--text {
    color: #409eff;
    font-size: 13px;
  }
  .total-number {
    color: #f55448;
  }
</style>

Example 4

 async doAdd2() {
        openByDialog(
          "测试表单",
          FormTest,
          {
            props: {
              isShowCol: true,
            },
          },
          async (componentWrapper) => {
            await componentWrapper?.$children[0].validate();
            console.log("componentWrapper的数据", componentWrapper);
            componentWrapper.ruleForm.date1 = dayjs(
              componentWrapper.ruleForm.date1
            ).format("YYYYMMDD");

            let payload = componentWrapper.ruleForm;

            return await this.fetchData(payload);
          }
        );
      },


FormTest组件
<template>
  <el-form
    :model="ruleForm"
    :rules="rules"
    ref="ruleFormRef"
    label-width="100px"
    class="demo-ruleForm"
  >
    <el-form-item label="活动名称" prop="name">
      <el-input v-model="ruleForm.name"></el-input>
    </el-form-item>
    <el-form-item label="活动区域" prop="region">
      <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
        <el-option label="区域一" value="shanghai"></el-option>
        <el-option label="区域二" value="beijing"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="活动时间" required>
      <el-col :span="11">
        <el-form-item prop="date1">
          <el-date-picker
            type="date"
            placeholder="选择日期"
            v-model="ruleForm.date1"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
      </el-col>
      <el-col class="line" :span="2">-</el-col>
      <el-col :span="11">
        <el-form-item prop="date2">
          <el-time-picker
            placeholder="选择时间"
            v-model="ruleForm.date2"
            style="width: 100%"
          ></el-time-picker>
        </el-form-item>
      </el-col>
    </el-form-item>
    <el-form-item label="即时配送" prop="delivery">
      <el-switch v-model="ruleForm.delivery"></el-switch>
    </el-form-item>
    <!-- <el-form-item label="活动性质" prop="type">
        <el-checkbox-group v-model="ruleForm.type">
          <el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
          <el-checkbox label="地推活动" name="type"></el-checkbox>
          <el-checkbox label="线下主题活动" name="type"></el-checkbox>
          <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
        </el-checkbox-group>
      </el-form-item> -->
    <el-form-item label="特殊资源" prop="resource">
      <el-radio-group v-model="ruleForm.resource">
        <el-radio label="线上品牌商赞助"></el-radio>
        <el-radio label="线下场地免费"></el-radio>
      </el-radio-group>
    </el-form-item>
    <el-form-item label="活动形式" prop="desc">
      <el-input type="textarea" v-model="ruleForm.desc"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)">
        立即创建
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script>
  import { defineComponent, ref } from "vue-demi";
  export default defineComponent({
    name: "ruleFormTest",
    props: {
      isShowCol: {
        type: Boolean,
        default: false,
      },
    },
    setup(props) {
      const ruleFormRef = ref();
      let ruleForm = ref({
        name: "",
        region: "",
        date1: "",
        date2: "",
        delivery: props.isShowCol,
        type: [],
        resource: "",
        desc: "",
      });
      let rules = ref({
        name: [
          { required: true, message: "请输入活动名称", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
        ],
        region: [
          { required: true, message: "请选择活动区域", trigger: "change" },
        ],
        date1: [
          {
            type: "date",
            required: true,
            message: "请选择日期",
            trigger: "change",
          },
        ],
        date2: [
          {
            type: "date",
            required: true,
            message: "请选择时间",
            trigger: "change",
          },
        ],
        type: [
          {
            type: "array",
            required: true,
            message: "请至少选择一个活动性质",
            trigger: "change",
          },
        ],
        resource: [
          { required: true, message: "请选择活动资源", trigger: "change" },
        ],
        desc: [{ required: true, message: "请填写活动形式", trigger: "blur" }],
      });

      const submitForm = async (formEl) => {
        console.log("formEl", formEl);
        if (!formEl) return;
        await formEl.validate((valid, fields) => {
          if (valid) {
            console.log("submit!");
          } else {
            console.log("error submit!", fields);
          }
        });
      };
      const resetForm = (formName) => {};
      // expose({ submitForm });
      return {
        ruleForm,
        rules,
        ruleFormRef,
        submitForm,
        resetForm,
      };
    },
  });
</script>

Simulate an asynchronous return

      fetchData(params) {
        return new Promise((resolve, reject) => {
          // 模拟异步请求
          setTimeout(() => {
            const data = { name: "John", age: 30, ...params };
            // 模拟请求成功
            resolve(data);
            // this.$message.error("请求接口失败");
            // 模拟请求失败
            // reject('请求失败');
          }, 1000);
        });
      },

After filling in the required fields, initiate a backend request and get the data.

Guess you like

Origin blog.csdn.net/weixin_42125732/article/details/133084780