瀏覽代碼

feat: 新增函数式弹框组件,使用更便捷

xiaoxian521 2 年之前
父節點
當前提交
3705a0c11b
共有 4 個文件被更改,包括 335 次插入1 次删除
  1. 4 1
      src/App.vue
  2. 39 0
      src/components/ReDialog/index.ts
  3. 106 0
      src/components/ReDialog/index.vue
  4. 186 0
      src/components/ReDialog/type.ts

+ 4 - 1
src/App.vue

@@ -1,6 +1,7 @@
 <template>
   <el-config-provider :locale="currentLocale">
     <router-view />
+    <ReDialog />
   </el-config-provider>
 </template>
 
@@ -9,10 +10,12 @@ import { defineComponent } from "vue";
 import { ElConfigProvider } from "element-plus";
 import zhCn from "element-plus/lib/locale/lang/zh-cn";
 import en from "element-plus/lib/locale/lang/en";
+import { ReDialog } from "@/components/ReDialog";
 export default defineComponent({
   name: "app",
   components: {
-    [ElConfigProvider.name]: ElConfigProvider
+    [ElConfigProvider.name]: ElConfigProvider,
+    ReDialog
   },
   computed: {
     currentLocale() {

+ 39 - 0
src/components/ReDialog/index.ts

@@ -0,0 +1,39 @@
+import { ref } from "vue";
+import reDialog from "./index.vue";
+import { useTimeoutFn } from "@vueuse/core";
+import { withInstall } from "@pureadmin/utils";
+import type {
+  EventType,
+  ArgsType,
+  DialogProps,
+  ButtonProps,
+  DialogOptions
+} from "./type";
+
+const dialogStore = ref<Array<DialogOptions>>([]);
+
+const addDialog = (options: DialogOptions) => {
+  const open = () =>
+    dialogStore.value.push(Object.assign(options, { visible: true }));
+  if (options?.openDelay) {
+    useTimeoutFn(() => {
+      open();
+    }, options.openDelay);
+  } else {
+    open();
+  }
+};
+
+const closeDialog = (options: DialogOptions, index: number, args: any) => {
+  dialogStore.value.splice(index, 1);
+  options.closeCallBack && options.closeCallBack(args);
+};
+
+const closeAllDialog = () => {
+  dialogStore.value = [];
+};
+
+const ReDialog = withInstall(reDialog);
+
+export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
+export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };

+ 106 - 0
src/components/ReDialog/index.vue

@@ -0,0 +1,106 @@
+<script setup lang="ts">
+import { computed } from "vue";
+import { isFunction } from "@pureadmin/utils";
+import {
+  type DialogOptions,
+  type ButtonProps,
+  type EventType,
+  dialogStore,
+  closeDialog
+} from "./index";
+
+const footerButtons = computed(() => {
+  return (options: DialogOptions) => {
+    return options?.footerButtons?.length > 0
+      ? options.footerButtons
+      : ([
+          {
+            label: "取消",
+            text: true,
+            bg: true,
+            btnClick: ({ dialog: { options, index } }) => {
+              closeDialog(options, index, { command: "cancel" });
+            }
+          },
+          {
+            label: "确定",
+            type: "primary",
+            text: true,
+            bg: true,
+            btnClick: ({ dialog: { options, index } }) => {
+              closeDialog(options, index, { command: "sure" });
+            }
+          }
+        ] as Array<ButtonProps>);
+  };
+});
+
+function eventsCallBack(
+  event: EventType,
+  options: DialogOptions,
+  index: number
+) {
+  if (options?.[event] && isFunction(options?.[event])) {
+    return options?.[event]({ options, index });
+  }
+}
+
+function handleClose(
+  options: DialogOptions,
+  index: number,
+  args = { command: "close" }
+) {
+  closeDialog(options, index, args);
+  eventsCallBack("close", options, index);
+}
+</script>
+
+<template>
+  <el-dialog
+    v-for="(options, index) in dialogStore"
+    :key="index"
+    v-bind="options"
+    v-model="options.visible"
+    @opened="eventsCallBack('open', options, index)"
+    @close="handleClose(options, index)"
+    @openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
+    @closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
+  >
+    <!-- header -->
+    <template
+      v-if="options?.headerRenderer"
+      #header="{ close, titleId, titleClass }"
+    >
+      <component
+        :is="options?.headerRenderer({ close, titleId, titleClass })"
+      />
+    </template>
+    <!-- default -->
+    <component
+      v-bind="options?.props"
+      :is="options.contentRenderer({ options, index })"
+      @close="args => handleClose(options, index, args)"
+    />
+    <!-- footer -->
+    <template v-if="!options?.hideFooter" #footer>
+      <template v-if="options?.footerRenderer">
+        <component :is="options?.footerRenderer({ options, index })" />
+      </template>
+      <span v-else>
+        <el-button
+          v-for="(btn, key) in footerButtons(options)"
+          :key="key"
+          v-bind="btn"
+          @click="
+            btn.btnClick({
+              dialog: { options, index },
+              button: { btn, index: key }
+            })
+          "
+        >
+          {{ btn?.label }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>

+ 186 - 0
src/components/ReDialog/type.ts

@@ -0,0 +1,186 @@
+import type { CSSProperties, VNode, Component } from "vue";
+
+type DoneFn = (cancel?: boolean) => void;
+type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
+type ArgsType = {
+  /** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
+  command: "cancel" | "sure" | "close";
+};
+
+/** https://element-plus.org/zh-CN/component/dialog.html#attributes */
+type DialogProps = {
+  /** `Dialog` 的显示与隐藏 */
+  visible?: boolean;
+  /** `Dialog` 的标题 */
+  title?: string;
+  /** `Dialog` 的宽度,默认 `50%` */
+  width?: string | number;
+  /** 是否为全屏 `Dialog`,默认 `false` */
+  fullscreen?: boolean;
+  /** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
+  top?: string;
+  /** 是否需要遮罩层,默认 `true` */
+  modal?: boolean;
+  /** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
+  appendToBody?: boolean;
+  /** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
+  lockScroll?: boolean;
+  /** `Dialog` 的自定义类名 */
+  class?: string;
+  /** `Dialog` 的自定义样式 */
+  style?: CSSProperties;
+  /** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
+  openDelay?: number;
+  /** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
+  closeDelay?: number;
+  /** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
+  closeOnClickModal?: boolean;
+  /** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
+  closeOnPressEscape?: boolean;
+  /** 是否显示关闭按钮,默认 `true` */
+  showClose?: boolean;
+  /** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
+  beforeClose?: (done: DoneFn) => void;
+  /** 为 `Dialog` 启用可拖拽功能,默认 `false` */
+  draggable?: boolean;
+  /** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
+  center?: boolean;
+  /** 是否水平垂直对齐对话框,默认 `false` */
+  alignCenter?: boolean;
+  /** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
+  destroyOnClose?: boolean;
+};
+
+type BtnClickDialog = {
+  options?: DialogOptions;
+  index?: number;
+};
+type BtnClickButton = {
+  btn?: ButtonProps;
+  index?: number;
+};
+/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
+type ButtonProps = {
+  /** 按钮文字 */
+  label: string;
+  /** 按钮尺寸 */
+  size?: "large" | "default" | "small";
+  /** 按钮类型 */
+  type?: "primary" | "success" | "warning" | "danger" | "info";
+  /** 是否为朴素按钮,默认 `false` */
+  plain?: boolean;
+  /** 是否为文字按钮,默认 `false` */
+  text?: boolean;
+  /** 是否显示文字按钮背景颜色,默认 `false` */
+  bg?: boolean;
+  /** 是否为链接按钮,默认 `false` */
+  link?: boolean;
+  /** 是否为圆角按钮,默认 `false` */
+  round?: boolean;
+  /** 是否为圆形按钮,默认 `false` */
+  circle?: boolean;
+  /** 是否为加载中状态,默认 `false` */
+  loading?: boolean;
+  /** 自定义加载中状态图标组件 */
+  loadingIcon?: string | Component;
+  /** 按钮是否为禁用状态,默认 `false` */
+  disabled?: boolean;
+  /** 图标组件 */
+  icon?: string | Component;
+  /** 是否开启原生 `autofocus` 属性,默认 `false` */
+  autofocus?: boolean;
+  /** 原生 `type` 属性,默认 `button` */
+  nativeType?: "button" | "submit" | "reset";
+  /** 自动在两个中文字符之间插入空格 */
+  autoInsertSpace?: boolean;
+  /** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
+  color?: string;
+  /** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
+  dark?: boolean;
+  /** 自定义元素标签 */
+  tag?: string | Component;
+  /** 点击按钮后触发的回调 */
+  btnClick?: ({
+    dialog,
+    button
+  }: {
+    /** 当前 `Dialog` 信息 */
+    dialog: BtnClickDialog;
+    /** 当前 `button` 信息 */
+    button: BtnClickButton;
+  }) => void;
+};
+
+interface DialogOptions extends DialogProps {
+  /** 内容区组件的 `props`,可通过 `defineProps` 接收 */
+  props?: any;
+  /** 是否隐藏 `Dialog` 按钮操作区的内容 */
+  hideFooter?: boolean;
+  /**
+   * @description 自定义对话框标题的内容渲染器
+   * @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
+   */
+  headerRenderer?: ({
+    close,
+    titleId,
+    titleClass
+  }: {
+    close: Function;
+    titleId: string;
+    titleClass: string;
+  }) => VNode;
+  /** 自定义内容渲染器 */
+  contentRenderer?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => VNode;
+  /** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
+  footerRenderer?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => VNode;
+  /** 自定义底部按钮操作 */
+  footerButtons?: Array<ButtonProps>;
+  /** `Dialog` 打开后的回调 */
+  open?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => void;
+  /** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */
+  close?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => void;
+  /** `Dialog` 关闭后的回调,会返回 `command`。`command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页  */
+  closeCallBack?: (args: any) => void;
+  /** 输入焦点聚焦在 `Dialog` 内容时的回调 */
+  openAutoFocus?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => void;
+  /** 输入焦点从 `Dialog` 内容失焦时的回调 */
+  closeAutoFocus?: ({
+    options,
+    index
+  }: {
+    options: DialogOptions;
+    index: number;
+  }) => void;
+}
+
+export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };