feat(for): for

This commit is contained in:
ex_zhangwenlei@exiot.cmcc
2024-01-09 01:03:42 +08:00
parent bea54865ea
commit f34e850ff0
26 changed files with 500 additions and 404 deletions

View File

@@ -38,7 +38,7 @@ module.exports = {
], ],
"no-console": "warn", "no-console": "warn",
"no-debugger": "warn", "no-debugger": "warn",
complexity: ["warn", { max: 5 }], // complexity: ["warn", { max: 5 }],
// 禁止使用多个空格 // 禁止使用多个空格
"no-multi-spaces": "error", "no-multi-spaces": "error",
// 最大连续空行数 // 最大连续空行数

View File

@@ -21,6 +21,7 @@
"@tsparticles/vue3": "^3.0.0", "@tsparticles/vue3": "^3.0.0",
"@vueuse/core": "^10.6.1", "@vueuse/core": "^10.6.1",
"axios": "^1.6.1", "axios": "^1.6.1",
"canvas-confetti": "^1.9.2",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"particles.vue3": "^2.12.0", "particles.vue3": "^2.12.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
@@ -36,6 +37,7 @@
"vue": "^3.3.8", "vue": "^3.3.8",
"vue-accessible-color-picker": "^5.0.1", "vue-accessible-color-picker": "^5.0.1",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-toast-notification": "^3",
"vue3-colorpicker": "^2.2.3", "vue3-colorpicker": "^2.2.3",
"xlsx": "^0.18.5", "xlsx": "^0.18.5",
"zod": "^3.22.4" "zod": "^3.22.4"
@@ -45,6 +47,7 @@
"@iconify-json/fluent": "^1.1.40", "@iconify-json/fluent": "^1.1.40",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@testing-library/vue": "^8.0.0", "@testing-library/vue": "^8.0.0",
"@types/canvas-confetti": "^1.6.4",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"@types/three": "^0.160.0", "@types/three": "^0.160.0",
"@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/eslint-plugin": "^6.11.0",

26
pnpm-lock.yaml generated
View File

@@ -29,6 +29,9 @@ dependencies:
axios: axios:
specifier: ^1.6.1 specifier: ^1.6.1
version: 1.6.1 version: 1.6.1
canvas-confetti:
specifier: ^1.9.2
version: 1.9.2
localforage: localforage:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
@@ -74,6 +77,9 @@ dependencies:
vue-router: vue-router:
specifier: ^4.2.5 specifier: ^4.2.5
version: 4.2.5(vue@3.3.8) version: 4.2.5(vue@3.3.8)
vue-toast-notification:
specifier: ^3
version: 3.1.2(vue@3.3.8)
vue3-colorpicker: vue3-colorpicker:
specifier: ^2.2.3 specifier: ^2.2.3
version: 2.2.3(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@10.6.1)(gradient-parser@1.0.2)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1)(vue@3.3.8) version: 2.2.3(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@10.6.1)(gradient-parser@1.0.2)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1)(vue@3.3.8)
@@ -97,6 +103,9 @@ devDependencies:
'@testing-library/vue': '@testing-library/vue':
specifier: ^8.0.0 specifier: ^8.0.0
version: 8.0.0(@vue/compiler-sfc@3.4.5)(vue@3.3.8) version: 8.0.0(@vue/compiler-sfc@3.4.5)(vue@3.3.8)
'@types/canvas-confetti':
specifier: ^1.6.4
version: 1.6.4
'@types/node': '@types/node':
specifier: ^20.9.0 specifier: ^20.9.0
version: 20.9.0 version: 20.9.0
@@ -1098,6 +1107,10 @@ packages:
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
dev: true dev: true
/@types/canvas-confetti@1.6.4:
resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==}
dev: true
/@types/chai-subset@1.3.5: /@types/chai-subset@1.3.5:
resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==} resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==}
dependencies: dependencies:
@@ -2161,6 +2174,10 @@ packages:
resolution: {integrity: sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==} resolution: {integrity: sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==}
dev: true dev: true
/canvas-confetti@1.9.2:
resolution: {integrity: sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==}
dev: false
/cfb@1.2.2: /cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
@@ -6191,6 +6208,15 @@ packages:
he: 1.2.0 he: 1.2.0
dev: true dev: true
/vue-toast-notification@3.1.2(vue@3.3.8):
resolution: {integrity: sha512-oNRL/W9aaHoeScp+iTIW7k09vM16/+8aptp2maa+7qTB43JuxmAgKdXKFYtf+uvSNOYYq2BIWgLCeJ61pwom/A==}
engines: {node: '>=12.15.0'}
peerDependencies:
vue: ^3.0
dependencies:
vue: 3.3.8(typescript@5.2.2)
dev: false
/vue-tsc@1.8.22(typescript@5.2.2): /vue-tsc@1.8.22(typescript@5.2.2):
resolution: {integrity: sha512-j9P4kHtW6eEE08aS5McFZE/ivmipXy0JzrnTgbomfABMaVKx37kNBw//irL3+LlE3kOo63XpnRigyPC3w7+z+A==} resolution: {integrity: sha512-j9P4kHtW6eEE08aS5McFZE/ivmipXy0JzrnTgbomfABMaVKx37kNBw//irL3+LlE3kOo63XpnRigyPC3w7+z+A==}
hasBin: true hasBin: true

View File

@@ -1,17 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import { onMounted } from 'vue'
import useStore from '@/store' import useStore from '@/store'
import { storeToRefs } from 'pinia'
import PlayMusic from '@/components/PlayMusic/index.vue' import PlayMusic from '@/components/PlayMusic/index.vue'
import { themeChange } from 'theme-change' import { themeChange } from 'theme-change'
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const { getTheme: localTheme } = globalConfig const prizeConfig = useStore().prizeConfig
const { getTheme: localTheme } = storeToRefs(globalConfig)
const { getPrizeConfig: prizeList } = storeToRefs(prizeConfig)
const setLocalTheme = (theme: any) => { const setLocalTheme = (theme: any) => {
themeChange(theme.name) themeChange(theme.name)
} }
// 设置当前奖列表
const setCurrentPrize = () => {
if (prizeList.value.length <= 0) {
return
}
for (let i = 0; i < prizeList.value.length; i++) {
if (!prizeList.value[i].isUsed) {
prizeConfig.setCurrentPrize(prizeList.value[i])
break
}
}
return
}
onMounted(() => { onMounted(() => {
setLocalTheme(localTheme) setLocalTheme(localTheme.value)
setCurrentPrize()
}) })
</script> </script>
@@ -20,5 +40,4 @@ onMounted(() => {
<PlayMusic class="absolute right-0 bottom-1/2"></PlayMusic> <PlayMusic class="absolute right-0 bottom-1/2"></PlayMusic>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

1
src/components.d.ts vendored
View File

@@ -16,5 +16,6 @@ declare module 'vue' {
StarsBackground: typeof import('./components/StarsBackground/index.vue')['default'] StarsBackground: typeof import('./components/StarsBackground/index.vue')['default']
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default'] SvgIcon: typeof import('./components/SvgIcon/index.vue')['default']
Table: typeof import('./components/Table/index.vue')['default'] Table: typeof import('./components/Table/index.vue')['default']
ToTop: typeof import('./components/ToTop/index.vue')['default']
} }
} }

