在学习vben-admin源码过程中,看到v-loading指令,实现在元素上加上v-loading就能展示元素的加载状态。记录一下实现过程。
一、定义一个加载页面组件
<!-- -->
<template>
<div
class="full-loading"
:class="{ absolute, [theme]: !!theme }"
:style="[
background ? `background-color:${background}` : '',
color ? { color: color } : ''
]"
v-show="loading"
>
<m-icon name="Loading" :is-loading="loading" :size="sizeComputed"></m-icon>
<p class="tip">{
{ tip }}</p>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { SizeEnum } from '@/enums/sizeEnum'
import { isStr } from '@/utils/tools/is'
import { computed } from '@vue/reactivity'
import MIcon from '@/lib/icon/index.vue'
const props = defineProps({
absolute: Boolean,
theme: {
type: String as PropType<'dark' | 'light'>,
default: 'dark'
},
background: String,
loading: Boolean,
size: {
type: [String, Number],
default: 30,
validator: (v: number | string) => {
if (isStr(v)) {
return [
SizeEnum.DEFAULT + '',
SizeEnum.SMALL + '',
SizeEnum.LARGE + ''
].includes(v as string)
}
return true
}
},
tip: String,
color: String
})
const sizeComputed = computed(() => {
if (isStr(props.size)) {
switch (props.size) {
case SizeEnum.SMALL:
return 20
case SizeEnum.LARGE:
return 40
default:
return 30
}
} else {
return props.size as number
}
})
</script>
<style lang="scss" scoped>
.full-loading {
position: fixed;
top: 0;
left: 0;
z-index: 200;
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: rgb(240 242 245 / 40%);
&.absolute {
position: absolute;
z-index: 300;
}
&.dark {
background-color: rgb(10 10 10 / 40%);
color: #fff;
}
.tip {
line-height: 30px;
}
}
</style>
二、定义一个创建LoadingPage组件的函数
import { createVNode, defineComponent, h, reactive, render, VNode } from 'vue'
import BasicLoading from './index.vue'
import { LoadingProps } from './types'
export const createLoading = (props?: LoadingProps, target?: HTMLElement) => {
let vm: Nullable<VNode> = null
const data = reactive({
tip: '',
loading: true,
...props
})
const LoadingWrap = defineComponent({
render() {
return createVNode(BasicLoading, { ...data })
}
})
vm = createVNode(LoadingWrap)
const open = (target: HTMLElement = document.body) => {
if (!vm) return
render(vm, target)
}
const close = () => {
if (vm?.el && vm.el.parentNode) {
vm.el.parentNode.removeChild(vm.el)
}
}
const setTip = (tip: string) => {
data.tip = tip
}
const setLoading = (loading: boolean) => {
data.loading = loading
}
if (target) {
open(target)
}
return {
vm,
close,
open,
setTip,
setLoading,
get loading() {
return data.loading
},
get $el() {
return vm?.el as HTMLElement
}
}
}
三、定义指令
import { createLoading } from '@/components/basicLoading/createLoading'
type ExtendHTMLElement = HTMLElement & {
instance?: any
}
export const loading: Directive = {
mounted(el: ExtendHTMLElement, binding) {
const tip = el.getAttribute('loading-tip')
const background = el.getAttribute('loading-background')
const size = el.getAttribute('loading-size')
const theme = el.getAttribute('loading-theme')
const color = el.getAttribute('loading-color')
const fullscreen = !!binding.modifiers.fullscreen
const instance = createLoading(
{
tip: tip || undefined,
background: background || undefined,
size: (size || 'large') as SizeEnum,
loading: !!binding.value,
absolute: !fullscreen,
theme: (theme || 'dark') as 'light' | 'dark',
color: color || undefined
},
fullscreen ? document.body : el
)
el.instance = instance
},
updated(el, binding) {
const instance = el.instance
if (binding.oldValue !== binding.value) {
// if (!instance.loading) {
console.log('hdfgdsfd')
instance.setLoading(binding.value)
// }
}
},
unmounted(el) {
el.instance.close()
}
}
四、调用
<AddResource
v-model="modalVisible"
v-model:data="editRes"
@submit-success="updateTable"
v-loading="isLoading"
loading-tip="加载中..."
></AddResource>