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
+
+ click me!
+
+
+
+ 序号 | 姓名 | 队伍 | 操作 |
+
+
+
+
+ {{index}} | 张家齐 | 跳水 | 跳水队 |
+
+
+
+
+
+
+```
+
+### 多promise
+支持多个promise。
+
+click me!
+
+loading will show here2
+
+```html
+
+ click me!
+
+ loading will show here2
+
+
+
+```
+
+### 自定义样式
+通过 templateRef 自定义loading样式。
+
+Loading Style 1
+
+Loading Style 2
+
+Loading Style 3
+
+loading will show here1
+
+```html
+
+ Loading Style 1
+
+ Loading Style 2
+
+ Loading Style 3
+
+ loading will show here1
+
+
+
+```
+```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
+
+ click me show full screen loading!
+
+ click me show loading in target!
+
+ click me close loading in target!
+
+ loading will show here3
+
+
+
+```
+
+
\ No newline at end of file