feat: feat

This commit is contained in:
ex_zhangwenlei@exiot.cmcc
2024-01-11 00:11:27 +08:00
parent 3283a2d975
commit 3e429b95a2
32 changed files with 793 additions and 1377 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

118
README.md
View File

@@ -1,105 +1,21 @@
# Vue3 + Vite4 + Element + Windicss + Bootstrap
<p align="center">
<a href="http://www.form-create.com">
<svg t="1704902663531" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4318" width="200" height="200"><path d="M433.230769 630.153846m-275.692307 0a275.692308 275.692308 0 1 0 551.384615 0 275.692308 275.692308 0 1 0-551.384615 0Z" fill="#E2FFFF" p-id="4319"></path><path d="M512 0C228.903385 0 0 228.903385 0 512s228.903385 512 512 512 512-228.903385 512-512S795.096615 0 512 0z m42.968615 938.653538V883.396923c0-27.608615-21.464615-46.040615-46.040615-46.040615-24.536615 0-46.040615 21.504-46.040615 46.08v55.21723c-196.450462-21.464615-356.036923-181.090462-377.540923-377.540923H140.603077c24.536615 0 46.040615-21.504 46.040615-46.08 0-24.536615-21.504-46.001231-46.08-46.00123H85.385846c18.392615-199.522462 178.018462-362.220308 377.540923-383.684923v61.36123c0 24.576 21.504 46.08 46.08 46.08 24.536615 0 46.001231-21.504 46.001231-46.08V85.346462c202.594462 21.464615 365.292308 181.090462 383.684923 383.684923h-61.361231c-27.648 0-46.08 18.392615-46.08 46.040615 0 24.536615 21.504 46.040615 46.08 46.040615h61.361231c-21.464615 199.522462-184.162462 359.148308-383.684923 377.540923z" fill="#437DFF" p-id="4320"></path><path d="M499.396923 291.170462a19.692308 19.692308 0 0 1 25.245539 0.039384l2.520615 2.520616 9.964308 12.169846C625.427692 414.208 669.538462 495.340308 669.538462 549.218462 669.538462 637.44 599.04 708.923077 512 708.923077s-157.538462-71.483077-157.538462-159.704615c0-49.900308 37.809231-123.155692 113.506462-219.72677l18.904615-23.630769 9.964308-12.130461a19.692308 19.692308 0 0 1 2.599385-2.56z m12.603077 110.434461l-9.570462 13.075692-13.23323 18.747077-11.815385 17.644308C447.763692 496.679385 433.230769 530.313846 433.230769 549.218462c0 44.937846 35.524923 80.935385 78.769231 80.935384 43.244308 0 78.769231-35.997538 78.769231-80.935384 0-16.305231-11.027692-43.992615-33.437539-81.053539l-10.318769-16.462769a790.843077 790.843077 0 0 0-11.697231-17.565539l-13.115077-18.668307-10.200615-13.863385z" fill="#437DFF" p-id="4321"></path></svg>
</a>
</p>
## 文件夹介绍
### 🅰api
request.ts文件为axios封装可在此拦截操作请求和回复
### 🆒components
组件文件夹存放公共组件如SvgIcon图标组件、Table表格组件等
### 👀hooks
封装hooks函数公共函数的提取
### 🪪icons
存放图标组件图标组件使用vite插件vite-plugin-svg-icon引入
```ts
plugins[
...
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), "src/icons")],
// 指定symbolId格式
symbolId: "icon-[dir]-[name]",
}),
...
]
```
使用时在组件内按如下方法使用即可
```ts
<svg-icon :name="'menu'" class="svgMenu cursor-pointer"></svg-icon>
```
### 🏬layout
整体的的布局组件在router文件中根路径下引入。
包含Header、Main、Footer组件布局使用了bootstrap的响应式布局
如菜单列表的写法
```ts
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<!-- <span class="navbar-toggler-icon"></span> -->
<svg-icon :name="'open'"></svg-icon>
</button>
<div
class="nav-list collapse navbar-collapse"
id="navbarSupportedContent"
>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li
class="nav-item"
v-for="item in navList.data"
:key="item.id"
@click="skip(item.url)"
>
<a class="nav-link active" aria-current="page" href="#">{{
item.name
}}</a>
</li>
</ul>
</div>
</div>
</nav>
```
使用媒体查询监听屏幕宽度自适应调整布局
如Header组件中屏幕宽度大于1200px时始终保持headeer栏宽度为200px
```css
// @/layout/components/Header.vue
@media screen and (min-width: 1200px) {
.header-container {
width: 1200px;
margin: 0 auto;
}
}
```
# log-lottery
[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/LOG1997/log-lottery)
[![github](https://img.shields.io/badge/Author-xaboy-blue.svg)](https://github.com/log1997)
[![vue3](https://img.shields.io/badge/VUE-3.0-green.svg)](https://github.com/log1997)
其中Main组件中写入
```ts
<router-view class="main-container-content"></router-view>
```
### 🏳router
路由管理使用history模式
### 🛒store
状态管理使用pinia
### 🍟style
存放样式文件模板里主要存放的是主题文件使用scss。
文件中的函数以及样式在main.ts中引入过后即可使用
### 🥅types
定义的类型和接口
### 🎊views
界面组件
### 🙈App.vue
界面入口
### 🧵Main.ts
项目入口文件
### 🗽env文件
根据不同环境配置的路径地址,常量名称必须是`VITE_***`格式在vite项目中引入时的方式为`import.meta.env.VITE_***`
还需要在`vite.config.ts`文件中设置才可引用,具体见文件
### 🪔vite.config.ts
配置了icons的引入、elemnet的按需引入和自动注册、element图标的使用、windicss的引入、符号别名的设置、server的设置。
### 📦在.env文件中修改链接
## License
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2024-present log1997

41
__test__/Lottery.test.ts Normal file
View File

@@ -0,0 +1,41 @@
import Button from '@/components/Button/index.vue'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'
// 测试分组
describe('Button', () => {
// mount
test('Buttons slot text', () => {
// @vue/test-utils
const wrapper = shallowMount(Button, {
slots: {
default: 'Button',
},
})
// 断言
expect(wrapper.text()).toBe('Button')
})
test('Button click', () => {
const wrapper = shallowMount(Button)
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
test('Button disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: true,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
test('Button not disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: false,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})

View File

@@ -1,8 +1,9 @@
{
"name": "log-lottery",
"private": true,
"version": "0.0.0",
"version": "0.0.1",
"type": "module",
"license": "MIT",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vue-tsc --noEmit && vite build",
@@ -13,29 +14,19 @@
"lint": "eslint ./src --ext .vue,.js,.ts,.jsx,.tsx --fix"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@popperjs/core": "^2.11.8",
"@radial-color-picker/vue-color-picker": "^5.0.1",
"@tsparticles/engine": "^3.0.3",
"@tsparticles/slim": "^3.0.3",
"@tsparticles/vue3": "^3.0.0",
"@tweenjs/tween.js": "^21.0.0",
"@vueuse/core": "^10.6.1",
"axios": "^1.6.1",
"canvas-confetti": "^1.9.2",
"localforage": "^1.10.0",
"particles.vue3": "^2.12.0",
"pinia": "^2.1.7",
"pinia-plugin-persist": "^1.0.0",
"sparticles": "^1.3.1",
"svg-sprite-loader": "^6.0.11",
"theme-change": "^2.5.0",
"three": "^0.160.0",
"three-css2drender": "^1.0.0",
"tsparticles": "^3.0.3",
"tsparticles-engine": "^2.12.0",
"vcolorpicker": "^2.0.12",
"three-css3d": "^1.0.6",
"three-trackballcontrols": "^0.9.0",
"vue": "^3.3.8",
"vue-accessible-color-picker": "^5.0.1",
"vue-router": "^4.2.5",
"vue-toast-notification": "^3",
"vue3-colorpicker": "^2.2.3",
@@ -59,7 +50,6 @@
"daisyui": "^4.0.4",
"eslint": "^8.53.0",
"eslint-plugin-vue": "^9.18.1",
"fast-glob": "^3.3.2",
"happy-dom": "^12.10.3",
"husky": "^8.0.3",
"jsdom": "^22.1.0",

1005
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
src/assets/images/龙.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

@@ -15,8 +15,15 @@ const imgUrl=ref('')
const getImageStoreItem=async (item:any):Promise<string>=>{
let image=''
if(item.url=='Storage'){
const key=item.id;
const image=await imageDbStore.getItem(key) as string
image=await imageDbStore.getItem(key) as string
}
else{
image=item.url
}
return image
}
@@ -28,7 +35,7 @@ onMounted(async ()=>{
</script>
<template>
<img :src="imgUrl" Alt="Image"/>
<img :src="imgUrl" alt="Image" class="object-cover h-full rounded-xl"/>
</template>
<style lang='scss' scoped>

View File

@@ -26,6 +26,7 @@ export const useElementStyle=(element:any,cardColor:string,cardSize:{width:numbe
element.children[0].style.fontSize = textSize*0.5+'px'
element.children[1].style.fontSize = textSize+'px'
element.children[1].style.lineHeight = textSize*3+'px'
element.children[1].style.textShadow = `0 0 12px ${rgba(cardColor, 0.95)}`
element.children[2].style.fontSize = textSize*0.5+'px'

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><g fill="none"><rect width="256" height="256" fill="url(#skillIconsInstagram0)" rx="60"/><rect width="256" height="256" fill="url(#skillIconsInstagram1)" rx="60"/><path fill="#fff" d="M128.009 28c-27.158 0-30.567.119-41.233.604c-10.646.488-17.913 2.173-24.271 4.646c-6.578 2.554-12.157 5.971-17.715 11.531c-5.563 5.559-8.98 11.138-11.542 17.713c-2.48 6.36-4.167 13.63-4.646 24.271c-.477 10.667-.602 14.077-.602 41.236s.12 30.557.604 41.223c.49 10.646 2.175 17.913 4.646 24.271c2.556 6.578 5.973 12.157 11.533 17.715c5.557 5.563 11.136 8.988 17.709 11.542c6.363 2.473 13.631 4.158 24.275 4.646c10.667.485 14.073.604 41.23.604c27.161 0 30.559-.119 41.225-.604c10.646-.488 17.921-2.173 24.284-4.646c6.575-2.554 12.146-5.979 17.702-11.542c5.563-5.558 8.979-11.137 11.542-17.712c2.458-6.361 4.146-13.63 4.646-24.272c.479-10.666.604-14.066.604-41.225s-.125-30.567-.604-41.234c-.5-10.646-2.188-17.912-4.646-24.27c-2.563-6.578-5.979-12.157-11.542-17.716c-5.562-5.562-11.125-8.979-17.708-11.53c-6.375-2.474-13.646-4.16-24.292-4.647c-10.667-.485-14.063-.604-41.23-.604zm-8.971 18.021c2.663-.004 5.634 0 8.971 0c26.701 0 29.865.096 40.409.575c9.75.446 15.042 2.075 18.567 3.444c4.667 1.812 7.994 3.979 11.492 7.48c3.5 3.5 5.666 6.833 7.483 11.5c1.369 3.52 3 8.812 3.444 18.562c.479 10.542.583 13.708.583 40.396c0 26.688-.104 29.855-.583 40.396c-.446 9.75-2.075 15.042-3.444 18.563c-1.812 4.667-3.983 7.99-7.483 11.488c-3.5 3.5-6.823 5.666-11.492 7.479c-3.521 1.375-8.817 3-18.567 3.446c-10.542.479-13.708.583-40.409.583c-26.702 0-29.867-.104-40.408-.583c-9.75-.45-15.042-2.079-18.57-3.448c-4.666-1.813-8-3.979-11.5-7.479s-5.666-6.825-7.483-11.494c-1.369-3.521-3-8.813-3.444-18.563c-.479-10.542-.575-13.708-.575-40.413c0-26.704.096-29.854.575-40.396c.446-9.75 2.075-15.042 3.444-18.567c1.813-4.667 3.983-8 7.484-11.5c3.5-3.5 6.833-5.667 11.5-7.483c3.525-1.375 8.819-3 18.569-3.448c9.225-.417 12.8-.542 31.437-.563zm62.351 16.604c-6.625 0-12 5.37-12 11.996c0 6.625 5.375 12 12 12s12-5.375 12-12s-5.375-12-12-12zm-53.38 14.021c-28.36 0-51.354 22.994-51.354 51.355c0 28.361 22.994 51.344 51.354 51.344c28.361 0 51.347-22.983 51.347-51.344c0-28.36-22.988-51.355-51.349-51.355zm0 18.021c18.409 0 33.334 14.923 33.334 33.334c0 18.409-14.925 33.334-33.334 33.334c-18.41 0-33.333-14.925-33.333-33.334c0-18.411 14.923-33.334 33.333-33.334"/><defs><radialGradient id="skillIconsInstagram0" cx="0" cy="0" r="1" gradientTransform="matrix(0 -253.715 235.975 0 68 275.717)" gradientUnits="userSpaceOnUse"><stop stop-color="#FD5"/><stop offset=".1" stop-color="#FD5"/><stop offset=".5" stop-color="#FF543E"/><stop offset="1" stop-color="#C837AB"/></radialGradient><radialGradient id="skillIconsInstagram1" cx="0" cy="0" r="1" gradientTransform="matrix(22.25952 111.2061 -458.39518 91.75449 -42.881 18.441)" gradientUnits="userSpaceOnUse"><stop stop-color="#3771C8"/><stop offset=".128" stop-color="#3771C8"/><stop offset="1" stop-color="#60F" stop-opacity="0"/></radialGradient></defs></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="39.2" height="32" viewBox="0 0 256 209"><path fill="#55acee" d="M256 25.45a105.04 105.04 0 0 1-30.166 8.27c10.845-6.5 19.172-16.793 23.093-29.057a105.183 105.183 0 0 1-33.351 12.745C205.995 7.201 192.346.822 177.239.822c-29.006 0-52.523 23.516-52.523 52.52c0 4.117.465 8.125 1.36 11.97c-43.65-2.191-82.35-23.1-108.255-54.876c-4.52 7.757-7.11 16.78-7.11 26.404c0 18.222 9.273 34.297 23.365 43.716a52.312 52.312 0 0 1-23.79-6.57c-.003.22-.003.44-.003.661c0 25.447 18.104 46.675 42.13 51.5a52.592 52.592 0 0 1-23.718.9c6.683 20.866 26.08 36.05 49.062 36.475c-17.975 14.086-40.622 22.483-65.228 22.483c-4.24 0-8.42-.249-12.529-.734c23.243 14.902 50.85 23.597 80.51 23.597c96.607 0 149.434-80.031 149.434-149.435c0-2.278-.05-4.543-.152-6.795A106.748 106.748 0 0 0 256 25.45"/></svg>

After

Width:  |  Height:  |  Size: 824 B

View File

@@ -28,7 +28,7 @@ export const configRoutes={
name:'AllPersonConfig',
component:()=>import('@/views/Config/Person/PersonAll.vue'),
meta:{
title:'所有人员',
title:'人员名单',
icon:'all'
}
},
@@ -37,7 +37,7 @@ export const configRoutes={
name:'AlreadyPerson',
component:()=>import('@/views/Config/Person/PersonAlready.vue'),
meta:{
title:'已领取人员',
title:'中奖名单人员',
icon:'already'
}
},
@@ -84,7 +84,7 @@ export const configRoutes={
name:'ImageConfig',
component:()=>import('@/views/Config/Global/ImageConfig.vue'),
meta:{
title:'图片配置',
title:'图片列表',
icon:'image'
}
},
@@ -93,7 +93,7 @@ export const configRoutes={
name:'MusicConfig',
component:()=>import('@/views/Config/Global/MusicConfig.vue'),
meta:{
title:'音乐配置',
title:'音乐列表',
icon:'music'
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +1,26 @@
import { defineStore } from 'pinia';
import { defaultMusicList,defaultImageList } from './data'
// import { IPrizeConfig } from '@/types/prizeConfig';
import {IMusic,IImage} from '@/types/storeType';
// import { IPrizeConfig } from '@/types/storeType';
export const useGlobalConfig = defineStore('global', {
state() {
return {
globalConfig: {
rowCount: 12,
rowCount: 17,
isSHowPrizeList:true,
topTitle:'大明内阁六部御前奏对',
theme: {
name: 'dark',
detail: { primary: '#0f5fd3' },
cardColor: 'rgba(0, 255, 255)',
cardColor: '#ff79c6',
cardWidth: 140,
cardHeight: 200,
textColor: '#ffffff',
luckyCardColor:'#ECB1AC',
textSize: 30
},
musicList: defaultMusicList,
imageList:defaultImageList,
musicList: defaultMusicList as IMusic[],
imageList:defaultImageList as IImage[],
},
currentMusic: {
item:defaultMusicList[0],
@@ -122,7 +123,7 @@ export const useGlobalConfig = defineStore('global', {
this.globalConfig.theme.textSize = textSize;
},
// 添加音乐
addMusic(music: any) {
addMusic(music: IMusic) {
// 验证音乐是否已存在看name字段
for (let i = 0; i < this.globalConfig.musicList.length; i++) {
if (this.globalConfig.musicList[i].name === music.name) {
@@ -141,7 +142,7 @@ export const useGlobalConfig = defineStore('global', {
}
},
// 设置当前播放音乐
setCurrentMusic(musicItem: any,paused:boolean=true) {
setCurrentMusic(musicItem: IMusic,paused:boolean=true) {
this.currentMusic={
item:musicItem,
paused:paused,
@@ -149,14 +150,14 @@ export const useGlobalConfig = defineStore('global', {
},
// 重置音乐列表
resetMusicList() {
this.globalConfig.musicList = defaultMusicList;
this.globalConfig.musicList = defaultMusicList as IMusic[];
},
// 清空音乐列表
clearMusicList() {
this.globalConfig.musicList = [];
this.globalConfig.musicList = [] as IMusic[];
},
// 添加图片
addImage(image:any){
addImage(image:IImage){
for (let i = 0; i < this.globalConfig.imageList.length; i++) {
if (this.globalConfig.imageList[i].name === image.name) {
return;
@@ -175,11 +176,11 @@ export const useGlobalConfig = defineStore('global', {
},
// 重置图片列表
resetImageList() {
this.globalConfig.imageList = defaultImageList;
this.globalConfig.imageList = defaultImageList as IImage[];
},
// 清空图片列表
clearImageList() {
this.globalConfig.imageList = [];
this.globalConfig.imageList = [] as IImage[]
},
// 设置是否显示奖品列表
setIsShowPrizeList(isShowPrizeList: boolean) {
@@ -202,11 +203,11 @@ export const useGlobalConfig = defineStore('global', {
textSize: 30
},
musicList: defaultMusicList,
imageList:defaultImageList,
musicList: defaultMusicList as IMusic[],
imageList:defaultImageList as IImage[],
},
this.currentMusic= {
item:defaultMusicList[0],
item:defaultMusicList[0] as IMusic,
paused:true,
}
}

View File

@@ -1,16 +1,13 @@
import { defineStore } from 'pinia';
import { IPersonConfig } from '@/types/personConfig';
import { IPrizeConfig } from '@/types/prizeConfig';
import { IPersonConfig } from '@/types/storeType';
import { IPrizeConfig } from '@/types/storeType';
import { defaultPersonList } from './data'
export const usePersonConfig = defineStore('person', {
state() {
return {
personConfig: {
allPersonList: [] as IPersonConfig[],
// alreadyPersonList: [] as IPersonConfig[],
// notPersonList: [] as IPersonConfig[],
tableRowCount: 12,
showField: [] as any[]
alreadyPersonList: [] as IPersonConfig[],
}
};
},
@@ -29,20 +26,16 @@ export const usePersonConfig = defineStore('person', {
return item.isWin === true;
});
},
// 获取中奖人员详情
getAlreadyPersonDetail(state) {
return state.personConfig.alreadyPersonList
},
// 获取未中奖人员名单
getNotPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item.isWin === false;
});
},
// 获取table列数
getTableRowCount(state) {
return state.personConfig.tableRowCount;
},
// 获取要展示那些字段
getShowField(state) {
return state.personConfig.showField;
}
},
actions: {
// 添加未中奖人员
@@ -63,15 +56,23 @@ export const usePersonConfig = defineStore('person', {
this.personConfig.allPersonList.map((item: IPersonConfig) => {
if (item.id === person.id && prize != null) {
item.isWin = true
item.prizeName = prize.name
// person.isWin = true
item.prizeName += prize.name
// person.prizeName += prize.name
item.prizeTime = new Date().toString()
// person.prizeTime = new Date().toString()
}
});
console.log('person;;',person)
this.personConfig.alreadyPersonList.push(person);
});
},
// 从已中奖移动到未中奖
moveAlreadyToNot(person: IPersonConfig) {
if (person.id != undefined || person.id != null) {
if (person.id == undefined || person.id == null) {
return
}
const alreadyPersonListLength= this.personConfig.alreadyPersonList.length
for (let i = 0; i < this.personConfig.allPersonList.length; i++) {
if (person.id === this.personConfig.allPersonList[i].id) {
this.personConfig.allPersonList[i].isWin = false
@@ -81,39 +82,49 @@ export const usePersonConfig = defineStore('person', {
return
}
}
for(let i=0;i<alreadyPersonListLength;i++){
this.personConfig.alreadyPersonList=this.personConfig.alreadyPersonList.filter((item:IPersonConfig)=>{
return item.id!==person.id
})
}
},
// 删除指定人员
deletePerson(person: IPersonConfig) {
if (person.id != undefined || person.id != null) {
this.personConfig.allPersonList = this.personConfig.allPersonList.filter((item: IPersonConfig) => item.id !== person.id);
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) => item.id!== person.id);
}
},
// 删除所有人员
deleteAllPerson() {
this.personConfig.allPersonList = [];
this.personConfig.alreadyPersonList = [];
},
// 重置所有人员
// 删除所有人员
resetPerson() {
this.personConfig.allPersonList = [];
this.personConfig.alreadyPersonList = [];
},
// 重置已中奖人员
resetAlreadyPerson() {
// 把已中奖人员合并到未中奖人员,要验证是否已存在
this.personConfig.allPersonList.forEach((item: IPersonConfig) => {
item.isWin = false;
item.prizeName = '';
item.prizeTime = '';
});
this.personConfig.alreadyPersonList=[];
},
setDefaultPersonList() {
this.personConfig.allPersonList = defaultPersonList;
this.personConfig.alreadyPersonList=[];
},
// 重置所有配置
reset() {
this.personConfig = {
allPersonList: [] as IPersonConfig[],
tableRowCount: 12,
showField: [] as string[]
alreadyPersonList: [] as IPersonConfig[],
}
},
},

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { IPrizeConfig } from '@/types/prizeConfig';
import { IPrizeConfig } from '@/types/storeType';
import { defaultPrizeList } from './data';
export const usePrizeConfig = defineStore('prize', {
state() {
@@ -71,11 +71,11 @@ return state.prizeConfig.prizeList;
},
// 删除全部奖项
deleteAllPrizeConfig() {
this.prizeConfig.prizeList = [];
this.prizeConfig.prizeList = [] as IPrizeConfig[];
},
// 设置当前奖项
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.currentPrize = prizeConfigItem;
this.prizeConfig.currentPrize = prizeConfigItem
},
// 重置所有配置
resetDefault() {
@@ -97,7 +97,7 @@ return state.prizeConfig.prizeList;
isShow: true,
isUsed: false,
frequency: 1,
},
} as IPrizeConfig
}
}
},

View File

@@ -1,13 +0,0 @@
export interface IPersonConfig {
id: number;
uid: string;
name: string;
department:string;
isWin:boolean;
x:number;
y:number
createTime: string;
updateTime: string;
prizeName: string;
prizeTime: string;
}

View File

@@ -1,17 +0,0 @@
export interface IPrizeConfig {
id: number|string;
name:string;
sort:number;
isAll:boolean;
count:number;
isUsedCount:number,
picture:{
id:string|number,
name:string,
url:string
};
desc:string;
isShow:boolean;
isUsed:boolean,
frequency:number;
}

41
src/types/storeType.ts Normal file
View File

@@ -0,0 +1,41 @@
export interface IPersonConfig {
id: number;
uid: string;
name: string;
department:string;
isWin:boolean;
x:number;
y:number
createTime: string;
updateTime: string;
prizeName: string;
prizeTime: string;
}
export interface IPrizeConfig {
id: number|string;
name:string;
sort:number;
isAll:boolean;
count:number;
isUsedCount:number,
picture:{
id:string|number,
name:string,
url:string
};
desc:string;
isShow:boolean;
isUsed:boolean,
frequency:number;
}
export interface IMusic{
id:string,
name:string,
url:string,
}
export interface IImage{
id:string,
name:string,
url:string,
}

View File

@@ -1,18 +0,0 @@
export interface TableItemType {
prop: string;
label: string;
width: number;
image?: boolean;
actions?: actionsItemType[];
aligen?: string;
formatter?: ItemFuncType;
getType?: ItemFuncType;
}
interface ItemFuncType {
(row: any): any;
}
interface actionsItemType {
name: string;
func: any;
type?: any;
}

View File

@@ -1,6 +0,0 @@
// 用户的类型声明文件
export interface IUser {
name: string;
age: number;
}
// ...

View File

@@ -1,4 +1,4 @@
export const readFile = (file: any): Promise<any> => {
export const readFileBinary = (file: any): Promise<any> => {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsBinaryString(file)
@@ -8,7 +8,7 @@ export const readFile = (file: any): Promise<any> => {
})
}
export const readImage = (file: any): Promise<any> => {
export const readFileData = (file: any): Promise<{dataUrl:string,fileName:string}> => {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsDataURL(file)
@@ -17,15 +17,3 @@ export const readImage = (file: any): Promise<any> => {
}
})
}
export const readMusic = (file: any): Promise<any> => {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (ev: any) => {
resolve({dataUrl:ev.target.result,fileName:file.name})
}
})
}

View File

@@ -97,7 +97,9 @@ watch(() => formData.value.rowCount, () => {
formErr.value.rowCount = err.issues[0].message
})
})
watch(topTitleValue,(val)=>{
globalConfig.setTopTitle(val)
}),
watch(themeValue, (val: any) => {
const selectedThemeDetail = daisyuiThemeList.value[val]

View File

@@ -1,6 +1,7 @@
<script setup lang='ts'>
import { ref, onMounted, watch } from 'vue'
import { readImage } from '@/utils/file'
import { IImage } from '@/types/storeType'
import { readFileData } from '@/utils/file'
import localforage from 'localforage'
import useStore from '@/store'
import { storeToRefs } from 'pinia'
@@ -14,14 +15,14 @@ const imgUploadToast = ref(0) //0是不显示1是成功2是失败,3是不
const imageDbStore = localforage.createInstance({
name: 'imgStore'
})
const handleFileChange = async (e: any) => {
const isImage= /image*/.test(e.target.files[0].type)
const handleFileChange = async (e: Event) => {
const isImage= /image*/.test(((e.target as HTMLInputElement).files as FileList)[0].type)
if (!isImage) {
imgUploadToast.value = 3
return
}
let { dataUrl, fileName } = await readImage(e.target.files[0])
let { dataUrl, fileName } = await readFileData(((e.target as HTMLInputElement).files as FileList)[0])
imageDbStore.setItem(new Date().getTime().toString() + '+' + fileName, dataUrl)
.then(() => {
imgUploadToast.value = 1
@@ -45,7 +46,7 @@ const getImageDbStore =async () => {
}
}
const removeImage=(item:any)=>{
const removeImage=(item:IImage)=>{
if(item.url=='Storage'){
imageDbStore.removeItem(item.id).then(() => {
globalConfig.removeImage(item.id)
@@ -91,8 +92,8 @@ watch(() => imgUploadToast.value, (val) => {
<div class="flex items-center gap-8">
<div class="avatar h-14">
<div class="w-12 h-12 mask mask-squircle hover:w-14 hover:h-14">
<img v-if="item.url!=='Storage'" :src="item.url" alt="Avatar Tailwind CSS Component" />
<ImageSync v-else :imgItem="item"></ImageSync>
<!-- <img v-if="item.url!=='Storage'" :src="item.url" alt="Avatar Tailwind CSS Component" /> -->
<ImageSync :imgItem="item"></ImageSync>
</div>
</div>
<div class="w-64">

View File

@@ -1,7 +1,8 @@
<script setup lang='ts'>
import { ref, onMounted } from 'vue'
import {storeToRefs } from 'pinia'
import { readMusic } from '@/utils/file'
import { IMusic } from '@/types/storeType';
import { readFileData } from '@/utils/file'
import useStore from '@/store';
import localforage from 'localforage'
@@ -15,11 +16,11 @@ const globalConfig = useStore().globalConfig
const { getMusicList: localMusicList } = storeToRefs(globalConfig);
const limitType = ref('audio/*')
const localMusicListValue = ref(localMusicList)
const play = async (item: any) => {
const play = async (item: IMusic) => {
globalConfig.setCurrentMusic(item,false)
}
const deleteMusic = (item: any) => {
const deleteMusic = (item: IMusic) => {
globalConfig.removeMusic(item.id)
audioDbStore.removeItem(item.name)
// setTimeout(()=>{
@@ -46,14 +47,14 @@ const getMusicDbStore = async () => {
})
}
}
const handleFileChange = async (e: any) => {
const isAudio = /audio*/.test(e.target.files[0].type)
const handleFileChange = async (e: Event) => {
const isAudio = /audio*/.test(((e.target as HTMLInputElement).files as FileList)[0].type)
if (!isAudio) {
audioUploadToast.value = 3
return
}
let { dataUrl, fileName } = await readMusic(e.target.files[0])
let { dataUrl, fileName } = await readFileData(((e.target as HTMLInputElement).files as FileList)[0])
audioDbStore.setItem(new Date().getTime().toString() + '+' + fileName, dataUrl)
.then(() => {
audioUploadToast.value = 1

View File

@@ -2,9 +2,10 @@
<script setup lang='ts'>
import { ref, onMounted } from 'vue';
import useStore from '@/store'
import { IPersonConfig } from '@/types/storeType';
import { storeToRefs } from 'pinia'
import * as XLSX from 'xlsx'
import { readFile } from '@/utils/file'
import { readFileBinary } from '@/utils/file'
import { filterData, addOtherInfo } from '@/utils'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
@@ -16,9 +17,11 @@ const limitType = '.xlsx,.xls'
const excelData = ref<any[]>([])
// const personList = ref<any[]>([])
const resetDataDialog=ref()
const delAllDataDialog=ref()
const handleFileChange = async (e: any) => {
let dataBinary = await readFile(e.target.files[0])
const handleFileChange = async (e: Event) => {
let dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
let workBook = XLSX.read(dataBinary, { type: 'binary', cellDates: true })
let workSheet = workBook.Sheets[workBook.SheetNames[0]]
excelData.value = XLSX.utils.sheet_to_json(workSheet)
@@ -27,12 +30,51 @@ const handleFileChange = async (e: any) => {
personConfig.resetPerson()
personConfig.addNotPersonList(allData)
}
const 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
// 修改字段名称
if (data[i].isWin) {
data[i].isWin = '是'
} else {
data[i].isWin = '否'
}
}
let dataString = JSON.stringify(data)
dataString = dataString
.replaceAll(/uid/g, '编号')
.replaceAll(/isWin/g, '是否中奖')
.replaceAll(/department/g, '部门')
.replaceAll(/name/g, '姓名')
.replaceAll(/identity/g, '身份')
.replaceAll(/prizeName/g, '获奖')
.replaceAll(/prizeTime/g, '获奖时间')
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')
}
}
const resetData = () => {
personConfig.resetAlreadyPerson()
}
const deleteAll = () => {
personConfig.deleteAllPerson()
}
const delPersonItem = (row: any) => {
const delPersonItem = (row: IPersonConfig) => {
personConfig.deletePerson(row)
}
@@ -56,7 +98,7 @@ const tableColumns = [
{
label: '是否已中奖',
props: 'isWin',
formatValue(row: any) {
formatValue(row: IPersonConfig) {
return row.isWin ? '是' : '否'
}
},
@@ -73,7 +115,7 @@ const tableColumns = [
{
label: '删除',
type: 'btn-error',
onClick: (row: any) => {
onClick: (row: IPersonConfig) => {
delPersonItem(row)
}
},
@@ -86,11 +128,38 @@ onMounted(() => {
</script>
<template>
<dialog id="my_modal_1" ref="resetDataDialog" class="border-none modal">
<div class="modal-box">
<h3 class="text-lg font-bold">提示!</h3>
<p class="py-4">该操作会重置所有人员数据是否继续</p>
<div class="modal-action">
<form method="dialog" class="flex gap-3">
<!-- if there is a button in form, it will close the modal -->
<button class="btn" @click="resetDataDialog.close()">取消</button>
<button class="btn" @click="resetData">确定</button>
</form>
</div>
</div>
</dialog>
<dialog id="my_modal_1" ref="delAllDataDialog" class="border-none modal">
<div class="modal-box">
<h3 class="text-lg font-bold">提示!</h3>
<p class="py-4">该操作会删除所有人员数据是否继续</p>
<div class="modal-action">
<form method="dialog" class="flex gap-3">
<!-- if there is a button in form, it will close the modal -->
<button class="btn" @click="delAllDataDialog.close()">取消</button>
<button class="btn" @click="deleteAll">确定</button>
</form>
</div>
</div>
</dialog>
<div class="min-w-1000px">
<div class="flex gap-3 justify-">
<button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button>
<button class="btn btn-error btn-sm" @click="delAllDataDialog.showModal()">全部删除</button>
<div class="tooltip tooltip-bottom" data-tip="下载文件后请在excel中填写数据并保存为xlsx格式">
<a class="no-underline btn btn-secondary btn-sm" download="人口登记表.xlsx" target="_blank" href="/log-lottery/人口登记表.xlsx">下载模板</a>
<a class="no-underline btn btn-secondary btn-sm" download="人口登记表.xlsx" target="_blank"
href="/log-lottery/人口登记表.xlsx">下载模板</a>
</div>
<div class="">
<label for="explore">
@@ -105,6 +174,8 @@ onMounted(() => {
<!-- <button class="btn btn-primary btn-sm">上传excel</button> -->
</div>
<button class="btn btn-error btn-sm" @click="resetDataDialog.showModal()">重置数据</button>
<button class="btn btn-accent btn-sm" @click="exportData">导出结果</button>
<div>
<span>中奖人数</span>
<span>{{ alreadyPersonList.length }}</span>

View File

@@ -1,13 +1,14 @@
<!-- eslint-disable vue/no-parsing-error -->
<script setup lang='ts'>
// import { ref } from 'vue';
import { ref } from 'vue';
import useStore from '@/store'
import { IPersonConfig } from '@/types/storeType';
import { storeToRefs } from 'pinia';
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
const personConfig = useStore().personConfig
const { getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: alreadyPersonDetail } = storeToRefs(personConfig)
// const personList = ref<any[]>(
// alreadyPersonList
// )
@@ -16,11 +17,57 @@ const { getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
// const deleteAll = () => {
// personConfig.deleteAllPerson()
// }
const handleMoveNotPerson=(row:any)=>{
const isDetail = ref(false)
const handleMoveNotPerson = (row: IPersonConfig) => {
personConfig.moveAlreadyToNot(row)
}
const tableColumns = [
const tableColumnsList = [
{
label: '编号',
props: 'uid',
sort: true
},
{
label: '姓名',
props: 'name',
},
{
label: '部门',
props: 'department',
},
{
label: '身份',
props: 'identity',
},
{
label: '奖品',
props: 'prizeName',
sort: true
},
{
label: '操作',
actions: [
{
label: '移入未中奖名单',
type: 'btn-info',
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
}
},
// {
// label: '删除',
// type: 'btn-error',
// onClick: (row: any) => {
// console.log('删除:', row)
// }
// },
]
},
]
const tableColumnsDetail = [
{
label: '编号',
props: 'uid',
@@ -54,17 +101,10 @@ const tableColumns = [
{
label: '移入未中奖名单',
type: 'btn-info',
onClick: (row: any) => {
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
}
},
{
label: '删除',
type: 'btn-error',
onClick: (row: any) => {
console.log('删除:', row)
}
},
]
},
@@ -73,14 +113,24 @@ const tableColumns = [
<template>
<div class="overflow-y-auto">
<div class="flex justify-start gap-3">
<div class="flex items-center justify-start gap-10">
<!-- <button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button> -->
<div>
<span>中奖人数</span>
<span>{{ alreadyPersonList.length }}</span>
</div>
<div class="flex flex-col">
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">详细信息:</span>
<input type="checkbox" class="border-solid toggle toggle-primary border-1" v-model="isDetail" />
</label>
</div>
<DaiysuiTable :tableColumns="tableColumns" :data="alreadyPersonList"></DaiysuiTable>
</div>
</div>
<DaiysuiTable v-if="!isDetail" :tableColumns="tableColumnsList" :data="alreadyPersonList"></DaiysuiTable>
<DaiysuiTable v-if="isDetail" :tableColumns="tableColumnsDetail" :data="alreadyPersonDetail"></DaiysuiTable>
</div>
</template>

View File

@@ -1,9 +1,9 @@
<script setup lang='ts'>
import { ref, onMounted, watch } from 'vue'
import useStore from '@/store'
import { IPrizeConfig } from '@/types/storeType'
import { storeToRefs } from 'pinia'
import localforage from 'localforage'
import { IPrizeConfig } from '@/types/prizeConfig';
const imageDbStore = localforage.createInstance({
name: 'imgStore'
@@ -51,7 +51,7 @@ const getImageDbStore = async () => {
}
}
const sort = (item: any, isUp: number) => {
const sort = (item: IPrizeConfig, isUp: number) => {
const itemIndex = prizeList.value.indexOf(item)
if (isUp == 1) {
prizeList.value.splice(itemIndex, 1)
@@ -70,7 +70,7 @@ const delAll = async () => {
onMounted(() => {
getImageDbStore()
})
watch(() => prizeList, (val:any) => {
watch(() => prizeList.value, (val:IPrizeConfig[]) => {
prizeConfig.setPrizeConfig(val)
}, { deep: true })
</script>
@@ -136,18 +136,21 @@ watch(() => prizeList, (val:any) => {
<span class="label-text">图片</span>
</div>
<select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture">
<option v-if="item.picture.id" :value="{id:'',name:'',url:''}"><span></span></option>
<option disabled selected>选择一张图片</option>
<option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}
</option>
</select>
</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">
<span class="label-text">展示在主界面</span>
</div>
<input type="checkbox" :checked="item.isShow" @change="item.isShow = !item.isShow"
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
</label>
</label> -->
<!-- <label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">抽取次数</span>

View File

@@ -64,26 +64,17 @@ const skip = (path: string) => {
</div>
<footer class="p-10 rounded footer footer-center bg-base-200 text-base-content">
<nav class="grid grid-flow-col gap-4">
<a class="link link-hover">About us</a>
<a class="link link-hover">Contact</a>
<a class="link link-hover">Jobs</a>
<a class="link link-hover">Press kit</a>
<a class="link link-hover">行有不得反求诸己</a>
</nav>
<nav>
<div class="grid grid-flow-col gap-4">
<a href="https://github.com/LOG1997/log-lottery" target="_blank" class="cursor-pointer">
<a href="https://github.com/LOG1997/log-lottery" target="_blank" class="cursor-pointer text-inherit">
<svg-icon name="github"></svg-icon>
</a>
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
<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">
</path>
</svg></a>
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current">
<path
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z">
</path>
</svg></a>
<a href="https://twitter.com/TaborSwift" target="_blank" class="cursor-pointer "><svg-icon name="twitter"></svg-icon></a>
<a href="https://www.instagram.com/log.z1997/" target="_blank" class="cursor-pointer ">
<svg-icon name="instagram"></svg-icon>
</a>
</div>
</nav>
<aside>

View File

@@ -3,6 +3,9 @@ import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import useStore from '@/store'
import ImageSync from '@/components/ImageSync/index.vue'
import defaultPrizeImage from '@/assets/images/龙.png'
const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
@@ -39,7 +42,8 @@ onMounted(() => {
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 z-50 w-full h-full bg-gray-800/70 item-mask rounded-xl"></div>
<figure class="w-10 h-10 rounded-xl">
<img :src="item.picture.url" alt="Shoes" class="object-cover h-full rounded-xl" />
<ImageSync v-if="item.picture.url" :imgItem="item.picture"></ImageSync>
<img v-else :src="defaultPrizeImage" alt="Prize" class="object-cover h-full rounded-xl" />
</figure>
<div class="items-center p-0 text-center card-body">
<h2 class="p-0 m-0 card-title">{{ item.name }}</h2>

View File

@@ -5,12 +5,16 @@ import { useElementStyle, useElementPosition } from '@/hooks/useElement'
import StarsBackground from '@/components/StarsBackground/index.vue'
import confetti from 'canvas-confetti'
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 * as THREE from 'three'
import {Scene,PerspectiveCamera,Object3D,Vector3} from 'three'
// import {
// CSS3DRenderer, CSS3DObject
// } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
import {CSS3DRenderer, CSS3DObject} from 'three-css3d'
// import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';
import TrackballControls from 'three-trackballcontrols';
// import TWEEN from 'three/examples/jsm/libs/tween.module.js';
import * as TWEEN from '@tweenjs/tween.js'
import useStore from '@/store'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
@@ -28,7 +32,6 @@ const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const { getTopTitle: topTitle, getCardColor: cardColor, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount } = storeToRefs(globalConfig)
const tableData = ref<any[]>([])
// const tableData = ref<any[]>(JSON.parse(JSON.stringify(alreadyPersonList.value)).concat(JSON.parse(JSON.stringify(notPersonList.value))))
const currentStatus = ref(0) // 0为初始状态 1为抽奖准备状态2为抽奖中状态3为抽奖结束状态
const ballRotationY = ref(0)
const containerRef = ref<HTMLElement>()
@@ -80,8 +83,8 @@ const init = () => {
const farPlane = 10000;
const WebGLoutput = containerRef.value
scene.value = new THREE.Scene();
camera.value = new THREE.PerspectiveCamera(felidView, aspect, nearPlane, farPlane);
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)
@@ -138,7 +141,7 @@ const init = () => {
const tableLen = tableData.value.length;
for (let i = 0; i < tableLen; i++) {
const object = new THREE.Object3D();
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;
@@ -151,12 +154,12 @@ const init = () => {
function createSphereVertices() {
let i = 0;
const objLength = objects.value.length;
const vector = new THREE.Vector3();
const vector = new Vector3();
for (; i < objLength; ++i) {
let phi = Math.acos(-1 + (2 * i) / objLength);
let theta = Math.sqrt(objLength * Math.PI) * phi;
const object = new THREE.Object3D();
const object = new Object3D();
object.position.x = 800 * Math.cos(theta) * Math.sin(phi);
object.position.y = 800 * Math.sin(theta) * Math.sin(phi);
@@ -171,12 +174,12 @@ const init = () => {
}
function createHelixVertices() {
let i = 0;
const vector = new THREE.Vector3();
const vector = new Vector3();
const objLength = objects.value.length;
for (; i < objLength; ++i) {
let phi = i * 0.213 + Math.PI;
const object = new THREE.Object3D();
const object = new Object3D();
object.position.x = 800 * Math.sin(phi);
object.position.y = -(i * 8) + 450;
@@ -378,14 +381,13 @@ const stopLottery = async () => {
rollBall(0, 1)
currentStatus.value = 0
// 抽奖池是否为全体人员
// const personPool=currentPrize.value.isAll?
// const notPersonListLength = notPersonList.value.length;
const personPool = currentPrize.value.isAll ? allPersonList.value : notPersonList.value
// const notPersonListLength = personPool.length;
// 每次最多抽十个
let luckyCount = 10
const leftover = currentPrize.value.count - currentPrize.value.isUsedCount
leftover < luckyCount ? luckyCount = leftover : luckyCount
if (notPersonList.value.length < leftover) {
if (personPool.length < leftover) {
toast.open({
message: '抽奖人数不够',
type: 'warning',
@@ -396,24 +398,25 @@ const stopLottery = async () => {
return;
}
for (let i = 0; i < luckyCount; i++) {
if (notPersonList.value.length > 0) {
const randomIndex = Math.round(Math.random() * notPersonList.value.length - 1)
luckyTargets.value.push(notPersonList.value[randomIndex])
// console.log(
// 'leftover:', leftover, '\n',
// 'luckyCount', luckyCount, '\n',
// 'currentPrize.value.isUsedCount', currentPrize.value.isUsedCount, '\n',
// 'randomIndex', randomIndex, '\n',
// 'notPersonList.value.length - 1', notPersonList.value.length - 1, '\n',
// 'notPersonList.value[randomIndex]', notPersonList.value[randomIndex], '\n',
// 'cadd id:', notPersonList.value[randomIndex].id
// )
let LuckyCard = objects.value[notPersonList.value[randomIndex].id]
luckyCardList.value.push(LuckyCard)
if (personPool.length > 0) {
const randomIndex = Math.round(Math.random() * (personPool.length-1))
luckyTargets.value.push(personPool[randomIndex])
console.log(
'leftover:', leftover, '\n',
'luckyCount', luckyCount, '\n',
'currentPrize.value.isUsedCount', currentPrize.value.isUsedCount, '\n',
'randomIndex', randomIndex, '\n',
'personPool.length - 1', personPool.length - 1, '\n',
'personPool[randomIndex]', personPool[randomIndex], '\n',
)
console.log(
'cadd id:', personPool[randomIndex].id)
let luckyCard = objects.value[personPool[randomIndex].id]
luckyCardList.value.push(luckyCard)
notPersonList.value.splice(randomIndex, 1)
personPool.splice(randomIndex, 1)
// console.log(
// 'objects.value[notPersonList.value[randomIndex].id]', LuckyCard
// 'objects.value[personPool[randomIndex].id]', LuckyCard
// )
}
}
@@ -443,16 +446,16 @@ const stopLottery = async () => {
resetCamera()
})
})
currentPrize.value.isUsedCount += luckyCount
if (currentPrize.value.isUsedCount >= currentPrize.value.count) {
currentPrize.value.isUsed = true
}
// currentPrize.value.isUsedCount += luckyCount
// if (currentPrize.value.isUsedCount >= currentPrize.value.count) {
// currentPrize.value.isUsed = true
// }
// luckyCardList.value = []
personConfig.addAlreadyPersonList(luckyTargets.value, currentPrize.value)
prizeConfig.updatePrizeConfig(currentPrize.value)
// personConfig.addAlreadyPersonList(luckyTargets.value, currentPrize.value)
// prizeConfig.updatePrizeConfig(currentPrize.value)
prizeConfig.setCurrentPrize(currentPrize.value)
// prizeConfig.setCurrentPrize(currentPrize.value)
luckyTargets.value = []
}
// 庆祝动画
@@ -588,11 +591,13 @@ onUnmounted(() => {
margin: 0 auto;
font-size: 32px;
}
.start {
// 居中
display: flex;
justify-content: center;
}
.btn-start {
cursor: pointer;
display: flex;
@@ -764,6 +769,7 @@ strong {
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;

1
src/vite-env.d.ts vendored
View File

@@ -7,3 +7,4 @@ declare module '*.vue' {
}
declare module 'sparticles'
declare module 'three-trackballcontrols'

View File

@@ -1,5 +1,7 @@
默认图片
-- 默认图片
临时增加抽奖
从全员或者未中奖中人抽取
-- 从全员或者未中奖中人抽取
-- 导出表格