feat: 添加Loading效果
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, provide, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { loadingKey, loadingState } from '@/components/Loading'
|
||||||
import PlayMusic from '@/components/PlayMusic/index.vue'
|
import PlayMusic from '@/components/PlayMusic/index.vue'
|
||||||
import useStore from '@/store'
|
import useStore from '@/store'
|
||||||
import { themeChange } from '@/utils'
|
import { themeChange } from '@/utils'
|
||||||
|
|
||||||
|
provide(loadingKey, loadingState)
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const globalConfig = useStore().globalConfig
|
const globalConfig = useStore().globalConfig
|
||||||
const prizeConfig = useStore().prizeConfig
|
const prizeConfig = useStore().prizeConfig
|
||||||
@@ -14,7 +16,6 @@ const { getTheme: localTheme } = storeToRefs(globalConfig)
|
|||||||
const { getPrizeConfig: prizeList } = storeToRefs(prizeConfig)
|
const { getPrizeConfig: prizeList } = storeToRefs(prizeConfig)
|
||||||
|
|
||||||
const tipDialog = ref()
|
const tipDialog = ref()
|
||||||
|
|
||||||
// 设置当前奖列表
|
// 设置当前奖列表
|
||||||
function setCurrentPrize() {
|
function setCurrentPrize() {
|
||||||
if (prizeList.value.length <= 0) {
|
if (prizeList.value.length <= 0) {
|
||||||
|
|||||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -13,6 +13,7 @@ declare module 'vue' {
|
|||||||
EditSeparateDialog: typeof import('./components/NumberSeparate/EditSeparateDialog.vue')['default']
|
EditSeparateDialog: typeof import('./components/NumberSeparate/EditSeparateDialog.vue')['default']
|
||||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||||
ImageSync: typeof import('./components/ImageSync/index.vue')['default']
|
ImageSync: typeof import('./components/ImageSync/index.vue')['default']
|
||||||
|
Loading: typeof import('./components/Loading/index.vue')['default']
|
||||||
PlayMusic: typeof import('./components/PlayMusic/index.vue')['default']
|
PlayMusic: typeof import('./components/PlayMusic/index.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|||||||
5
src/components/Loading/index.ts
Normal file
5
src/components/Loading/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { LoadingOptions } from './loading-context'
|
||||||
|
import Loading from './index.vue'
|
||||||
|
import { loadingKey, loadingState } from './loading-context'
|
||||||
|
|
||||||
|
export { Loading, loadingKey, LoadingOptions, loadingState }
|
||||||
22
src/components/Loading/index.vue
Normal file
22
src/components/Loading/index.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang='ts'>
|
||||||
|
import type { LoadingOptions } from './loading-context'
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import { loadingKey } from './loading-context'
|
||||||
|
|
||||||
|
// 注入全局状态
|
||||||
|
const loading = inject(loadingKey) as LoadingOptions
|
||||||
|
|
||||||
|
// 解构状态(响应式)
|
||||||
|
const { visible, text } = loading
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="visible" class="fixed top-0 left-0 w-screen h-screen bg-[rgba(0,0,0,0.5)] flex flex-col gap-6 justify-center items-center z-50">
|
||||||
|
<span v-if="visible" class="loading loading-spinner loading-xl" />
|
||||||
|
<span>{{ text ? text : '加载中' }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
61
src/components/Loading/loading-context.ts
Normal file
61
src/components/Loading/loading-context.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// src/contexts/loading-context.ts
|
||||||
|
import type { InjectionKey, Ref } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// 定义 Loading 配置类型
|
||||||
|
export interface LoadingOptions {
|
||||||
|
visible: Ref<boolean>
|
||||||
|
text: Ref<string>
|
||||||
|
fullscreen: Ref<boolean>
|
||||||
|
zIndex: Ref<number>
|
||||||
|
count: Ref<number>
|
||||||
|
show: (options?: Partial<{ text: string, fullscreen: boolean, zIndex: number }>) => void
|
||||||
|
hide: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注入密钥(Symbol 确保唯一性)
|
||||||
|
export const loadingKey: InjectionKey<LoadingOptions> = Symbol('loading')
|
||||||
|
|
||||||
|
// 全局状态(单例)
|
||||||
|
const visible = ref(false)
|
||||||
|
const text = ref('')
|
||||||
|
const fullscreen = ref(true)
|
||||||
|
const zIndex = ref(9999)
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
// 显示 Loading
|
||||||
|
function show(options?: Partial<{ text: string, fullscreen: boolean, zIndex: number }>) {
|
||||||
|
count.value++
|
||||||
|
if (count.value > 1)
|
||||||
|
return
|
||||||
|
visible.value = true
|
||||||
|
if (options) {
|
||||||
|
text.value = options.text || ''
|
||||||
|
fullscreen.value = options.fullscreen ?? true
|
||||||
|
zIndex.value = options.zIndex || 9999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏 Loading
|
||||||
|
function hide() {
|
||||||
|
if (count.value <= 0)
|
||||||
|
return
|
||||||
|
count.value--
|
||||||
|
if (count.value === 0) {
|
||||||
|
visible.value = false
|
||||||
|
text.value = ''
|
||||||
|
fullscreen.value = true
|
||||||
|
zIndex.value = 9999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出全局状态(供根组件提供)
|
||||||
|
export const loadingState: LoadingOptions = {
|
||||||
|
visible,
|
||||||
|
text,
|
||||||
|
fullscreen,
|
||||||
|
zIndex,
|
||||||
|
count,
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ToTop from '@/components/ToTop/index.vue'
|
|
||||||
import { useScroll } from '@vueuse/core'
|
import { useScroll } from '@vueuse/core'
|
||||||
// import Header from './Header/index.vue';
|
// import Header from './Header/index.vue';
|
||||||
// import Footer from './Footer/index.vue';
|
// import Footer from './Footer/index.vue';
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { Loading } from '@/components/Loading'
|
||||||
|
import ToTop from '@/components/ToTop/index.vue'
|
||||||
|
|
||||||
const mainContainer = ref<HTMLElement | null>(null)
|
const mainContainer = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
const { y } = useScroll(mainContainer)
|
const { y } = useScroll(mainContainer)
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
@@ -19,6 +19,7 @@ function scrollToTop() {
|
|||||||
<!-- <header class="shadow-2xl head-container h-14">
|
<!-- <header class="shadow-2xl head-container h-14">
|
||||||
<Header></Header>
|
<Header></Header>
|
||||||
</header> -->
|
</header> -->
|
||||||
|
<Loading />
|
||||||
<ToTop v-if="y > 400" @click="scrollToTop" />
|
<ToTop v-if="y > 400" @click="scrollToTop" />
|
||||||
<main ref="mainContainer" class="box-content w-screen h-screen overflow-x-hidden overflow-y-auto main-container">
|
<main ref="mainContainer" class="box-content w-screen h-screen overflow-x-hidden overflow-y-auto main-container">
|
||||||
<router-view class="h-full main-container-content" />
|
<router-view class="h-full main-container-content" />
|
||||||
|
|||||||
16
src/main.ts
16
src/main.ts
@@ -1,20 +1,20 @@
|
|||||||
import svgIcon from '@/components/SvgIcon/index.vue'
|
// pinia
|
||||||
import i18n from '@/locales/i18n'
|
import { createPinia } from 'pinia'
|
||||||
|
// pinia持久化
|
||||||
|
import piniaPluginPersist from 'pinia-plugin-persist'
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import VueDOMPurifyHTML from 'vue-dompurify-html'
|
import VueDOMPurifyHTML from 'vue-dompurify-html'
|
||||||
|
import svgIcon from '@/components/SvgIcon/index.vue'
|
||||||
|
import i18n from '@/locales/i18n'
|
||||||
|
// svg全局组件// 路由
|
||||||
|
import router from '@/router'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import './style/markdown.css'
|
import './style/markdown.css'
|
||||||
import './style/style.scss'
|
import './style/style.scss'
|
||||||
// 全局svg组件
|
// 全局svg组件
|
||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
// svg全局组件// 路由
|
|
||||||
import router from '@/router'
|
|
||||||
// pinia
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
// pinia持久化
|
|
||||||
import piniaPluginPersist from 'pinia-plugin-persist'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ globalThis.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
globalThis.postMessage({
|
||||||
|
type: 'fail',
|
||||||
|
data: null,
|
||||||
|
message: '读取失败',
|
||||||
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
|
|||||||
import CustomDialog from '@/components/Dialog/index.vue'
|
import CustomDialog from '@/components/Dialog/index.vue'
|
||||||
import { useViewModel } from './useViewModel'
|
import { useViewModel } from './useViewModel'
|
||||||
|
|
||||||
const { resetData, deleteAll, handleFileChange, exportData, alreadyPersonList, allPersonList, tableColumns } = useViewModel()
|
|
||||||
const { t } = useI18n()
|
|
||||||
const limitType = '.xlsx,.xls'
|
|
||||||
|
|
||||||
const resetDataDialogRef = ref()
|
const resetDataDialogRef = ref()
|
||||||
const delAllDataDialogRef = ref()
|
const delAllDataDialogRef = ref()
|
||||||
|
const exportInputFileRef = ref()
|
||||||
|
const { resetData, deleteAll, handleFileChange, exportData, alreadyPersonList, allPersonList, tableColumns } = useViewModel({ exportInputFileRef })
|
||||||
|
const { t } = useI18n()
|
||||||
|
const limitType = '.xlsx,.xls'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -45,8 +45,8 @@ const delAllDataDialogRef = ref()
|
|||||||
|
|
||||||
<div class="tooltip tooltip-bottom" :data-tip="t('tooltip.uploadExcelTip')">
|
<div class="tooltip tooltip-bottom" :data-tip="t('tooltip.uploadExcelTip')">
|
||||||
<input
|
<input
|
||||||
id="explore" type="file" class="" style="display: none" :accept="limitType"
|
id="explore" ref="exportInputFileRef" type="file" class="" style="display: none"
|
||||||
@change="handleFileChange"
|
:accept="limitType" @change="handleFileChange"
|
||||||
>
|
>
|
||||||
|
|
||||||
<span class="btn btn-primary btn-sm">{{ t('button.importData') }}</span>
|
<span class="btn btn-primary btn-sm">{{ t('button.importData') }}</span>
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
import type { IPersonConfig } from '@/types/storeType'
|
import type { IPersonConfig } from '@/types/storeType'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { inject } from 'vue'
|
||||||
import * as XLSX from 'xlsx'
|
import * as XLSX from 'xlsx'
|
||||||
|
import { loadingKey } from '@/components/Loading'
|
||||||
import i18n from '@/locales/i18n'
|
import i18n from '@/locales/i18n'
|
||||||
import useStore from '@/store'
|
import useStore from '@/store'
|
||||||
import { readFileBinary } from '@/utils/file'
|
import { readFileBinary } from '@/utils/file'
|
||||||
import ImportExcelWorker from './importExcel.worker?worker'
|
import ImportExcelWorker from './importExcel.worker?worker'
|
||||||
|
|
||||||
export function useViewModel() {
|
export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref<HTMLInputElement> }) {
|
||||||
const worker: Worker | null = new ImportExcelWorker()
|
const worker: Worker | null = new ImportExcelWorker()
|
||||||
|
const loading = inject(loadingKey)
|
||||||
const personConfig = useStore().personConfig
|
const personConfig = useStore().personConfig
|
||||||
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
|
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
@@ -70,6 +74,7 @@ export function useViewModel() {
|
|||||||
}
|
}
|
||||||
/// 开始导入
|
/// 开始导入
|
||||||
function startWorker(data: Event) {
|
function startWorker(data: Event) {
|
||||||
|
loading?.show()
|
||||||
sendWorkerMessage({ type: 'start', data })
|
sendWorkerMessage({ type: 'start', data })
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -82,12 +87,21 @@ export function useViewModel() {
|
|||||||
if (e.data.type === 'done') {
|
if (e.data.type === 'done') {
|
||||||
personConfig.resetPerson()
|
personConfig.resetPerson()
|
||||||
personConfig.addNotPersonList(e.data.data)
|
personConfig.addNotPersonList(e.data.data)
|
||||||
|
// 导入成功后清空file input
|
||||||
|
clearFileInput()
|
||||||
}
|
}
|
||||||
|
loading?.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
|
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
|
||||||
startWorker(dataBinary)
|
startWorker(dataBinary)
|
||||||
}
|
}
|
||||||
|
// 清空file input
|
||||||
|
function clearFileInput() {
|
||||||
|
if (exportInputFileRef.value) {
|
||||||
|
exportInputFileRef.value.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
/// 导出数据
|
/// 导出数据
|
||||||
function exportData() {
|
function exportData() {
|
||||||
let data = JSON.parse(JSON.stringify(allPersonList.value))
|
let data = JSON.parse(JSON.stringify(allPersonList.value))
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -9,3 +9,4 @@ declare module '*.vue' {
|
|||||||
|
|
||||||
declare module 'sparticles'
|
declare module 'sparticles'
|
||||||
declare module 'three-trackballcontrols'
|
declare module 'three-trackballcontrols'
|
||||||
|
declare module 'virtual:svg-icons-register'
|
||||||
|
|||||||
Reference in New Issue
Block a user