From b1077b23e65214924c2093d6cfe7799b1753c9cd Mon Sep 17 00:00:00 2001 From: LOG1997 <2694233102@qq.com> Date: Sun, 21 Sep 2025 14:38:57 +0800 Subject: [PATCH 01/16] =?UTF-8?q?refactor:=20=E9=A6=96=E9=A1=B5=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E6=A0=B7=E5=BC=8F=E8=87=B3=E5=8D=95=E7=8B=AC=E7=9A=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Home/index.scss | 341 +++++++++++++++++++++++++++++++++++ src/views/Home/index.vue | 368 ++------------------------------------ 2 files changed, 357 insertions(+), 352 deletions(-) create mode 100644 src/views/Home/index.scss diff --git a/src/views/Home/index.scss b/src/views/Home/index.scss new file mode 100644 index 0000000..b882304 --- /dev/null +++ b/src/views/Home/index.scss @@ -0,0 +1,341 @@ +#menu { + position: absolute; + z-index: 100; + width: 100%; + bottom: 50px; + text-align: center; + margin: 0 auto; + font-size: 32px; +} + +.header-title { + -webkit-animation: tracking-in-expand-fwd 0.8s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; + animation: tracking-in-expand-fwd 0.8s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; +} + +.start { + // 居中 + display: flex; + justify-content: center; +} + +.btn-start { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + width: 13rem; + overflow: hidden; + height: 3rem; + background-size: 300% 300%; + backdrop-filter: blur(1rem); + border-radius: 5rem; + transition: 0.5s; + animation: gradient_301 5s ease infinite; + border: double 4px transparent; + background-image: linear-gradient(#212121, #212121), linear-gradient(137.48deg, #ffdb3b 10%, #FE53BB 45%, #8F51EA 67%, #0044ff 87%); + background-origin: border-box; + background-clip: content-box, border-box; + -webkit-animation: pulsate-fwd 1.2s ease-in-out infinite both; + animation: pulsate-fwd 1.2s ease-in-out infinite both; +} + +.btn-cancel { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + width: 13rem; + overflow: hidden; + height: 3rem; + background-size: 300% 300%; + backdrop-filter: blur(1rem); + border-radius: 5rem; + transition: 0.5s; + animation: gradient_301 5s ease infinite; + border: double 4px transparent; + background-image: linear-gradient(#212121, #212121), linear-gradient(137.48deg, #ffdb3b 10%, #FE53BB 45%, #8F51EA 67%, #0044ff 87%); + background-origin: border-box; + background-clip: content-box, border-box; +} + +#container-stars { + position: absolute; + z-index: -1; + width: 100%; + height: 100%; + overflow: hidden; + transition: 0.5s; + backdrop-filter: blur(1rem); + border-radius: 5rem; +} + +strong { + z-index: 2; + font-family: 'Avalors Personal Use'; + font-size: 12px; + letter-spacing: 5px; + color: #FFFFFF; + text-shadow: 0 0 4px white; +} + +#glow { + position: absolute; + display: flex; + width: 12rem; +} + +.circle { + width: 100%; + height: 30px; + filter: blur(2rem); + animation: pulse_3011 4s infinite; + z-index: -1; +} + +.circle:nth-of-type(1) { + background: rgba(254, 83, 186, 0.636); +} + +.circle:nth-of-type(2) { + background: rgba(142, 81, 234, 0.704); +} + +.btn-start:hover #container-stars { + z-index: 1; + background-color: #212121; +} + +.btn-start:hover { + transform: scale(1.1) +} + +.btn-start:active { + border: double 4px #FE53BB; + background-origin: border-box; + background-clip: content-box, border-box; + animation: none; +} + +.btn-start:active .circle { + background: #FE53BB; +} + +#stars { + position: relative; + background: transparent; + width: 200rem; + height: 200rem; +} + +#stars::after { + content: ""; + position: absolute; + top: -10rem; + left: -100rem; + width: 100%; + height: 100%; + animation: animStarRotate 90s linear infinite; +} + +#stars::after { + background-image: radial-gradient(#ffffff 1px, transparent 1%); + background-size: 50px 50px; +} + +#stars::before { + content: ""; + position: absolute; + top: 0; + left: -50%; + width: 170%; + height: 500%; + animation: animStar 60s linear infinite; +} + +#stars::before { + background-image: radial-gradient(#ffffff 1px, transparent 1%); + background-size: 50px 50px; + opacity: 0.5; +} + +@keyframes animStar { + from { + transform: translateY(0); + } + + to { + transform: translateY(-135rem); + } +} + +@keyframes animStarRotate { + from { + transform: rotate(360deg); + } + + to { + transform: rotate(0); + } +} + +@keyframes gradient_301 { + 0% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } + + 100% { + background-position: 0% 50%; + } +} + +@keyframes pulse_3011 { + 0% { + transform: scale(0.75); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); + } + + 100% { + transform: scale(0.75); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + } +} + +.btn-end { + -webkit-animation: pulsate-fwd 0.9s ease-in-out infinite both; + animation: pulsate-fwd 0.9s ease-in-out infinite both; + cursor: pointer; +} + +.btn-end { + --glow-color: rgb(217, 176, 255); + --glow-spread-color: rgba(191, 123, 255, 0.781); + --enhanced-glow-color: rgb(231, 206, 255); + --btn-color: rgb(100, 61, 136); + border: .25em solid var(--glow-color); + padding: 1em 3em; + color: var(--glow-color); + font-size: 15px; + font-weight: bold; + background-color: var(--btn-color); + border-radius: 1em; + outline: none; + box-shadow: 0 0 1em .25em var(--glow-color), + 0 0 4em 1em var(--glow-spread-color), + inset 0 0 .75em .25em var(--glow-color); + text-shadow: 0 0 .5em var(--glow-color); + position: relative; + transition: all 0.3s; + -webkit-animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both; + animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both; +} + +.btn-end::after { + pointer-events: none; + content: ""; + position: absolute; + top: 120%; + left: 0; + height: 100%; + width: 100%; + background-color: var(--glow-spread-color); + filter: blur(2em); + opacity: .7; + transform: perspective(1.5em) rotateX(35deg) scale(1, .6); +} + +.btn-end:hover { + color: var(--btn-color); + background-color: var(--glow-color); + box-shadow: 0 0 1em .25em var(--glow-color), + 0 0 4em 2em var(--glow-spread-color), + inset 0 0 .75em .25em var(--glow-color); +} + +.btn-end:active { + box-shadow: 0 0 0.6em .25em var(--glow-color), + 0 0 2.5em 2em var(--glow-spread-color), + inset 0 0 .5em .25em var(--glow-color); +} + +// 按钮动画 +@-webkit-keyframes pulsate-fwd { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes pulsate-fwd { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.2); + transform: scale(1.2); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@-webkit-keyframes tracking-in-expand-fwd { + 0% { + letter-spacing: -0.5em; + -webkit-transform: translateZ(-700px); + transform: translateZ(-700px); + opacity: 0; + } + + 40% { + opacity: 0.6; + } + + 100% { + -webkit-transform: translateZ(0); + transform: translateZ(0); + opacity: 1; + } +} + +@keyframes tracking-in-expand-fwd { + 0% { + letter-spacing: -0.5em; + -webkit-transform: translateZ(-700px); + transform: translateZ(-700px); + opacity: 0; + } + + 40% { + opacity: 0.6; + } + + 100% { + -webkit-transform: translateZ(0); + transform: translateZ(0); + opacity: 1; + } +} \ No newline at end of file diff --git a/src/views/Home/index.vue b/src/views/Home/index.vue index 9351c44..584d9f0 100644 --- a/src/views/Home/index.vue +++ b/src/views/Home/index.vue @@ -119,29 +119,33 @@ function init() { const number = document.createElement('div') number.className = 'card-id' number.textContent = tableData.value[i].uid - if(isShowAvatar.value) number.style.display = 'none' + if (isShowAvatar.value) + number.style.display = 'none' element.appendChild(number) const symbol = document.createElement('div') symbol.className = 'card-name' symbol.textContent = tableData.value[i].name - if(isShowAvatar.value) symbol.className = 'card-name card-avatar-name' + if (isShowAvatar.value) + symbol.className = 'card-name card-avatar-name' element.appendChild(symbol) const detail = document.createElement('div') detail.className = 'card-detail' detail.innerHTML = `${tableData.value[i].department}
${tableData.value[i].identity}` - if(isShowAvatar.value) detail.style.display = 'none' + if (isShowAvatar.value) + detail.style.display = 'none' element.appendChild(detail) - const avatar = document.createElement('img'); - avatar.className = 'card-avatar'; - avatar.src = tableData.value[i].avatar; - avatar.alt = 'avatar'; - avatar.style.width = '140px'; - avatar.style.height = '140px'; - if(!isShowAvatar.value) avatar.style.display = 'none' - element.appendChild(avatar); + const avatar = document.createElement('img') + avatar.className = 'card-avatar' + avatar.src = tableData.value[i].avatar + avatar.alt = 'avatar' + avatar.style.width = '140px' + avatar.style.height = '140px' + if (!isShowAvatar.value) + avatar.style.display = 'none' + element.appendChild(avatar) element = useElementStyle(element, tableData.value[i], i, patternList.value, patternColor.value, cardColor.value, cardSize.value, textSize.value) const object = new CSS3DObject(element) @@ -783,345 +787,5 @@ onUnmounted(() => { From 88e773da37f734ea3ee609c93f611cf384711e60 Mon Sep 17 00:00:00 2001 From: LOG1997 <2694233102@qq.com> Date: Sun, 21 Sep 2025 20:24:53 +0800 Subject: [PATCH 02/16] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=8F=90=E5=8F=96=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 3 + src/utils/index.ts | 78 ++++--- .../Home/components/PrizeList/index.scss | 138 ++++++++++++ .../PrizeList/index.vue} | 151 +------------ src/views/Home/index.ts | 131 +++++++++++ src/views/Home/index.vue | 208 ++++-------------- 6 files changed, 362 insertions(+), 347 deletions(-) create mode 100644 src/views/Home/components/PrizeList/index.scss rename src/views/Home/{PrizeList.vue => components/PrizeList/index.vue} (80%) create mode 100644 src/views/Home/index.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9cda78..7aa8dd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: localforage: specifier: ^1.10.0 version: 1.10.0 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 markdown-it: specifier: ^14.1.0 version: 14.1.0 diff --git a/src/utils/index.ts b/src/utils/index.ts index 0e714f0..046d246 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,50 +1,56 @@ import dayjs from 'dayjs' -// 筛选人员数据 -export function filterData(tableData: any[], localRowCount: number) { - const dataLength = tableData.length - let j = 0 - for (let i = 0; i < dataLength; i++) { - if (i % localRowCount === 0) { - j++ - } - tableData[i].x = i % localRowCount + 1 - tableData[i].y = j - tableData[i].id = i - // 是否中奖 - } - return tableData +/** + * @description: 处理表格数据,添加x,y,id等信息 + * @param tableData 表格数据 + * @param localRowCount 每一行有多少个元素 + * @returns 处理后的表格数据 + */ +export function filterData(tableData: any[], localRowCount: number) { + const dataLength = tableData.length + let j = 0 + for (let i = 0; i < dataLength; i++) { + if (i % localRowCount === 0) { + j++ + } + tableData[i].x = i % localRowCount + 1 + tableData[i].y = j + tableData[i].id = i + // 是否中奖 + } + + return tableData } export function addOtherInfo(personList: any[]) { - const len = personList.length - for (let i = 0; i < len; i++) { - personList[i].id = i - personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') - personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') - personList[i].prizeName = [] as string[] - personList[i].prizeTime = [] as string[] - personList[i].prizeId = [] - personList[i].isWin = false - } + const len = personList.length + for (let i = 0; i < len; i++) { + personList[i].id = i + personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') + personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') + personList[i].prizeName = [] as string[] + personList[i].prizeTime = [] as string[] + personList[i].prizeId = [] + personList[i].isWin = false + } - return personList + return personList } export function selectCard(cardIndexArr: number[], tableLength: number, personId: number): number { - const cardIndex = Math.floor(Math.random() * (tableLength - 1)) - if (cardIndexArr.includes(cardIndex)) { - return selectCard(cardIndexArr, tableLength, personId) - } + const cardIndex = Math.floor(Math.random() * (tableLength - 1)) + if (cardIndexArr.includes(cardIndex)) { + return selectCard(cardIndexArr, tableLength, personId) + } - return cardIndex + return cardIndex } export function themeChange(theme: string) { - // 获取根html - const html = document.querySelectorAll('html') - if (html) { - html[0].setAttribute('data-theme', theme) - localStorage.setItem('theme', theme) - } + // 获取根html + const html = document.querySelectorAll('html') + if (html) { + html[0].setAttribute('data-theme', theme) + localStorage.setItem('theme', theme) + } } diff --git a/src/views/Home/components/PrizeList/index.scss b/src/views/Home/components/PrizeList/index.scss new file mode 100644 index 0000000..1d63d31 --- /dev/null +++ b/src/views/Home/components/PrizeList/index.scss @@ -0,0 +1,138 @@ +.label { + width: 120px; +} + +.prize-list-enter-active { + -webkit-animation: slide-right 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; + animation: slide-right 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; +} + +.prize-list-leave-active { + -webkit-animation: slide-left 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; + animation: slide-left 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; +} + +.prize-operate-enter-active { + // 延时显示 + animation: show-operate 0.6s; + -webkit-animation: show-operate 0.6s; +} + +.current-prize { + position: relative; + display: block; + overflow: hidden; + isolation: isolate; + + border-radius: 20px; + padding: 3px; +} + +.current-prize::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 400%; + height: 100%; + background: linear-gradient(115deg, #4fcf70, #fad648, #a767e5, #12bcfe, #44ce7b); + background-size: 25% 100%; + animation: an-at-keyframe-css-at-rule-that-translates-via-the-transform-property-the-background-by-negative-25-percent-of-its-width-so-that-it-gives-a-nice-border-animation_-We-use-the-translate-property-to-have-a-nice-transition-so-it_s-not-a-jerk-of-a-start-or-stop .75s linear infinite; + // animation-play-state: paused; + translate: -5% 0%; + transition: translate 0.25s ease-out; + animation-play-state: running; + transition-duration: 0.75s; + translate: 0% 0%; +} + +.current-prize::after { + content: ""; + position: absolute; + inset: 4px; + border-top-left-radius: 20px; + border-bottom-right-radius: 20px; + z-index: -1; +} + +@keyframes an-at-keyframe-css-at-rule-that-translates-via-the-transform-property-the-background-by-negative-25-percent-of-its-width-so-that-it-gives-a-nice-border-animation_-We-use-the-translate-property-to-have-a-nice-transition-so-it_s-not-a-jerk-of-a-start-or-stop { + to { + transform: translateX(-25%); + } +} + +@-webkit-keyframes slide-right { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + -webkit-transform: translateX(30px); + transform: translateX(30px); + } +} + +@keyframes slide-right { + 0% { + -webkit-transform: translateX(-200px); + transform: translateX(-200px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@-webkit-keyframes slide-left { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + -webkit-transform: translateX(-100px); + transform: translateX(-100px); + } +} + +@keyframes slide-left { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + -webkit-transform: translateX(-400px); + transform: translateX(-400px); + } +} + +@-webkit-keyframes show-operate { + 0% { + opacity: 0; + } + + 99% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes show-operate { + 0% { + opacity: 0; + } + + 99% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/views/Home/PrizeList.vue b/src/views/Home/components/PrizeList/index.vue similarity index 80% rename from src/views/Home/PrizeList.vue rename to src/views/Home/components/PrizeList/index.vue index ea9cfcd..ac745aa 100644 --- a/src/views/Home/PrizeList.vue +++ b/src/views/Home/components/PrizeList/index.vue @@ -1,17 +1,17 @@ diff --git a/src/views/Home/components/StarsBackground/index.vue b/src/views/Home/components/StarsBackground/index.vue new file mode 100644 index 0000000..3da65bc --- /dev/null +++ b/src/views/Home/components/StarsBackground/index.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/views/Home/index.scss b/src/views/Home/index.scss deleted file mode 100644 index b882304..0000000 --- a/src/views/Home/index.scss +++ /dev/null @@ -1,341 +0,0 @@ -#menu { - position: absolute; - z-index: 100; - width: 100%; - bottom: 50px; - text-align: center; - margin: 0 auto; - font-size: 32px; -} - -.header-title { - -webkit-animation: tracking-in-expand-fwd 0.8s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; - animation: tracking-in-expand-fwd 0.8s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; -} - -.start { - // 居中 - display: flex; - justify-content: center; -} - -.btn-start { - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - width: 13rem; - overflow: hidden; - height: 3rem; - background-size: 300% 300%; - backdrop-filter: blur(1rem); - border-radius: 5rem; - transition: 0.5s; - animation: gradient_301 5s ease infinite; - border: double 4px transparent; - background-image: linear-gradient(#212121, #212121), linear-gradient(137.48deg, #ffdb3b 10%, #FE53BB 45%, #8F51EA 67%, #0044ff 87%); - background-origin: border-box; - background-clip: content-box, border-box; - -webkit-animation: pulsate-fwd 1.2s ease-in-out infinite both; - animation: pulsate-fwd 1.2s ease-in-out infinite both; -} - -.btn-cancel { - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - width: 13rem; - overflow: hidden; - height: 3rem; - background-size: 300% 300%; - backdrop-filter: blur(1rem); - border-radius: 5rem; - transition: 0.5s; - animation: gradient_301 5s ease infinite; - border: double 4px transparent; - background-image: linear-gradient(#212121, #212121), linear-gradient(137.48deg, #ffdb3b 10%, #FE53BB 45%, #8F51EA 67%, #0044ff 87%); - background-origin: border-box; - background-clip: content-box, border-box; -} - -#container-stars { - position: absolute; - z-index: -1; - width: 100%; - height: 100%; - overflow: hidden; - transition: 0.5s; - backdrop-filter: blur(1rem); - border-radius: 5rem; -} - -strong { - z-index: 2; - font-family: 'Avalors Personal Use'; - font-size: 12px; - letter-spacing: 5px; - color: #FFFFFF; - text-shadow: 0 0 4px white; -} - -#glow { - position: absolute; - display: flex; - width: 12rem; -} - -.circle { - width: 100%; - height: 30px; - filter: blur(2rem); - animation: pulse_3011 4s infinite; - z-index: -1; -} - -.circle:nth-of-type(1) { - background: rgba(254, 83, 186, 0.636); -} - -.circle:nth-of-type(2) { - background: rgba(142, 81, 234, 0.704); -} - -.btn-start:hover #container-stars { - z-index: 1; - background-color: #212121; -} - -.btn-start:hover { - transform: scale(1.1) -} - -.btn-start:active { - border: double 4px #FE53BB; - background-origin: border-box; - background-clip: content-box, border-box; - animation: none; -} - -.btn-start:active .circle { - background: #FE53BB; -} - -#stars { - position: relative; - background: transparent; - width: 200rem; - height: 200rem; -} - -#stars::after { - content: ""; - position: absolute; - top: -10rem; - left: -100rem; - width: 100%; - height: 100%; - animation: animStarRotate 90s linear infinite; -} - -#stars::after { - background-image: radial-gradient(#ffffff 1px, transparent 1%); - background-size: 50px 50px; -} - -#stars::before { - content: ""; - position: absolute; - top: 0; - left: -50%; - width: 170%; - height: 500%; - animation: animStar 60s linear infinite; -} - -#stars::before { - background-image: radial-gradient(#ffffff 1px, transparent 1%); - background-size: 50px 50px; - opacity: 0.5; -} - -@keyframes animStar { - from { - transform: translateY(0); - } - - to { - transform: translateY(-135rem); - } -} - -@keyframes animStarRotate { - from { - transform: rotate(360deg); - } - - to { - transform: rotate(0); - } -} - -@keyframes gradient_301 { - 0% { - background-position: 0% 50%; - } - - 50% { - background-position: 100% 50%; - } - - 100% { - background-position: 0% 50%; - } -} - -@keyframes pulse_3011 { - 0% { - transform: scale(0.75); - box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); - } - - 70% { - transform: scale(1); - box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); - } - - 100% { - transform: scale(0.75); - box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); - } -} - -.btn-end { - -webkit-animation: pulsate-fwd 0.9s ease-in-out infinite both; - animation: pulsate-fwd 0.9s ease-in-out infinite both; - cursor: pointer; -} - -.btn-end { - --glow-color: rgb(217, 176, 255); - --glow-spread-color: rgba(191, 123, 255, 0.781); - --enhanced-glow-color: rgb(231, 206, 255); - --btn-color: rgb(100, 61, 136); - border: .25em solid var(--glow-color); - padding: 1em 3em; - color: var(--glow-color); - font-size: 15px; - font-weight: bold; - background-color: var(--btn-color); - border-radius: 1em; - outline: none; - box-shadow: 0 0 1em .25em var(--glow-color), - 0 0 4em 1em var(--glow-spread-color), - inset 0 0 .75em .25em var(--glow-color); - text-shadow: 0 0 .5em var(--glow-color); - position: relative; - transition: all 0.3s; - -webkit-animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both; - animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both; -} - -.btn-end::after { - pointer-events: none; - content: ""; - position: absolute; - top: 120%; - left: 0; - height: 100%; - width: 100%; - background-color: var(--glow-spread-color); - filter: blur(2em); - opacity: .7; - transform: perspective(1.5em) rotateX(35deg) scale(1, .6); -} - -.btn-end:hover { - color: var(--btn-color); - background-color: var(--glow-color); - box-shadow: 0 0 1em .25em var(--glow-color), - 0 0 4em 2em var(--glow-spread-color), - inset 0 0 .75em .25em var(--glow-color); -} - -.btn-end:active { - box-shadow: 0 0 0.6em .25em var(--glow-color), - 0 0 2.5em 2em var(--glow-spread-color), - inset 0 0 .5em .25em var(--glow-color); -} - -// 按钮动画 -@-webkit-keyframes pulsate-fwd { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 50% { - -webkit-transform: scale(1.1); - transform: scale(1.1); - } - - 100% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes pulsate-fwd { - 0% { - -webkit-transform: scale(1); - transform: scale(1); - } - - 50% { - -webkit-transform: scale(1.2); - transform: scale(1.2); - } - - 100% { - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@-webkit-keyframes tracking-in-expand-fwd { - 0% { - letter-spacing: -0.5em; - -webkit-transform: translateZ(-700px); - transform: translateZ(-700px); - opacity: 0; - } - - 40% { - opacity: 0.6; - } - - 100% { - -webkit-transform: translateZ(0); - transform: translateZ(0); - opacity: 1; - } -} - -@keyframes tracking-in-expand-fwd { - 0% { - letter-spacing: -0.5em; - -webkit-transform: translateZ(-700px); - transform: translateZ(-700px); - opacity: 0; - } - - 40% { - opacity: 0.6; - } - - 100% { - -webkit-transform: translateZ(0); - transform: translateZ(0); - opacity: 1; - } -} \ No newline at end of file diff --git a/src/views/Home/index.vue b/src/views/Home/index.vue index b3f9a93..70f295d 100644 --- a/src/views/Home/index.vue +++ b/src/views/Home/index.vue @@ -1,665 +1,42 @@ diff --git a/src/views/Home/type.ts b/src/views/Home/type.ts new file mode 100644 index 0000000..1a2f9c3 --- /dev/null +++ b/src/views/Home/type.ts @@ -0,0 +1,12 @@ +export enum LotteryStatus { + init = 0, + ready = 1, + running = 2, + end = 3, +} +export interface TargetType { + grid: any[] + helix: any[] + table: any[] + sphere: any[] +} diff --git a/src/views/Home/useViewModel.ts b/src/views/Home/useViewModel.ts new file mode 100644 index 0000000..cebfdf3 --- /dev/null +++ b/src/views/Home/useViewModel.ts @@ -0,0 +1,606 @@ +import type { Material, Object3D } from 'three' +import type { TargetType } from './type' +import type { IPersonConfig } from '@/types/storeType' +import * as TWEEN from '@tweenjs/tween.js' +import { storeToRefs } from 'pinia' +import { PerspectiveCamera, Scene } from 'three' +import { CSS3DObject, CSS3DRenderer } from 'three-css3d' +import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js' +import { nextTick, onMounted, onUnmounted, ref } from 'vue' +import { useToast } from 'vue-toast-notification' +import { useElementPosition, useElementStyle } from '@/hooks/useElement' +import i18n from '@/locales/i18n' +import useStore from '@/store' +import { selectCard } from '@/utils' +import { rgba } from '@/utils/color' +import { LotteryStatus } from './type' +import { confettiFire, createSphereVertices, createTableVertices, initTableData } from './util' + +export function useViewModel() { + const toast = useToast() + // store里面存储的值 + const { personConfig, globalConfig, prizeConfig } = useStore() + const { + getAllPersonList: allPersonList, + getNotPersonList: notPersonList, + getNotThisPrizePersonList: notThisPrizePersonList, + } = storeToRefs(personConfig) + const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig) + const { getCardColor: cardColor, getPatterColor: patternColor, getPatternList: patternList, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getIsShowAvatar: isShowAvatar } = storeToRefs(globalConfig) + // three初始值 + const ballRotationY = ref(0) + const containerRef = ref() + const canOperate = ref(true) + const cameraZ = ref(3000) + const scene = ref() + const camera = ref() + const renderer = ref() + const controls = ref() + const objects = ref([]) + const targets: TargetType = { + grid: [], + helix: [], + table: [], + sphere: [], + } + // 页面数据初始值 + const currentStatus = ref(LotteryStatus.init) // 0为初始状态, 1为抽奖准备状态,2为抽奖中状态,3为抽奖结束状态 + const tableData = ref([]) + const luckyTargets = ref([]) + const luckyCardList = ref([]) + const luckyCount = ref(10) + const personPool = ref([]) + const intervalTimer = ref(null) + + function init() { + const felidView = 40 + const width = window.innerWidth + const height = window.innerHeight + const aspect = width / height + const nearPlane = 1 + const farPlane = 10000 + const WebGLoutput = containerRef.value + + scene.value = new Scene() + camera.value = new PerspectiveCamera(felidView, aspect, nearPlane, farPlane) + camera.value.position.z = cameraZ.value + renderer.value = new CSS3DRenderer() + renderer.value.setSize(width, height * 0.9) + renderer.value.domElement.style.position = 'absolute' + // 垂直居中 + renderer.value.domElement.style.paddingTop = '50px' + renderer.value.domElement.style.top = '50%' + renderer.value.domElement.style.left = '50%' + renderer.value.domElement.style.transform = 'translate(-50%, -50%)' + WebGLoutput!.appendChild(renderer.value.domElement) + + controls.value = new TrackballControls(camera.value, renderer.value.domElement) + controls.value.rotateSpeed = 1 + controls.value.staticMoving = true + controls.value.minDistance = 500 + controls.value.maxDistance = 6000 + controls.value.addEventListener('change', render) + + const tableLen = tableData.value.length + for (let i = 0; i < tableLen; i++) { + let element = document.createElement('div') + element.className = 'element-card' + + const number = document.createElement('div') + number.className = 'card-id' + number.textContent = tableData.value[i].uid + if (isShowAvatar.value) + number.style.display = 'none' + element.appendChild(number) + + const symbol = document.createElement('div') + symbol.className = 'card-name' + symbol.textContent = tableData.value[i].name + if (isShowAvatar.value) + symbol.className = 'card-name card-avatar-name' + element.appendChild(symbol) + + const detail = document.createElement('div') + detail.className = 'card-detail' + detail.innerHTML = `${tableData.value[i].department}
${tableData.value[i].identity}` + if (isShowAvatar.value) + detail.style.display = 'none' + element.appendChild(detail) + + const avatar = document.createElement('img') + avatar.className = 'card-avatar' + avatar.src = tableData.value[i].avatar + avatar.alt = 'avatar' + avatar.style.width = '140px' + avatar.style.height = '140px' + if (!isShowAvatar.value) + avatar.style.display = 'none' + element.appendChild(avatar) + + element = useElementStyle(element, tableData.value[i], i, patternList.value, patternColor.value, cardColor.value, cardSize.value, textSize.value) + const object = new CSS3DObject(element) + object.position.x = Math.random() * 4000 - 2000 + object.position.y = Math.random() * 4000 - 2000 + object.position.z = Math.random() * 4000 - 2000 + scene.value.add(object) + + objects.value.push(object) + } + // 创建横铺的界面 + const tableVertices = createTableVertices({ tableData: tableData.value, rowCount: rowCount.value, cardSize: cardSize.value }) + targets.table = tableVertices + // 创建球体 + const sphereVertices = createSphereVertices({ objectsLength: objects.value.length }) + targets.sphere = sphereVertices + window.addEventListener('resize', onWindowResize, false) + transform(targets.table, 1000) + render() + } + function render() { + if (renderer.value) { + renderer.value.render(scene.value, camera.value) + } + } + /** + * @description: 位置变换 + * @param targets 目标位置 + * @param duration 持续时间 + */ + function transform(targets: any[], duration: number) { + TWEEN.removeAll() + if (intervalTimer.value) { + clearInterval(intervalTimer.value) + intervalTimer.value = null + randomBallData('sphere') + } + + return new Promise((resolve) => { + const objLength = objects.value.length + for (let i = 0; i < objLength; ++i) { + const object = objects.value[i] + const target = targets[i] + // console.log('target', i, target, targets) + new TWEEN.Tween(object.position) + .to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration) + .easing(TWEEN.Easing.Exponential.InOut) + .start() + + new TWEEN.Tween(object.rotation) + .to({ x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration) + .easing(TWEEN.Easing.Exponential.InOut) + .start() + .onComplete(() => { + if (luckyCardList.value.length) { + luckyCardList.value.forEach((cardIndex: any) => { + const item = objects.value[cardIndex] + useElementStyle(item.element, {} as any, i, patternList.value, patternColor.value, cardColor.value, cardSize.value, textSize.value, 'sphere') + }) + } + luckyTargets.value = [] + luckyCardList.value = [] + canOperate.value = true + }) + } + + // 这个补间用来在位置与旋转补间同步执行,通过onUpdate在每次更新数据后渲染scene和camera + new TWEEN.Tween({}) + .to({}, duration * 2) + .onUpdate(render) + .start() + .onComplete(() => { + canOperate.value = true + resolve('') + }) + }) + } + /** + * @description: 窗口大小改变时重新设置渲染器的大小 + */ + function onWindowResize() { + camera.value.aspect = window.innerWidth / window.innerHeight + camera.value.updateProjectionMatrix() + + renderer.value.setSize(window.innerWidth, window.innerHeight) + render() + } + + /** + * [animation update all tween && controls] + */ + function animation() { + TWEEN.update() + if (controls.value) { + controls.value.update() + } + // 设置自动旋转 + // 设置相机位置 + requestAnimationFrame(animation) + } + /** + * @description: 旋转的动画 + * @param rotateY 绕y轴旋转圈数 + * @param duration 持续时间,单位秒 + */ + function rollBall(rotateY: number, duration: number) { + TWEEN.removeAll() + + return new Promise((resolve) => { + scene.value.rotation.y = 0 + ballRotationY.value = Math.PI * rotateY * 1000 + const rotateObj = new TWEEN.Tween(scene.value.rotation) + rotateObj + .to( + { + // x: Math.PI * rotateX * 1000, + x: 0, + y: ballRotationY.value, + // z: Math.PI * rotateZ * 1000 + z: 0, + }, + duration * 1000, + ) + .onUpdate(render) + .start() + .onStop(() => { + resolve('') + }) + .onComplete(() => { + resolve('') + }) + }) + } + /** + * @description: 视野转回正面 + */ + function resetCamera() { + new TWEEN.Tween(camera.value.position) + .to( + { + x: 0, + y: 0, + z: 3000, + }, + 1000, + ) + .onUpdate(render) + .start() + .onComplete(() => { + new TWEEN.Tween(camera.value.rotation) + .to( + { + x: 0, + y: 0, + z: 0, + }, + 1000, + ) + .onUpdate(render) + .start() + .onComplete(() => { + canOperate.value = true + // camera.value.lookAt(scene.value.position) + camera.value.position.y = 0 + camera.value.position.x = 0 + camera.value.position.z = 3000 + camera.value.rotation.x = 0 + camera.value.rotation.y = 0 + camera.value.rotation.z = -0 + controls.value.reset() + }) + }) + } + + /** + * @description: 开始抽奖,由横铺变换为球体(或其他图形) + * @returns 随机抽取球数据 + */ + /// description 进入抽奖准备状态 + async function enterLottery() { + if (!canOperate.value) { + return + } + if (!intervalTimer.value) { + randomBallData() + } + if (patternList.value.length) { + for (let i = 0; i < patternList.value.length; i++) { + if (i < rowCount.value * 7) { + objects.value[patternList.value[i] - 1].element.style.backgroundColor = rgba(cardColor.value, Math.random() * 0.5 + 0.25) + } + } + } + canOperate.value = false + await transform(targets.sphere, 1000) + currentStatus.value = LotteryStatus.ready + rollBall(0.1, 2000) + } + /** + * @description 开始抽奖 + */ + function startLottery() { + if (!canOperate.value) { + return + } + // 验证是否已抽完全部奖项 + if (currentPrize.value.isUsed || !currentPrize.value) { + toast.open({ + message: i18n.global.t('error.personIsAllDone'), + type: 'warning', + position: 'top-right', + duration: 10000, + }) + + return + } + personPool.value = currentPrize.value.isAll ? notThisPrizePersonList.value : notPersonList.value + // 验证抽奖人数是否还够 + if (personPool.value.length < currentPrize.value.count - currentPrize.value.isUsedCount) { + toast.open({ + message: i18n.global.t('error.personNotEnough'), + type: 'warning', + position: 'top-right', + duration: 10000, + }) + + return + } + luckyCount.value = 10 + // 自定义抽奖个数 + + let leftover = currentPrize.value.count - currentPrize.value.isUsedCount + const customCount = currentPrize.value.separateCount + if (customCount && customCount.enable && customCount.countList.length > 0) { + for (let i = 0; i < customCount.countList.length; i++) { + if (customCount.countList[i].isUsedCount < customCount.countList[i].count) { + leftover = customCount.countList[i].count - customCount.countList[i].isUsedCount + break + } + } + } + luckyCount.value = leftover < luckyCount.value ? leftover : luckyCount.value + for (let i = 0; i < luckyCount.value; i++) { + if (personPool.value.length > 0) { + // 解决随机元素概率过于不均等问题 + const randomIndex = Math.floor(Math.random() * (personPool.value.length - 1)) + luckyTargets.value.push(personPool.value[randomIndex]) + personPool.value.splice(randomIndex, 1) + } + } + + toast.open({ + // message: `现在抽取${currentPrize.value.name} ${leftover}人`, + message: i18n.global.t('error.startDraw', { count: currentPrize.value.name, leftover }), + type: 'default', + position: 'top-right', + duration: 8000, + }) + currentStatus.value = LotteryStatus.running + rollBall(10, 3000) + } + /** + * @description: 停止抽奖,抽出幸运人 + */ + async function stopLottery() { + if (!canOperate.value) { + return + } + // clearInterval(intervalTimer.value) + // intervalTimer.value = null + canOperate.value = false + rollBall(0, 1) + + const windowSize = { width: window.innerWidth, height: window.innerHeight } + luckyTargets.value.forEach((person: IPersonConfig, index: number) => { + const cardIndex = selectCard(luckyCardList.value, tableData.value.length, person.id) + luckyCardList.value.push(cardIndex) + const totalLuckyCount = luckyTargets.value.length + const item = objects.value[cardIndex] + const { xTable, yTable } = useElementPosition(item, rowCount.value, totalLuckyCount, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, windowSize, index) + new TWEEN.Tween(item.position) + .to({ + x: xTable, + y: yTable, + z: 1000, + }, 1200) + .easing(TWEEN.Easing.Exponential.InOut) + .onStart(() => { + item.element = useElementStyle(item.element, person, cardIndex, patternList.value, patternColor.value, luckyColor.value, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, textSize.value * 2, 'lucky') + }) + .start() + .onComplete(() => { + canOperate.value = true + currentStatus.value = LotteryStatus.end + }) + new TWEEN.Tween(item.rotation) + .to({ + x: 0, + y: 0, + z: 0, + }, 900) + .easing(TWEEN.Easing.Exponential.InOut) + .start() + .onComplete(() => { + confettiFire() + resetCamera() + }) + }) + } + /** + * @description: 继续,意味着这抽奖作数,计入数据库 + */ + async function continueLottery() { + if (!canOperate.value) { + return + } + + const customCount = currentPrize.value.separateCount + if (customCount && customCount.enable && customCount.countList.length > 0) { + for (let i = 0; i < customCount.countList.length; i++) { + if (customCount.countList[i].isUsedCount < customCount.countList[i].count) { + customCount.countList[i].isUsedCount += luckyCount.value + break + } + } + } + currentPrize.value.isUsedCount += luckyCount.value + luckyCount.value = 0 + if (currentPrize.value.isUsedCount >= currentPrize.value.count) { + currentPrize.value.isUsed = true + currentPrize.value.isUsedCount = currentPrize.value.count + } + personConfig.addAlreadyPersonList(luckyTargets.value, currentPrize.value) + prizeConfig.updatePrizeConfig(currentPrize.value) + await enterLottery() + } + /** + * @description: 放弃本次抽奖,回到初始状态 + */ + function quitLottery() { + enterLottery() + currentStatus.value = LotteryStatus.init + } + + /** + * @description: 随机替换卡片中的数据(不改变原有的值,只是显示) + * @param {string} mod 模式 + */ + function randomBallData(mod: 'default' | 'lucky' | 'sphere' = 'default') { + // 两秒执行一次 + intervalTimer.value = setInterval(() => { + // 产生随机数数组 + const indexLength = 4 + const cardRandomIndexArr: number[] = [] + const personRandomIndexArr: number[] = [] + for (let i = 0; i < indexLength; i++) { + // 解决随机元素概率过于不均等问题 + const randomCardIndex = Math.floor(Math.random() * (tableData.value.length - 1)) + const randomPersonIndex = Math.floor(Math.random() * (allPersonList.value.length - 1)) + if (luckyCardList.value.includes(randomCardIndex)) { + continue + } + cardRandomIndexArr.push(randomCardIndex) + personRandomIndexArr.push(randomPersonIndex) + } + for (let i = 0; i < cardRandomIndexArr.length; i++) { + if (!objects.value[cardRandomIndexArr[i]]) { + continue + } + objects.value[cardRandomIndexArr[i]].element = useElementStyle(objects.value[cardRandomIndexArr[i]].element, allPersonList.value[personRandomIndexArr[i]], cardRandomIndexArr[i], patternList.value, patternColor.value, cardColor.value, { width: cardSize.value.width, height: cardSize.value.height }, textSize.value, mod, 'change') + } + }, 200) + } + /** + * @description: 键盘监听,快捷键操作 + */ + function listenKeyboard(e: any) { + if ((e.keyCode !== 32 || e.keyCode !== 27) && !canOperate.value) { + return + } + if (e.keyCode === 27 && currentStatus.value === LotteryStatus.running) { + quitLottery() + } + if (e.keyCode !== 32) { + return + } + switch (currentStatus.value) { + case LotteryStatus.init: + enterLottery() + break + case LotteryStatus.ready: + startLottery() + break + case LotteryStatus.running: + stopLottery() + break + case LotteryStatus.end: + continueLottery() + break + default: + break + } + } + /** + * @description: 清理资源,避免内存溢出 + */ + function cleanup() { + clearInterval(intervalTimer.value) + intervalTimer.value = null + if (scene.value) { + scene.value.traverse((object: Object3D) => { + if ((object as any).material) { + if (Array.isArray((object as any).material)) { + (object as any).material.forEach((material: Material) => { + material.dispose() + }) + } + else { + (object as any).material.dispose() + } + } + if ((object as any).geometry) { + (object as any).geometry.dispose() + } + if ((object as any).texture) { + (object as any).texture.dispose() + } + }) + scene.value.clear() + } + + if (objects.value) { + objects.value.forEach((object) => { + if (object.element) { + object.element.remove() + } + }) + objects.value = [] + } + + if (controls.value) { + controls.value.removeEventListener('change') + controls.value.dispose() + } + // 移除所有事件监听 + window.removeEventListener('resize', onWindowResize) + scene.value = null + camera.value = null + renderer.value = null + controls.value = null + } + /** + * @description: 设置默认人员列表 + */ + function setDefaultPersonList() { + personConfig.setDefaultPersonList() + // 刷新页面 + window.location.reload() + } + onMounted(() => { + tableData.value = initTableData({ allPersonList: allPersonList.value, rowCount: rowCount.value }) + init() + animation() + containerRef.value!.style.color = `${textColor}` + randomBallData() + window.addEventListener('keydown', listenKeyboard) + }) + onUnmounted(() => { + nextTick(() => { + cleanup() + }) + clearInterval(intervalTimer.value) + intervalTimer.value = null + window.removeEventListener('keydown', listenKeyboard) + }) + + return { + setDefaultPersonList, + startLottery, + continueLottery, + quitLottery, + containerRef, + stopLottery, + enterLottery, + tableData, + currentStatus, + } +} diff --git a/src/views/Home/index.ts b/src/views/Home/util.ts similarity index 100% rename from src/views/Home/index.ts rename to src/views/Home/util.ts From 2b920557a1b529ff9c73848ce2cc18a0a93c5063 Mon Sep 17 00:00:00 2001 From: LOG1997 <2694233102@qq.com> Date: Wed, 1 Oct 2025 11:02:33 +0800 Subject: [PATCH 05/16] =?UTF-8?q?refactor:=20=E4=BA=BA=E5=91=98=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E9=87=8D=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components.d.ts | 1 + src/components/Dialog/index.vue | 56 ++++ src/router/index.ts | 240 +++++++++--------- .../Person/PersonAll/importExcel.worker.ts | 32 +++ src/views/Config/Person/PersonAll/index.vue | 73 ++++++ .../Config/Person/PersonAll/useViewModel.ts | 153 +++++++++++ 6 files changed, 435 insertions(+), 120 deletions(-) create mode 100644 src/components/Dialog/index.vue create mode 100644 src/views/Config/Person/PersonAll/importExcel.worker.ts create mode 100644 src/views/Config/Person/PersonAll/index.vue create mode 100644 src/views/Config/Person/PersonAll/useViewModel.ts diff --git a/src/components.d.ts b/src/components.d.ts index 04d884e..7b0c306 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -9,6 +9,7 @@ export {} declare module 'vue' { export interface GlobalComponents { DaiysuiTable: typeof import('./components/DaiysuiTable/index.vue')['default'] + Dialog: typeof import('./components/Dialog/index.vue')['default'] EditSeparateDialog: typeof import('./components/NumberSeparate/EditSeparateDialog.vue')['default'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] ImageSync: typeof import('./components/ImageSync/index.vue')['default'] diff --git a/src/components/Dialog/index.vue b/src/components/Dialog/index.vue new file mode 100644 index 0000000..a004158 --- /dev/null +++ b/src/components/Dialog/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index 1a37f75..ef753a3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,142 +1,142 @@ +import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' import Layout from '@/layout/index.vue' import i18n from '@/locales/i18n' import Home from '@/views/Home/index.vue' -import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router' export const configRoutes = { - path: '/log-lottery/config', - name: 'Config', - component: () => import('@/views/Config/index.vue'), - children: [ - { - path: '', - redirect: '/log-lottery/config/person', - }, - { - path: '/log-lottery/config/person', - name: 'PersonConfig', - component: () => import('@/views/Config/Person/PersonConfig.vue'), - meta: { - title: i18n.global.t('sidebar.personConfiguration'), - icon: 'person', - }, - children: [ + path: '/log-lottery/config', + name: 'Config', + component: () => import('@/views/Config/index.vue'), + children: [ { - path: '', - redirect: '/log-lottery/config/person/all', + path: '', + redirect: '/log-lottery/config/person', }, { - path: '/log-lottery/config/person/all', - name: 'AllPersonConfig', - component: () => import('@/views/Config/Person/PersonAll.vue'), - meta: { - title: i18n.global.t('sidebar.personList'), - icon: 'all', - }, + path: '/log-lottery/config/person', + name: 'PersonConfig', + component: () => import('@/views/Config/Person/PersonConfig.vue'), + meta: { + title: i18n.global.t('sidebar.personConfiguration'), + icon: 'person', + }, + children: [ + { + path: '', + redirect: '/log-lottery/config/person/all', + }, + { + path: '/log-lottery/config/person/all', + name: 'AllPersonConfig', + component: () => import('@/views/Config/Person/PersonAll/index.vue'), + meta: { + title: i18n.global.t('sidebar.personList'), + icon: 'all', + }, + }, + { + path: '/log-lottery/config/person/already', + name: 'AlreadyPerson', + component: () => import('@/views/Config/Person/PersonAlready.vue'), + meta: { + title: i18n.global.t('sidebar.winnerList'), + icon: 'already', + }, + }, + // { + // path:'other', + // name:'OtherPersonConfig', + // component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'), + // meta:{ + // title:'其他配置', + // icon:'other' + // } + // } + ], }, { - path: '/log-lottery/config/person/already', - name: 'AlreadyPerson', - component: () => import('@/views/Config/Person/PersonAlready.vue'), - meta: { - title: i18n.global.t('sidebar.winnerList'), - icon: 'already', - }, - }, - // { - // path:'other', - // name:'OtherPersonConfig', - // component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'), - // meta:{ - // title:'其他配置', - // icon:'other' - // } - // } - ], - }, - { - path: '/log-lottery/config/prize', - name: 'PrizeConfig', - component: () => import('@/views/Config/Prize/PrizeConfig.vue'), - meta: { - title: i18n.global.t('sidebar.prizeConfiguration'), - icon: 'prize', - }, - }, - { - path: '/log-lottery/config/global', - name: 'GlobalConfig', - redirect: '/log-lottery/config/global/all', - meta: { - title: i18n.global.t('sidebar.globalSetting'), - icon: 'global', - }, - children: [ - { - path: '/log-lottery/config/global/face', - name: 'FaceConfig', - component: () => import('@/views/Config/Global/FaceConfig.vue'), - meta: { - title: i18n.global.t('sidebar.viewSetting'), - icon: 'face', - }, + path: '/log-lottery/config/prize', + name: 'PrizeConfig', + component: () => import('@/views/Config/Prize/PrizeConfig.vue'), + meta: { + title: i18n.global.t('sidebar.prizeConfiguration'), + icon: 'prize', + }, }, { - path: '/log-lottery/config/global/image', - name: 'ImageConfig', - component: () => import('@/views/Config/Global/ImageConfig.vue'), - meta: { - title: i18n.global.t('sidebar.imagesManagement'), - icon: 'image', - }, + path: '/log-lottery/config/global', + name: 'GlobalConfig', + redirect: '/log-lottery/config/global/all', + meta: { + title: i18n.global.t('sidebar.globalSetting'), + icon: 'global', + }, + children: [ + { + path: '/log-lottery/config/global/face', + name: 'FaceConfig', + component: () => import('@/views/Config/Global/FaceConfig.vue'), + meta: { + title: i18n.global.t('sidebar.viewSetting'), + icon: 'face', + }, + }, + { + path: '/log-lottery/config/global/image', + name: 'ImageConfig', + component: () => import('@/views/Config/Global/ImageConfig.vue'), + meta: { + title: i18n.global.t('sidebar.imagesManagement'), + icon: 'image', + }, + }, + { + path: '/log-lottery/config/global/music', + name: 'MusicConfig', + component: () => import('@/views/Config/Global/MusicConfig.vue'), + meta: { + title: i18n.global.t('sidebar.musicManagement'), + icon: 'music', + }, + }, + ], }, { - path: '/log-lottery/config/global/music', - name: 'MusicConfig', - component: () => import('@/views/Config/Global/MusicConfig.vue'), - meta: { - title: i18n.global.t('sidebar.musicManagement'), - icon: 'music', - }, + path: '/log-lottery/config/readme', + name: 'Readme', + component: () => import('@/views/Config/Readme/index.vue'), + meta: { + title: i18n.global.t('sidebar.operatingInstructions'), + icon: 'readme', + }, }, - ], - }, - { - path: '/log-lottery/config/readme', - name: 'Readme', - component: () => import('@/views/Config/Readme/index.vue'), - meta: { - title: i18n.global.t('sidebar.operatingInstructions'), - icon: 'readme', - }, - }, - ], + ], } const routes = [ - { - path: '/log-lottery', - component: Layout, - redirect: '/log-lottery/home', - children: [ - { - path: '/log-lottery/home', - name: 'Home', - component: Home, - }, - { - path: '/log-lottery/demo', - name: 'Demo', - component: () => import('@/views/Demo/index.vue'), - }, - configRoutes, - ], - }, -]; -const envMode=import.meta.env.MODE; + { + path: '/log-lottery', + component: Layout, + redirect: '/log-lottery/home', + children: [ + { + path: '/log-lottery/home', + name: 'Home', + component: Home, + }, + { + path: '/log-lottery/demo', + name: 'Demo', + component: () => import('@/views/Demo/index.vue'), + }, + configRoutes, + ], + }, +] +const envMode = import.meta.env.MODE const router = createRouter({ // 读取环境变量 - history: envMode==='file'?createWebHashHistory():createWebHistory(), - routes, + history: envMode === 'file' ? createWebHashHistory() : createWebHistory(), + routes, }) export default router diff --git a/src/views/Config/Person/PersonAll/importExcel.worker.ts b/src/views/Config/Person/PersonAll/importExcel.worker.ts new file mode 100644 index 0000000..6b9a39e --- /dev/null +++ b/src/views/Config/Person/PersonAll/importExcel.worker.ts @@ -0,0 +1,32 @@ +import * as XLSX from 'xlsx' +import { addOtherInfo } from '@/utils' +// 定义消息类型 +interface WorkerMessage { + type: 'start' | 'stop' | 'reset' + data: any +} + +let allData: any[] = [] + +// 接收主线程消息 +globalThis.onmessage = async (e: MessageEvent) => { + switch (e.data.type) { + case 'start': + { + const fileData = e.data.data + // const dataBinary = await readFileBinary(((fileEvent.target as HTMLInputElement).files as FileList)[0]!) + const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true }) + const workSheet = workBook.Sheets[workBook.SheetNames[0]] + const excelData = XLSX.utils.sheet_to_json(workSheet) + allData = addOtherInfo(excelData) + globalThis.postMessage({ + type: 'done', + data: allData, + message: '读取完成', + }) + break + } + default: + break + } +} diff --git a/src/views/Config/Person/PersonAll/index.vue b/src/views/Config/Person/PersonAll/index.vue new file mode 100644 index 0000000..7d52530 --- /dev/null +++ b/src/views/Config/Person/PersonAll/index.vue @@ -0,0 +1,73 @@ + + + + + + diff --git a/src/views/Config/Person/PersonAll/useViewModel.ts b/src/views/Config/Person/PersonAll/useViewModel.ts new file mode 100644 index 0000000..3a1de31 --- /dev/null +++ b/src/views/Config/Person/PersonAll/useViewModel.ts @@ -0,0 +1,153 @@ +import type { IPersonConfig } from '@/types/storeType' +import { storeToRefs } from 'pinia' +import * as XLSX from 'xlsx' +import i18n from '@/locales/i18n' +import useStore from '@/store' +import { readFileBinary } from '@/utils/file' +import ImportExcelWorker from './importExcel.worker?worker' + +export function useViewModel() { + const worker: Worker | null = new ImportExcelWorker() + const personConfig = useStore().personConfig + const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig) + const tableColumns = [ + { + label: i18n.global.t('data.number'), + props: 'uid', + }, + { + label: i18n.global.t('data.name'), + props: 'name', + }, + { + label: i18n.global.t('data.department'), + props: 'department', + }, + { + label: i18n.global.t('data.avatar'), + props: 'avatar', + formatValue(row: any) { + return row.avatar ? `avatar` : '-' + }, + }, + { + label: i18n.global.t('data.identity'), + props: 'identity', + }, + { + label: i18n.global.t('data.isWin'), + props: 'isWin', + formatValue(row: IPersonConfig) { + return row.isWin ? i18n.global.t('data.yes') : i18n.global.t('data.no') + }, + }, + { + label: i18n.global.t('data.operation'), + actions: [ + // { + // label: '编辑', + // type: 'btn-info', + // onClick: (row: any) => { + // delPersonItem(row) + // } + // }, + { + label: i18n.global.t('data.delete'), + type: 'btn-error', + onClick: (row: IPersonConfig) => { + delPersonItem(row) + }, + }, + + ], + }, + ] + /// 向worker发送消息 + function sendWorkerMessage(message: any) { + if (worker) { + worker.postMessage(message) + } + } + /// 开始导入 + function startWorker(data: Event) { + sendWorkerMessage({ type: 'start', data }) + } + /** + * 获取用户数据 + */ + async function handleFileChange(e: Event) { + // worker = new ImportExcelWorker() + if (worker) { + worker.onmessage = (e) => { + if (e.data.type === 'done') { + personConfig.resetPerson() + personConfig.addNotPersonList(e.data.data) + } + } + } + const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!) + startWorker(dataBinary) + } + /// 导出数据 + function exportData() { + let data = JSON.parse(JSON.stringify(allPersonList.value)) + // 排除一些字段 + for (let i = 0; i < data.length; i++) { + delete data[i].x + delete data[i].y + delete data[i].id + delete data[i].createTime + delete data[i].updateTime + delete data[i].prizeId + // 修改字段名称 + if (data[i].isWin) { + data[i].isWin = i18n.global.t('data.yes') + } + else { + data[i].isWin = i18n.global.t('data.no') + } + // 格式化数组为 + data[i].prizeTime = data[i].prizeTime.join(',') + data[i].prizeName = data[i].prizeName.join(',') + } + let dataString = JSON.stringify(data) + dataString = dataString + .replaceAll(/uid/g, i18n.global.t('data.number')) + .replaceAll(/isWin/g, i18n.global.t('data.isWin')) + .replaceAll(/department/g, i18n.global.t('data.department')) + .replaceAll(/name/g, i18n.global.t('data.name')) + .replaceAll(/identity/g, i18n.global.t('data.identity')) + .replaceAll(/prizeName/g, i18n.global.t('data.prizeName')) + .replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime')) + + data = JSON.parse(dataString) + + if (data.length > 0) { + const dataBinary = XLSX.utils.json_to_sheet(data) + const dataBinaryBinary = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1') + XLSX.writeFile(dataBinaryBinary, 'data.xlsx') + } + } + + function resetData() { + personConfig.resetAlreadyPerson() + } + + function deleteAll() { + personConfig.deleteAllPerson() + } + + function delPersonItem(row: IPersonConfig) { + personConfig.deletePerson(row) + } + return { + resetData, + deleteAll, + handleFileChange, + exportData, + alreadyPersonList, + allPersonList, + tableColumns, + } +} From 173dd796a40c613f2ed73c1d6720ee860b08f008 Mon Sep 17 00:00:00 2001 From: log1997 <2694233102@qq.com> Date: Fri, 3 Oct 2025 11:39:20 +0800 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20:sparkles:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components.d.ts | 1 + src/components/Dialog/index.vue | 59 +++++ src/router/index.ts | 240 +++++++++--------- .../Person/PersonAll/importExcel.worker.ts | 32 +++ src/views/Config/Person/PersonAll/index.vue | 73 ++++++ .../Config/Person/PersonAll/useViewModel.ts | 153 +++++++++++ 6 files changed, 438 insertions(+), 120 deletions(-) create mode 100644 src/components/Dialog/index.vue create mode 100644 src/views/Config/Person/PersonAll/importExcel.worker.ts create mode 100644 src/views/Config/Person/PersonAll/index.vue create mode 100644 src/views/Config/Person/PersonAll/useViewModel.ts diff --git a/src/components.d.ts b/src/components.d.ts index 04d884e..7b0c306 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -9,6 +9,7 @@ export {} declare module 'vue' { export interface GlobalComponents { DaiysuiTable: typeof import('./components/DaiysuiTable/index.vue')['default'] + Dialog: typeof import('./components/Dialog/index.vue')['default'] EditSeparateDialog: typeof import('./components/NumberSeparate/EditSeparateDialog.vue')['default'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] ImageSync: typeof import('./components/ImageSync/index.vue')['default'] diff --git a/src/components/Dialog/index.vue b/src/components/Dialog/index.vue new file mode 100644 index 0000000..a57bb97 --- /dev/null +++ b/src/components/Dialog/index.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index 1a37f75..ef753a3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,142 +1,142 @@ +import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' import Layout from '@/layout/index.vue' import i18n from '@/locales/i18n' import Home from '@/views/Home/index.vue' -import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router' export const configRoutes = { - path: '/log-lottery/config', - name: 'Config', - component: () => import('@/views/Config/index.vue'), - children: [ - { - path: '', - redirect: '/log-lottery/config/person', - }, - { - path: '/log-lottery/config/person', - name: 'PersonConfig', - component: () => import('@/views/Config/Person/PersonConfig.vue'), - meta: { - title: i18n.global.t('sidebar.personConfiguration'), - icon: 'person', - }, - children: [ + path: '/log-lottery/config', + name: 'Config', + component: () => import('@/views/Config/index.vue'), + children: [ { - path: '', - redirect: '/log-lottery/config/person/all', + path: '', + redirect: '/log-lottery/config/person', }, { - path: '/log-lottery/config/person/all', - name: 'AllPersonConfig', - component: () => import('@/views/Config/Person/PersonAll.vue'), - meta: { - title: i18n.global.t('sidebar.personList'), - icon: 'all', - }, + path: '/log-lottery/config/person', + name: 'PersonConfig', + component: () => import('@/views/Config/Person/PersonConfig.vue'), + meta: { + title: i18n.global.t('sidebar.personConfiguration'), + icon: 'person', + }, + children: [ + { + path: '', + redirect: '/log-lottery/config/person/all', + }, + { + path: '/log-lottery/config/person/all', + name: 'AllPersonConfig', + component: () => import('@/views/Config/Person/PersonAll/index.vue'), + meta: { + title: i18n.global.t('sidebar.personList'), + icon: 'all', + }, + }, + { + path: '/log-lottery/config/person/already', + name: 'AlreadyPerson', + component: () => import('@/views/Config/Person/PersonAlready.vue'), + meta: { + title: i18n.global.t('sidebar.winnerList'), + icon: 'already', + }, + }, + // { + // path:'other', + // name:'OtherPersonConfig', + // component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'), + // meta:{ + // title:'其他配置', + // icon:'other' + // } + // } + ], }, { - path: '/log-lottery/config/person/already', - name: 'AlreadyPerson', - component: () => import('@/views/Config/Person/PersonAlready.vue'), - meta: { - title: i18n.global.t('sidebar.winnerList'), - icon: 'already', - }, - }, - // { - // path:'other', - // name:'OtherPersonConfig', - // component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'), - // meta:{ - // title:'其他配置', - // icon:'other' - // } - // } - ], - }, - { - path: '/log-lottery/config/prize', - name: 'PrizeConfig', - component: () => import('@/views/Config/Prize/PrizeConfig.vue'), - meta: { - title: i18n.global.t('sidebar.prizeConfiguration'), - icon: 'prize', - }, - }, - { - path: '/log-lottery/config/global', - name: 'GlobalConfig', - redirect: '/log-lottery/config/global/all', - meta: { - title: i18n.global.t('sidebar.globalSetting'), - icon: 'global', - }, - children: [ - { - path: '/log-lottery/config/global/face', - name: 'FaceConfig', - component: () => import('@/views/Config/Global/FaceConfig.vue'), - meta: { - title: i18n.global.t('sidebar.viewSetting'), - icon: 'face', - }, + path: '/log-lottery/config/prize', + name: 'PrizeConfig', + component: () => import('@/views/Config/Prize/PrizeConfig.vue'), + meta: { + title: i18n.global.t('sidebar.prizeConfiguration'), + icon: 'prize', + }, }, { - path: '/log-lottery/config/global/image', - name: 'ImageConfig', - component: () => import('@/views/Config/Global/ImageConfig.vue'), - meta: { - title: i18n.global.t('sidebar.imagesManagement'), - icon: 'image', - }, + path: '/log-lottery/config/global', + name: 'GlobalConfig', + redirect: '/log-lottery/config/global/all', + meta: { + title: i18n.global.t('sidebar.globalSetting'), + icon: 'global', + }, + children: [ + { + path: '/log-lottery/config/global/face', + name: 'FaceConfig', + component: () => import('@/views/Config/Global/FaceConfig.vue'), + meta: { + title: i18n.global.t('sidebar.viewSetting'), + icon: 'face', + }, + }, + { + path: '/log-lottery/config/global/image', + name: 'ImageConfig', + component: () => import('@/views/Config/Global/ImageConfig.vue'), + meta: { + title: i18n.global.t('sidebar.imagesManagement'), + icon: 'image', + }, + }, + { + path: '/log-lottery/config/global/music', + name: 'MusicConfig', + component: () => import('@/views/Config/Global/MusicConfig.vue'), + meta: { + title: i18n.global.t('sidebar.musicManagement'), + icon: 'music', + }, + }, + ], }, { - path: '/log-lottery/config/global/music', - name: 'MusicConfig', - component: () => import('@/views/Config/Global/MusicConfig.vue'), - meta: { - title: i18n.global.t('sidebar.musicManagement'), - icon: 'music', - }, + path: '/log-lottery/config/readme', + name: 'Readme', + component: () => import('@/views/Config/Readme/index.vue'), + meta: { + title: i18n.global.t('sidebar.operatingInstructions'), + icon: 'readme', + }, }, - ], - }, - { - path: '/log-lottery/config/readme', - name: 'Readme', - component: () => import('@/views/Config/Readme/index.vue'), - meta: { - title: i18n.global.t('sidebar.operatingInstructions'), - icon: 'readme', - }, - }, - ], + ], } const routes = [ - { - path: '/log-lottery', - component: Layout, - redirect: '/log-lottery/home', - children: [ - { - path: '/log-lottery/home', - name: 'Home', - component: Home, - }, - { - path: '/log-lottery/demo', - name: 'Demo', - component: () => import('@/views/Demo/index.vue'), - }, - configRoutes, - ], - }, -]; -const envMode=import.meta.env.MODE; + { + path: '/log-lottery', + component: Layout, + redirect: '/log-lottery/home', + children: [ + { + path: '/log-lottery/home', + name: 'Home', + component: Home, + }, + { + path: '/log-lottery/demo', + name: 'Demo', + component: () => import('@/views/Demo/index.vue'), + }, + configRoutes, + ], + }, +] +const envMode = import.meta.env.MODE const router = createRouter({ // 读取环境变量 - history: envMode==='file'?createWebHashHistory():createWebHistory(), - routes, + history: envMode === 'file' ? createWebHashHistory() : createWebHistory(), + routes, }) export default router diff --git a/src/views/Config/Person/PersonAll/importExcel.worker.ts b/src/views/Config/Person/PersonAll/importExcel.worker.ts new file mode 100644 index 0000000..6b9a39e --- /dev/null +++ b/src/views/Config/Person/PersonAll/importExcel.worker.ts @@ -0,0 +1,32 @@ +import * as XLSX from 'xlsx' +import { addOtherInfo } from '@/utils' +// 定义消息类型 +interface WorkerMessage { + type: 'start' | 'stop' | 'reset' + data: any +} + +let allData: any[] = [] + +// 接收主线程消息 +globalThis.onmessage = async (e: MessageEvent) => { + switch (e.data.type) { + case 'start': + { + const fileData = e.data.data + // const dataBinary = await readFileBinary(((fileEvent.target as HTMLInputElement).files as FileList)[0]!) + const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true }) + const workSheet = workBook.Sheets[workBook.SheetNames[0]] + const excelData = XLSX.utils.sheet_to_json(workSheet) + allData = addOtherInfo(excelData) + globalThis.postMessage({ + type: 'done', + data: allData, + message: '读取完成', + }) + break + } + default: + break + } +} diff --git a/src/views/Config/Person/PersonAll/index.vue b/src/views/Config/Person/PersonAll/index.vue new file mode 100644 index 0000000..7d52530 --- /dev/null +++ b/src/views/Config/Person/PersonAll/index.vue @@ -0,0 +1,73 @@ + + + + + + diff --git a/src/views/Config/Person/PersonAll/useViewModel.ts b/src/views/Config/Person/PersonAll/useViewModel.ts new file mode 100644 index 0000000..c116a96 --- /dev/null +++ b/src/views/Config/Person/PersonAll/useViewModel.ts @@ -0,0 +1,153 @@ +import type { IPersonConfig } from '@/types/storeType' +import { storeToRefs } from 'pinia' +import * as XLSX from 'xlsx' +import i18n from '@/locales/i18n' +import useStore from '@/store' +import { readFileBinary } from '@/utils/file' +import ImportExcelWorker from './importExcel.worker?worker' + +export function useViewModel() { + const worker: Worker | null = new ImportExcelWorker() + const personConfig = useStore().personConfig + const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig) + const tableColumns = [ + { + label: i18n.global.t('data.number'), + props: 'uid', + }, + { + label: i18n.global.t('data.name'), + props: 'name', + }, + { + label: i18n.global.t('data.department'), + props: 'department', + }, + { + label: i18n.global.t('data.avatar'), + props: 'avatar', + formatValue(row: any) { + return row.avatar ? `avatar` : '-' + }, + }, + { + label: i18n.global.t('data.identity'), + props: 'identity', + }, + { + label: i18n.global.t('data.isWin'), + props: 'isWin', + formatValue(row: IPersonConfig) { + return row.isWin ? i18n.global.t('data.yes') : i18n.global.t('data.no') + }, + }, + { + label: i18n.global.t('data.operation'), + actions: [ + // { + // label: '编辑', + // type: 'btn-info', + // onClick: (row: any) => { + // delPersonItem(row) + // } + // }, + { + label: i18n.global.t('data.delete'), + type: 'btn-error', + onClick: (row: IPersonConfig) => { + delPersonItem(row) + }, + }, + + ], + }, + ] + /// 向worker发送消息 + function sendWorkerMessage(message: any) { + if (worker) { + worker.postMessage(message) + } + } + /// 开始导入 + function startWorker(data: Event) { + sendWorkerMessage({ type: 'start', data }) + } + /** + * 获取用户数据 + */ + async function handleFileChange(e: Event) { + // worker = new ImportExcelWorker() + if (worker) { + worker.onmessage = (e) => { + if (e.data.type === 'done') { + personConfig.resetPerson() + personConfig.addNotPersonList(e.data.data) + } + } + } + const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!) + startWorker(dataBinary) + } + /// 导出数据 + function exportData() { + let data = JSON.parse(JSON.stringify(allPersonList.value)) + // 排除一些字段 + for (let i = 0; i < data.length; i++) { + delete data[i].x + delete data[i].y + delete data[i].id + delete data[i].createTime + delete data[i].updateTime + delete data[i].prizeId + // 修改字段名称 + if (data[i].isWin) { + data[i].isWin = i18n.global.t('data.yes') + } + else { + data[i].isWin = i18n.global.t('data.no') + } + // 格式化数组为 + data[i].prizeTime = data[i].prizeTime.join(',') + data[i].prizeName = data[i].prizeName.join(',') + } + let dataString = JSON.stringify(data) + dataString = dataString + .replaceAll(/uid/g, i18n.global.t('data.number')) + .replaceAll(/isWin/g, i18n.global.t('data.isWin')) + .replaceAll(/department/g, i18n.global.t('data.department')) + .replaceAll(/name/g, i18n.global.t('data.name')) + .replaceAll(/identity/g, i18n.global.t('data.identity')) + .replaceAll(/prizeName/g, i18n.global.t('data.prizeName')) + .replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime')) + + data = JSON.parse(dataString) + + if (data.length > 0) { + const dataBinary = XLSX.utils.json_to_sheet(data) + const dataBinaryBinary = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1') + XLSX.writeFile(dataBinaryBinary, 'data.xlsx') + } + } + + function resetData() { + personConfig.resetAlreadyPerson() + } + + function deleteAll() { + personConfig.deleteAllPerson() + } + + function delPersonItem(row: IPersonConfig) { + personConfig.deletePerson(row) + } + return { + resetData, + deleteAll, + handleFileChange, + exportData, + alreadyPersonList, + allPersonList, + tableColumns, + } +} From 27fd0768c1e3213b456d19154d6d2774fe465410 Mon Sep 17 00:00:00 2001 From: LOG1997 <2694233102@qq.com> Date: Sun, 12 Oct 2025 22:30:54 +0800 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0Loading?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 5 +- src/components.d.ts | 1 + src/components/Loading/index.ts | 5 + src/components/Loading/index.vue | 22 ++ src/components/Loading/loading-context.ts | 61 ++++ src/layout/index.vue | 5 +- src/main.ts | 16 +- .../Person/PersonAll/importExcel.worker.ts | 5 + src/views/Config/Person/PersonAll/index.vue | 12 +- .../Config/Person/PersonAll/useViewModel.ts | 282 +++++++++--------- src/vite-env.d.ts | 7 +- 11 files changed, 266 insertions(+), 155 deletions(-) create mode 100644 src/components/Loading/index.ts create mode 100644 src/components/Loading/index.vue create mode 100644 src/components/Loading/loading-context.ts diff --git a/src/App.vue b/src/App.vue index 8512bab..96991db 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,13 @@ + + + + diff --git a/src/components/Loading/loading-context.ts b/src/components/Loading/loading-context.ts new file mode 100644 index 0000000..6093acf --- /dev/null +++ b/src/components/Loading/loading-context.ts @@ -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 + text: Ref + fullscreen: Ref + zIndex: Ref + count: Ref + show: (options?: Partial<{ text: string, fullscreen: boolean, zIndex: number }>) => void + hide: () => void +} + +// 注入密钥(Symbol 确保唯一性) +export const loadingKey: InjectionKey = 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, +} diff --git a/src/layout/index.vue b/src/layout/index.vue index acd521c..10cc77e 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -1,12 +1,12 @@