Skip to content

feat(loading): [loading] added type definition and interface of Loading component to optimize loading style processing #3253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,7 @@
"type": "component",
"exclude": false,
"mode": [
"mobile-first",
"pc"
]
},
Expand All @@ -2245,14 +2246,25 @@
"type": "component",
"exclude": false,
"mode": [
"mobile-first",
"pc"
]
},
"SkeletonItemMobileFirst": {
"path": "vue/src/skeleton-item/src/mobile-first.vue",
"type": "template",
"exclude": false
},
"SkeletonItemPc": {
"path": "vue/src/skeleton-item/src/pc.vue",
"type": "template",
"exclude": false
},
"SkeletonMobileFirst": {
"path": "vue/src/skeleton/src/mobile-first.vue",
"type": "template",
"exclude": false
},
"SkeletonPc": {
"path": "vue/src/skeleton/src/pc.vue",
"type": "template",
Expand Down
3 changes: 2 additions & 1 deletion packages/vue-common/src/adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export type {
DefineComponent,
ComponentPublicInstance,
SetupContext,
ComputedRef
ComputedRef,
App
} from 'virtual:common/adapter/vue'

export default vue
3 changes: 2 additions & 1 deletion packages/vue-common/src/adapter/vue2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ export type {
ExtractPropTypes,
ComponentPublicInstance,
SetupContext,
ComputedRef
ComputedRef,
App
} from '@vue/composition-api'

export type DefineComponent = typeof defineComponent
Expand Down
3 changes: 2 additions & 1 deletion packages/vue-common/src/adapter/vue3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,5 +493,6 @@ export type {
DefineComponent,
ComponentPublicInstance,
SetupContext,
ComputedRef
ComputedRef,
App
} from 'vue'
3 changes: 2 additions & 1 deletion packages/vue-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ export type {
DefineComponent,
ComponentPublicInstance,
SetupContext,
ComputedRef
ComputedRef,
App
} from './adapter'

export {
Expand Down
62 changes: 53 additions & 9 deletions packages/vue/src/loading/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,59 @@ import service from './src/service'
import directive from './src/directive'
import { setupComponent } from '@opentiny/vue-common'
import { version } from './package.json'
import type { App } from '@opentiny/vue-common'
import type { ILoadingState } from '@opentiny/vue-renderless/types/loading.type'

const Loadings: any = {
install(app) {
// 定义LoadingConfig接口
interface LoadingConfig {
text?: string | null
body?: boolean
lock?: boolean
customClass?: string
fullscreen?: boolean
iconSize?: string
target?: HTMLElement | string
size?: string
loadingImg?: string
tiny_mode?: string
}

// 定义LoadingInstance接口
interface LoadingInstance {
state: ILoadingState
$el: HTMLElement
originalPosition?: string
originalOverflow?: string
}

// 定义Loading服务的类型
type LoadingService = (config?: LoadingConfig) => LoadingInstance

// 定义LoadingPlugin接口
interface LoadingPlugin {
install: (app: App) => void
service: LoadingService
directive: any
version: string
}

// 定义Root接口
interface Root {
$apiPrefix?: string
[key: string]: any
}

// 向setupComponent添加TINYLoading属性
// @ts-expect-error setupComponent上没有TINYLoading属性,但是在运行时会添加这个属性
setupComponent.TINYLoading = {
init(root: Root) {
let prefix = root.$apiPrefix || '$'
root[`${prefix}loading`] = service
}
}

const Loadings: LoadingPlugin = {
install(app: App) {
app.directive('loading', directive)
},
service,
Expand All @@ -26,15 +76,9 @@ const Loadings: any = {
/* istanbul ignore next */
if (process.env.BUILD_TARGET === 'runtime') {
if (typeof window !== 'undefined' && window.Vue) {
// @ts-expect-error window.Vue可能是Vue2或Vue3实例,类型兼容性在运行时处理
Loadings.install(window.Vue)
}
}

setupComponent.TINYLoading = {
init(root) {
let prefix = root.$apiPrefix || '$'
root[`${prefix}loading`] = service
}
}

export default Loadings
54 changes: 39 additions & 15 deletions packages/vue/src/loading/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,29 @@ import { PopupManager } from '@opentiny/utils'
import { getStyle, addClass } from '@opentiny/utils'
import { createComponent, hooks, appProperties } from '@opentiny/vue-common'
import Loading from './index'
import type { ILoadingState } from '@opentiny/vue-renderless/types/loading.type'

interface LoadingConfig {
text?: string | null
body?: boolean
lock?: boolean
customClass?: string
fullscreen?: boolean
iconSize?: string
target?: HTMLElement | string
size?: string
loadingImg?: string
tiny_mode?: string
}

interface LoadingInstance {
state: ILoadingState
$el: HTMLElement
originalPosition?: string
originalOverflow?: string
}
Comment on lines +17 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Duplicate interface definitions between files.

The LoadingConfig and LoadingInstance interfaces are defined both here and in index.ts. Consider moving these to a shared types file to avoid duplication and potential inconsistencies.

// Create a new file: packages/vue/src/loading/types.ts
+export interface LoadingConfig {
+  text?: string | null
+  body?: boolean
+  lock?: boolean
+  customClass?: string
+  fullscreen?: boolean
+  iconSize?: string
+  target?: HTMLElement | string
+  size?: string
+  loadingImg?: string
+  tiny_mode?: string
+}
+
+export interface LoadingInstance {
+  state: ILoadingState
+  $el: HTMLElement
+  originalPosition?: string
+  originalOverflow?: string
+}

Then import these types in both files:

// In index.ts and service.ts
+import type { LoadingConfig, LoadingInstance } from './types'

Committable suggestion skipped: line range outside the PR's diff.


export const defaults = {
export const defaults: LoadingConfig = {
text: null,
body: false,
lock: false,
Expand All @@ -24,7 +45,7 @@ export const defaults = {
iconSize: ''
}

let fullscreenLoading = null
let fullscreenLoading: LoadingInstance | null = null

export const constants = {
TEXT_ATTR: 'tiny-loading__text',
Expand All @@ -36,45 +57,48 @@ export const constants = {
PARENT_RELATIVE_CLS: 'tiny-loading__parent-relative',
LOAD_ICON_TEXT: 'ui.load.dot'
}
const addStyle = (options, parent, instance) => {
let maskStyle = {}

const addStyle = (options: LoadingConfig, parent: HTMLElement, instance: LoadingInstance) => {
let maskStyle: Record<string, string> = {}

if (options.fullscreen) {
instance.originalPosition = getStyle(document.body, 'position')
instance.originalOverflow = getStyle(document.body, 'overflow')
maskStyle.zIndex = PopupManager.nextZIndex()
instance.originalPosition = getStyle(document.body, 'position') || ''
instance.originalOverflow = getStyle(document.body, 'overflow') || ''
maskStyle.zIndex = PopupManager.nextZIndex().toString()
} else if (options.body) {
const clientRect = options.target.getBoundingClientRect()
const target = options.target as HTMLElement
const clientRect = target?.getBoundingClientRect()

instance.originalPosition = getStyle(document.body, 'position')
instance.originalPosition = getStyle(document.body, 'position') || ''

const direction = ['top', 'left']

direction.forEach((property) => {
let scroll = property === 'top' ? 'scrollTop' : 'scrollLeft'

maskStyle[property] = clientRect[property] + document.body[scroll] + document.documentElement[scroll] + 'px'
maskStyle[property] =
(clientRect?.[property] || 0) + document.body[scroll] + document.documentElement[scroll] + 'px'
})

const size = ['height', 'width']

size.forEach((property) => {
maskStyle[property] = clientRect[property] + 'px'
maskStyle[property] = (clientRect?.[property] || 0) + 'px'
})
} else {
instance.originalPosition = getStyle(parent, 'position')
instance.originalPosition = getStyle(parent, 'position') || ''
}

Object.keys(maskStyle).forEach((property) => {
instance.$el.style[property] = maskStyle[property]
})
}

export default (configs = {}) => {
export default (configs: LoadingConfig = {}) => {
configs = { ...defaults, ...configs }

if (typeof configs.target === 'string') {
configs.target = document.querySelector(configs.target)
configs.target = document.querySelector(configs.target) as HTMLElement
}
Comment on lines +97 to 102
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Unsafe type assertion for querySelector result.

The type assertion as HTMLElement on line 101 could be unsafe if the selector doesn't match any element, as it would assert null as HTMLElement.

Consider a more defensive approach:

-  configs.target = document.querySelector(configs.target) as HTMLElement
+  const targetElement = document.querySelector(configs.target)
+  if (!targetElement) {
+    console.warn(`[Loading] Target element not found: ${configs.target}`)
+  }
+  configs.target = targetElement as HTMLElement
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default (configs: LoadingConfig = {}) => {
configs = { ...defaults, ...configs }
if (typeof configs.target === 'string') {
configs.target = document.querySelector(configs.target)
configs.target = document.querySelector(configs.target) as HTMLElement
}
export default (configs: LoadingConfig = {}) => {
configs = { ...defaults, ...configs }
if (typeof configs.target === 'string') {
const targetElement = document.querySelector(configs.target)
if (!targetElement) {
console.warn(`[Loading] Target element not found: ${configs.target}`)
}
configs.target = targetElement as HTMLElement
}
// ... rest of the code
}


configs.target = configs.target || document.body
Expand Down Expand Up @@ -104,7 +128,7 @@ export default (configs = {}) => {
tiny_mode: configs.tiny_mode || appProperties().tiny_mode?.value
},
el: document.createElement('div')
})
}) as LoadingInstance

for (const key in configs) {
if (Object.prototype.hasOwnProperty.call(configs, key)) {
Expand Down