• vue3 封装公共弹窗函数


    前言:

    博主封装了一个公共弹窗函数接收四个参数,(title:弹窗标题, ContentComponent:弹窗中显示的组件内容, opt:接收弹窗本身的属性和props, beforeSure:点击确定做的操作(请求后端接口))

    封装的公共函数:

    1. import { defineComponent, h, ref, getCurrentInstance } from "vue-demi";
    2. import Vue from "vue";
    3. import { isFunction, isUndefined, noop, isPlainObject } from "lodash";
    4. const WRAPPED = "wrapped";
    5. function generateDialogComponent(wrapped, opt, listeners = {}) {
    6. return defineComponent({
    7. setup() {
    8. const loading = ref(false);
    9. const vm = getCurrentInstance();
    10. const visible = opt.visible; // Ref
    11. const closeable = opt.closeable; // Ref
    12. const showCancelButton = isUndefined(opt.dialog.showCancelButton)
    13. ? true
    14. : opt.dialog.showCancelButton;
    15. const showSureBtn =
    16. isUndefined(opt.dialog.showSureBtn) &&
    17. isUndefined(opt.dialog.showSureButton)
    18. ? true
    19. : opt.dialog.showSureBtn || opt.dialog.showSureButton;
    20. const showFooter = isUndefined(opt.dialog.showFooter)
    21. ? true
    22. : opt.dialog.showFooter;
    23. const confirmButtonText = opt.dialog.confirmButtonText || "确定";
    24. const cancelButtonText = opt.dialog.cancelButtonText || "取消";
    25. return () => {
    26. const sure = listeners.sure || (() => Promise.resolve());
    27. const cancel = listeners.cancel || noop;
    28. const destroy = listeners.destroy || noop;
    29. // footer
    30. const sureBtn = h(
    31. "el-button",
    32. {
    33. props: {
    34. size: "mini",
    35. type: "primary",
    36. loading: loading.value,
    37. },
    38. on: {
    39. click: () => {
    40. loading.value = true;
    41. const wrappedVm = vm.proxy.$refs[WRAPPED];
    42. return sure(wrappedVm)
    43. .then(() => {
    44. visible.value = false;
    45. })
    46. .finally(() => (loading.value = false));
    47. },
    48. },
    49. },
    50. confirmButtonText
    51. );
    52. const cancelBtn = h(
    53. "el-button",
    54. {
    55. props: {
    56. size: "mini",
    57. },
    58. on: {
    59. click: () => {
    60. visible.value = false;
    61. },
    62. },
    63. },
    64. cancelButtonText
    65. );
    66. const footer = h(
    67. "div",
    68. {
    69. slot: "footer",
    70. style: {
    71. display: "flex",
    72. justifyContent: "space-between",
    73. },
    74. },
    75. [
    76. h("div", [opt.dialog?.leftFooter?.()]),
    77. h("div", [
    78. closeable.value && showCancelButton && cancelBtn,
    79. showSureBtn && sureBtn,
    80. ]),
    81. ]
    82. );
    83. return h(
    84. "el-dialog",
    85. {
    86. props: {
    87. closeOnClickModal: false,
    88. visible: visible.value,
    89. ...opt.dialog,
    90. closeOnClickModal: closeable.value,
    91. closeOnPressEscape: closeable.value,
    92. showClose: closeable.value,
    93. },
    94. on: {
    95. "update:visible": (val) => {
    96. visible.value = val;
    97. },
    98. closed: () => {
    99. cancel();
    100. destroy();
    101. },
    102. },
    103. },
    104. [
    105. h("div", { style: { padding: "20px" } }, [
    106. h(wrapped, {
    107. ref: WRAPPED,
    108. attrs: Object.assign({}, opt.props),
    109. on: {
    110. close: sure, // 组件内部可以通过 emit('close') 来关闭弹窗
    111. },
    112. }),
    113. ]),
    114. showFooter && footer,
    115. ]
    116. );
    117. };
    118. },
    119. });
    120. }
    121. function openDialog(title, ContentComponent, opt, beforeSure) {
    122. const defaultOpt = {
    123. dialog: {},
    124. props: {},
    125. };
    126. // 参数格式化
    127. if (isUndefined(opt)) {
    128. opt = defaultOpt;
    129. }
    130. if (isFunction(opt)) {
    131. opt = defaultOpt;
    132. beforeSure = opt;
    133. }
    134. if (!isFunction(beforeSure)) {
    135. beforeSure = (vm) => vm.submit?.();
    136. }
    137. if (isPlainObject(opt)) {
    138. if (isUndefined(opt.props)) {
    139. opt = {
    140. ...opt,
    141. props: opt,
    142. };
    143. }
    144. }
    145. opt.dialog = opt.dialog || opt || {};
    146. opt.dialog.title = title;
    147. const mountComponent = ($vueconfig) => {
    148. const vm = new Vue($vueconfig);
    149. const anchor = document.createElement("div");
    150. document.body.appendChild(anchor);
    151. vm.$mount(anchor);
    152. return () => {
    153. vm.$destroy();
    154. document.body.removeChild(vm.$el);
    155. };
    156. };
    157. // 控制 dialog 显隐
    158. const visible = ref(false);
    159. const closeDialog = () => {
    160. visible.value = false;
    161. };
    162. // 控制是否可以关闭
    163. const closeable = ref(true);
    164. // 不可关闭弹窗
    165. const freeze = () => {
    166. closeable.value = false;
    167. };
    168. // 可关闭弹窗
    169. const unfreeze = () => {
    170. closeable.value = true;
    171. };
    172. const wait = new Promise((resolve, reject) => {
    173. let disposer = null;
    174. const destroy = () => isFunction(disposer) && disposer();
    175. const cancel = () => {
    176. reject(new Error("cancel"));
    177. };
    178. const sure = async (wrappedComp) => {
    179. const promise = await beforeSure(wrappedComp);
    180. resolve(promise);
    181. };
    182. disposer = mountComponent(
    183. generateDialogComponent(
    184. ContentComponent,
    185. { ...opt, visible, closeable },
    186. { sure, cancel, destroy }
    187. )
    188. );
    189. // 打开弹窗
    190. setTimeout(() => (visible.value = true), 20);
    191. });
    192. return {
    193. close: closeDialog,
    194. promise: wait,
    195. freeze,
    196. unfreeze,
    197. };
    198. }
    199. export function pickByDialog(...args) {
    200. const { promise } = openDialog(...args);
    201. return promise;
    202. }
    203. /**
    204. * 让 pickByDialog 静默失败, 并且提供一个手动关闭弹窗的函数
    205. * @Returns { close }
    206. */
    207. export const openByDialog = (...args) => {
    208. const { close, freeze, unfreeze, promise } = openDialog(...args);
    209. promise.catch(() => {
    210. // no throw error
    211. });
    212. return {
    213. close,
    214. freeze,
    215. unfreeze,
    216. };
    217. };

    部分代码解释:

    generateDialogComponent函数解释:

    1. generateDialogComponent函数定义了一个组件,并返回该组件。
    2. 在组件的setup函数中,首先定义了一些变量和常量,包括:
      • loading:一个用于表示加载状态的响应式变量(Ref)。
      • vm:当前组件实例的引用。
      • visible:一个响应式变量,表示对话框的可见性。
      • closeable:一个响应式变量,表示对话框是否可关闭。
      • showCancelButton:一个布尔值,表示是否显示取消按钮,默认为true
      • showSureBtn:一个布尔值,表示是否显示确定按钮,默认为true
      • showFooter:一个布尔值,表示是否显示底部内容,默认为true
      • confirmButtonText:确定按钮的文本,默认为"确定"。
      • cancelButtonText:取消按钮的文本,默认为"取消"。
    1. 返回一个函数,该函数使用Vue 3的Composition API语法,作为组件的渲染函数(render function)。
    2. 渲染函数返回一个el-dialog组件,该组件是一个基于Element UI库的对话框组件。
    3. el-dialog组件的属性包括:
      • closeOnClickModal:控制是否在点击模态框时关闭对话框,根据closeable的值进行设置。
      • visible:控制对话框的可见性,根据visible的值进行设置。
      • ...opt.dialog:将opt.dialog对象中的所有属性都作为el-dialog组件的属性。
      • closeOnClickModal:控制是否在点击模态框时关闭对话框,根据closeable的值进行设置。
      • closeOnPressEscape:控制是否在按下Esc键时关闭对话框,根据closeable的值进行设置。
      • showClose:控制是否显示关闭按钮,根据closeable的值进行设置。
    1. el-dialog组件的事件包括:
      • update:visible:当对话框的可见性发生变化时,更新visible的值。
      • closed:当对话框关闭时,触发canceldestroy函数。
    1. el-dialog组件的插槽包括:
      • 默认插槽:包含一个具有样式{ padding: "20px" }div元素,其中包含了wrapped组件。
      • showFootertrue时,底部插槽:包含一个具有样式{ display: "flex", justifyContent: "space-between" }div元素,其中包含了底部内容。
    1. 返回生成的组件。

    openDialog的函数解释:

    1. openDialog函数接受四个参数:title(对话框标题),ContentComponent(对话框内容组件),opt(可选配置对象),和beforeSure(可选的确定按钮点击前的回调函数)。
    2. 定义了一个名为defaultOpt的默认配置对象,包含dialogprops两个属性。
    3. 对传入的optbeforeSure进行格式化处理:
      • 如果optundefined,则将其设置为defaultOpt的值。
      • 如果opt为函数,则将其设置为defaultOpt的值,并将beforeSure设置为该函数。
      • 如果beforeSure不是函数,则将其设置为一个默认函数,该函数会调用传入的组件实例的submit方法(如果存在)。
    1. opt进行进一步处理:
      • 如果opt是普通对象且没有props属性,则将opt的值复制给opt.props
    1. opt.dialog设置为optopt.dialog的值,并将title设置为opt.dialog.title
    2. 定义了一个名为mountComponent的函数,用于将组件挂载到DOM上,并返回一个销毁函数。
      • 在函数内部,创建了一个新的Vue实例,并将其挂载到一个新创建的div元素上。
      • 将该div元素添加到document.body中。
      • 返回一个函数,该函数在调用时会销毁Vue实例,并从document.body中移除该div元素。
    1. 创建了一个响应式的变量visible,用于控制对话框的显示和隐藏。
    2. 定义了一个closeDialog函数,用于关闭对话框。
    3. 创建了一个响应式的变量closeable,用于控制对话框是否可以关闭。
    4. 定义了freeze函数,将closeable设置为false,使对话框不可关闭。
    5. 定义了unfreeze函数,将closeable设置为true,使对话框可关闭。
    6. 创建了一个Promise对象wait,用于等待对话框的操作完成。
    7. wait的执行函数中,定义了一些内部函数和变量:

    • disposer:用于存储销毁函数的引用。
    • destroy函数:用于执行销毁函数。
    • cancel函数:用于拒绝Promise并抛出一个取消错误。
    • sure函数:在确定按钮点击时执行的回调函数,调用beforeSure函数并传入wrappedComp作为参数,并将返回的Promise解析为resolve的值。
    • disposer设置为调用mountComponent函数,并传入generateDialogComponent函数生成的对话框组件。
    • 使用setTimeout延迟20毫秒后,将visible设置为true,打开对话框。

    1. 返回一个对象,包含以下属性和方法:

    • close:关闭对话框的方法。
    • promise:返回等待对话框操作完成的Promise对象。
    • freeze:使对话框不可关闭的方法。
    • unfreeze:使对话框可关闭的方法。

    使用方法例子如下

    例子一

    1. const { close } = openByDialog(
    2. "自定义列表",
    3. Setting2,
    4. {
    5. props: menuProps,
    6. dialog: {
    7. width: "980px",
    8. confirmButtonText: "保存",
    9. leftFooter: () =>
    10. h(
    11. "el-button",
    12. {
    13. style: { color: "#2783fe" },
    14. props: {
    15. size: "mini",
    16. type: "text",
    17. loading: reseting.value,
    18. },
    19. on: {
    20. click: () => doReset(),
    21. },
    22. },
    23. "恢复系统默认设置"
    24. ),
    25. },
    26. },
    27. async (componentWrapper) => {
    28. const updatedColumns = await componentWrapper?.updateColumnSetting();
    29. this.emitChangeColumns2(updatedColumns);
    30. }
    31. );

    例子二

    1. //组件弹窗测试
    2. async doImportLog() {
    3. const [cancel, blob] = await to(
    4. pickByDialog(
    5. "弹窗导出测试",
    6. getLogComp(this), //这个是组件
    7. {
    8. dialog: { width: "35%" },
    9. // props: { value: this.value },
    10. // on: {
    11. // input: (val) => {
    12. // this.value = val
    13. // },
    14. // },
    15. },
    16. async (vm) => {
    17. const [changeDateBt, changeDateEt] = vm.date;
    18. console.log("changeDateBt", changeDateBt);
    19. console.log("changeDateEt", changeDateEt);
    20. }
    21. )
    22. );
    23. if (cancel) {
    24. this.$message.info(this.$tof("cancel"));
    25. return;
    26. }
    27. const curDate = dayjs().format("YYYYMMDD");
    28. console.log("curDate", curDate);
    29. saveAs(blob.data, this.$tof("file_name", { curDate }));
    30. },
    31. function getLogComp(vm) {
    32. return {
    33. data() {
    34. return {
    35. date: [],
    36. };
    37. },
    38. render(h) {
    39. return h("el-row", { style: { margin: "100px 50px" } }, [
    40. h(
    41. "el-col",
    42. { style: { lineHeight: "36px" }, attrs: { span: 8 } },
    43. "导出范围时间"
    44. ),
    45. h("el-col", { attrs: { span: 16 } }, [
    46. h("el-date-picker", {
    47. attrs: {
    48. type: "daterange",
    49. rangeSeparator: "-",
    50. startPlaceholder: "开始日期",
    51. endPlaceholder: "结束日期",
    52. value: this.date,
    53. valueFormat: "yyyy-MM-dd",
    54. },
    55. style: { width: "auto !important" },
    56. on: {
    57. input: (val) => {
    58. this.date = val;
    59. },
    60. },
    61. }),
    62. ]),
    63. ]);
    64. },
    65. };
    66. }

    例子三

    1. //组件弹窗测试222
    2. async doAdd() {
    3. const [cancel] = await to(
    4. pickByDialog(
    5. "新增客户",
    6. GroupCreate, //组件
    7. {
    8. dialog: {
    9. width: "80%",
    10. confirmButtonText: "保存",
    11. leftFooter: () =>
    12. h(
    13. "el-button",
    14. {
    15. style: { color: "#2783fe" },
    16. props: {
    17. size: "mini",
    18. type: "text",
    19. loading: false,
    20. },
    21. on: {
    22. click: () => doReset(),
    23. },
    24. },
    25. "恢复系统默认设置"
    26. ),
    27. },
    28. props: {},
    29. },
    30. async (vm) => {
    31. console.log("测试点击确定", vm);
    32. }
    33. )
    34. );
    35. if (cancel) {
    36. this.$message.info("已取消");
    37. return;
    38. }
    39. function doReset() {
    40. console.log("测试左边操作");
    41. }
    42. },
    43. GroupCreate组件:
    44. <script>
    45. // import { customerinfoFindCustByCustNameOrCustCodeToCommodity } from '@/api/commodity/price'
    46. import to from "await-to-js";
    47. import { snakeCase } from "lodash";
    48. export default {
    49. name: "GroupCreate",
    50. components: {
    51. FieldRender: {
    52. functional: true,
    53. props: {
    54. scope: {
    55. type: Object,
    56. default: () => ({}),
    57. },
    58. render: Function,
    59. field: String,
    60. },
    61. render: (h, { props }) => {
    62. const { render, scope, field } = props;
    63. return render
    64. ? render(h, { ...scope, field })
    65. : h("span", null, scope.row[field]);
    66. },
    67. },
    68. },
    69. data(vm) {
    70. return {
    71. ...getSchemaList(vm),
    72. list: [],
    73. columns: getColumList(vm),
    74. pageNum: 1,
    75. pageSize: 10,
    76. totalPages: 0,
    77. loading: false,
    78. layout: {
    79. gutter: 60,
    80. justify: "start",
    81. },
    82. };
    83. },
    84. watch: {
    85. pageNum: {
    86. handler() {
    87. this.doInquiry();
    88. },
    89. },
    90. pageSize: {
    91. handler() {
    92. this.doInquiry();
    93. },
    94. },
    95. },
    96. async created() {
    97. // const options = await this.$ops({});
    98. // Object.assign(this.schemaOptions, options);
    99. this.doInquiry();
    100. },
    101. methods: {
    102. async doInquiry() {
    103. console.log("测试");
    104. // this.loading = true
    105. // const [err, data] = await to(
    106. // customerinfoFindCustByCustNameOrCustCodeToCommodity(
    107. // Object.assign(this.schemaModel, {
    108. // page: this.pageNum - 1,
    109. // size: this.pageSize,
    110. // })
    111. // )
    112. // ).finally(() => (this.loading = false))
    113. // if (err) {
    114. // return
    115. // }
    116. // const { content, totalElements = 5 } = data
    117. // this.totalPages = +totalElements
    118. // this.list = content.map((i) => ({
    119. // custCode: i.customerCode,
    120. // custName: i.customerName,
    121. // }))
    122. },
    123. doReset() {
    124. this.schemaModel = {};
    125. },
    126. doDelete() {},
    127. doAdd() {},
    128. async doSave() {
    129. const [err] = await to(this.$refs["filterForm"].validate());
    130. if (err) {
    131. return;
    132. }
    133. },
    134. doBack() {
    135. this.$store.dispatch("tabsBar/delVisitedRoute", this.$route.fullPath);
    136. this.$router.back();
    137. },
    138. },
    139. };
    140. function getColumList(vm) {
    141. const COLUM_FIELDS = ["custCode", "custName"];
    142. const FORM_FIELDS_NAME = {
    143. custName: "客户名称",
    144. custCode: "客户编码",
    145. };
    146. const colProperties = {};
    147. return COLUM_FIELDS.map((prop) =>
    148. Object.assign(
    149. { prop, label: FORM_FIELDS_NAME[prop], field: prop },
    150. colProperties[prop] || {
    151. sortable: true,
    152. }
    153. )
    154. );
    155. }
    156. function getSchemaList(vm) {
    157. const FORM_FIELDS = ["custCode", "custName"];
    158. const FORM_FIELDS_NAME = {
    159. custName: "客户名称",
    160. custCode: "客户编码",
    161. };
    162. let properties = {
    163. custCode: {},
    164. custName: {},
    165. };
    166. const array = FORM_FIELDS.map((prop) => {
    167. const label = FORM_FIELDS_NAME[prop];
    168. const attrs = properties[prop];
    169. return {
    170. prop,
    171. label,
    172. clearable: true,
    173. filterable: true,
    174. component: "input",
    175. ...attrs,
    176. };
    177. });
    178. const schemaList = [array];
    179. return {
    180. schemaList,
    181. schemaModel: { custStatus: ["20"], custCode: "", custNameList: [] },
    182. schemaOptions: { status: [] },
    183. };
    184. }
    185. script>
    186. <style lang="scss" scoped>
    187. ::v-deep .el-button--text {
    188. color: #409eff;
    189. font-size: 13px;
    190. }
    191. .total-number {
    192. color: #f55448;
    193. }
    194. style>

    例子四

    1. async doAdd2() {
    2. openByDialog(
    3. "测试表单",
    4. FormTest,
    5. {
    6. props: {
    7. isShowCol: true,
    8. },
    9. },
    10. async (componentWrapper) => {
    11. await componentWrapper?.$children[0].validate();
    12. console.log("componentWrapper的数据", componentWrapper);
    13. componentWrapper.ruleForm.date1 = dayjs(
    14. componentWrapper.ruleForm.date1
    15. ).format("YYYYMMDD");
    16. let payload = componentWrapper.ruleForm;
    17. return await this.fetchData(payload);
    18. }
    19. );
    20. },
    21. FormTest组件
    22. <script>
    23. import { defineComponent, ref } from "vue-demi";
    24. export default defineComponent({
    25. name: "ruleFormTest",
    26. props: {
    27. isShowCol: {
    28. type: Boolean,
    29. default: false,
    30. },
    31. },
    32. setup(props) {
    33. const ruleFormRef = ref();
    34. let ruleForm = ref({
    35. name: "",
    36. region: "",
    37. date1: "",
    38. date2: "",
    39. delivery: props.isShowCol,
    40. type: [],
    41. resource: "",
    42. desc: "",
    43. });
    44. let rules = ref({
    45. name: [
    46. { required: true, message: "请输入活动名称", trigger: "blur" },
    47. { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
    48. ],
    49. region: [
    50. { required: true, message: "请选择活动区域", trigger: "change" },
    51. ],
    52. date1: [
    53. {
    54. type: "date",
    55. required: true,
    56. message: "请选择日期",
    57. trigger: "change",
    58. },
    59. ],
    60. date2: [
    61. {
    62. type: "date",
    63. required: true,
    64. message: "请选择时间",
    65. trigger: "change",
    66. },
    67. ],
    68. type: [
    69. {
    70. type: "array",
    71. required: true,
    72. message: "请至少选择一个活动性质",
    73. trigger: "change",
    74. },
    75. ],
    76. resource: [
    77. { required: true, message: "请选择活动资源", trigger: "change" },
    78. ],
    79. desc: [{ required: true, message: "请填写活动形式", trigger: "blur" }],
    80. });
    81. const submitForm = async (formEl) => {
    82. console.log("formEl", formEl);
    83. if (!formEl) return;
    84. await formEl.validate((valid, fields) => {
    85. if (valid) {
    86. console.log("submit!");
    87. } else {
    88. console.log("error submit!", fields);
    89. }
    90. });
    91. };
    92. const resetForm = (formName) => {};
    93. // expose({ submitForm });
    94. return {
    95. ruleForm,
    96. rules,
    97. ruleFormRef,
    98. submitForm,
    99. resetForm,
    100. };
    101. },
    102. });
    103. script>

    模拟一个异步返回

    1. fetchData(params) {
    2. return new Promise((resolve, reject) => {
    3. // 模拟异步请求
    4. setTimeout(() => {
    5. const data = { name: "John", age: 30, ...params };
    6. // 模拟请求成功
    7. resolve(data);
    8. // this.$message.error("请求接口失败");
    9. // 模拟请求失败
    10. // reject('请求失败');
    11. }, 1000);
    12. });
    13. },

    填写完必填项发起后端请求,拿到数据

  • 相关阅读:
    什么是驱动签名?如何为驱动程序获取数字签名?
    Espresso Test 6: RecyclerView
    【Pytorch】pytorch中保存模型的三种方式
    C语言笔记第15篇:文件操作
    Jenkins 构建报错 Could not load
    Spring SSM整合步骤
    2022算能生态合作伙伴大会,英码科技应邀出席共同探讨生态合作和发展问题
    【web开发网页制作】html+css家乡长沙旅游网页制作(4页面附源码)
    1.12.C++项目:仿muduo库实现并发服务器之LoopThreadPool模块的设计
    北京程序员的真实一天!!!!!
  • 原文地址:https://blog.csdn.net/weixin_42125732/article/details/133084780