38 changed files with 539 additions and 342 deletions
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
@ -1,55 +0,0 @@ |
|||
<template> |
|||
<section class="basic-loading"> |
|||
<img |
|||
src="/@/assets/images/loading.svg" |
|||
alt="" |
|||
:height="getLoadingIconSize" |
|||
:width="getLoadingIconSize" |
|||
/> |
|||
<span class="mt-4" v-if="tip"> {{ tip }}</span> |
|||
</section> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
// components |
|||
import { defineComponent, computed } from 'vue'; |
|||
|
|||
import { SizeEnum, sizeMap } from '/@/enums/sizeEnum'; |
|||
|
|||
import { BasicLoadingProps } from './type'; |
|||
|
|||
export default defineComponent({ |
|||
inheritAttrs: false, |
|||
name: 'BasicLoading', |
|||
props: { |
|||
tip: { |
|||
type: String as PropType<string>, |
|||
default: '', |
|||
}, |
|||
size: { |
|||
type: String as PropType<SizeEnum>, |
|||
default: SizeEnum.DEFAULT, |
|||
validator: (v: SizeEnum): boolean => { |
|||
return [SizeEnum.DEFAULT, SizeEnum.SMALL, SizeEnum.LARGE].includes(v); |
|||
}, |
|||
}, |
|||
}, |
|||
setup(props: BasicLoadingProps) { |
|||
const getLoadingIconSize = computed(() => { |
|||
const { size } = props; |
|||
return sizeMap.get(size); |
|||
}); |
|||
|
|||
return { getLoadingIconSize }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.basic-loading { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
} |
|||
</style> |
|||
@ -1,50 +0,0 @@ |
|||
<template> |
|||
<section class="full-loading" :style="getStyle"> |
|||
<BasicLoading :tip="tip" :size="SizeEnum.DEFAULT" /> |
|||
</section> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { PropType } from 'vue'; |
|||
import { defineComponent, computed } from 'vue'; |
|||
import BasicLoading from './BasicLoading.vue'; |
|||
|
|||
import { SizeEnum } from '/@/enums/sizeEnum'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'FullLoading', |
|||
components: { BasicLoading }, |
|||
props: { |
|||
tip: { |
|||
type: String as PropType<string>, |
|||
default: '', |
|||
}, |
|||
absolute: Boolean as PropType<boolean>, |
|||
}, |
|||
setup(props) { |
|||
const getStyle = computed((): any => { |
|||
return props.absolute |
|||
? { |
|||
position: 'absolute', |
|||
left: 0, |
|||
top: 0, |
|||
'z-index': 1, |
|||
} |
|||
: {}; |
|||
}); |
|||
|
|||
return { getStyle, SizeEnum }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.full-loading { |
|||
display: flex; |
|||
width: 100%; |
|||
height: 100%; |
|||
// background: rgba(255, 255, 255, 0.3); |
|||
// background: #f0f2f5; |
|||
background: rgba(240, 242, 245, 0.5); |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
@ -1,2 +1,8 @@ |
|||
export { default as BasicLoading } from './BasicLoading.vue'; |
|||
export { default as FullLoading } from './FullLoading.vue'; |
|||
import './src/indicator'; |
|||
import LoadingLib from './src/index.vue'; |
|||
import { withInstall } from '../util'; |
|||
|
|||
export { useLoading } from './src/useLoading'; |
|||
export { createLoading } from './src/createLoading'; |
|||
|
|||
export const Loading = withInstall(LoadingLib); |
|||
|
|||
@ -0,0 +1,60 @@ |
|||
import { VNode, defineComponent } from 'vue'; |
|||
import type { LoadingProps } from './types'; |
|||
|
|||
import { createVNode, render, reactive, h } from 'vue'; |
|||
import Loading from './index.vue'; |
|||
|
|||
export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElement) { |
|||
let vm: Nullable<VNode> = null; |
|||
const data = reactive({ |
|||
tip: '', |
|||
loading: true, |
|||
...props, |
|||
}); |
|||
|
|||
const LoadingWrap = defineComponent({ |
|||
setup() { |
|||
return () => { |
|||
return h(Loading, { ...data }); |
|||
}; |
|||
}, |
|||
}); |
|||
|
|||
vm = createVNode(LoadingWrap); |
|||
|
|||
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; |
|||
}, |
|||
}; |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<template> |
|||
<section class="full-loading" :class="{ absolute }" v-show="loading" :style="getStyle"> |
|||
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" /> |
|||
</section> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { computed, CSSProperties, PropType } from 'vue'; |
|||
|
|||
import { defineComponent } from 'vue'; |
|||
import { Spin } from 'ant-design-vue'; |
|||
|
|||
import { SizeEnum } from '/@/enums/sizeEnum'; |
|||
import { ThemeEnum } from '/@/enums/appEnum'; |
|||
|
|||
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); |
|||
}, |
|||
}, |
|||
absolute: { |
|||
type: Boolean as PropType<boolean>, |
|||
default: false, |
|||
}, |
|||
loading: { |
|||
type: Boolean as PropType<boolean>, |
|||
default: false, |
|||
}, |
|||
background: { |
|||
type: String as PropType<string>, |
|||
}, |
|||
theme: { |
|||
type: String as PropType<'dark' | 'light'>, |
|||
default: 'light', |
|||
}, |
|||
}, |
|||
setup(props) { |
|||
const getStyle = computed( |
|||
(): CSSProperties => { |
|||
const { background, theme } = props; |
|||
const bgColor = background |
|||
? background |
|||
: theme === ThemeEnum.DARK |
|||
? 'rgba(0, 0, 0, 0.2)' |
|||
: 'rgba(240, 242, 245, 0.4)'; |
|||
return { background: bgColor }; |
|||
} |
|||
); |
|||
|
|||
return { getStyle }; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less" scoped> |
|||
.full-loading { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 200; |
|||
display: flex; |
|||
width: 100%; |
|||
height: 100%; |
|||
justify-content: center; |
|||
align-items: center; |
|||
|
|||
&.absolute { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 1; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,9 @@ |
|||
// If you need to modify the default icon, you can open the comment and modify it here
|
|||
|
|||
// import { Spin } from 'ant-design-vue';
|
|||
// import { LoadingOutlined } from '@ant-design/icons-vue';
|
|||
// Spin.setDefaultIndicator({
|
|||
// indicator: () => {
|
|||
// return <LoadingOutlined spin />;
|
|||
// },
|
|||
// });
|
|||
@ -0,0 +1,10 @@ |
|||
import { SizeEnum } from '/@/enums/sizeEnum'; |
|||
|
|||
export interface LoadingProps { |
|||
tip: string; |
|||
size: SizeEnum; |
|||
absolute: boolean; |
|||
loading: boolean; |
|||
background: string; |
|||
theme: 'dark' | 'light'; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
import { unref } from 'vue'; |
|||
import { createLoading } from './createLoading'; |
|||
import type { LoadingProps } from './types'; |
|||
import type { Ref } from 'vue'; |
|||
|
|||
export interface UseLoadingOptions { |
|||
target?: HTMLElement | Ref<ElRef>; |
|||
props?: Partial<LoadingProps>; |
|||
} |
|||
|
|||
export function useLoading(props: Partial<LoadingProps>): [Fn, Fn]; |
|||
export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn]; |
|||
|
|||
export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>): [Fn, Fn] { |
|||
let props: Partial<LoadingProps>; |
|||
let target: HTMLElement | Ref<ElRef> = document.body; |
|||
|
|||
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); |
|||
|
|||
const open = (): void => { |
|||
const t = unref(target); |
|||
if (!t) return; |
|||
instance.open(t); |
|||
}; |
|||
|
|||
const close = (): void => { |
|||
instance.close(); |
|||
}; |
|||
|
|||
return [open, close]; |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
import { SizeEnum } from '/@/enums/sizeEnum'; |
|||
|
|||
export interface BasicLoadingProps { |
|||
// 提示语
|
|||
tip: string; |
|||
|
|||
size: SizeEnum; |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
import { Spin } from 'ant-design-vue'; |
|||
import svgImg from '/@/assets/images/loading.svg'; |
|||
|
|||
Spin.setDefaultIndicator({ |
|||
indicator: () => { |
|||
return ( |
|||
<div class="app-svg-loading"> |
|||
<img src={svgImg} alt="" height="32" width="32" /> |
|||
</div> |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,43 @@ |
|||
import { createLoading } from '/@/components/Loading'; |
|||
import type { Directive, App } from 'vue'; |
|||
|
|||
const loadingDirective: Directive = { |
|||
mounted(el, binding) { |
|||
const tip = el.getAttribute('loading-tip'); |
|||
const background = el.getAttribute('loading-background'); |
|||
const theme = el.getAttribute('loading-theme'); |
|||
const size = el.getAttribute('loading-size'); |
|||
const fullscreen = !!binding.modifiers.fullscreen; |
|||
const instance = createLoading( |
|||
{ |
|||
tip, |
|||
background, |
|||
theme, |
|||
size: size || 'large', |
|||
loading: !!binding.value, |
|||
absolute: !fullscreen, |
|||
}, |
|||
fullscreen ? document.body : el |
|||
); |
|||
el.instance = instance; |
|||
}, |
|||
updated(el, binding) { |
|||
const instance = el.instance; |
|||
if (!instance) return; |
|||
instance.setTip(el.getAttribute('loading-tip')); |
|||
if (binding.oldValue !== binding.value) { |
|||
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; |
|||
@ -0,0 +1,96 @@ |
|||
<template> |
|||
<div class="p-5" ref="wrapEl" v-loading="loadingRef" loading-tip="加载中..."> |
|||
<a-alert message="组件方式" /> |
|||
<a-button class="my-4 mr-4" type="primary" @click="openCompFullLoading">全屏 Loading</a-button> |
|||
<a-button class="my-4" type="primary" @click="openCompAbsolute">容器内 Loading</a-button> |
|||
<Loading :loading="loading" :absolute="absolute" :tip="tip" /> |
|||
|
|||
<a-alert message="函数方式" /> |
|||
|
|||
<a-button class="my-4 mr-4" type="primary" @click="openFnFullLoading">全屏 Loading</a-button> |
|||
<a-button class="my-4" type="primary" @click="openFnWrapLoading">容器内 Loading</a-button> |
|||
|
|||
<a-alert message="指令方式" /> |
|||
<a-button class="my-4 mr-4" type="primary" @click="openDirectiveLoading"> |
|||
打开指令Loading |
|||
</a-button> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent, reactive, toRefs, ref } from 'vue'; |
|||
import { Loading, useLoading } from '/@/components/Loading'; |
|||
export default defineComponent({ |
|||
components: { Loading }, |
|||
setup() { |
|||
const wrapEl = ref<ElRef>(null); |
|||
|
|||
const loadingRef = ref(false); |
|||
const compState = reactive({ |
|||
absolute: false, |
|||
loading: false, |
|||
tip: '加载中...', |
|||
}); |
|||
const [openFullLoading, closeFullLoading] = useLoading({ |
|||
tip: '加载中...', |
|||
}); |
|||
|
|||
const [openWrapLoading, closeWrapLoading] = useLoading({ |
|||
target: wrapEl, |
|||
props: { |
|||
tip: '加载中...', |
|||
absolute: true, |
|||
}, |
|||
}); |
|||
|
|||
function openLoading(absolute: boolean) { |
|||
compState.absolute = absolute; |
|||
compState.loading = true; |
|||
setTimeout(() => { |
|||
compState.loading = false; |
|||
}, 2000); |
|||
} |
|||
|
|||
function openCompFullLoading() { |
|||
openLoading(false); |
|||
} |
|||
|
|||
function openCompAbsolute() { |
|||
openLoading(true); |
|||
} |
|||
|
|||
function openFnFullLoading() { |
|||
openFullLoading(); |
|||
|
|||
setTimeout(() => { |
|||
closeFullLoading(); |
|||
}, 2000); |
|||
} |
|||
|
|||
function openFnWrapLoading() { |
|||
openWrapLoading(); |
|||
|
|||
setTimeout(() => { |
|||
closeWrapLoading(); |
|||
}, 2000); |
|||
} |
|||
|
|||
function openDirectiveLoading() { |
|||
loadingRef.value = true; |
|||
setTimeout(() => { |
|||
loadingRef.value = false; |
|||
}, 2000); |
|||
} |
|||
|
|||
return { |
|||
openCompFullLoading, |
|||
openFnFullLoading, |
|||
openFnWrapLoading, |
|||
openCompAbsolute, |
|||
wrapEl, |
|||
loadingRef, |
|||
openDirectiveLoading, |
|||
...toRefs(compState), |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
Loading…
Reference in new issue