Skip to content
On this page

loading

可以使用插件形式,也可以使用服务的形式

使用插件形式

ts
// main.ts
app.use(Loading, {
  text: "loading",
  fullscreen: false,
  background: "red",
  customClass: "abcd",
  duration:400
});

使用服务形式

ts
const a1 =  Loading({
  text:"abcd",
  target:'.outer'
})

let x = a1.crateInstance();

setTimeout(()=>{
  x.close()
},1000)

主要核心在于 实例 = createApp(组件),然后 实例.mount(document.createElement('div'))

最后使用 真实元素.appendChild(vm.$el);

然后就是 withDirectives 的使用

js
withDirectives(
          h(
              "div",
              {
                  class: [resolvedOption.customClass, "mask"],
              },
              [spinner, spinnerText]
          ),
        [[vShow, afterLeaveFlag]]
    ),

render函数接收 props

ts
export default {
  props: ['message'],
  render() {
    return [
      // <div><slot /></div>
      h('div', this.$slots.default()),

      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        this.$slots.footer({
          text: this.message
        })
      )
    ]
  }
}
tsx
import {
  App,
  Component,
  DirectiveBinding,
  h,
  ObjectDirective,
  ref,
  Transition,
  withDirectives,
  vShow,
  createApp,
  nextTick,
} from "vue";

interface Option {
  text?: string;
  fullscreen?: boolean;
  background?: string;
  customClass?: string;
  duration?: number;
  target?: Element | string;
  onClose?: Function;
}

interface HTMLElementWidthClose extends HTMLElement {
  close: Function;
}

// 服务形式
function Loading(option: Option) {
  let resolvedOption = resolveOption(option);

  // 格式化 option
  function resolveOption(
    option: Option
  ): Required<Option>{
    return {
      text: option.text ?? "loading",
      fullscreen: option.fullscreen ?? false,
      background: option.background ?? "rgba(0,0,0,0.5)",
      customClass: option.customClass ?? "",
      duration: option.duration ?? 400,
      target: option.target || '',
      onClose: option.onClose ?? (() => {}),
    };
  }

  // 创建实例
  function crateInstance() {
    // 如果没有就是 el
    const afterLeaveFlag = ref(true);
    let afterLeaveTimer: number;

    const handleAfterLeave = () => {
      afterLeaveFlag.value = false;
      loadingInstance.unmount();
      vm.$el?.parentNode?.removeChild(vm.$el);
    };

    const close = () => {
      afterLeaveFlag.value = true;
      clearTimeout(afterLeaveTimer);
      resolvedOption.onClose();
      afterLeaveTimer = window.setTimeout(
        handleAfterLeave,
        resolvedOption.duration
      );
    };

    const elLoadingComponent: Component = {
      setup: () => {
        return () => {
          const spinnerText = h("p", [resolvedOption.text]);
          const spinner = h(
            "svg",
            {
              class: "circular",
              viewBox: "0 0 50 50",
            },
            [
              h("circle", {
                class: "path",
                cx: "25",
                cy: "25",
                r: "20",
                fill: "none",
              }),
            ]
          );
          return h(
            Transition,
            {
              name: "fade",
              onAfterLeave: handleAfterLeave,
            },
            {
              default: () =>
                withDirectives(
                  h(
                    "div",
                    {
                      class: [resolvedOption.customClass, "mask"],
                    },
                    [spinner, spinnerText]
                  ),
                  [[vShow, afterLeaveFlag]]
                ),
            }
          );
        };
      },
    };

    const loadingInstance = createApp(elLoadingComponent);
    const vm = loadingInstance.mount(document.createElement("div"));

    nextTick(() => {
      if (typeof resolvedOption.target == "string") {
        let parent = document.querySelector(resolvedOption.target);
        if (parent) {
          parent.appendChild(vm.$el);
        }
      }else {
        resolvedOption.target.appendChild(vm.$el);
      }
    });

    return {
      close,
    };
  }

  function add(el: HTMLElement, binding: DirectiveBinding) {
    if (binding.value !== true) return;
    resolvedOption.target = el;
    const { close } = crateInstance();
    (el as any).close = close;
  }

  function update(el: HTMLElementWidthClose, binding: DirectiveBinding) {
    if (binding.value == false) {
      el.close?.();
    } else {
      resolvedOption.target = el;
      crateInstance();
    }
  }

  return {
    add,
    update,
    crateInstance,
  };
}

const loading = {
  install: function (app: App, option: Option) {
    const loading = Loading(option);
    app.directive("load", {
      mounted: loading.add.bind(loading),
      updated: loading.update.bind(loading),
    });
  },
};

export { Loading };

export default loading;