View File

@@ -14,9 +14,9 @@ const imageDbStore = localforage.createInstance({
const imgUrl=ref('') const imgUrl=ref('')
const getImageStoreItem=async (item:any)=>{ const getImageStoreItem=async (item:any):Promise<string>=>{
const key=item.id; const key=item.id;
const image=await imageDbStore.getItem(key) const image=await imageDbStore.getItem(key) as string
return image return image
} }

View File

@@ -67,6 +67,7 @@ const enterHome = () => {
onMounted(() => { onMounted(() => {
currentMusic.value = localMusicListValue.value[0] currentMusic.value = localMusicListValue.value[0]
onPlayEnd() onPlayEnd()
// 不使用空格控制audio
}) })
onUnmounted(() => { onUnmounted(() => {
audio.value.removeEventListener('ended', nextPlay) audio.value.removeEventListener('ended', nextPlay)

View File

@@ -0,0 +1,13 @@
<script setup lang='ts'>
</script>
<template>
<div class="fixed z-50 flex items-center justify-center w-10 h-10 rounded-full shadow-lg cursor-pointer right-12 bottom-12 bg-slate-700 hover:bg-slate-600">
<svg-icon name="totop"></svg-icon>
</div>
</template>
<style lang='scss' scoped>
</style>

View File

@@ -33,6 +33,23 @@ export const useElementStyle=(element:any,cardColor:string,cardSize:{width:numbe
return element return element
} }
export const useElementPosition=(element:any,count:number,cardSize:{width:number,height:number},containerSize:{width:number,height:number})=>{ export const useElementPosition=(element:any,count:number,cardSize:{width:number,height:number},windowSize:{width:number,height:number},cardIndex:number)=>{
const rowCount=Math.floor(windowSize.width/(cardSize.width+100))
const colCount=Math.ceil(count/rowCount)
const centerPosition={
x:0,
y:windowSize.height/2-cardSize.height/2
}
const index =cardIndex%5
if(index==0){
element.position.x=centerPosition.x
element.position.y=centerPosition.y-Math.floor(cardIndex/5)*(cardSize.height+60)
}
else{
element.position.x=index%2===0?Math.ceil(index/2)*(cardSize.width+100):-Math.ceil(index/2)*(cardSize.width+100)
element.position.y=centerPosition.y-Math.floor(cardIndex/5)*(cardSize.height+60)
}
return element
} }

1
src/icons/totop.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1704702652534" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4194" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><path d="M1013.64736 885.56544H11.13088c-6.12352 0-11.13088 5.00736-11.13088 11.14112v83.53792c0 6.12352 5.00736 11.14112 11.13088 11.14112h1002.50624c6.13376 0 11.14112-5.0176 11.14112-11.14112v-83.53792c0.01024-6.12352-4.99712-11.14112-11.13088-11.14112zM338.33984 253.00992h102.90176v471.04c0 6.12352 5.00736 11.14112 11.14112 11.14112h83.53792c6.12352 0 11.13088-5.0176 11.13088-11.14112v-471.04h103.17824c9.32864 0 14.47936-10.72128 8.76544-17.96096L503.06048 37.61152c-4.4544-5.71392-13.08672-5.71392-17.54112 0L329.5744 234.91584c-5.71392 7.3728-0.5632 18.09408 8.76544 18.09408z m0 0" p-id="4195"></path></svg>

After

Width:  |  Height:  |  Size: 951 B

View File

@@ -1,6 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
// import Header from './Header/index.vue'; // import Header from './Header/index.vue';
// import Footer from './Footer/index.vue'; // import Footer from './Footer/index.vue';
import {ref} from 'vue';
import ToTop from '@/components/ToTop/index.vue'
import { useScroll } from '@vueuse/core'
const mainContainer = ref<HTMLElement | null>(null)
const { y} = useScroll(mainContainer)
const scrollToTop=()=>{
y.value=0
}
</script> </script>
<template> <template>
@@ -8,8 +19,10 @@
<!-- <header class="shadow-2xl head-container h-14"> <!-- <header class="shadow-2xl head-container h-14">
<Header></Header> <Header></Header>
</header> --> </header> -->
<main class="box-content w-screen h-screen overflow-x-hidden overflow-y-auto main-container"> <ToTop @click="scrollToTop" v-if="y>400"></ToTop>
<main ref="mainContainer" class="box-content w-screen h-screen overflow-x-hidden overflow-y-auto main-container">
<router-view class="h-full main-container-content"></router-view> <router-view class="h-full main-container-content"></router-view>
</main> </main>
<!-- <footer class="w-screen footer-container"> <!-- <footer class="w-screen footer-container">
<Footer></Footer> <Footer></Footer>

View File

@@ -65,6 +65,7 @@ export const defaultPrizeList=<IPrizeConfig[]>[
sort:1, sort:1,
isAll:true, isAll:true,
count:1, count:1,
isUsedCount:0,
picture:{ picture:{
id:'0', id:'0',
name:'一等奖', name:'一等奖',
@@ -81,6 +82,7 @@ export const defaultPrizeList=<IPrizeConfig[]>[
sort:2, sort:2,
isAll:true, isAll:true,
count:1, count:1,
isUsedCount:0,
picture: { picture: {
id:'1', id:'1',
name:'二等奖', name:'二等奖',
@@ -97,6 +99,7 @@ export const defaultPrizeList=<IPrizeConfig[]>[
sort:3, sort:3,
isAll:true, isAll:true,
count:1, count:1,
isUsedCount:0,
picture: { picture: {
id:'2', id:'2',
name:'三等奖', name:'三等奖',
@@ -113,6 +116,7 @@ export const defaultPrizeList=<IPrizeConfig[]>[
sort:4, sort:4,
isAll:true, isAll:true,
count:1, count:1,
isUsedCount:0,
picture: { picture: {
id:'3', id:'3',
name:'超级奖', name:'超级奖',
@@ -129,6 +133,7 @@ export const defaultPrizeList=<IPrizeConfig[]>[
sort:5, sort:5,
isAll:true, isAll:true,
count:1, count:1,
isUsedCount:0,
picture:{ picture:{
id:'4', id:'4',
name:'特别奖', name:'特别奖',

View File

@@ -7,6 +7,7 @@ export const useGlobalConfig = defineStore('global', {
globalConfig: { globalConfig: {
rowCount: 12, rowCount: 12,
isSHowPrizeList:true, isSHowPrizeList:true,
topTitle:'大明内阁六部御前奏对',
theme: { theme: {
name: 'dark', name: 'dark',
detail: { primary: '#0f5fd3' }, detail: { primary: '#0f5fd3' },
@@ -27,6 +28,10 @@ export const useGlobalConfig = defineStore('global', {
getGlobalConfig(state) { getGlobalConfig(state) {
return state.globalConfig; return state.globalConfig;
}, },
// 获取标题
getTopTitle(state) {
return state.globalConfig.topTitle;
},
// 获取行数 // 获取行数
getRowCount(state) { getRowCount(state) {
return state.globalConfig.rowCount; return state.globalConfig.rowCount;
@@ -77,6 +82,10 @@ export const useGlobalConfig = defineStore('global', {
setRowCount(rowCount: number) { setRowCount(rowCount: number) {
this.globalConfig.rowCount = rowCount; this.globalConfig.rowCount = rowCount;
}, },
// 设置标题
setTopTitle(topTitle: string) {
this.globalConfig.topTitle = topTitle;
},
// 设置主题 // 设置主题
setTheme(theme: any) { setTheme(theme: any) {
const { name, detail } = theme; const { name, detail } = theme;
@@ -165,6 +174,7 @@ export const useGlobalConfig = defineStore('global', {
reset() { reset() {
this.globalConfig = { this.globalConfig = {
rowCount: 12, rowCount: 12,
topTitle:'大明内阁六部御前奏对',
isSHowPrizeList:true, isSHowPrizeList:true,
theme: { theme: {
name: 'dark', name: 'dark',

View File

@@ -1,5 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { IPersonConfig } from '@/types/personConfig'; import { IPersonConfig } from '@/types/personConfig';
import { IPrizeConfig } from '@/types/prizeConfig';
export const usePersonConfig = defineStore('person', { export const usePersonConfig = defineStore('person', {
state() { state() {
return { return {
@@ -48,16 +49,31 @@ export const usePersonConfig = defineStore('person', {
}); });
}, },
// 添加已中奖人员 // 添加已中奖人员
addAlreadyPersonList(personList: IPersonConfig[]) { addAlreadyPersonList(personList: IPersonConfig[], prize: IPrizeConfig | null) {
if (personList.length <= 0) { if (personList.length <= 0) {
return return
} }
personList.forEach((person: IPersonConfig) => { personList.forEach((person: IPersonConfig) => {
this.personConfig.notPersonList = this.personConfig.notPersonList.filter((item: IPersonConfig) => this.personConfig.notPersonList = this.personConfig.notPersonList.filter((item: IPersonConfig) =>
item.id !== person.id) item.id !== person.id)
if (prize != null) {
person.isWin = true
person.prizeName = prize.name
person.prizeTime = new Date().toString()
}
this.personConfig.alreadyPersonList.push(person); this.personConfig.alreadyPersonList.push(person);
}); });
}, },
// 从已中奖移动到未中奖
moveAlreadyToNot(person: IPersonConfig) {
if (person.id != undefined || person.id != null) {
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) => item.id !== person.id);
person.isWin = false
person.prizeTime = ''
person.prizeName = ''
this.personConfig.notPersonList.push(person);
}
},
// 删除指定人员 // 删除指定人员
deletePerson(person: IPersonConfig) { deletePerson(person: IPersonConfig) {
if (person.id != undefined || person.id != null) { if (person.id != undefined || person.id != null) {
@@ -70,14 +86,7 @@ export const usePersonConfig = defineStore('person', {
this.personConfig.alreadyPersonList = []; this.personConfig.alreadyPersonList = [];
this.personConfig.notPersonList = []; this.personConfig.notPersonList = [];
}, },
// 设置table列数
setTableRowCount(tableRowCount: number) {
this.personConfig.tableRowCount = tableRowCount;
},
// 设置要展示那些字段
setShowFields(showField: any[]) {
this.personConfig.showField = showField;
},
// 重置所有人员 // 重置所有人员
resetPerson() { resetPerson() {
this.personConfig.alreadyPersonList = []; this.personConfig.alreadyPersonList = [];

View File

@@ -12,6 +12,7 @@ export const usePrizeConfig = defineStore('prize', {
sort: 1, sort: 1,
isAll: true, isAll: true,
count: 1, count: 1,
isUsedCount:0,
picture: { picture: {
id: '0', id: '0',
name: '一等奖', name: '一等奖',
@@ -63,6 +64,10 @@ export const usePrizeConfig = defineStore('prize', {
updatePrizeConfig(prizeConfigItem: IPrizeConfig) { updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
const index = this.prizeConfig.prizeList.findIndex(item => item.id === prizeConfigItem.id); const index = this.prizeConfig.prizeList.findIndex(item => item.id === prizeConfigItem.id);
this.prizeConfig.prizeList[index] = prizeConfigItem; this.prizeConfig.prizeList[index] = prizeConfigItem;
if(prizeConfigItem.isUsed&&index+1<this.prizeConfig.prizeList.length){
// 设置下一个为currentPrize
this.setCurrentPrize(this.prizeConfig.prizeList[index+1]);
}
}, },
// 删除全部奖项 // 删除全部奖项
deleteAllPrizeConfig() { deleteAllPrizeConfig() {
@@ -82,6 +87,7 @@ export const usePrizeConfig = defineStore('prize', {
sort: 1, sort: 1,
isAll: true, isAll: true,
count: 1, count: 1,
isUsedCount:0,
picture: { picture: {
id: '0', id: '0',
name: '一等奖', name: '一等奖',
@@ -101,7 +107,7 @@ export const usePrizeConfig = defineStore('prize', {
{ {
// 如果要存储在localStorage中 // 如果要存储在localStorage中
storage: localStorage, storage: localStorage,
key: 'personConfig', key: 'prizeConfig',
}, },
], ],
}, },

View File

@@ -3,8 +3,11 @@ export interface IPersonConfig {
uid: string; uid: string;
name: string; name: string;
department:string; department:string;
other:string;
isWin:boolean; isWin:boolean;
x:number; x:number;
y:number y:number
createTime: string;
updateTime: string;
prizeName: string;
prizeTime: string;
} }

View File

@@ -4,6 +4,7 @@ export interface IPrizeConfig {
sort:number; sort:number;
isAll:boolean; isAll:boolean;
count:number; count:number;
isUsedCount:number,
picture:{ picture:{
id:string|number, id:string|number,
name:string, name:string,

View File

@@ -15,3 +15,15 @@ export const filterData = (tableData: any[],localRowCount: number) => {
return tableData return tableData
} }
export const addOtherInfo=(personList:any[])=>{
const len=personList.length;
for(let i=0;i<len;i++){
personList[i].createTime=new Date().toString();
personList[i].updateTime=new Date().toString();
personList[i].prizeName='';
personList[i].prizeTime='';
}
return personList
}

View File

@@ -12,7 +12,7 @@ import { isRgbOrRgba, isHex } from '@/utils/color'
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const { getTheme: localTheme, getCardColor: cardColor,getLuckyColor:luckyCardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount,getIsShowPrizeList:isShowPrizeList } = storeToRefs(globalConfig) const {getTopTitle:topTitle, getTheme: localTheme, getCardColor: cardColor,getLuckyColor:luckyCardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount,getIsShowPrizeList:isShowPrizeList } = storeToRefs(globalConfig)
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig) const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
const colorPickerRef = ref() const colorPickerRef = ref()
@@ -21,6 +21,7 @@ interface ThemeDaType {
} }
const isRowCountChange = ref(0) //0未改变1改变,2加载中 const isRowCountChange = ref(0) //0未改变1改变,2加载中
const themeValue = ref(localTheme.value.name) const themeValue = ref(localTheme.value.name)
const topTitleValue= ref(structuredClone(topTitle.value))
const cardColorValue = ref(structuredClone(cardColor.value)) const cardColorValue = ref(structuredClone(cardColor.value))
const luckyCardColorValue = ref(structuredClone(luckyCardColor.value)) const luckyCardColorValue = ref(structuredClone(luckyCardColor.value))
const textColorValue = ref(structuredClone(textColor.value)) const textColorValue = ref(structuredClone(textColor.value))
@@ -71,7 +72,7 @@ const resetPersonLayout = () => {
const newNotPersonList = newList.slice(alreadyLen, notLen + alreadyLen) const newNotPersonList = newList.slice(alreadyLen, notLen + alreadyLen)
personConfig.deleteAllPerson() personConfig.deleteAllPerson()
personConfig.addNotPersonList(newNotPersonList) personConfig.addNotPersonList(newNotPersonList)
personConfig.addAlreadyPersonList(newAlreadyPersonList) personConfig.addAlreadyPersonList(newAlreadyPersonList,null)
isRowCountChange.value = 0 isRowCountChange.value = 0
}, 1000) }, 1000)
@@ -130,6 +131,15 @@ onMounted(() => {
<template> <template>
<div> <div>
<label class="flex flex-row items-center w-full gap-24 mb-10 form-control">
<div class="">
<div class="label">
<span class="label-text">标题</span>
</div>
<input type="text" v-model="topTitleValue" placeholder="输入标题"
class="w-full max-w-xs input input-bordered" />
</div>
</label>
<label class="flex flex-row items-center w-full gap-24 mb-10 form-control"> <label class="flex flex-row items-center w-full gap-24 mb-10 form-control">
<div class=""> <div class="">
<div class="label"> <div class="label">

View File

@@ -5,7 +5,7 @@ import useStore from '@/store'
import {storeToRefs } from 'pinia' import {storeToRefs } from 'pinia'
import * as XLSX from 'xlsx' import * as XLSX from 'xlsx'
import { readFile } from '@/utils/file' import { readFile } from '@/utils/file'
import {filterData} from '@/utils' import {filterData,addOtherInfo} from '@/utils'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue' import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
@@ -23,9 +23,9 @@ const handleFileChange = async (e: any) => {
let workSheet = workBook.Sheets[workBook.SheetNames[0]] let workSheet = workBook.Sheets[workBook.SheetNames[0]]
excelData.value = XLSX.utils.sheet_to_json(workSheet) excelData.value = XLSX.utils.sheet_to_json(workSheet)
const uploadData = filterData(excelData.value,rowCount.value) const uploadData = filterData(excelData.value,rowCount.value)
const allData=addOtherInfo(uploadData);
personConfig.resetPerson() personConfig.resetPerson()
personConfig.addNotPersonList(uploadData) personConfig.addNotPersonList(allData)
} }
const deleteAll = () => { const deleteAll = () => {

View File

@@ -2,19 +2,22 @@
<script setup lang='ts'> <script setup lang='ts'>
import { ref } from 'vue'; import { ref } from 'vue';
import useStore from '@/store' import useStore from '@/store'
import { storeToRefs } from 'pinia';
import DaiysuiTable from '@/components/DaiysuiTable/index.vue' import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const { getAlreadyPersonList: alreadyPersonList } = personConfig const { getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const personList = ref<any[]>( // const personList = ref<any[]>(
alreadyPersonList // alreadyPersonList
) // )
const deleteAll = () => { const deleteAll = () => {
personConfig.deleteAllPerson() personConfig.deleteAllPerson()
personList.value = alreadyPersonList }
const handleMoveNotPerson=(row:any)=>{
personConfig.moveAlreadyToNot(row)
} }
const tableColumns = [ const tableColumns = [
@@ -34,14 +37,23 @@ const tableColumns = [
label: '职位', label: '职位',
props: 'other', props: 'other',
}, },
{
label:'奖品',
props:'prizeName'
},
{
label: '中奖时间',
props: 'prizeTime',
},
{ {
label: '操作', label: '操作',
actions: [ actions: [
{ {
label: '编辑', label: '移入未中奖名单',
type: 'btn-info', type: 'btn-info',
onClick: (row: any) => { onClick: (row: any) => {
console.log('编辑:', row) handleMoveNotPerson(row)
} }
}, },
{ {
@@ -63,7 +75,7 @@ const tableColumns = [
<button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button> <button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button>
</div> </div>
<DaiysuiTable :tableColumns="tableColumns" :data="personList"></DaiysuiTable> <DaiysuiTable :tableColumns="tableColumns" :data="alreadyPersonList"></DaiysuiTable>
</div> </div>
</template> </template>

View File

@@ -10,7 +10,7 @@ const imageDbStore = localforage.createInstance({
}) })
const prizeConfig = useStore().prizeConfig const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const { getPrizeConfig:localPrizeList} = storeToRefs(prizeConfig) const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const { getImageList: localImageList } = storeToRefs(globalConfig) const { getImageList: localImageList } = storeToRefs(globalConfig)
const prizeList = ref(localPrizeList) const prizeList = ref(localPrizeList)
@@ -22,6 +22,7 @@ const addPrize = () => {
sort: 0, sort: 0,
isAll: true, isAll: true,
count: 1, count: 1,
isUsedCount:0,
picture: { picture: {
id: '', id: '',
name: '', name: '',
@@ -85,19 +86,24 @@ watch(()=>prizeList,()=>{
</div> </div>
<ul> <ul>
<li v-for="item in prizeList" :key="item.id" class="flex gap-10"> <li v-for="item in prizeList" :key="item.id" class="flex gap-10"
:class="currentPrize.id == item.id ? 'border-1 border-solid rounded-xl' : null">
<label class="max-w-xs mb-10 form-control"> <label class="max-w-xs mb-10 form-control">
<!-- 向上向下 --> <!-- 向上向下 -->
<div class="flex flex-col items-center gap-2 pt-5"> <div class="flex flex-col items-center gap-2 pt-5">
<svg-icon class="cursor-pointer hover:text-blue-400" :class="prizeList.indexOf(item)==0?'opacity-0 cursor-default':''" name="up" @click="sort(item,1)"></svg-icon> <svg-icon class="cursor-pointer hover:text-blue-400"
<svg-icon class="cursor-pointer hover:text-blue-400" name="down" @click="sort(item,0)" :class="prizeList.indexOf(item)==prizeList.length-1?'opacity-0 cursor-default':''"></svg-icon> :class="prizeList.indexOf(item) == 0 ? 'opacity-0 cursor-default' : ''" name="up"
@click="sort(item, 1)"></svg-icon>
<svg-icon class="cursor-pointer hover:text-blue-400" name="down" @click="sort(item, 0)"
:class="prizeList.indexOf(item) == prizeList.length - 1 ? 'opacity-0 cursor-default' : ''"></svg-icon>
</div> </div>
</label> </label>
<label class="w-full max-w-xs mb-10 form-control"> <label class="w-full max-w-xs mb-10 form-control">
<div class="label"> <div class="label">
<span class="label-text">名称</span> <span class="label-text">名称</span>
</div> </div>
<input type="text" v-model="item.name" placeholder="名称" class="w-full max-w-xs input-sm input input-bordered" /> <input type="text" v-model="item.name" placeholder="名称"
class="w-full max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="w-full max-w-xs mb-10 form-control"> <label class="w-full max-w-xs mb-10 form-control">
<div class="label"> <div class="label">
@@ -113,13 +119,20 @@ watch(()=>prizeList,()=>{
<input type="number" v-model="item.count" placeholder="获奖人数" <input type="number" v-model="item.count" placeholder="获奖人数"
class="w-full max-w-xs input-sm input input-bordered" /> class="w-full max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">已获奖人数</span>
</div>
<input disabled type="number" v-model="item.isUsedCount" placeholder="获奖人数" class="w-full max-w-xs input-sm input input-bordered" />
</label>
<label class="w-full max-w-xs mb-10 form-control"> <label class="w-full max-w-xs mb-10 form-control">
<div class="label"> <div class="label">
<span class="label-text">图片</span> <span class="label-text">图片</span>
</div> </div>
<select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture"> <select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture">
<option disabled selected>选择一张图片</option> <option disabled selected>选择一张图片</option>
<option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}</option> <option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}
</option>
</select> </select>
</label> </label>
<label class="w-full max-w-xs mb-10 form-control"> <label class="w-full max-w-xs mb-10 form-control">
@@ -133,7 +146,8 @@ watch(()=>prizeList,()=>{
<div class="label"> <div class="label">
<span class="label-text">抽取次数</span> <span class="label-text">抽取次数</span>
</div> </div>
<input type="text" v-model="item.frequency" placeholder="抽取次数" class="w-full max-w-xs input-sm input input-bordered" /> <input type="text" v-model="item.frequency" placeholder="抽取次数"
class="w-full max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="w-full max-w-xs mb-10 form-control"> <label class="w-full max-w-xs mb-10 form-control">
<div class="label"> <div class="label">
@@ -145,7 +159,6 @@ watch(()=>prizeList,()=>{
</label> </label>
</li> </li>
</ul> </ul>
</div> </div></template>
</template>
<style lang='scss' scoped></style> <style lang='scss' scoped></style>

View File

@@ -71,11 +71,9 @@ const skip = (path: string) => {
</nav> </nav>
<nav> <nav>
<div class="grid grid-flow-col gap-4"> <div class="grid grid-flow-col gap-4">
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"> <a href="https://github.com/LOG1997/log-lottery" target="_blank" class="cursor-pointer">
<path <svg-icon name="github"></svg-icon>
d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"> </a>
</path>
</svg></a>
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"> <a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
<path <path
d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"> d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z">

View File

@@ -1,225 +0,0 @@
<script setup lang="ts">
import { ref, onMounted,computed,watch,toRef } from 'vue'
// import { tableData2 as tableData } from './data'
import { rgba } from '@/utils/color'
import {filterData} from '@/utils'
import * as THREE from 'three'
import {
CSS3DRenderer, CSS3DObject
} from 'three/examples/jsm/renderers/CSS3DRenderer.js';
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';
import TWEEN from 'three/examples/jsm/libs/tween.module.js';
import useStore from '@/store'
const props=defineProps({
luckyPersonList:{
type:Array as ()=>Array<any>,
default:()=>{[]}
}
})
// const emits=defineEmits()=
const globalConfig = useStore().globalConfig
const { getLuckyColor: luckyCardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount } = globalConfig
const tableData = computed(()=>{
console.log('pppppprops:',props.luckyPersonList)
const cloneData=toRef(props.luckyPersonList)
console.log(props.luckyPersonList,cloneData.value)
const luckyList=filterData(cloneData.value,Math.floor(rowCount/2))
console.log('lucskskskskksks:',luckyList)
return luckyList
})
const luckyContainerRef = ref<HTMLElement>()
const scene = ref()
const camera = ref()
const renderer = ref()
const controls = ref()
const objects = ref<any[]>([])
const targets = {
table: <any[]>[],
};
const init = () => {
const felidView = 40;
const width = window.innerWidth;
const height = window.innerHeight;
const aspect = width / height;
const nearPlane = 1;
const farPlane = 10000;
const WebGLoutput = luckyContainerRef.value
scene.value = new THREE.Scene();
camera.value = new THREE.PerspectiveCamera(felidView, aspect, nearPlane, farPlane);
camera.value.position.z = 3000;
renderer.value = new CSS3DRenderer()
renderer.value.setSize(width, height)
renderer.value.domElement.style.position = 'absolute';
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++) {
const element = document.createElement('div');
element.className = 'lucky-element-card';
// element.style.backgroundColor = `rgba( 0, 127, 127, ${Math.random() * 0.5 + 0.25} )`;
element.style.backgroundColor = rgba(luckyCardColor, Math.random() * 0.5 + 0.25)
element.style.border = `1px solid ${rgba(luckyCardColor, 0.25)}`
element.style.boxShadow = `0 0 12px ${rgba(luckyCardColor, 0.5)}`
// hover style
element.addEventListener('mouseover', function () {
this.style.border = `1px solid ${rgba(luckyCardColor, 0.75)}`
this.style.boxShadow = `0 0 12px ${rgba(luckyCardColor, 0.75)}`
})
element.addEventListener('mouseout', function () {
this.style.border = `1px solid ${rgba(luckyCardColor, 0.25)}`
this.style.boxShadow = `0 0 12px ${rgba(luckyCardColor, 0.5)}`
})
element.style.width = `${cardSize.width*2}px`;
element.style.height = `${cardSize.height*2}px`;
// element.style.color=localTheme.detail.primary
const number = document.createElement('div');
number.className = 'lucky-card-id';
// number.textContent = (i / 5 + 1).toString();
number.textContent = tableData.value[i].uid;
number.style.fontSize = `${textSize}px`;
element.appendChild(number);
const symbol = document.createElement('div');
symbol.className = 'lucky-card-name';
symbol.textContent = tableData.value[i].name;
symbol.style.textShadow = `0 0 12px ${rgba(luckyCardColor, 0.95)}`
symbol.style.fontSize = `${textSize*2}px`;
element.appendChild(symbol);
const detail = document.createElement('div');
detail.className = 'lucky-card-detail';
detail.innerHTML = `${tableData.value[i].department}<br/>${tableData.value[i].other}`;
detail.style.fontSize = `${textSize}px`;
element.appendChild(detail);
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);
}
createTableVertices();
function createTableVertices() {
const tableLen = tableData.value.length;
for (let i = 0; i < tableLen; i++) {
const object = new THREE.Object3D();
object.position.x = tableData.value[i].x * (cardSize.width*2 + 40) - rowCount * 90;
object.position.y = -tableData.value[i].y * (cardSize.height*2 + 20) + 1000;
object.position.z = 0;
targets.table.push(object);
}
}
window.addEventListener('resize', onWindowResize, false);
transform(targets.table, 2000)
render();
}
const transform = (targets: any[], duration: number) => {
TWEEN.removeAll();
const objLength = objects.value.length;
for (let i = 0; i < objLength; ++i) {
let object = objects.value[i];
let target = targets[i];
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();
}
// 这个补间用来在位置与旋转补间同步执行通过onUpdate在每次更新数据后渲染scene和camera
new TWEEN.Tween({})
.to({}, duration * 2)
.onUpdate(render)
.start();
// 整体自动旋转
}
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();
controls.value.update();
// 设置自动旋转
// console.log('animation',controls.value.target);
// 设置相机位置
requestAnimationFrame(animation);
}
function render() {
renderer.value.render(scene.value, camera.value);
}
const submit=()=>{
tableData.value.length=0;
}
onMounted(() => {
});
watch(()=>props.luckyPersonList,()=>{
luckyContainerRef.value!.style.zIndex='100'
init();
animation();
luckyContainerRef.value!.style.color = `${textColor}`
console.log('tableData',tableData.value)
},{deep:true})
</script>
<template>
<div class="absolute top-0 w-full h-full bg-gray-400/10 -z-50" ref="luckyContainerRef">
<button class="btn glass" @click="submit">确定</button>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -19,17 +19,6 @@ const getPrizeListHeight = () => {
} }
const prizeShow = ref(structuredClone(isShowPrizeList.value)) const prizeShow = ref(structuredClone(isShowPrizeList.value))
const delAll = () => {
prizeConfig.deleteAllPrizeConfig()
}
const resetDefault = () => {
prizeConfig.resetDefault()
}
const print = () => {
console.log(prizeConfig)
}
const addTemporaryPrize = () => { const addTemporaryPrize = () => {
console.log('addTemporaryPrize') console.log('addTemporaryPrize')
} }
@@ -44,23 +33,25 @@ onMounted(() => {
<transition name="prize-list" :appear="true"> <transition name="prize-list" :appear="true">
<div v-if="prizeShow" class="flex items-center"> <div v-if="prizeShow" class="flex items-center">
<ul class="flex flex-col gap-1 p-2 rounded-xl bg-slate-500/50" ref="prizeListRef"> <ul class="flex flex-col gap-1 p-2 rounded-xl bg-slate-500/50" ref="prizeListRef">
<li v-for="item in localPrizeList" :key="item.id" <li v-for="item in localPrizeList" :key="item.id"
:class="currentPrize.id == item.id ? 'current-prize' : ''"> :class="currentPrize.id == item.id ? 'current-prize' : ''">
<div <div
class="relative flex flex-row items-center justify-between w-64 h-20 shadow-xl card bg-base-100"> class="relative flex flex-row items-center justify-between w-64 h-20 shadow-xl card bg-base-100" v-if="item.isShow">
<div v-if="item.isUsed" class="absolute w-full h-full bg-gray-800/90 item-mask z-200 rounded-xl"></div> <div v-if="item.isUsed" class="absolute z-50 w-full h-full bg-gray-800/70 item-mask rounded-xl"></div>
<figure class="w-10 h-10 rounded-xl"> <figure class="w-10 h-10 rounded-xl">
<img :src="item.picture.url" alt="Shoes" class="object-cover h-full rounded-xl" /> <img :src="item.picture.url" alt="Shoes" class="object-cover h-full rounded-xl" />
</figure> </figure>
<div class="items-center text-center card-body"> <div class="items-center p-0 text-center card-body">
<h2 class="card-title">{{ item.name }}</h2> <h2 class="p-0 m-0 card-title">{{ item.name }}</h2>
<p class="absolute z-40 p-0 m-0 text-gray-300/80 pt-9">{{ item.isUsedCount }}/{{ item.count }}</p>
<progress class="w-3/4 h-6 progress progress-primary" :value="item.isUsedCount" :max="item.count"></progress>
<!-- <p class="p-0 m-0">{{ item.isUsedCount }}/{{ item.count }}</p> -->
</div> </div>
</div> </div>
</li> </li>
</ul> </ul>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="tooltip" data-tip=""> <div class="tooltip" data-tip="项列表">
<div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50" <div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
@click="prizeShow = !prizeShow"> @click="prizeShow = !prizeShow">
<svg-icon name="arrow_left" class="w-full h-full"></svg-icon> <svg-icon name="arrow_left" class="w-full h-full"></svg-icon>
@@ -78,7 +69,7 @@ onMounted(() => {
</div> </div>
<transition name="prize-operate" :appear="true"> <transition name="prize-operate" :appear="true">
<div class="tooltip" data-tip="" v-show="!prizeShow"> <div class="tooltip" data-tip="项列表" v-show="!prizeShow">
<div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50" <div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
@click="prizeShow = !prizeShow"> @click="prizeShow = !prizeShow">
<svg-icon name="arrow_right" class="w-full h-full"></svg-icon> <svg-icon name="arrow_right" class="w-full h-full"></svg-icon>

View File

@@ -1,12 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted,onUnmounted, watch } from 'vue'
// import { tableData2 as tableData } from './data'
import { rgba } from '@/utils/color'
// import PlayMusic from './PlayMusic.vue'
import PrizeList from './PrizeList.vue' import PrizeList from './PrizeList.vue'
import { useElementStyle } from '@/hooks/useElement' import { useElementStyle, useElementPosition } from '@/hooks/useElement'
import StarsBackground from '@/components/StarsBackground/index.vue' import StarsBackground from '@/components/StarsBackground/index.vue'
import LuckyView from './LuckyThree.vue' import confetti from 'canvas-confetti'
import * as THREE from 'three' import * as THREE from 'three'
import { import {
CSS3DRenderer, CSS3DObject CSS3DRenderer, CSS3DObject
@@ -14,23 +11,26 @@ import {
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'; import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';
import TWEEN from 'three/examples/jsm/libs/tween.module.js'; import TWEEN from 'three/examples/jsm/libs/tween.module.js';
import useStore from '@/store' import useStore from '@/store'
import { storeToRefs } from 'pinia'
import { useToast } from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
const toast = useToast();
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const prizeConfig = useStore().prizeConfig const prizeConfig = useStore().prizeConfig
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = personConfig const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
const { getCurrentPrize: currentPrize } = prizeConfig const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const { getCardColor: cardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount } = globalConfig const {getTopTitle:topTitle, getCardColor: cardColor, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount } = storeToRefs(globalConfig)
const tableData = ref( const tableData = ref(JSON.parse(JSON.stringify(alreadyPersonList.value)).concat(JSON.parse(JSON.stringify(notPersonList.value))))
alreadyPersonList.concat(notPersonList)
)
const currentStatus = ref(0) // 0为初始状态 1为抽奖准备状态2为抽奖中状态3为抽奖结束状态 const currentStatus = ref(0) // 0为初始状态 1为抽奖准备状态2为抽奖中状态3为抽奖结束状态
const ballRotationY = ref(0) const ballRotationY = ref(0)
const containerRef = ref<HTMLElement>() const containerRef = ref<HTMLElement>()
// const LuckyViewRef= ref() // const LuckyViewRef= ref()
const canOperate = ref(true)
const cameraZ = ref(3000)
const scene = ref() const scene = ref()
const camera = ref() const camera = ref()
@@ -47,6 +47,7 @@ const targets = {
const luckyTargets = ref<any[]>([]) const luckyTargets = ref<any[]>([])
const luckyCardList = ref<any[]>([]) const luckyCardList = ref<any[]>([])
const currentPrizeValue=ref(JSON.parse(JSON.stringify(currentPrize.value)))
const init = () => { const init = () => {
const felidView = 40; const felidView = 40;
const width = window.innerWidth; const width = window.innerWidth;
@@ -58,11 +59,27 @@ const init = () => {
scene.value = new THREE.Scene(); scene.value = new THREE.Scene();
camera.value = new THREE.PerspectiveCamera(felidView, aspect, nearPlane, farPlane); camera.value = new THREE.PerspectiveCamera(felidView, aspect, nearPlane, farPlane);
camera.value.position.z = 3000; camera.value.position.z = cameraZ.value
// 侦听camera position变化
// watch(() => camera.value.position.z, (value) => {
// console.log('code line-63 \n\r😍 camara posi:\n\r',value);
// console.log('code line-63 \n\r😍 camara rrrr:\n\r',camera.value.rotation);
// cameraZ.value = value
// })
// watch(() => camera.value.rotation.z, (value) => {
// console.log('code line-68 \n\r😁 camraea rotation:\n\r',value);
// // cameraZ.value = value
// })
renderer.value = new CSS3DRenderer() renderer.value = new CSS3DRenderer()
renderer.value.setSize(width, height) renderer.value.setSize(width, height*0.9)
renderer.value.domElement.style.position = 'absolute'; 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); WebGLoutput!.appendChild(renderer.value.domElement);
controls.value = new TrackballControls(camera.value, renderer.value.domElement); controls.value = new TrackballControls(camera.value, renderer.value.domElement);
@@ -76,25 +93,6 @@ const init = () => {
for (let i = 0; i < tableLen; i++) { for (let i = 0; i < tableLen; i++) {
let element = document.createElement('div'); let element = document.createElement('div');
element.className = 'element-card'; element.className = 'element-card';
// element.style.backgroundColor = `rgba( 0, 127, 127, ${Math.random() * 0.5 + 0.25} )`;
// element.style.backgroundColor = rgba(cardColor, Math.random() * 0.5 + 0.25)
// element.style.border = `1px solid ${rgba(cardColor, 0.25)}`
// element.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.5)}`
// element.style.width = `${cardSize.width}px`;
// element.style.height = `${cardSize.height}px`;
// element.addEventListener('mouseover', function () {
// console.log(this)
// this.style.border = `1px solid ${rgba(cardColor, 0.75)}`
// this.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.75)}`
// })
// element.addEventListener('mouseout', function () {
// this.style.border = `1px solid ${rgba(cardColor, 0.25)}`
// this.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.5)}`
// })
// hover style
// element.style.color=localTheme.detail.primary
const number = document.createElement('div'); const number = document.createElement('div');
number.className = 'card-id'; number.className = 'card-id';
@@ -116,7 +114,7 @@ const init = () => {
// detail.style.fontSize = `${textSize * 0.5}px`; // detail.style.fontSize = `${textSize * 0.5}px`;
element.appendChild(detail); element.appendChild(detail);
element = useElementStyle(element, cardColor, cardSize, textSize) element = useElementStyle(element, cardColor.value, cardSize.value, textSize.value)
const object = new CSS3DObject(element); const object = new CSS3DObject(element);
object.position.x = Math.random() * 4000 - 2000; object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000; object.position.y = Math.random() * 4000 - 2000;
@@ -136,8 +134,8 @@ const init = () => {
for (let i = 0; i < tableLen; i++) { for (let i = 0; i < tableLen; i++) {
const object = new THREE.Object3D(); const object = new THREE.Object3D();
object.position.x = tableData.value[i].x * (cardSize.width + 40) - rowCount * 90; object.position.x = tableData.value[i].x * (cardSize.value.width + 40) - rowCount.value * 90;
object.position.y = -tableData.value[i].y * (cardSize.height + 20) + 1000; object.position.y = -tableData.value[i].y * (cardSize.value.height + 20) + 1000;
object.position.z = 0; object.position.z = 0;
targets.table.push(object); targets.table.push(object);
@@ -214,10 +212,12 @@ const transform = (targets: any[], duration: number) => {
.onComplete(() => { .onComplete(() => {
if (luckyCardList.value.length) { if (luckyCardList.value.length) {
luckyCardList.value.forEach((item: any) => { luckyCardList.value.forEach((item: any) => {
return useElementStyle(item.element, cardColor, { width: cardSize.width, height: cardSize.height }, textSize) return useElementStyle(item.element, cardColor.value, cardSize.value, textSize.value)
}) })
} }
luckyCardList.value = []; luckyCardList.value = [];
canOperate.value = true
}); });
} }
@@ -248,11 +248,11 @@ function animation() {
requestAnimationFrame(animation); requestAnimationFrame(animation);
} }
// // 自动旋转的动画 // // 旋转的动画
function rollBall(rotateY: number, duration: number, mod: 'default' | 'restore' = 'default') { function rollBall(rotateY: number, duration: number) {
TWEEN.removeAll(); TWEEN.removeAll();
return new Promise((resolve, reject) => { return new Promise((resolve) => {
scene.value.rotation.y = 0; scene.value.rotation.y = 0;
ballRotationY.value = Math.PI * rotateY * 1000 ballRotationY.value = Math.PI * rotateY * 1000
const rotateObj = new TWEEN.Tween(scene.value.rotation); const rotateObj = new TWEEN.Tween(scene.value.rotation);
@@ -270,85 +270,240 @@ function rollBall(rotateY: number, duration: number, mod: 'default' | 'restore'
.onUpdate(render) .onUpdate(render)
.start() .start()
.onStop(() => { .onStop(() => {
scene.value.rotation.y = 0;
resolve('') resolve('')
}) })
.onComplete(() => { .onComplete(() => {
resolve('') resolve('')
canOperate.value = true
})
})
}
// 将视野转回正面
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()
}) })
}) })
} }
function render() { function render() {
renderer.value.render(scene.value, camera.value); renderer.value.render(scene.value, camera.value);
} }
const enterLottery = async () => { const enterLottery = async () => {
if (!canOperate.value) {
return
}
canOperate.value = false
transform(targets.sphere, 1000) transform(targets.sphere, 1000)
currentStatus.value = 1 currentStatus.value = 1
// setTimeout(() => { setTimeout(() => {
// rollBall(0.1,3000) rollBall(0.1, 2000)
// }, 3000) }, 2000)
} }
// 开始抽奖 // 开始抽奖
const startLottery = () => { const startLottery = () => {
if (!canOperate.value) {
return
}
currentStatus.value = 2 currentStatus.value = 2
rollBall(10, 3000) rollBall(10, 3000)
} }
const stopLottery = async () => { const stopLottery = async () => {
if (!canOperate.value) {
return
}
canOperate.value = false
TWEEN.removeAll(); TWEEN.removeAll();
rollBall(0, 1) rollBall(0, 1)
// scene正面
currentStatus.value = 0 currentStatus.value = 0
// 从notPersonList随机抽取currentPrize.count个 const notPersonListLength = notPersonList.value.length;
const notPersonListLength = notPersonList.length;
for (let i = 0; i < currentPrize.count; i++) { // 每次最多抽十个
let luckyCount = 10
const leftover = currentPrize.value.count - currentPrize.value.isUsedCount
leftover < luckyCount ? luckyCount = leftover : luckyCount
if (notPersonListLength < luckyCount) {
toast.open({
message: '抽奖人数不够',
type: 'warning',
position: 'top-right',
duration: 10000
})
return;
}
for (let i = 0; i < luckyCount; i++) {
if (notPersonListLength > 0) { if (notPersonListLength > 0) {
const randomIndex = Math.floor(Math.random() * notPersonListLength); const randomIndex = Math.floor(Math.random() * notPersonListLength);
luckyTargets.value.push(notPersonList[randomIndex]) luckyTargets.value.push(notPersonList.value[randomIndex])
let LuckyCard = objects.value[randomIndex] let LuckyCard = objects.value[randomIndex]
console.log(LuckyCard) luckyCardList.value.push(LuckyCard)
LuckyCard.element = useElementStyle(LuckyCard.element, '#ffd700', { width: cardSize.width * 2, height: cardSize.height * 2 }, textSize * 2, 'lucky') }
// 重新设置位置 }
LuckyCard.position.x = 100 const luckyCardListLength = luckyCardList.value.length;
LuckyCard.position.y = 300 const windowSize = { width: window.innerWidth, height: window.innerHeight }
LuckyCard.position.z = 0 luckyCardListLength && luckyCardList.value.forEach((item: any, index: number) => {
// LuckyCard.rotation.x = 0 item.element = useElementStyle(item.element, luckyColor.value, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, textSize.value * 2, 'lucky')
// LuckyCard.rotation.y = 0 item = useElementPosition(item, rowCount.value, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, windowSize, index)
// LuckyCard.rotation.z = 0 new TWEEN.Tween(item.position)
new TWEEN.Tween(LuckyCard.position)
.to({ .to({
x: LuckyCard.x, x: item.x,
y: LuckyCard.y, y: item.y,
z: 1200 z: 1000
}, 1000) }, 700)
.start() .start()
new TWEEN.Tween(LuckyCard.rotation) new TWEEN.Tween(item.rotation)
.to({ .to({
x: 0, x: 0,
y: 0, y: 0,
z: 0 z: 0
}, 1000) }, 600)
.start() .start()
luckyCardList.value.push(LuckyCard) .onComplete(() => {
confettiFire()
resetCamera()
})
})
currentPrizeValue.value.isUsedCount += luckyCount
if(currentPrizeValue.value.isUsedCount>=currentPrizeValue.value.count){
currentPrizeValue.value.isUsed=true
} }
prizeConfig.setCurrentPrize(currentPrizeValue.value)
prizeConfig.updatePrizeConfig(currentPrizeValue.value)
personConfig.addAlreadyPersonList(luckyTargets.value, currentPrize.value)
} }
// 庆祝动画
const confettiFire = () => {
var duration = 3 * 1000;
var 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 }
});
personConfig.addAlreadyPersonList(luckyTargets.value) // 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,
});
}
const centerFire = (particleRatio: number, opts: any) => {
const count = 200
confetti({
origin: { y: 0.7 },
...opts,
particleCount: Math.floor(count * particleRatio)
});
}
// 监听空格键
const listenSpaceKey=()=>{
window.addEventListener('keydown', (e) => {
console.log('code line-468 \n\r😒 e:\n\r',e);
//
if (e.code !== 'Space') {
return
}
if(currentStatus.value==0){
enterLottery()
}
else if(currentStatus.value==1){
startLottery()
}
else if(currentStatus.value==2){
stopLottery()
}
})
}
onMounted(() => { onMounted(() => {
init(); init();
animation(); animation();
containerRef.value!.style.color = `${textColor}` containerRef.value!.style.color = `${textColor}`
listenSpaceKey()
}); });
onUnmounted(() => {
window.removeEventListener('keydown', listenSpaceKey)
})
watch(()=>currentPrizeValue.value.isUsed,(val)=>{
if(val){
currentPrizeValue.value=JSON.parse(JSON.stringify(currentPrize.value))
}
})
</script> </script>
<template> <template>
<div id="container" ref="containerRef">
<h2 class="absolute w-full pt-12 m-0 font-mono tracking-wide text-center leading-12" :style="{fontSize:textSize*1.5+'px',color:textColor}">{{ topTitle }}</h2>
<div id="container" ref="containerRef" class="3dContainer">
<!-- 选中菜单结构 start--> <!-- 选中菜单结构 start-->
<div id="menu"> <div id="menu">
<button class="btn glass" @click="enterLottery" v-if="currentStatus == 0">进入抽奖</button> <button class="btn glass" @click="enterLottery" v-if="currentStatus == 0">进入抽奖</button>
@@ -356,8 +511,10 @@ onMounted(() => {
<button class="btn glass" @click="startLottery" v-if="currentStatus == 1">开始</button> <button class="btn glass" @click="startLottery" v-if="currentStatus == 1">开始</button>
<button class="btn glass" @click="stopLottery" v-if="currentStatus == 2">结束</button> <button class="btn glass" @click="stopLottery" v-if="currentStatus == 2">结束</button>
<button id="table" @click="transform(targets.table, 2000)">TABLE</button>
<button id="helix" @click="transform(targets.helix, 2000)">HELIX</button>
<!-- <button id="table" @click="transform(targets.table, 2000)">TABLE</button> -->
<!-- <button id="helix" @click="transform(targets.helix, 2000)">HELIX</button> -->
</div> </div>
<!-- end --> <!-- end -->