refactor: 重构首页代码,提取处理函数
This commit is contained in:
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
|||||||
localforage:
|
localforage:
|
||||||
specifier: ^1.10.0
|
specifier: ^1.10.0
|
||||||
version: 1.10.0
|
version: 1.10.0
|
||||||
|
lodash-es:
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.21
|
||||||
markdown-it:
|
markdown-it:
|
||||||
specifier: ^14.1.0
|
specifier: ^14.1.0
|
||||||
version: 14.1.0
|
version: 14.1.0
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
// 筛选人员数据
|
|
||||||
|
/**
|
||||||
|
* @description: 处理表格数据,添加x,y,id等信息
|
||||||
|
* @param tableData 表格数据
|
||||||
|
* @param localRowCount 每一行有多少个元素
|
||||||
|
* @returns 处理后的表格数据
|
||||||
|
*/
|
||||||
export function filterData(tableData: any[], localRowCount: number) {
|
export function filterData(tableData: any[], localRowCount: number) {
|
||||||
const dataLength = tableData.length
|
const dataLength = tableData.length
|
||||||
let j = 0
|
let j = 0
|
||||||
|
|||||||
138
src/views/Home/components/PrizeList/index.scss
Normal file
138
src/views/Home/components/PrizeList/index.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import type { IPrizeConfig } from '../../types/storeType'
|
import type { IPrizeConfig } from '@/types/storeType'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import defaultPrizeImage from '@/assets/images/龙.png'
|
import defaultPrizeImage from '@/assets/images/龙.png'
|
||||||
import ImageSync from '@/components/ImageSync/index.vue'
|
import ImageSync from '@/components/ImageSync/index.vue'
|
||||||
|
|
||||||
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
|
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
|
||||||
|
|
||||||
import i18n from '@/locales/i18n'
|
import i18n from '@/locales/i18n'
|
||||||
import useStore from '@/store'
|
import useStore from '@/store'
|
||||||
|
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const prizeConfig = useStore().prizeConfig
|
const prizeConfig = useStore().prizeConfig
|
||||||
const globalConfig = useStore().globalConfig
|
const globalConfig = useStore().globalConfig
|
||||||
@@ -318,142 +318,5 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
.label {
|
@import "./index.scss";
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
131
src/views/Home/index.ts
Normal file
131
src/views/Home/index.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import type { IPersonConfig } from '@/types/storeType'
|
||||||
|
import confetti from 'canvas-confetti'
|
||||||
|
import { Object3D, Vector3 } from 'three'
|
||||||
|
import { filterData } from '@/utils'
|
||||||
|
/**
|
||||||
|
* @description 初始化表格数据
|
||||||
|
* @param0 allPersonList 所有人的列表
|
||||||
|
* @param1 rowCount 行数,默认是7行
|
||||||
|
* @returns 表格数据
|
||||||
|
*/
|
||||||
|
export function initTableData({ allPersonList, rowCount }: { allPersonList: IPersonConfig[], rowCount: number }): IPersonConfig[] {
|
||||||
|
let tableData: IPersonConfig[] = []
|
||||||
|
if (allPersonList.length <= 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const totalCount = rowCount * 7
|
||||||
|
const allPersonLength = allPersonList.length
|
||||||
|
if (allPersonLength < totalCount) {
|
||||||
|
tableData = Array.from({ length: totalCount }, () => JSON.parse(JSON.stringify(allPersonList))).flat()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tableData = allPersonList.slice(0, totalCount)
|
||||||
|
}
|
||||||
|
tableData = filterData(tableData.slice(0, totalCount), rowCount)
|
||||||
|
return tableData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 横铺图形:处理数据,把每个卡片在界面的位置写入
|
||||||
|
* @param0 tableData 表格数据
|
||||||
|
* @param1 rowCount 每行有多少个元素
|
||||||
|
* @param2 cardSize 卡片的大小
|
||||||
|
* @returns Object3D[]
|
||||||
|
*/
|
||||||
|
export function createTableVertices({ tableData, rowCount, cardSize }: { tableData: IPersonConfig[], rowCount: number, cardSize: { width: number, height: number } }): Object3D[] {
|
||||||
|
const tableLen = tableData.length
|
||||||
|
const objects: Object3D[] = []
|
||||||
|
for (let i = 0; i < tableLen; i++) {
|
||||||
|
const object = new Object3D()
|
||||||
|
|
||||||
|
object.position.x = tableData[i].x * (cardSize.width + 40) - rowCount * 90
|
||||||
|
object.position.y = -tableData[i].y * (cardSize.height + 20) + 1000
|
||||||
|
object.position.z = 0
|
||||||
|
objects.push(object)
|
||||||
|
// targets.table.push(object)
|
||||||
|
}
|
||||||
|
return objects
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 创建球体
|
||||||
|
* @param0 objectsLength 物体的个数
|
||||||
|
* @returns Object3D[]
|
||||||
|
*/
|
||||||
|
export function createSphereVertices({ objectsLength }: { objectsLength: number }): Object3D[] {
|
||||||
|
let i = 0
|
||||||
|
const resObjects: Object3D[] = []
|
||||||
|
// const objLength = objects.value.length
|
||||||
|
const vector = new Vector3()
|
||||||
|
|
||||||
|
for (; i < objectsLength; ++i) {
|
||||||
|
const phi = Math.acos(-1 + (2 * i) / objectsLength)
|
||||||
|
const theta = Math.sqrt(objectsLength * Math.PI) * phi
|
||||||
|
const object = new Object3D()
|
||||||
|
|
||||||
|
object.position.x = 800 * Math.cos(theta) * Math.sin(phi)
|
||||||
|
object.position.y = 800 * Math.sin(theta) * Math.sin(phi)
|
||||||
|
object.position.z = -800 * Math.cos(phi)
|
||||||
|
|
||||||
|
// rotation object
|
||||||
|
vector.copy(object.position).multiplyScalar(2)
|
||||||
|
object.lookAt(vector)
|
||||||
|
resObjects.push(object)
|
||||||
|
}
|
||||||
|
return resObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confettiFire() {
|
||||||
|
const duration = 3 * 1000
|
||||||
|
const end = Date.now() + duration;
|
||||||
|
(function frame() {
|
||||||
|
// launch a few confetti from the left edge
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 60,
|
||||||
|
spread: 55,
|
||||||
|
origin: { x: 0 },
|
||||||
|
})
|
||||||
|
// and launch a few from the right edge
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 120,
|
||||||
|
spread: 55,
|
||||||
|
origin: { x: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
|
// keep going until we are out of time
|
||||||
|
if (Date.now() < end) {
|
||||||
|
requestAnimationFrame(frame)
|
||||||
|
}
|
||||||
|
}())
|
||||||
|
centerFire(0.25, {
|
||||||
|
spread: 26,
|
||||||
|
startVelocity: 55,
|
||||||
|
})
|
||||||
|
centerFire(0.2, {
|
||||||
|
spread: 60,
|
||||||
|
})
|
||||||
|
centerFire(0.35, {
|
||||||
|
spread: 100,
|
||||||
|
decay: 0.91,
|
||||||
|
scalar: 0.8,
|
||||||
|
})
|
||||||
|
centerFire(0.1, {
|
||||||
|
spread: 120,
|
||||||
|
startVelocity: 25,
|
||||||
|
decay: 0.92,
|
||||||
|
scalar: 1.2,
|
||||||
|
})
|
||||||
|
centerFire(0.1, {
|
||||||
|
spread: 120,
|
||||||
|
startVelocity: 45,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function centerFire(particleRatio: number, opts: any) {
|
||||||
|
const count = 200
|
||||||
|
confetti({
|
||||||
|
origin: { y: 0.7 },
|
||||||
|
...opts,
|
||||||
|
particleCount: Math.floor(count * particleRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Material, Object3D } from 'three'
|
||||||
import type { IPersonConfig } from '@/types/storeType'
|
import type { IPersonConfig } from '@/types/storeType'
|
||||||
import type { Material } from 'three'
|
|
||||||
import StarsBackground from '@/components/StarsBackground/index.vue'
|
|
||||||
import { useElementPosition, useElementStyle } from '@/hooks/useElement'
|
|
||||||
import i18n from '@/locales/i18n'
|
|
||||||
import useStore from '@/store'
|
|
||||||
import { filterData, selectCard } from '@/utils'
|
|
||||||
import { rgba } from '@/utils/color'
|
|
||||||
import * as TWEEN from '@tweenjs/tween.js'
|
import * as TWEEN from '@tweenjs/tween.js'
|
||||||
import confetti from 'canvas-confetti'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { Object3D, PerspectiveCamera, Scene, Vector3 } from 'three'
|
import { PerspectiveCamera, Scene, Vector3 } from 'three'
|
||||||
import { CSS3DObject, CSS3DRenderer } from 'three-css3d'
|
import { CSS3DObject, CSS3DRenderer } from 'three-css3d'
|
||||||
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
||||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import PrizeList from './PrizeList.vue'
|
import StarsBackground from '@/components/StarsBackground/index.vue'
|
||||||
|
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 PrizeList from './components/PrizeList/index.vue'
|
||||||
|
import { confettiFire, createSphereVertices, createTableVertices, initTableData } from './index'
|
||||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -32,7 +32,14 @@ const { getAllPersonList: allPersonList, getNotPersonList: notPersonList, getNot
|
|||||||
const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
||||||
const { getTopTitle: topTitle, getCardColor: cardColor, getPatterColor: patternColor, getPatternList: patternList, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getBackground: homeBackground, getIsShowAvatar: isShowAvatar } = storeToRefs(globalConfig)
|
const { getTopTitle: topTitle, getCardColor: cardColor, getPatterColor: patternColor, getPatternList: patternList, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getBackground: homeBackground, getIsShowAvatar: isShowAvatar } = storeToRefs(globalConfig)
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
const currentStatus = ref(0) // 0为初始状态, 1为抽奖准备状态,2为抽奖中状态,3为抽奖结束状态
|
|
||||||
|
enum LotteryStatus {
|
||||||
|
init = 0,
|
||||||
|
ready = 1,
|
||||||
|
running = 2,
|
||||||
|
end = 3,
|
||||||
|
}
|
||||||
|
const currentStatus = ref<LotteryStatus>(LotteryStatus.init) // 0为初始状态, 1为抽奖准备状态,2为抽奖中状态,3为抽奖结束状态
|
||||||
const ballRotationY = ref(0)
|
const ballRotationY = ref(0)
|
||||||
const containerRef = ref<HTMLElement>()
|
const containerRef = ref<HTMLElement>()
|
||||||
const canOperate = ref(true)
|
const canOperate = ref(true)
|
||||||
@@ -62,26 +69,6 @@ const luckyCount = ref(10)
|
|||||||
const personPool = ref<IPersonConfig[]>([])
|
const personPool = ref<IPersonConfig[]>([])
|
||||||
|
|
||||||
const intervalTimer = ref<any>(null)
|
const intervalTimer = ref<any>(null)
|
||||||
// 填充数据,填满七行
|
|
||||||
function initTableData() {
|
|
||||||
if (allPersonList.value.length <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const totalCount = rowCount.value * 7
|
|
||||||
const originPersonData = JSON.parse(JSON.stringify(allPersonList.value))
|
|
||||||
const originPersonLength = originPersonData.length
|
|
||||||
if (originPersonLength < totalCount) {
|
|
||||||
const repeatCount = Math.ceil(totalCount / originPersonLength)
|
|
||||||
// 复制数据
|
|
||||||
for (let i = 0; i < repeatCount; i++) {
|
|
||||||
tableData.value = tableData.value.concat(JSON.parse(JSON.stringify(originPersonData)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tableData.value = originPersonData.slice(0, totalCount)
|
|
||||||
}
|
|
||||||
tableData.value = filterData(tableData.value.slice(0, totalCount), rowCount.value)
|
|
||||||
}
|
|
||||||
function init() {
|
function init() {
|
||||||
const felidView = 40
|
const felidView = 40
|
||||||
const width = window.innerWidth
|
const width = window.innerWidth
|
||||||
@@ -156,70 +143,12 @@ function init() {
|
|||||||
|
|
||||||
objects.value.push(object)
|
objects.value.push(object)
|
||||||
}
|
}
|
||||||
|
// 创建横铺的界面
|
||||||
createTableVertices()
|
const tableVertices = createTableVertices({ tableData: tableData.value, rowCount: rowCount.value, cardSize: cardSize.value })
|
||||||
createSphereVertices()
|
targets.table = tableVertices
|
||||||
createHelixVertices()
|
// 创建球体
|
||||||
|
const sphereVertices = createSphereVertices({ objectsLength: objects.value.length })
|
||||||
function createTableVertices() {
|
targets.sphere = sphereVertices
|
||||||
const tableLen = tableData.value.length
|
|
||||||
|
|
||||||
for (let i = 0; i < tableLen; i++) {
|
|
||||||
const object = new Object3D()
|
|
||||||
|
|
||||||
object.position.x = tableData.value[i].x * (cardSize.value.width + 40) - rowCount.value * 90
|
|
||||||
object.position.y = -tableData.value[i].y * (cardSize.value.height + 20) + 1000
|
|
||||||
object.position.z = 0
|
|
||||||
|
|
||||||
targets.table.push(object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSphereVertices() {
|
|
||||||
let i = 0
|
|
||||||
const objLength = objects.value.length
|
|
||||||
const vector = new Vector3()
|
|
||||||
|
|
||||||
for (; i < objLength; ++i) {
|
|
||||||
const phi = Math.acos(-1 + (2 * i) / objLength)
|
|
||||||
const theta = Math.sqrt(objLength * Math.PI) * phi
|
|
||||||
const object = new Object3D()
|
|
||||||
|
|
||||||
object.position.x = 800 * Math.cos(theta) * Math.sin(phi)
|
|
||||||
object.position.y = 800 * Math.sin(theta) * Math.sin(phi)
|
|
||||||
object.position.z = -800 * Math.cos(phi)
|
|
||||||
|
|
||||||
// rotation object
|
|
||||||
|
|
||||||
vector.copy(object.position).multiplyScalar(2)
|
|
||||||
object.lookAt(vector)
|
|
||||||
targets.sphere.push(object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function createHelixVertices() {
|
|
||||||
let i = 0
|
|
||||||
const vector = new Vector3()
|
|
||||||
const objLength = objects.value.length
|
|
||||||
for (; i < objLength; ++i) {
|
|
||||||
const phi = i * 0.213 + Math.PI
|
|
||||||
|
|
||||||
const object = new Object3D()
|
|
||||||
|
|
||||||
object.position.x = 800 * Math.sin(phi)
|
|
||||||
object.position.y = -(i * 8) + 450
|
|
||||||
object.position.z = 800 * Math.cos(phi + Math.PI)
|
|
||||||
|
|
||||||
object.scale.set(1.1, 1.1, 1.1)
|
|
||||||
|
|
||||||
vector.x = object.position.x * 2
|
|
||||||
vector.y = object.position.y
|
|
||||||
vector.z = object.position.z * 2
|
|
||||||
|
|
||||||
object.lookAt(vector)
|
|
||||||
|
|
||||||
targets.helix.push(object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', onWindowResize, false)
|
window.addEventListener('resize', onWindowResize, false)
|
||||||
transform(targets.table, 1000)
|
transform(targets.table, 1000)
|
||||||
render()
|
render()
|
||||||
@@ -238,6 +167,7 @@ function transform(targets: any[], duration: number) {
|
|||||||
for (let i = 0; i < objLength; ++i) {
|
for (let i = 0; i < objLength; ++i) {
|
||||||
const object = objects.value[i]
|
const object = objects.value[i]
|
||||||
const target = targets[i]
|
const target = targets[i]
|
||||||
|
// console.log('target', i, target, targets)
|
||||||
new TWEEN.Tween(object.position)
|
new TWEEN.Tween(object.position)
|
||||||
.to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration)
|
.to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration)
|
||||||
.easing(TWEEN.Easing.Exponential.InOut)
|
.easing(TWEEN.Easing.Exponential.InOut)
|
||||||
@@ -366,6 +296,7 @@ function render() {
|
|||||||
renderer.value.render(scene.value, camera.value)
|
renderer.value.render(scene.value, camera.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 进入抽奖,从平铺转为球体
|
||||||
async function enterLottery() {
|
async function enterLottery() {
|
||||||
if (!canOperate.value) {
|
if (!canOperate.value) {
|
||||||
return
|
return
|
||||||
@@ -382,7 +313,7 @@ async function enterLottery() {
|
|||||||
}
|
}
|
||||||
canOperate.value = false
|
canOperate.value = false
|
||||||
await transform(targets.sphere, 1000)
|
await transform(targets.sphere, 1000)
|
||||||
currentStatus.value = 1
|
currentStatus.value = LotteryStatus.ready
|
||||||
rollBall(0.1, 2000)
|
rollBall(0.1, 2000)
|
||||||
}
|
}
|
||||||
// 开始抽奖
|
// 开始抽奖
|
||||||
@@ -443,10 +374,10 @@ function startLottery() {
|
|||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
duration: 8000,
|
duration: 8000,
|
||||||
})
|
})
|
||||||
currentStatus.value = 2
|
currentStatus.value = LotteryStatus.running
|
||||||
rollBall(10, 3000)
|
rollBall(10, 3000)
|
||||||
}
|
}
|
||||||
|
// 停止抽奖,开始选取中将卡片并展示
|
||||||
async function stopLottery() {
|
async function stopLottery() {
|
||||||
if (!canOperate.value) {
|
if (!canOperate.value) {
|
||||||
return
|
return
|
||||||
@@ -476,7 +407,7 @@ async function stopLottery() {
|
|||||||
.start()
|
.start()
|
||||||
.onComplete(() => {
|
.onComplete(() => {
|
||||||
canOperate.value = true
|
canOperate.value = true
|
||||||
currentStatus.value = 3
|
currentStatus.value = LotteryStatus.end
|
||||||
})
|
})
|
||||||
new TWEEN.Tween(item.rotation)
|
new TWEEN.Tween(item.rotation)
|
||||||
.to({
|
.to({
|
||||||
@@ -519,63 +450,7 @@ async function continueLottery() {
|
|||||||
}
|
}
|
||||||
function quitLottery() {
|
function quitLottery() {
|
||||||
enterLottery()
|
enterLottery()
|
||||||
currentStatus.value = 0
|
currentStatus.value = LotteryStatus.init
|
||||||
}
|
|
||||||
// 庆祝动画
|
|
||||||
function confettiFire() {
|
|
||||||
const duration = 3 * 1000
|
|
||||||
const end = Date.now() + duration;
|
|
||||||
(function frame() {
|
|
||||||
// launch a few confetti from the left edge
|
|
||||||
confetti({
|
|
||||||
particleCount: 2,
|
|
||||||
angle: 60,
|
|
||||||
spread: 55,
|
|
||||||
origin: { x: 0 },
|
|
||||||
})
|
|
||||||
// and launch a few from the right edge
|
|
||||||
confetti({
|
|
||||||
particleCount: 2,
|
|
||||||
angle: 120,
|
|
||||||
spread: 55,
|
|
||||||
origin: { x: 1 },
|
|
||||||
})
|
|
||||||
|
|
||||||
// keep going until we are out of time
|
|
||||||
if (Date.now() < end) {
|
|
||||||
requestAnimationFrame(frame)
|
|
||||||
}
|
|
||||||
}())
|
|
||||||
centerFire(0.25, {
|
|
||||||
spread: 26,
|
|
||||||
startVelocity: 55,
|
|
||||||
})
|
|
||||||
centerFire(0.2, {
|
|
||||||
spread: 60,
|
|
||||||
})
|
|
||||||
centerFire(0.35, {
|
|
||||||
spread: 100,
|
|
||||||
decay: 0.91,
|
|
||||||
scalar: 0.8,
|
|
||||||
})
|
|
||||||
centerFire(0.1, {
|
|
||||||
spread: 120,
|
|
||||||
startVelocity: 25,
|
|
||||||
decay: 0.92,
|
|
||||||
scalar: 1.2,
|
|
||||||
})
|
|
||||||
centerFire(0.1, {
|
|
||||||
spread: 120,
|
|
||||||
startVelocity: 45,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function centerFire(particleRatio: number, opts: any) {
|
|
||||||
const count = 200
|
|
||||||
confetti({
|
|
||||||
origin: { y: 0.7 },
|
|
||||||
...opts,
|
|
||||||
particleCount: Math.floor(count * particleRatio),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultPersonList() {
|
function setDefaultPersonList() {
|
||||||
@@ -614,23 +489,23 @@ function listenKeyboard(e: any) {
|
|||||||
if ((e.keyCode !== 32 || e.keyCode !== 27) && !canOperate.value) {
|
if ((e.keyCode !== 32 || e.keyCode !== 27) && !canOperate.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (e.keyCode === 27 && currentStatus.value === 3) {
|
if (e.keyCode === 27 && currentStatus.value === LotteryStatus.running) {
|
||||||
quitLottery()
|
quitLottery()
|
||||||
}
|
}
|
||||||
if (e.keyCode !== 32) {
|
if (e.keyCode !== 32) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (currentStatus.value) {
|
switch (currentStatus.value) {
|
||||||
case 0:
|
case LotteryStatus.init:
|
||||||
enterLottery()
|
enterLottery()
|
||||||
break
|
break
|
||||||
case 1:
|
case LotteryStatus.ready:
|
||||||
startLottery()
|
startLottery()
|
||||||
break
|
break
|
||||||
case 2:
|
case LotteryStatus.running:
|
||||||
stopLottery()
|
stopLottery()
|
||||||
break
|
break
|
||||||
case 3:
|
case LotteryStatus.end:
|
||||||
continueLottery()
|
continueLottery()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -639,7 +514,6 @@ function listenKeyboard(e: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
// animationRunning.value = false
|
|
||||||
clearInterval(intervalTimer.value)
|
clearInterval(intervalTimer.value)
|
||||||
intervalTimer.value = null
|
intervalTimer.value = null
|
||||||
if (scene.value) {
|
if (scene.value) {
|
||||||
@@ -685,7 +559,7 @@ function cleanup() {
|
|||||||
controls.value = null
|
controls.value = null
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initTableData()
|
tableData.value = initTableData({ allPersonList: allPersonList.value, rowCount: rowCount.value })
|
||||||
init()
|
init()
|
||||||
animation()
|
animation()
|
||||||
containerRef.value!.style.color = `${textColor}`
|
containerRef.value!.style.color = `${textColor}`
|
||||||
@@ -728,11 +602,11 @@ onUnmounted(() => {
|
|||||||
<div id="container" ref="containerRef" class="3dContainer">
|
<div id="container" ref="containerRef" class="3dContainer">
|
||||||
<!-- 选中菜单结构 start -->
|
<!-- 选中菜单结构 start -->
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<button v-if="currentStatus === 0 && tableData.length > 0" class="btn-end " @click="enterLottery">
|
<button v-if="currentStatus === LotteryStatus.init && tableData.length > 0" class="btn-end " @click="enterLottery">
|
||||||
{{ t('button.enterLottery') }}
|
{{ t('button.enterLottery') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-if="currentStatus === 1" class="start">
|
<div v-if="currentStatus === LotteryStatus.ready" class="start">
|
||||||
<button class="btn-start" @click="startLottery">
|
<button class="btn-start" @click="startLottery">
|
||||||
<strong>{{ t('button.start') }}</strong>
|
<strong>{{ t('button.start') }}</strong>
|
||||||
<div id="container-stars">
|
<div id="container-stars">
|
||||||
@@ -746,11 +620,11 @@ onUnmounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="currentStatus === 2" class="btn-end btn glass btn-lg" @click="stopLottery">
|
<button v-if="currentStatus === LotteryStatus.running" class="btn-end btn glass btn-lg" @click="stopLottery">
|
||||||
{{ t('button.selectLucky') }}
|
{{ t('button.selectLucky') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-if="currentStatus === 3" class="flex justify-center gap-6 enStop">
|
<div v-if="currentStatus === LotteryStatus.end" class="flex justify-center gap-6 enStop">
|
||||||
<div class="start">
|
<div class="start">
|
||||||
<button class="btn-start" @click="continueLottery">
|
<button class="btn-start" @click="continueLottery">
|
||||||
<strong>{{ t('button.continue') }}</strong>
|
<strong>{{ t('button.continue') }}</strong>
|
||||||
|
|||||||
Reference in New Issue
Block a user