ant-design-vue 库中 Spin :用于页面和区块的加载中状态。页面局部处于等待异步数据或正在渲染过程时,合适的加载动效会有效缓解用户的焦虑。

重构 Loading 组件

<!-- src/components/Loading/components/Loading.vue -->
<template>
  <section
    class="full-loading"
    v-show="loading"
  >
    <Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
  </section>
</template>
<script lang="ts">
  import { PropType } from 'vue';
  import { defineComponent } from 'vue';
  import { Spin } from 'ant-design-vue';
  import { SizeEnum } from '/@/enums/sizeEnum';

  export default defineComponent({
    name: 'Loading',
    components: { Spin },
    props: {
      tip: {
        type: String as PropType<string>,
        default: '',
      },
      size: {
        type: String as PropType<SizeEnum>,
        default: SizeEnum.LARGE,
        validator: (v: SizeEnum): boolean => {
          return [SizeEnum.DEFAULT, SizeEnum.SMALL, SizeEnum.LARGE].includes(v);
        },
      },
      loading: {
        type: Boolean as PropType<boolean>,
        default: false,
      },
    }
  })
</script>

// 对应枚举值维护
export enum SizeEnum {
  DEFAULT = 'default',
  SMALL = 'small',
  LARGE = 'large',
}

useLoading 函数

在使用 useLoadig 函数之前,调用 createLoading 创建 loading 实例。具体代码如下:

// src/components/Loading/components/createLoading.ts
import { VNode, defineComponent, createVNode, render, reactive, h } from 'vue'
import Loading from './Loading.vue';

export function createLoading(props, target, wait = false) {
  const vm: Nullable<VNode> = null;
  const data = reactive({
    tip: '',
    loading: true,
    ...props
  })

  const LoadingWrap = defineComponent({
    render () {
      return h(Loading, { ...data })
    }
  })

  vm = createVNode(LoadingWrap)

  if (wait) {
    setTimeout(() => {
      render(vm, document.createElement('div'));
    }, 0)
  } else {
    render(vm, document.createElement('div'));
  }

  function close() {
    if (vm?.el && vm.el.parentNode) {
      vm.el.parentNode.removeChild(vm.el)
    }
  }

  function open(target: HTMLElement = document.body) {
    if (!vm || !vm.el) {
      return
    }
    target.appendChild(vm.el as HTMLElement)
  }

  if (target) {
    open(target)
  }

  return {
    vm,
    close,
    open,
    setTip: (tip: String) => {
      data.tip = tip
    },
    setLoading: (loading: boolean) => {
      data.loading = loading
    },
    get loading () {
      return data.loading
    },
    get $el() {
      return vm?.el as HTMLElement
    }
  }
}

useLoadig 函数对外提供相应的方法。

// src/components/Loading/components/useLoading.ts
import { unref } from 'vue'
import { createLoading } from './createLoading'
import type { LoadingProps } from './typing';
import type { Ref } from 'vue';

export interface UseLoadingOptions {
  target?: any,
  props?: Partial<LoadingProps>,
}

interface Fn {
  (): void;
}

export function useLoading(props: Partial<LoadingProps>): [Fn, Fn, (string) => void]
export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn, (string) => void]

export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>): [Fn, Fn, (string) => void] {
  let props: Partial<LoadingProps>
  let target: HTMLElement | Ref<ElRef> = document.body

  // 判断使用哪种 interface
  if (Reflect.has(opt, 'target') || Reflect.has(opt, 'props')) {
    const options = opt as Partial<UseLoadingOptions>
    props = options.props || {}
    target = options.target || document.body
  } else {
    props = opt as Partial<LoadingProps>
  }

  const instance = createLoading(props, undefined, true)

  const open = (): void => {
    const t = unref(target as Ref(ElRef))
    if (!t) return
    instance.open(t)
  }

  const close = (): void => {
    instance.close()
  }

  const setTip = (tip: string) => {
    instance.setTip(tip)
  }

  return [open, close, setTip]
}

v-loading 指令

// src/directives/loading.ts
import { createLoading } from '/@/components/Loading';

const loadingDirective: Directive = {
  mounted(el, binding) {
    const tip = el.getAttribut('loading-tip')
    const background = el.getAttribut('loading-background')
    const size = el.getAttribut('loading-size')
    
    const fullscreen = !!binding.modifiers.fullscreen
    const instance = createLoading({
      tip,
      background,
      size: size || 'large',
      loading: !!binding.value,
      absolute: !fullscreen,
    }, fullscreen ? document.body : el)
    el.instance = instance
  },
  update (el, binding) {
    const instance = el.instance
    if (!instance) return
    instance.setTip(el.getAttribute('loading-tip'))
    if (binding.oldValue !== binding.value) {
      instance.setLoading?.(binding.value && !instance.loading)
    }
  },
  unmounted (el) {
    el?.instance?.close()
  }
}

export function setupLoadingDirective(app: App) {
  app.directive('loading', loadingDirective)
}
export default loadingDirective;

全局注册使用

// src/main.ts
import { createApp } from 'vue'
import { setupGlobDirectives } from '/@/directives'

async function bootstrap() {
  const app = createApp()
  // 注册全局指令
  setupGlobDirectives(app)
}
bootstrap()

// src/directives/index.ts
// 配置注册全局指令
import type { App } from 'vue';
import { setupLoadingDirective } from './loading';

export function setupGlobDirectives(app: App) {
  setupLoadingDirective(app);
}

组件使用

对外暴露方法

import Loading from './components/Loading.vue'

export { Loading }
export { useLoading } from './components/useLoading'
export { createLoading } from './components/createLoading'

使用案例

<template>
  <div v-loading="loadingRef" loading-tip="加载中..." title="Loading组件示例"></div>
  <Loading
    :loading="loading"
    :absolute="absolute"
    :theme="theme"
    :background="background"
    :tip="tip"
  />
</template>
<script lang="ts">
  import { defineComponent, reactive, toRefs, ref } from 'vue';
  import { Loading, useLoading } from '/@/components/Loading';
  export default defineComponent({
    setup () {
	  const wrapEl = ref<ElRef>(null);
      
      const compState = reactive({
        loading: false,
        tip: '加载中...',
      })
	  // 1.组件方式
      function openLoading(absolute) {
        compState.loaded = true
        setTimeout(() => {
          compState.loaded = false
        }, 2000);
      }

      // 2. 函数方式
       const [openFullLoading, closeFullLoading] = useLoading({
        tip: '加载中...',
      });
      const [openWrapLoading, closeWrapLoading] = useLoading({
        target: wrapEl,
        props: {
          tip: '加载中...',
        },
      });
      
      // 3. 指令方式
      const loadingRef = ref(false)
      function openDirectiveLoading () {
        loadingRef.value = true
        setTimeout(() => {
          loadingRef.value = false
        }, 2000);
      }

      return {
        loadingRef,
        openDirectiveLoading,
        ...toRefs(compState),
      }
    }
  })
</script>
Logo

前往低代码交流专区

更多推荐