diff --git a/devui/loading/__tests__/loading.spec.ts b/devui/loading/__tests__/loading.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2fb756c35e99bc5327c4dffc9934b216715b188 --- /dev/null +++ b/devui/loading/__tests__/loading.spec.ts @@ -0,0 +1,313 @@ +import { mount } from '@vue/test-utils'; +import { ref, Ref, nextTick, h, shallowReactive } from 'vue'; +import loading from '../index'; + +// 服务方式 +const LoadingService = loading.LoadingService + +// 全局属性 +const globalOption = { + directives: { + dLoading: loading.dLoading + } +} + +describe('Loading as directive', () => { + it('loading init render', async () => { + const wrapper = mount( + { + template: `
` + }, + { + global: globalOption + } + ) + + await nextTick() + const loadingEl = wrapper.find('.devui-loading-contanier') + expect(loadingEl.exists()).toBeTruthy() + const loadingMask = wrapper.find('.devui-loading-mask') + expect(loadingMask.exists()).toBeTruthy() + }) + + it('loading test mask', async () => { + const wrapper = mount( + { + template: `
` + }, + { + global: globalOption + } + ) + + const loadingMask = wrapper.find('.devui-loading-mask') + expect(loadingMask.exists()).toBeFalsy() + }) + + it('loading test positionType', async () => { + const wrapper = mount( + { + template: `
` + }, + { + global: globalOption + } + ) + + const loadingPType = wrapper.find('#testLoading') + expect(loadingPType).toBeTruthy() + // @ts-ignore + const targetEle = loadingPType.wrapperElement.instance.vnode.el + expect(targetEle.parentNode.style.position).toEqual('absolute') + }) + + it('loading test loadingTemplateRef', async () => { + const wrapper = mount( + { + template: `
`, + data() { + return { + ele: h('div', { + className: 'test-component' + }, '正在加载中...') + } + } + }, + { + global: globalOption + } + ) + + await nextTick() + const loadingComp = wrapper.find('.test-component') + expect(loadingComp.exists()).toBeTruthy() + expect(loadingComp.text()).toEqual('正在加载中...') + + const loadingContainer = wrapper.find('.devui-loading-wrapper') + expect(loadingContainer.exists()).toBeFalsy() + }) + + it('loading test vLoading', async () => { + const wrapper = mount( + { + template: ` +
+ +
+
+ `, + setup() { + const isShow = ref(false) + const click = () => { + isShow.value = !isShow.value + } + return { + isShow, + click + } + } + }, + { + global: globalOption + } + ) + + await nextTick() + const loadingContainer = wrapper.find('.devui-loading-contanier') + expect(loadingContainer.exists()).toBeFalsy() + const btn = wrapper.find('#testbtn') + expect(btn.exists()).toBeTruthy() + + await btn.trigger('click') + expect(wrapper.find('.devui-loading-contanier').exists()).toBeTruthy() + + await btn.trigger('click') + expect(wrapper.find('.devui-loading-contanier').exists()).toBeFalsy() + + }) + + // TODO Promise 的单元测试, 需完善 + it('loading test Promise', async () => { + const wrapper = mount( + { + template: ` +
+ +
+
+ `, + setup() { + const loading: Ref | undefined | boolean> = ref(undefined) + + const click = () => { + loading.value = new Promise((res: any) => { + res(111) + }) + } + + return { + loading, + click + } + } + }, + { + global: globalOption + } + ) + + const btn = wrapper.find('#testbtn') + expect(btn.exists()).toBeTruthy() + + await btn.trigger('click') + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + }) + + // TODO 多个 Promise 的单元测试, 需完善 + it('loading test mutiple Promise', async () => { + const wrapper = mount( + { + template: ` +
+ +
+
+ `, + setup() { + let promises: any = shallowReactive({ + value: [] + }) + const fetchMutiplePromise = () => { + let list = [] + for (let i = 0; i < 3; i++) { + list.push(new Promise((res: any) => { + res(true) + })) + } + promises.value = list + } + + return { + fetchMutiplePromise, + promises + } + } + }, + { + global: globalOption + } + ) + + await nextTick() + const btn = wrapper.find('#testbtn') + expect(btn.exists()).toBeTruthy() + + await btn.trigger('click') + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + }) +}) + +describe('Loading as Service', () => { + it('service init', async () => { + const loading = LoadingService.open() + + await nextTick() + let ele = document.querySelector('.devui-loading-contanier') + expect(ele).toBeTruthy() + expect(ele.parentNode == document.body).toBe(true) + + loading.loadingInstance.close() + await nextTick() + let ele2 = document.querySelector('.devui-loading-contanier') + expect(ele2).toBe(null) + }) + + it('service target', async () => { + const div = document.createElement('div') + document.body.appendChild(div) + + const loading = LoadingService.open({ + target: div + }) + + await nextTick() + let ele = document.querySelector('.devui-loading-contanier') + expect(ele).toBeTruthy() + expect(ele.parentNode === div).toBe(true) + + loading.loadingInstance.close() + }) + + it('service message', async () => { + const loading = LoadingService.open({ + message: '正在加载中...' + }) + + await nextTick() + let ele = document.querySelector('.devui-loading-contanier') + expect(ele).toBeTruthy() + expect(ele.textContent).toBe('正在加载中...') + + loading.loadingInstance.close() + }) + + it('service Style', async () => { + const loading = LoadingService.open({ + positionType: 'absolute', + view: { + top: '40%', + left: '60%' + }, + zIndex: 1000 + }) + + await nextTick() + let ele = document.querySelector('.devui-loading-contanier') + expect(ele).toBeTruthy() + // @ts-ignore + expect(ele.parentNode.style.position).toBe('absolute') + + let loadingEle = ele.querySelector('.devui-loading-area') + // @ts-ignore + const style = loadingEle.style + expect(style.top).toBe('40%') + expect(style.left).toBe('60%') + expect(style.zIndex).toBe('1000') + + loading.loadingInstance.close() + }) + + it('service template', async () => { + const loading = LoadingService.open({ + loadingTemplateRef: h('div', { + className: 'test-class' + }, '正在加载中') + }) + + await nextTick() + const ele = document.querySelector('.test-class') + expect(ele).toBeTruthy() + expect(ele.textContent).toBe('正在加载中') + + const originEle = document.querySelector('.devui-loading-wrapper') + expect(originEle).toBeFalsy() + + loading.loadingInstance.close() + }) + + it('service mask', async () => { + const loading = LoadingService.open({ + backdrop: false + }) + + await nextTick() + + const wrapper = document.querySelector('.devui-loading-wrapper') + const mask = document.querySelector('.devui-loading-mask') + + expect(wrapper).toBeTruthy() + expect(mask).toBeFalsy() + + loading.loadingInstance.close() + }) +}) \ No newline at end of file diff --git a/devui/loading/demo/loading-demo.tsx b/devui/loading/demo/loading-demo.tsx deleted file mode 100644 index adf82c4d1620c5096697697690b26b08ae9758bf..0000000000000000000000000000000000000000 --- a/devui/loading/demo/loading-demo.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { defineComponent } from 'vue' - -export default defineComponent({ - name: 'd-loading-demo', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-loading-demo
- } - } -}) \ No newline at end of file diff --git a/devui/loading/demo/loading.route.ts b/devui/loading/demo/loading.route.ts deleted file mode 100644 index d511a03033c644bd39ebbc2f9cdfeaf646d4daba..0000000000000000000000000000000000000000 --- a/devui/loading/demo/loading.route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import LoadingDemoComponent from './loading-demo' -import DevUIApiComponent from '../../shared/devui-api/devui-api' - -import ApiCn from '../doc/api-cn.md' -import ApiEn from '../doc/api-en.md' -const routes = [ - { path: '', redirectTo: 'demo' }, - { path: 'demo', component: LoadingDemoComponent}, - { path: 'api', component: DevUIApiComponent, meta: { - 'zh-cn': ApiCn, - 'en-us': ApiEn - }} -] - -export default routes diff --git a/devui/loading/index.ts b/devui/loading/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9538392bb965572c89f2b773448dfca07aaf6950 --- /dev/null +++ b/devui/loading/index.ts @@ -0,0 +1,15 @@ +import { App } from 'vue' +import Loading from './src/directive' +import LoadingService from './src/service' + +export default { + install(app: App) { + app.directive('dLoading', Loading) + app.config.globalProperties.$loadingService = LoadingService + } +} + +export { + LoadingService, + Loading +} diff --git a/devui/loading/loading.tsx b/devui/loading/loading.tsx deleted file mode 100644 index a17f0afffea46b005f85779aefe76aaa7e657756..0000000000000000000000000000000000000000 --- a/devui/loading/loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { defineComponent } from 'vue' - -export default defineComponent({ - name: 'd-loading', - props: { - }, - setup(props, ctx) { - return () => { - return
devui-loading
- } - } -}) \ No newline at end of file diff --git a/devui/loading/src/component.ts b/devui/loading/src/component.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b1d28eb6770200f6f36c758b5aa6ee7da6d5814 --- /dev/null +++ b/devui/loading/src/component.ts @@ -0,0 +1,24 @@ +import { h, render } from 'vue' + +const COMPONENT_CONTAINER_SYMBOL = Symbol('dev_component_container') + +/** + * 创建组件实例对象 + * 返回的实例和调用 getCurrentComponent() 返回的一致 + * @param {*} Component + */ +export function createComponent(Component: any, props: any, children: any = null) { + const vnode: any = h(Component, { ...props }, children) + const container = document.createElement('div') + vnode[COMPONENT_CONTAINER_SYMBOL] = container + render(vnode, container) + return vnode.component +} + +/** + * 销毁组件实例对象 + * @param {*} ComponnetInstance 通过createComponent方法得到的组件实例对象 + */ +export function unmountComponent(ComponnetInstance: any) { + render(null, ComponnetInstance.vnode[COMPONENT_CONTAINER_SYMBOL]) +} diff --git a/devui/loading/src/directive.ts b/devui/loading/src/directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..0927697b13a4724ea4b53097ad504eb94c3b1e34 --- /dev/null +++ b/devui/loading/src/directive.ts @@ -0,0 +1,134 @@ +import { defineComponent } from 'vue' +import Loading from './loading' +import { LoadingProps, BindingType, TargetHTMLElement } from './types' + +import { createComponent, unmountComponent } from './component' + +const loadingConstructor = defineComponent(Loading) + +const cacheInstance = new WeakSet() + +const isEmpty = (val: any) => { + if (!val) return true + + if (Array.isArray(val)) return val.length === 0 + + if (val instanceof Set || val instanceof Map) return val.size === 0 + + if (val instanceof Promise) return false + + if (typeof val === 'object') { + try { + return Object.keys(val).length === 0 + } catch(e) { + return false + } + } + + return false +} + +const getType = (vari: any) => { + return Object.prototype.toString.call(vari).slice(8, -1).toLowerCase() +} + +const isPromise = (value: any) => { + const type = getType(value) + + switch(type) { + case 'promise': + return [value] + case 'array': + if (value.some((val: any) => getType(val) !== 'promise')) { + console.error(new TypeError('Binding values should all be of type Promise')) + return 'error' + } + return value + default: + return false + } +} + +const unmount = (el: TargetHTMLElement) => { + cacheInstance.delete(el) + el.instance.proxy.close() + unmountComponent(el.instance) +} + +const toggleLoading = (el: TargetHTMLElement, binding: BindingType) => { + if (binding.value) { + const vals: Promise[] | false | 'error' = isPromise(binding.value) + if (vals === 'error') return + + el.instance.proxy.open() + el.appendChild(el.mask!) + cacheInstance.add(el) + + if (vals) { + Promise.all(vals).then((res: Array) => { + }).catch((err: any) => { + console.error(new Error('Promise handling errors'), err) + }).finally(() => { + unmount(el) + }) + } + } else { + unmount(el) + } +} + +const removeAttribute = (el: TargetHTMLElement) => { + el.removeAttribute('zindex') + el.removeAttribute('positiontype') + el.removeAttribute('backdrop') + el.removeAttribute('message') + el.removeAttribute('view') + el.removeAttribute('loadingtemplateref') +} + +const handleProps = (el: TargetHTMLElement, vprops: LoadingProps) => { + const props = { + ...new LoadingProps(), + ...vprops + } + + let loadingTemplateRef = props.loadingTemplateRef + + const loadingInstance = createComponent( + loadingConstructor, + { ...props }, + loadingTemplateRef ? () => loadingTemplateRef : null + ) + + el.style.position = props.positionType! + el.options = props + el.instance = loadingInstance + el.mask = loadingInstance.proxy.$el +} + +const loadingDirective = { + mounted: function (el: TargetHTMLElement, binding: BindingType, vnode: any) { + + handleProps(el, vnode.props) + + removeAttribute(el) + + !isEmpty(binding.value) && toggleLoading(el, binding) + }, + + updated: function (el: TargetHTMLElement, binding: BindingType, vnode: any) { + + if ((!isEmpty(binding.value) && cacheInstance.has(el)) || + (isEmpty(binding.value) && !cacheInstance.has(el))) return + + !cacheInstance.has(el) && handleProps(el, vnode.props) + + removeAttribute(el) + + toggleLoading(el, binding) + }, + + unmounted: function () { } +} + +export default loadingDirective diff --git a/devui/loading/src/loading.scss b/devui/loading/src/loading.scss new file mode 100644 index 0000000000000000000000000000000000000000..5233d84b1a306de3a1760be23568ca9fc378adbf --- /dev/null +++ b/devui/loading/src/loading.scss @@ -0,0 +1,92 @@ +@import '../../style/theme/color'; + +@keyframes devui-busy-spinner-anim { + 0% { + transform:rotate(0deg) + scale(1) + } + 50% { + transform:rotate(135deg) + scale(1.5) + } + to { + transform:rotate(270deg) + scale(1) + } +} + +.devui-loading-mask { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + background-color: $devui-line; + opacity: .3; +} +.devui-loading-wrapper { + text-align: center; +} +.devui-loading--full { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 9999; +} +.devui-loading--hidden { + overflow: hidden; +} +.devui-loading-text { + vertical-align: super; + margin-left: 10px; +} + +// 默认加载样式 +.devui-loading-area { + position: absolute; + transform: translate(-50%, -50%); + padding: 12px 14px; + background: var(--devui-base-bg, #fff); + border-radius: var(--devui-border-radius-card, 6px); +} +.devui-busy-default-spinner { + position: relative; + display: inline-block; + width: 15px; + height: 15px; + animation: devui-busy-spinner-anim 1s linear infinite; + div { + position: absolute; + left: 44.5%; + top: 37%; + width: 6px; + height: 6px; + border-radius: 50%; + } + .devui-loading-bar1 { + top: 0; + left: 0; + background: #5e7ce0; + background: var(--devui-brand,#5e7ce0); + } + .devui-loading-bar2 { + top: 0; + left: 9px; + background: #859bff; + background: var(--devui-brand-foil,#859bff); + } + .devui-loading-bar3 { + top: 9px; + left: 0; + background: #859bff; + background: var(--devui-brand-foil,#859bff); + } + .devui-loading-bar4 { + top: 9px; + left: 9px; + background: #5e7ce0; + background: var(--devui-brand,#5e7ce0); + } +} \ No newline at end of file diff --git a/devui/loading/src/loading.tsx b/devui/loading/src/loading.tsx new file mode 100644 index 0000000000000000000000000000000000000000..64414cb170d0f212069a3666bdd8bfa73d415f76 --- /dev/null +++ b/devui/loading/src/loading.tsx @@ -0,0 +1,76 @@ +import { CSSProperties, defineComponent, ref } from 'vue' +import { componentProps, ComponentProps } from './types' + +import './loading.scss'; + +export default defineComponent({ + name: 'd-loading', + inheritAttrs: false, + props: componentProps, + setup(props: ComponentProps) { + + const style: CSSProperties = { + top: props.view.top, + left: props.view.left, + zIndex: props.zIndex + } + if (!props.message) { + style.background = 'none' + } + const isShow = ref(false) + + const open = () => { + isShow.value = true + } + + const close = () => { + isShow.value = false + } + + return { + style, + isShow, + open, + close + } + }, + render() { + const { + isShow, + isFull, + backdrop, + style, + message, + $slots + } = this + + return ( + isShow && +
+ { + $slots.default?.() || +
+ { + backdrop + ?
+ : null + } +
+
+
+
+
+
+
+ { + message + ? {message} + : null + } +
+
+ } +
+ ) + } +}) \ No newline at end of file diff --git a/devui/loading/src/service.ts b/devui/loading/src/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..355f6a8c371318132166ecc28290244e366d2659 --- /dev/null +++ b/devui/loading/src/service.ts @@ -0,0 +1,51 @@ +import { defineComponent } from 'vue' +import { createComponent } from './component' +import Loading from './loading' + +import { LoadingProps } from './types' + +const loadingConstructor = defineComponent(Loading) + +interface TargetElement extends Element { + style ?: any +} + +const cacheTarget = new WeakMap() + +const loading = { + open(options: LoadingProps = {}) { + + const parent: TargetElement = options.target || document.body + + if (cacheTarget.has(parent)) { + return cacheTarget.get(parent) + } + + parent.style.position = options.positionType + + const isFull = document.body === parent + + options = {...new LoadingProps(), ...options} + + const instance = createComponent(loadingConstructor, { + ...options, + isFull + }, options.loadingTemplateRef ? () => options.loadingTemplateRef : null) + + cacheTarget.set(parent, instance) + + instance.proxy.open() + parent.appendChild(instance.proxy.$el) + + const close = instance.proxy.close + instance.loadingInstance = instance.proxy + instance.loadingInstance.close = () => { + cacheTarget.delete(parent) + close.apply(null, arguments) + } + + return instance + } +} + +export default loading \ No newline at end of file diff --git a/devui/loading/src/types.ts b/devui/loading/src/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..32e3dd03d3623d49553916c6e92c87968233c172 --- /dev/null +++ b/devui/loading/src/types.ts @@ -0,0 +1,45 @@ +import { ExtractPropTypes, PropType, VNode } from 'vue' + +type PositionType = 'static' | 'relative' | 'absolute' | 'fixed' |'sticky' + +export interface LoadingType { + value: Promise | Array> | undefined +} +export interface BindingType extends LoadingType { + [key: string] : any +} +export interface TargetHTMLElement extends HTMLElement { + mask ?: HTMLElement + instance ?: VNode | any + options ?: LoadingProps +} + +class View { + top ?: string = '50%' + left?: string = '50%' +} +export const componentProps = { + message: String, + backdrop: Boolean, + view: { + type: Object as PropType, + default: () => (new View()) + }, + zIndex: Number, + isFull: { + type: Boolean, + default: false + } +} as const + +export class LoadingProps { + target ?: Element | null + message ?: string + loadingTemplateRef ?: any + backdrop ?: boolean = true + positionType ?: PositionType = 'relative' + view ?: View = new View() + zIndex ?: number +} + +export type ComponentProps = ExtractPropTypes \ No newline at end of file diff --git a/devui/vue-devui.ts b/devui/vue-devui.ts index 009113da50a79204110c000d7ead26df9f6c6c31..2a0a2d82e5fdcb3e24f3903733d4ed1491650413 100644 --- a/devui/vue-devui.ts +++ b/devui/vue-devui.ts @@ -9,7 +9,8 @@ import Panel from './panel'; import Tabs from './tabs'; // 反馈 -import Alert from './alert'; +import Alert from './alert/alert'; +import DLoading, { LoadingService, Loading } from './loading'; // 数据录入 import Checkbox from './checkbox'; @@ -23,7 +24,7 @@ import Avatar from './avatar'; import Carousel from './carousel'; function install(app: App): void { - const packages = [ Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel ]; + const packages = [ Button, Icon, Panel, Tabs, Alert, DLoading, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel ]; packages.forEach((item: any) => { if (item.install) { app.use(item); @@ -33,5 +34,5 @@ function install(app: App): void { }); } -export { Button, Icon, Panel, Tabs, Alert, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel }; -export default { install, version: '0.0.1' }; \ No newline at end of file +export { Button, Icon, Panel, Tabs, Alert, LoadingService, Loading, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel }; +export default { install, version: '0.0.1' }; diff --git a/sites/.vitepress/config/sidebar.ts b/sites/.vitepress/config/sidebar.ts index 9866743ecf258dd005a61ca4e41adf522e84e8be..c49dde11376d8e7494751e682b8cc726a8e13356 100644 --- a/sites/.vitepress/config/sidebar.ts +++ b/sites/.vitepress/config/sidebar.ts @@ -19,6 +19,7 @@ const sidebar = { text: '反馈', children: [ { text: 'Alert 警告', link: '/components/alert/' }, + { text: 'Loading 加载提示', link: '/components/loading/' }, ] }, { diff --git a/sites/components/loading/customStyle.scss b/sites/components/loading/customStyle.scss new file mode 100644 index 0000000000000000000000000000000000000000..5f89cd60d57fd6073dd21b39d204dce4dbb3d192 --- /dev/null +++ b/sites/components/loading/customStyle.scss @@ -0,0 +1,20 @@ +.devui-infinity-loading { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.devui-circle-loading-container-2 { + position: absolute; + left: 5px; + top: 50%; + transform: translateY(-50%); +} + +.devui-circle-loading-container-3 { + position: absolute; + right: -20px; + top: 50%; + transform: translateY(-50%); +} \ No newline at end of file diff --git a/sites/components/loading/index.md b/sites/components/loading/index.md new file mode 100644 index 0000000000000000000000000000000000000000..edab79c2027336557df896514b69c98ef4b80085 --- /dev/null +++ b/sites/components/loading/index.md @@ -0,0 +1,470 @@ +# Loading 加载提示 + +提示用户页面正在执行指令,需要等待。 + +### 何时使用 + +当执行指令时间较长(需要数秒以上)时,向用户展示正在执行的状态。 + +### 参数 + +| **参数** | **类型** | **默认** | **说明** | **跳转 Demo** | +| ------------------ | ------------------------------------------------------------ | ------------------------- | ------------------------------------------------------------ | ---------------------------- | +| v-dLoading | Promise\ / Array\\> / Boolean / undefined | -- | 可选,指令方式,控制 loading 状态 | [基本用法](#基本用法) | +| target | Element | document.body | 可选,服务方式,Loading 需要覆盖的 DOM 节点 | [服务方式调用](#服务方式调用) | +| message | String | -- | 可选,loading 时的提示信息 | [多promise](#多promise) | +| loadingTemplateRef | VNode | -- | 可选,自定义 loading 模板 | [自定义样式](#自定义样式) | +| backdrop | Boolean | true | 可选,loading 时是否显示遮罩 | [基本用法](#基本用法) | +| positionType | String | relative | 可选,指定`dLoading`宿主元素的定位类型,取值与 css position 属性一致。 | [基本用法](#基本用法) | +| view | {top?:string,left?:string} | {top: '50%', left: '50%'} | 可选,调整 loading 的显示位置,相对于宿主元素的顶部距离与左侧距离 | [基本用法](#基本用法) | +| zIndex | Number | -- | 可选,loading加载提示的 z-index 值 | [基本用法](#基本用法) | + + + + +### 基本用法 +展示加载表格数据的场景中的基本使用方法。 + +click me! + + + + + + + + + + + +
序号姓名队伍操作
{{index}}张家齐跳水跳水队
+ +```html + + + +``` + +### 多promise +支持多个promise。 + +click me! + +
loading will show here2
+ +```html + + + +``` + +### 自定义样式 +通过 templateRef 自定义loading样式。 + +Loading Style 1 + +Loading Style 2 + +Loading Style 3 + +
loading will show here1
+ +```html + + + +``` +```scss +// ./customStyle.scss +.devui-infinity-loading { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.devui-circle-loading-container-2 { + position: absolute; + left: 5px; + top: 50%; + transform: translateY(-50%); +} + +.devui-circle-loading-container-3 { + position: absolute; + right: -20px; + top: 50%; + transform: translateY(-50%); +} +``` + +### 服务方式调用 +使用服务的方式全屏加载loading组件或者在指定宿主上加载loading组件。 + +click me show full screen loading! + +click me show loading in target! + +click me close loading in target! + +
loading will show here3
+ +```html + + + +``` + + \ No newline at end of file