From 978713b3168990f6f7137cb517c375e754b1ce7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=BC=A0?= <2091066548@qq.com> Date: Tue, 23 Sep 2025 18:09:49 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9Eloading=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Loading.vue | 198 +++++++++++++ src/components/common/LoadingExample.vue | 337 +++++++++++++++++++++++ src/composables/useLoading.ts | 243 ++++++++++++++++ src/types/loading.ts | 77 ++++++ src/views/CourseExchanged.vue | 115 ++++++-- 5 files changed, 952 insertions(+), 18 deletions(-) create mode 100644 src/components/common/Loading.vue create mode 100644 src/components/common/LoadingExample.vue create mode 100644 src/composables/useLoading.ts create mode 100644 src/types/loading.ts diff --git a/src/components/common/Loading.vue b/src/components/common/Loading.vue new file mode 100644 index 0000000..59bbaa8 --- /dev/null +++ b/src/components/common/Loading.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/components/common/LoadingExample.vue b/src/components/common/LoadingExample.vue new file mode 100644 index 0000000..9090726 --- /dev/null +++ b/src/components/common/LoadingExample.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/src/composables/useLoading.ts b/src/composables/useLoading.ts new file mode 100644 index 0000000..6cad30f --- /dev/null +++ b/src/composables/useLoading.ts @@ -0,0 +1,243 @@ +import { ref } from 'vue' +import type { + LoadingOptions, + LoadingWrapOptions, + UseLoadingReturn +} from '@/types/loading' + +// 全局 Loading 状态(保留以备后用) +// const globalLoading = ref(false) +// const globalLoadingText = ref('加载中...') +// const globalLoadingOptions = ref({}) + +// 创建全局遮罩元素 +let globalLoadingElement: HTMLElement | null = null + +/** + * 创建全局 Loading 遮罩 + */ +function createGlobalLoadingElement() { + if (globalLoadingElement) return + + globalLoadingElement = document.createElement('div') + globalLoadingElement.id = 'global-loading-overlay' + globalLoadingElement.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + ` + + const content = document.createElement('div') + content.style.cssText = ` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + min-width: 120px; + ` + + const spinner = document.createElement('div') + spinner.style.cssText = ` + width: 32px; + height: 32px; + border: 3px solid #f3f3f3; + border-top: 3px solid #1890ff; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 12px; + ` + + const text = document.createElement('div') + text.id = 'global-loading-text' + text.style.cssText = ` + color: #666; + font-size: 14px; + text-align: center; + ` + text.textContent = '加载中...' + + // 添加旋转动画 + const style = document.createElement('style') + style.textContent = ` + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + ` + document.head.appendChild(style) + + content.appendChild(spinner) + content.appendChild(text) + globalLoadingElement.appendChild(content) + document.body.appendChild(globalLoadingElement) +} + +/** + * 显示全局 Loading + */ +function showGlobalLoading(options: LoadingOptions = {}) { + createGlobalLoadingElement() + + if (globalLoadingElement) { + const textElement = globalLoadingElement.querySelector('#global-loading-text') as HTMLElement + if (textElement) { + textElement.textContent = options.text || '加载中...' + } + + // 更新样式 + if (options.color) { + const spinner = globalLoadingElement.querySelector('div > div') as HTMLElement + if (spinner) { + spinner.style.borderTopColor = options.color + } + } + + if (options.backgroundColor) { + globalLoadingElement.style.background = options.backgroundColor + } + + if (options.zIndex) { + globalLoadingElement.style.zIndex = options.zIndex.toString() + } + + // 显示 + globalLoadingElement.style.opacity = '1' + globalLoadingElement.style.visibility = 'visible' + } +} + +/** + * 隐藏全局 Loading + */ +function hideGlobalLoading() { + if (globalLoadingElement) { + globalLoadingElement.style.opacity = '0' + globalLoadingElement.style.visibility = 'hidden' + } +} + +/** + * 更新全局 Loading 文本 + */ +function updateGlobalLoadingText(text: string) { + if (globalLoadingElement) { + const textElement = globalLoadingElement.querySelector('#global-loading-text') as HTMLElement + if (textElement) { + textElement.textContent = text + } + } +} + +/** + * 销毁全局 Loading + */ +function destroyGlobalLoading() { + if (globalLoadingElement) { + document.body.removeChild(globalLoadingElement) + globalLoadingElement = null + } +} + +/** + * 组件内使用的 Loading Hook + */ +export function useLoading(initialLoading = false): UseLoadingReturn { + const loading = ref(initialLoading) + const loadingText = ref('加载中...') + + const showLoading = (text = '加载中...') => { + loadingText.value = text + loading.value = true + } + + const hideLoading = () => { + loading.value = false + } + + const updateLoadingText = (text: string) => { + loadingText.value = text + } + + return { + loading, + loadingText, + showLoading, + hideLoading, + updateLoadingText + } +} + +/** + * 全局 Loading 方法 + */ +export const Loading = { + /** + * 显示全局 Loading + */ + show: (options?: LoadingOptions) => { + showGlobalLoading(options) + }, + + /** + * 隐藏全局 Loading + */ + hide: () => { + hideGlobalLoading() + }, + + /** + * 更新 Loading 文本 + */ + updateText: (text: string) => { + updateGlobalLoadingText(text) + }, + + /** + * 销毁全局 Loading + */ + destroy: () => { + destroyGlobalLoading() + }, + + /** + * 异步操作包装器 + */ + async wrap( + asyncFn: () => Promise, + options?: LoadingWrapOptions + ): Promise { + try { + Loading.show(options) + const result = await asyncFn() + return result + } catch (error) { + if (options?.onError) { + options.onError(error) + } else { + console.error('Loading.wrap error:', error) + } + throw error + } finally { + Loading.hide() + if (options?.finallyCallback) { + options.finallyCallback() + } + } + } +} + +export default Loading diff --git a/src/types/loading.ts b/src/types/loading.ts new file mode 100644 index 0000000..e3e0162 --- /dev/null +++ b/src/types/loading.ts @@ -0,0 +1,77 @@ +/** + * Loading 组件相关类型定义 + */ + +export interface LoadingOptions { + /** 加载文本 */ + text?: string + /** 尺寸:small | medium | large */ + size?: 'small' | 'medium' | 'large' + /** 自定义颜色 */ + color?: string + /** 文本颜色 */ + textColor?: string + /** 背景颜色(遮罩模式) */ + backgroundColor?: string + /** 透明度(遮罩模式) */ + opacity?: number + /** z-index */ + zIndex?: number +} + +export interface LoadingWrapOptions extends LoadingOptions { + /** 错误处理回调 */ + onError?: (error: any) => void + /** 最终回调(无论成功失败都会执行) */ + finallyCallback?: () => void +} + +export interface LoadingInstance { + /** 显示 Loading */ + show: (options?: LoadingOptions) => void + /** 隐藏 Loading */ + hide: () => void + /** 更新文本 */ + updateText: (text: string) => void + /** 销毁实例 */ + destroy: () => void +} + +export interface UseLoadingReturn { + /** 加载状态 */ + loading: Ref + /** 加载文本 */ + loadingText: Ref + /** 显示加载 */ + showLoading: (text?: string) => void + /** 隐藏加载 */ + hideLoading: () => void + /** 更新加载文本 */ + updateLoadingText: (text: string) => void +} + +export interface LoadingProps { + /** 是否显示加载状态 */ + loading?: boolean + /** 加载文本 */ + text?: string + /** 是否显示遮罩层 */ + overlay?: boolean + /** 尺寸:small | medium | large */ + size?: 'small' | 'medium' | 'large' + /** 自定义颜色 */ + color?: string + /** 文本颜色 */ + textColor?: string + /** 背景颜色(遮罩模式) */ + backgroundColor?: string + /** 透明度(遮罩模式) */ + opacity?: number + /** 自定义类名 */ + customClass?: string + /** z-index */ + zIndex?: number +} + +// 导入 Vue 的 Ref 类型 +import type { Ref } from 'vue' diff --git a/src/views/CourseExchanged.vue b/src/views/CourseExchanged.vue index 6c134d5..332013e 100644 --- a/src/views/CourseExchanged.vue +++ b/src/views/CourseExchanged.vue @@ -355,14 +355,14 @@ -
+
评论 (1251)
- 用户头像 +