ant-design-vue 库 Loading 组件封装
ant-design-vue 库中 Spin :用于页面和区块的加载中状态。页面局部处于等待异步数据或正在渲染过程时,合适的加载动效会有效缓解用户的焦虑。
·
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>
更多推荐
已为社区贡献60条内容
所有评论(0)