feat: fork同步代码
This commit is contained in:
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -1,12 +0,0 @@
|
||||
.md
|
||||
node_modules
|
||||
public
|
||||
package.json
|
||||
*.yaml
|
||||
.gitignore
|
||||
.eslintrc*
|
||||
.babelrc
|
||||
.eslintignore
|
||||
.commitlintrc*
|
||||
.env*
|
||||
tsconfig*
|
||||
@@ -1,57 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-essential",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
overrides: [],
|
||||
parser: "vue-eslint-parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
parser: "@typescript-eslint/parser",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["vue", "@typescript-eslint"],
|
||||
rules: {
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
extendDefaults: true,
|
||||
types: {
|
||||
"{}": false,
|
||||
},
|
||||
},
|
||||
],
|
||||
// 关闭typescript类型为any的警告
|
||||
"@typescript-eslint/no-explicit-any": ["off"],
|
||||
// 驼峰命名,但忽略index
|
||||
"vue/multi-word-component-names": [
|
||||
"error",
|
||||
{
|
||||
ignores: ["index"], //需要忽略的组件名
|
||||
},
|
||||
],
|
||||
"no-console": "warn",
|
||||
"no-debugger": "warn",
|
||||
// complexity: ["warn", { max: 5 }],
|
||||
// 禁止使用多个空格
|
||||
"no-multi-spaces": "error",
|
||||
// 最大连续空行数
|
||||
"no-multiple-empty-lines": ["error", { max: 2, maxEOF: 1, maxBOF: 0 }],
|
||||
// 代码块中去除前后空行
|
||||
"padded-blocks": ["error", "never"],
|
||||
// 使用单引号,字符串中包含了一个其它引号 允许"a string containing 'single' quotes"
|
||||
quotes: ["error", "single", { avoidEscape: true }],
|
||||
// return之前必须空行
|
||||
"newline-before-return": "error",
|
||||
//文件末尾强制换行
|
||||
"eol-last": ["error", "always"],
|
||||
//禁止空格和 tab 的混合缩进
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
},
|
||||
};
|
||||
4
.github/workflows/node.js.yml
vendored
4
.github/workflows/node.js.yml
vendored
@@ -4,8 +4,6 @@
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
@@ -17,7 +15,7 @@ jobs:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
node-version: [20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,7 +6,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
pnpm-lock.yaml
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
@@ -128,3 +128,4 @@ dist
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
target
|
||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# 使用官方的 Node 镜像作为基础镜像
|
||||
FROM node:20.12.2
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# 将本地的 Vite 项目文件复制到工作目录
|
||||
COPY . .
|
||||
|
||||
# 安装依赖
|
||||
|
||||
RUN npm install pnpm -g
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
# 执行 Vite 构建命令,生成 dist 目录
|
||||
RUN pnpm build
|
||||
|
||||
# 使用 Nginx 镜像作为运行时镜像
|
||||
FROM nginx:1.26
|
||||
|
||||
# 修改nginx配置
|
||||
# 向 #error_page 前添加内容
|
||||
# location /log-lottery {
|
||||
# alias /usr/share/nginx/log-lottery;
|
||||
# index index.html index.htm;
|
||||
# try_files $uri $uri/ /log-lottery/index.html;
|
||||
# }
|
||||
RUN sed -i 's/#error_page/location \/log-lottery {\n alias \/usr\/share\/nginx\/log-lottery;\n index index.html index.htm;\n try_files $uri $uri\/ \/log-lottery\/index.html;\n }\n#error_page/' /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 将 Vite 项目的 dist 目录复制到 Nginx 的默认静态文件目录
|
||||
COPY --from=0 /usr/src/app/dist /usr/share/nginx/log-lottery
|
||||
|
||||
# 暴露容器的 80 端口
|
||||
EXPOSE 80
|
||||
|
||||
# Nginx 会在容器启动时自动运行,无需手动设置 CMD
|
||||
71
README.md
71
README.md
@@ -1,20 +1,26 @@
|
||||
<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>
|
||||
<div align="center">
|
||||
<a href="https://log1997.github.io/log-lottery/">
|
||||
<img src="./static/images/lottery.png" width="100" height="100" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# log-lottery 🚀🚀🚀🚀
|
||||
|
||||
[](https://github.com/LOG1997/log-lottery)
|
||||
[](https://github.com/LOG1997/log-lottery)
|
||||
[](https://github.com/log1997)
|
||||
[](https://github.com/log1997)
|
||||
[](https://github.com/log1997)
|
||||
[](https://github.com/log1997)
|
||||
|
||||
</div>
|
||||
|
||||
log-lottery是一个可配置可定制化的抽奖应用,炫酷3D球体,可用于年会抽奖等活动,支持奖品、人员、界面、图片音乐配置。
|
||||
|
||||
> 因原域名到期,现将原域名 (<https://24years.top/log-lottery>)
|
||||
迁移到 (<https://1kw20.fun/log-lottery>) 。
|
||||
如果进入到新域名遇到图片无法访问的情况,请到【全局配置】-【界面配置】菜单中点击【重置所有数据】按钮进行更新
|
||||
|
||||
> 如果进入网站遇到图片无法显示或有报错的情况,请先到【全局配置】-【界面配置】菜单中点击【重置所有数据】按钮清除数据后进行更新。
|
||||
|
||||
> 请尽量拉取代码进行构建部署,本线上网站代码会保持更新,可能影响原有功能和数据。
|
||||
|
||||
## 要求
|
||||
|
||||
@@ -38,10 +44,13 @@ or
|
||||
- [x] 🖼️ excel表格导入人员名单、抽奖结果使用excel导出
|
||||
- [x] 🎈 可增加临时抽奖
|
||||
- [x] 🧨 国际化多语言
|
||||
- [ ] 添加docker构建部署和镜像
|
||||
- [ ] 更换背景图片
|
||||
- [x] 🍃 更换背景图片
|
||||
- [x] 🚅 添加docker构建
|
||||
- [x] 😘 弹幕(开发中)
|
||||
- [ ] 🧵 卡片组成多种形状
|
||||
|
||||
...
|
||||
需要更多功能请留言
|
||||
需要更多功能或发现bug请留言[issues](https://github.com/LOG1997/log-lottery/issues)
|
||||
|
||||
## 详细介绍
|
||||
|
||||
@@ -105,24 +114,64 @@ or
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
or
|
||||
npm install
|
||||
```
|
||||
|
||||
开发运行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
or
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打包
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
or
|
||||
npm run build
|
||||
```
|
||||
|
||||
预览
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
or
|
||||
npm run preview
|
||||
```
|
||||
|
||||
若想直接以打开html文件的方式运行,请执行以下命令进行打包。打包完成后在dist目录中直接打开index.html即可。
|
||||
|
||||
```bash
|
||||
pnpm build:file
|
||||
or
|
||||
npm run build:file
|
||||
```
|
||||
|
||||
> 项目思路来源于 <https://github.com/moshang-xc/lottery>
|
||||
|
||||
## Docker支持
|
||||
|
||||
构建镜像
|
||||
|
||||
```bash
|
||||
docker build -t log-lottery .
|
||||
```
|
||||
|
||||
运行容器
|
||||
|
||||
```bash
|
||||
docker run -d -p 9279:80 log-lottery
|
||||
```
|
||||
|
||||
容器运行成功后即可在本地通过<http://localhost:9279/log-lottery/>访问
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#LOG1997/log-lottery&Date)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](http://opensource.org/licenses/MIT)
|
||||
|
||||
Copyright (c) 2024-present log1997
|
||||
|
||||
7
eslint.config.js
Normal file
7
eslint.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
ignores: ['**/node_modules', '**/public', '**/dist', '**/package.json', '**/*.yaml', '**/.gitignore', '**/.env*', '**/tsconfig*'],
|
||||
},
|
||||
)
|
||||
13
package.json
13
package.json
@@ -1,17 +1,19 @@
|
||||
{
|
||||
"name": "log-lottery",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"build:pre": "vue-tsc --noEmit && vite build --mode prebuild",
|
||||
"build:file": "vue-tsc --noEmit && vite build --mode file",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint ./src --ext .vue,.js,.ts,.jsx,.tsx --fix"
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tweenjs/tween.js": "^23.1.2",
|
||||
@@ -25,11 +27,11 @@
|
||||
"pinia": "^2.2.6",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"sparticles": "^1.3.1",
|
||||
"theme-change": "^2.5.0",
|
||||
"three": "^0.166.0",
|
||||
"three-css3d": "^1.0.6",
|
||||
"vue": "^3.5.13",
|
||||
"vue-dompurify-html": "^5.2.0",
|
||||
"vue-i18n": "^10.0.4",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-toast-notification": "^3",
|
||||
"vue3-colorpicker": "^2.3.0",
|
||||
@@ -37,6 +39,9 @@
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.9.2",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.15.0",
|
||||
"@iconify-json/ep": "^1.2.1",
|
||||
"@iconify-json/fluent": "^1.2.8",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
@@ -47,6 +52,7 @@
|
||||
"@types/three": "^0.166.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.16.0",
|
||||
"@typescript-eslint/parser": "^8.16.0",
|
||||
"@vitejs/plugin-legacy": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"@vitest/ui": "^2.1.5",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
@@ -54,6 +60,7 @@
|
||||
"daisyui": "^4.12.14",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"globals": "^15.12.0",
|
||||
"happy-dom": "^15.11.6",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^25.0.1",
|
||||
|
||||
3096
pnpm-lock.yaml
generated
3096
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +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="1705479543818" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4421" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M341.333333 618.666667l-179.2 174.933333C185.6 834.133333 149.333333 853.333333 149.333333 853.333333s-27.733333-34.133333-53.333333-64c-14.933333-17.066667-14.933333-40.533333 10.666667-64l149.333333-149.333333 106.666667 42.666667h-21.333334z m554.666667-296.533334s-44.8-12.8-64 17.066667v2.133333l-196.266667 21.333334 8.533334 85.333333 166.4-21.333333s27.733333 2.133333 42.666666-21.333334 42.666667-83.2 42.666667-83.2zM253.866667 541.866667c4.266667-10.666667-2.133333-23.466667-12.8-27.733334-4.266667-2.133333-93.866667-36.266667-93.866667-132.266666 0-12.8-8.533333-21.333333-21.333333-21.333334s-21.333333 8.533333-21.333334 21.333334c0 125.866667 106.666667 174.933333 113.066667 177.066666 2.133333 0 14.933333-2.133333 17.066667-2.133333 8.533333-2.133333 17.066667-6.4 19.2-14.933333zM597.333333 149.333333s-57.6-44.8-106.666666 0c0 0 42.666667 44.8 64 85.333334 93.866667-44.8 42.666667-85.333333 42.666666-85.333334z" fill="#844419" p-id="4422"></path><path d="M896 128m-42.666667 0a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 1 0-85.333334 0Z" fill="#E91E63" p-id="4423"></path><path d="M917.333333 469.333333s-46.933333 53.333333-66.133333 76.8c-17.066667 17.066667-46.933333 4.266667-46.933333 4.266667l-147.2-46.933333c-12.8 17.066667-32 34.133333-59.733334 51.2-40.533333 27.733333-106.666667 51.2-172.8 66.133333L341.333333 746.666667l-42.666666 128c38.4 2.133333 42.666667 42.666667 42.666666 42.666666s-61.866667 4.266667-85.333333 0-51.2-32-42.666667-64l55.466667-183.466666c-6.4-12.8-17.066667-29.866667-34.133333-51.2-59.733333-74.666667 21.333333-128 21.333333-128s206.933333-134.4 256-170.666667c32-23.466667 40.533333-61.866667 42.666667-85.333333 93.866667-44.8 42.666667-85.333333 42.666666-85.333334s-2.133333-2.133333-4.266666-2.133333c12.8-8.533333 27.733333-14.933333 46.933333-19.2 49.066667-12.8 49.066667 21.333333 149.333333 21.333333 49.066667 0 10.666667 83.2-42.666666 106.666667-36.266667 14.933333-85.333333 49.066667-85.333334 85.333333 0 17.066667 17.066667 44.8 21.333334 81.066667l168.533333 49.066667s2.133333-4.266667 4.266667-4.266667c34.133333-25.6 61.866667 2.133333 61.866666 2.133333zM522.666667 185.6c23.466667-19.2 49.066667-40.533333 70.4-40.533333l-12.8-6.4c-21.333333-10.666667-57.6-19.2-89.6 10.666666 0 0-34.133333 44.8-17.066667 61.866667 8.533333 8.533333 27.733333-6.4 49.066667-25.6z" fill="#CC6C25" p-id="4424"></path><path d="M789.333333 149.333333h-21.333333s10.666667 34.133333 38.4 42.666667c8.533333-23.466667 6.4-42.666667-17.066667-42.666667z" fill="#5D4037" p-id="4425"></path><path d="M704 170.666667c0 12.8-8.533333 21.333333-21.333333 21.333333s-21.333333-21.333333-21.333334-21.333333 8.533333-21.333333 21.333334-21.333334 21.333333 8.533333 21.333333 21.333334z" fill="#3E2723" p-id="4426"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="#FFF" d="M128 249.018C61.09 249.018 6.982 194.91 6.982 128S61.09 6.982 128 6.982S249.018 61.091 249.018 128c0 66.91-54.109 121.018-121.018 121.018Z"/><path fill="#3F3F3F" d="M128 14.545c62.836 0 113.455 51.2 113.455 113.455c0 62.255-50.619 113.455-113.455 113.455S14.545 190.836 14.545 128S65.164 14.545 128 14.545ZM128 0C57.018 0 0 57.018 0 128s57.018 128 128 128s128-57.018 128-128S197.818 0 128 0Z"/><path fill="#0AB3E5" d="m74.473 119.273l23.272 18.618l32-52.364l40.728 65.164l22.109-17.455l24.436 36.073c5.818-12.8 10.473-27.927 10.473-42.473c0-55.272-43.636-98.909-98.91-98.909c-55.272 0-100.654 44.8-100.654 98.91c0 17.454 4.655 32 11.637 46.545l34.909-54.11Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 772 B |
BIN
public/personListTemplate-en.xlsx
Normal file
BIN
public/personListTemplate-en.xlsx
Normal file
Binary file not shown.
31
public/readme-en.md
Normal file
31
public/readme-en.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Operation Guide
|
||||
|
||||
## Steps
|
||||
|
||||
1. Upon first entry, no data will be displayed. You can choose to use default data to view the overall display effect. It is recommended to import your own data for operation. The steps are as follows:
|
||||
|
||||
a. Personnel Configuration - Personnel List - Download Template, download the data template and modify it with your data (please note that the header cannot be modified).
|
||||
|
||||
b. After modification, click 'Upload File' on the same page to upload the modified Excel table.
|
||||
|
||||
2. Enter the Prize Configuration to modify your prize information. Try to keep the name short for better display; "All Participants" indicates whether this award will be drawn from all participants (those who have already won can still participate); "Winners" refers to the number of people to be drawn for this award; "Already Won" cannot be edited; "Selected" means this award has been used, unselecting it will reset the award but not the winners; "Image" is the prize image displayed on the home page (you can upload images in the image list); "Left Icon" is used to adjust the order of prizes.
|
||||
|
||||
Completing the above two steps allows normal use.
|
||||
|
||||
## Function Description
|
||||
|
||||
1. Add Temporary Draw: There is a '+' button in the prize list on the draw page. Clicking it allows you to add a temporary draw. Note: Only one temporary draw can be added at a time. After adding successfully, the current prize will be set to the temporary prize, and after drawing, it will return to the normal prize list.
|
||||
2. Music and Image List: You can upload files yourself for use. After uploading images successfully, you can select them in the prize configuration for display. After uploading music successfully, it will be added to the play list.
|
||||
3. Music Playback: Left-click with the mouse to play/pause, right-click to play the next song.
|
||||
4. Interface Configuration - Pattern Settings: You can use the mouse to click and customize the highlighted patterns on the home page.
|
||||
5. If you do not want to display the prize list on the home page, uncheck 'Always Show Prize List' in the interface configuration.
|
||||
6. When clicking buttons on the home page, the button value will not update immediately but will only update after the animation ends. This is a normal phenomenon.
|
||||
|
||||
## Shortcuts
|
||||
|
||||
Shortcuts are set up on the draw page.
|
||||
|
||||
| Shortcut | Description |
|
||||
| --- | --- |
|
||||
| Space | Enter Draw / Start / Draw Lucky Winner / Continue |
|
||||
| Esc | Cancel |
|
||||
66
src/App.vue
66
src/App.vue
@@ -1,26 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted,ref } from 'vue'
|
||||
import useStore from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import PlayMusic from '@/components/PlayMusic/index.vue'
|
||||
import useStore from '@/store'
|
||||
import { themeChange } from '@/utils'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { themeChange } from 'theme-change'
|
||||
|
||||
const { t } = useI18n()
|
||||
const globalConfig = useStore().globalConfig
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const system=useStore().system
|
||||
const system = useStore().system
|
||||
const { getTheme: localTheme } = storeToRefs(globalConfig)
|
||||
const { getPrizeConfig: prizeList } = storeToRefs(prizeConfig)
|
||||
// const { getIsMobile: isMobile } = storeToRefs(system)
|
||||
|
||||
const tipDialog=ref()
|
||||
// const isMobileValue = ref(structuredClone(isMobile.value))
|
||||
const setLocalTheme = (theme: any) => {
|
||||
themeChange(theme.name)
|
||||
}
|
||||
const tipDialog = ref()
|
||||
|
||||
// 设置当前奖列表
|
||||
const setCurrentPrize = () => {
|
||||
function setCurrentPrize() {
|
||||
if (prizeList.value.length <= 0) {
|
||||
return
|
||||
}
|
||||
@@ -31,33 +27,31 @@ const setCurrentPrize = () => {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
// 判断是否手机端访问
|
||||
const judgeMobile=()=>{
|
||||
function judgeMobile() {
|
||||
const ua = navigator.userAgent
|
||||
const isAndroid = ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1
|
||||
const isIOS =!!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
|
||||
const isAndroid = ua.includes('Android') || ua.includes('Adr')
|
||||
const isIOS = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
|
||||
|
||||
system.setIsMobile(isAndroid||isIOS)
|
||||
system.setIsMobile(isAndroid || isIOS)
|
||||
|
||||
return isAndroid||isIOS
|
||||
return isAndroid || isIOS
|
||||
}
|
||||
// 判断是否chrome或者edge访问
|
||||
const judgeChromeOrEdge=()=>{
|
||||
function judgeChromeOrEdge() {
|
||||
const ua = navigator.userAgent
|
||||
const isChrome = ua.indexOf('Chrome') > -1
|
||||
const isEdge = ua.indexOf('Edg') > -1
|
||||
const isChrome = ua.includes('Chrome')
|
||||
const isEdge = ua.includes('Edg')
|
||||
|
||||
system.setIsChrome(isChrome)
|
||||
|
||||
return isChrome||isEdge
|
||||
return isChrome || isEdge
|
||||
}
|
||||
onMounted(() => {
|
||||
setLocalTheme(localTheme.value)
|
||||
themeChange(localTheme.value.name)
|
||||
setCurrentPrize()
|
||||
if(judgeMobile()||!judgeChromeOrEdge()){
|
||||
if (judgeMobile() || !judgeChromeOrEdge()) {
|
||||
tipDialog.value.showModal()
|
||||
}
|
||||
})
|
||||
@@ -66,19 +60,27 @@ onMounted(() => {
|
||||
<template>
|
||||
<dialog id="my_modal_1" ref="tipDialog" class="border-none modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">提示!</h3>
|
||||
<p class="py-4" v-if="judgeMobile()">请使用PC进行访问以获得最佳显示效果</p>
|
||||
<p class="py-4" v-if=" !judgeChromeOrEdge()">请使用最新版Chrome或者Edge浏览器</p>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ t('dialog.titleTip') }}
|
||||
</h3>
|
||||
<p v-if="judgeMobile()" class="py-4">
|
||||
{{ t('dialog.dialogPCWeb') }}
|
||||
</p>
|
||||
<p v-if=" !judgeChromeOrEdge()" class="py-4">
|
||||
{{ t('dialog.dialogLatestBrowser') }}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<form method="dialog" class="flex justify-start w-full gap-3">
|
||||
<!-- if there is a button in form, it will close the modal -->
|
||||
<button class="btn">确定</button>
|
||||
<button class="btn">
|
||||
{{ t('button.confirm') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<router-view></router-view>
|
||||
<PlayMusic class="absolute right-0 bottom-1/2"></PlayMusic>
|
||||
<router-view />
|
||||
<PlayMusic class="absolute right-0 bottom-1/2" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,61 +1,62 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import type { InternalAxiosRequestConfig } from 'axios';
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
||||
import axios from 'axios'
|
||||
|
||||
class Request {
|
||||
private instance: AxiosInstance;
|
||||
private instance: AxiosInstance
|
||||
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
this.instance = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
...config,
|
||||
});
|
||||
})
|
||||
|
||||
// 添加请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// 在发送请求之前做些什么
|
||||
console.log('请求拦截器被触发');
|
||||
console.log('请求拦截器被触发')
|
||||
|
||||
return config;
|
||||
return config
|
||||
},
|
||||
(error: any) => {
|
||||
// 对请求错误做些什么
|
||||
console.error('请求拦截器发生错误:', error);
|
||||
console.error('请求拦截器发生错误:', error)
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// 添加响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
// 对响应数据做些什么
|
||||
console.log('响应拦截器被触发');
|
||||
const reponseData = response.data;
|
||||
console.log('响应拦截器被触发')
|
||||
const responseData = response.data
|
||||
|
||||
return reponseData;
|
||||
return responseData
|
||||
},
|
||||
(error: any) => {
|
||||
// 对响应错误做些什么
|
||||
console.error('响应拦截器发生错误:', error);
|
||||
console.error('响应拦截器发生错误:', error)
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
public async request<T>(config: AxiosRequestConfig): Promise<T> {
|
||||
const response: AxiosResponse<T> = await this.instance.request(config);
|
||||
const response: AxiosResponse<T> = await this.instance.request(config)
|
||||
|
||||
return response.data;
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
// 函数
|
||||
function request<T>(config: AxiosRequestConfig): Promise<T> {
|
||||
const instance = new Request(config);
|
||||
const instance = new Request(config)
|
||||
|
||||
return instance.request(config);
|
||||
return instance.request(config)
|
||||
}
|
||||
|
||||
export default request;
|
||||
export default request
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<script setup lang='ts'>
|
||||
import { computed } from 'vue';
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array as any,
|
||||
default: [] as any[]
|
||||
default: [] as any[],
|
||||
},
|
||||
tableColumns: {
|
||||
type: Array,
|
||||
default: [] as any[]
|
||||
default: [] as any[],
|
||||
},
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const dataColumns = computed<any[]>(() => {
|
||||
// 不带有actions的列
|
||||
const columns = props.tableColumns.filter((item: any) => !item.actions)
|
||||
@@ -24,7 +26,6 @@ const actionsColumns = computed<any[]>(() => {
|
||||
|
||||
return columns
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -33,35 +34,43 @@ const actionsColumns = computed<any[]>(() => {
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th v-for="(item, index) in dataColumns" :key="index">{{ item.label }}</th>
|
||||
<th v-for="(item, index) in actionsColumns" :key="index">操作</th>
|
||||
<th></th>
|
||||
<th />
|
||||
<th v-for="(item, index) in dataColumns" :key="index">
|
||||
{{ item.label }}
|
||||
</th>
|
||||
<th v-for="(item, index) in actionsColumns" :key="index">
|
||||
{{ t('table.operation') }}
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="data.length > 0">
|
||||
<!-- row -->
|
||||
<tr class="hover" v-for="item in data" :key="item.id">
|
||||
<tr v-for="item in data" :key="item.id" class="hover">
|
||||
<th>{{ item.id }}</th>
|
||||
<td v-for="(column, index) in dataColumns" :key="index">
|
||||
<span v-if="column.formatValue" v-html="column.formatValue(item)"></span>
|
||||
<span v-if="column.formatValue">{{ column.formatValue(item) }}</span>
|
||||
<span v-else>{{ item[column.props] }}</span>
|
||||
</td>
|
||||
<!-- action -->
|
||||
<td v-for="(column, index) in actionsColumns" :key="index" class="flex gap-2">
|
||||
<button class="btn btn-xs" v-for="action in column.actions" :key="action.name" :class="action.type"
|
||||
@click="action.onClick(item)">{{ action.label }}</button>
|
||||
<button
|
||||
v-for="action in column.actions" :key="action.name" class="btn btn-xs" :class="action.type"
|
||||
@click="action.onClick(item)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">暂无数据</td>
|
||||
<td colspan="5" class="text-center">
|
||||
{{ t('table.noneData') }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- foot -->
|
||||
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>();
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0);
|
||||
const addCount = () => {
|
||||
count.value++;
|
||||
};
|
||||
const count = ref(0)
|
||||
function addCount() {
|
||||
count.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold py-6">{{ msg }}</h1>
|
||||
<h1 class="text-4xl font-bold py-6">
|
||||
{{ msg }}
|
||||
</h1>
|
||||
|
||||
<div class="card w-1200px">
|
||||
<button
|
||||
@@ -29,16 +31,16 @@ const addCount = () => {
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Install
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
in your IDE for a better DX
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and Vue logos to learn more
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
<script setup lang='ts'>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import localforage from 'localforage'
|
||||
const props=defineProps({
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
imgItem: {
|
||||
type:Object,
|
||||
default:()=>({})
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore'
|
||||
name: 'imgStore',
|
||||
})
|
||||
|
||||
const imgUrl=ref('')
|
||||
const imgUrl = ref('')
|
||||
|
||||
|
||||
const getImageStoreItem=async (item:any):Promise<string>=>{
|
||||
let image=''
|
||||
if(item.url=='Storage'){
|
||||
const key=item.id;
|
||||
image=await imageDbStore.getItem(key) as string
|
||||
async function getImageStoreItem(item: any): Promise<string> {
|
||||
let image = ''
|
||||
if (item.url === 'Storage') {
|
||||
const key = item.id
|
||||
image = await imageDbStore.getItem(key) as string
|
||||
}
|
||||
else{
|
||||
image=item.url
|
||||
else {
|
||||
image = item.url
|
||||
}
|
||||
|
||||
|
||||
return image
|
||||
return image
|
||||
}
|
||||
|
||||
onMounted(async ()=>{
|
||||
const image=await getImageStoreItem(props.imgItem)
|
||||
imgUrl.value=image
|
||||
onMounted(async () => {
|
||||
const image = await getImageStoreItem(props.imgItem)
|
||||
imgUrl.value = image
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img :src="imgUrl" alt="Image" class="object-cover h-full rounded-xl"/>
|
||||
<img :src="imgUrl" alt="Image" class="object-cover h-full rounded-xl">
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, watch, onMounted, toRefs } from 'vue'
|
||||
import { Separate } from '@/types/storeType'
|
||||
import type { Separate } from '@/types/storeType'
|
||||
import { onMounted, ref, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
totalNumber: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
separatedNumber: {
|
||||
type: Array<Separate>,
|
||||
default: []
|
||||
}
|
||||
default: [],
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['submitData'])
|
||||
|
||||
const { t } = useI18n()
|
||||
const separatedNumberRef = ref()
|
||||
const { separatedNumber, totalNumber } = toRefs(props)
|
||||
const scaleList = ref<number[]>([])
|
||||
const editScale = (item: number) => {
|
||||
if (item == totalNumber.value) {
|
||||
function editScale(item: number) {
|
||||
if (item === totalNumber.value) {
|
||||
return
|
||||
}
|
||||
if (scaleList.value.includes(item)) {
|
||||
@@ -32,12 +32,12 @@ const editScale = (item: number) => {
|
||||
scaleList.value.sort((a, b) => a - b)
|
||||
}
|
||||
}
|
||||
const clearData = () => {
|
||||
function clearData() {
|
||||
emits('submitData', separatedNumber.value)
|
||||
separatedNumberRef.value.close()
|
||||
}
|
||||
watch(scaleList, (val: number[]) => {
|
||||
separatedNumber.value.length=0
|
||||
separatedNumber.value.length = 0
|
||||
for (let i = 1; i < scaleList.value.length; i++) {
|
||||
separatedNumber.value[i - 1] = {
|
||||
id: i.toString(),
|
||||
@@ -53,11 +53,11 @@ watch(totalNumber, (val) => {
|
||||
}
|
||||
separatedNumberRef.value.showModal()
|
||||
// scaleList.value = [0, val]
|
||||
scaleList.value = new Array(separatedNumber.value.length + 1).fill(totalNumber.value)
|
||||
scaleList.value = Array.from({ length: separatedNumber.value.length + 1 }).fill(totalNumber.value) as number[]
|
||||
for (let i = separatedNumber.value.length - 1; i >= 0; i--) {
|
||||
scaleList.value[i] = scaleList.value[i + 1] - separatedNumber.value[i].count
|
||||
}
|
||||
if(scaleList.value[0]!==0){
|
||||
if (scaleList.value[0] !== 0) {
|
||||
scaleList.value.unshift(0)
|
||||
}
|
||||
})
|
||||
@@ -74,22 +74,34 @@ onMounted(() => {
|
||||
<template>
|
||||
<dialog id="my_modal_1" ref="separatedNumberRef" class="z-50 overflow-hidden border-none modal">
|
||||
<div class="overflow-hidden modal-box">
|
||||
<h3 class="pb-6 text-lg font-bold">提示!</h3>
|
||||
<p class="pb-8">单次抽取只能抽取10位</p>
|
||||
<h3 class="pb-6 text-lg font-bold">
|
||||
{{ t('dialog.titleTip') }}
|
||||
</h3>
|
||||
<p class="pb-8">
|
||||
{{ t('dialog.dialogSingleDrawLimit') }}
|
||||
</p>
|
||||
<div class="flex justify-between px-3 text-center separated-number">
|
||||
<div v-for="item in props.totalNumber" :key="item"
|
||||
class="relative flex flex-col items-center cursor-pointer">
|
||||
<div class="absolute mb-12 text-center tooltip -top-5 hover:text-lg" data-tip="左键切割"
|
||||
@click.left="editScale(item)">
|
||||
<div
|
||||
v-for="item in props.totalNumber" :key="item"
|
||||
class="relative flex flex-col items-center cursor-pointer"
|
||||
>
|
||||
<div
|
||||
class="absolute mb-12 text-center tooltip -top-5 hover:text-lg" :data-tip="t('tooltip.leftClick')"
|
||||
@click.left="editScale(item)"
|
||||
>
|
||||
<span> {{ item }}</span>
|
||||
</div>
|
||||
<div class="text-center" :class="scaleList.includes(item) ? 'text-red-500 font-extrabold' : ''">|</div>
|
||||
<div class="text-center" :class="scaleList.includes(item) ? 'text-red-500 font-extrabold' : ''">
|
||||
|
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<!-- if there is a button in form, it will close the modal -->
|
||||
<button class="btn" @click="clearData">关闭</button>
|
||||
<button class="btn" @click="clearData">
|
||||
{{ t('button.close') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, onMounted, onUnmounted,watch } from 'vue'
|
||||
import useStore from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useStore from '@/store'
|
||||
import localforage from 'localforage'
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const audioDbStore = localforage.createInstance({
|
||||
name: 'audioStore'
|
||||
name: 'audioStore',
|
||||
})
|
||||
const audio=ref(new Audio())
|
||||
const audio = ref(new Audio())
|
||||
const settingRef = ref()
|
||||
// const audio = ref(new Audio())
|
||||
const globalConfig = useStore().globalConfig
|
||||
const { getMusicList: localMusicList,getCurrentMusic:currentMusic } = storeToRefs(globalConfig);
|
||||
const { getMusicList: localMusicList, getCurrentMusic: currentMusic } = storeToRefs(globalConfig)
|
||||
// const localMusicListValue = ref(localMusicList)
|
||||
|
||||
const play = async (item: any) => {
|
||||
if(!item){
|
||||
async function play(item: any) {
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
// if (!audio.value.paused && !skip) {
|
||||
@@ -29,7 +32,7 @@ const play = async (item: any) => {
|
||||
if (!item.url) {
|
||||
return
|
||||
}
|
||||
if (item.url == 'Storage') {
|
||||
if (item.url === 'Storage') {
|
||||
audioUrl = await audioDbStore.getItem(item.name) as string
|
||||
}
|
||||
else {
|
||||
@@ -39,42 +42,42 @@ const play = async (item: any) => {
|
||||
audio.value.src = audioUrl
|
||||
audio.value.play()
|
||||
}
|
||||
const playMusic=(item:any,skip = false)=>{
|
||||
if(!item){
|
||||
function playMusic(item: any, skip = false) {
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
if(!currentMusic.value.paused&&!skip){
|
||||
globalConfig.setCurrentMusic(item,true)
|
||||
if (!currentMusic.value.paused && !skip) {
|
||||
globalConfig.setCurrentMusic(item, true)
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
globalConfig.setCurrentMusic(item,false)
|
||||
globalConfig.setCurrentMusic(item, false)
|
||||
}
|
||||
const nextPlay = () => {
|
||||
function nextPlay() {
|
||||
// 播放下一首
|
||||
if (localMusicList.value.length >= 1) {
|
||||
let index = localMusicList.value.findIndex((item: any) => item.name == currentMusic.value.item.name)
|
||||
let index = localMusicList.value.findIndex((item: any) => item.name === currentMusic.value.item.name)
|
||||
index++
|
||||
if (index >= localMusicList.value.length) {
|
||||
index = 0
|
||||
}
|
||||
globalConfig.setCurrentMusic(localMusicList.value[index],false)
|
||||
globalConfig.setCurrentMusic(localMusicList.value[index], false)
|
||||
}
|
||||
}
|
||||
// 监听播放成后开始下一首
|
||||
const onPlayEnd = () => {
|
||||
function onPlayEnd() {
|
||||
audio.value.addEventListener('ended', nextPlay)
|
||||
}
|
||||
|
||||
const enterConfig = () => {
|
||||
function enterConfig() {
|
||||
router.push('/log-lottery/config')
|
||||
}
|
||||
const enterHome = () => {
|
||||
function enterHome() {
|
||||
router.push('/log-lottery')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
globalConfig.setCurrentMusic(localMusicList.value[0],true)
|
||||
globalConfig.setCurrentMusic(localMusicList.value[0], true)
|
||||
onPlayEnd()
|
||||
// 不使用空格控制audio
|
||||
})
|
||||
@@ -82,38 +85,42 @@ onUnmounted(() => {
|
||||
audio.value.removeEventListener('ended', nextPlay)
|
||||
})
|
||||
watch(currentMusic, (val: any) => {
|
||||
if(!val.paused&&audio.value){
|
||||
if (!val.paused && audio.value) {
|
||||
play(val.item)
|
||||
}
|
||||
else{
|
||||
else {
|
||||
audio.value.pause()
|
||||
}
|
||||
},{deep:true})
|
||||
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-3" ref="settingRef">
|
||||
<div v-if="route.path.includes('/config')" class="tooltip tooltip-left" data-tip="主页">
|
||||
<div class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="enterHome">
|
||||
<svg-icon name="home"></svg-icon>
|
||||
<div ref="settingRef" class="flex flex-col gap-3">
|
||||
<div v-if="route.path.includes('/config')" class="tooltip tooltip-left" :data-tip="t('tooltip.toHome')">
|
||||
<div
|
||||
class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="enterHome"
|
||||
>
|
||||
<svg-icon name="home" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tooltip tooltip-left" data-tip="设置/配置">
|
||||
<div class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="enterConfig">
|
||||
<svg-icon name="setting"></svg-icon>
|
||||
<div v-else class="tooltip tooltip-left" :data-tip="t('tooltip.settingConfiguration')">
|
||||
<div
|
||||
class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="enterConfig"
|
||||
>
|
||||
<svg-icon name="setting" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tooltip tooltip-left" :data-tip="currentMusic.item ? currentMusic.item.name+'\n\r 右键下一曲' : '没有音乐可以播放'">
|
||||
<div class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="playMusic(currentMusic.item)" @click.right.prevent="nextPlay">
|
||||
<svg-icon :name="currentMusic.paused ? 'play' : 'pause'"></svg-icon>
|
||||
<div class="tooltip tooltip-left" :data-tip="currentMusic.item ? `${currentMusic.item.name}\n\r ${t('tooltip.nextSong')}` : t('tooltip.noSongPlay')">
|
||||
<div
|
||||
class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90"
|
||||
@click="playMusic(currentMusic.item)" @click.right.prevent="nextPlay"
|
||||
>
|
||||
<svg-icon :name="currentMusic.paused ? 'play' : 'pause'" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="bg-blue-300 cursor-pointer" @click="nextPlay">下一首</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,37 +1,71 @@
|
||||
<script setup lang='ts'>
|
||||
import Sparticles from 'sparticles';
|
||||
import {ref,onMounted,onUnmounted} from 'vue';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import localforage from 'localforage'
|
||||
const props = defineProps({
|
||||
homeBackground: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
id: '',
|
||||
name: '',
|
||||
url: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore'
|
||||
})
|
||||
const imgUrl = ref('')
|
||||
const starRef = ref();
|
||||
|
||||
const starRef=ref();
|
||||
|
||||
const { width, height}=useElementSize(starRef);
|
||||
let options = ref({ shape: 'star',parallax:1.2,rotate:true,twinkle:true, speed: 10,count:200 });
|
||||
function addSparticles(node:any,width:number,height:number) {
|
||||
new Sparticles(node, options.value,width,height);
|
||||
const { width, height } = useElementSize(starRef);
|
||||
let options = ref({ shape: 'star', parallax: 1.2, rotate: true, twinkle: true, speed: 10, count: 200 });
|
||||
function addSparticles(node: any, width: number, height: number) {
|
||||
new Sparticles(node, options.value, width, height);
|
||||
}
|
||||
// 页面大小改变时
|
||||
const listenWindowSize=()=>{
|
||||
window.addEventListener('resize',()=>{
|
||||
const listenWindowSize = () => {
|
||||
window.addEventListener('resize', () => {
|
||||
if (width.value && height.value) {
|
||||
addSparticles(starRef.value,width.value,height.value);
|
||||
addSparticles(starRef.value, width.value, height.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
addSparticles(starRef.value,width.value,height.value);
|
||||
const getImageStoreItem = async (item: any): Promise<string> => {
|
||||
let image = ''
|
||||
if (item.url == 'Storage') {
|
||||
const key = item.id;
|
||||
image = await imageDbStore.getItem(key) as string
|
||||
}
|
||||
else {
|
||||
image = item.url
|
||||
}
|
||||
|
||||
|
||||
return image
|
||||
}
|
||||
onMounted(() => {
|
||||
getImageStoreItem(props.homeBackground).then((image) => {
|
||||
imgUrl.value = image
|
||||
})
|
||||
addSparticles(starRef.value, width.value, height.value);
|
||||
listenWindowSize()
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
window.removeEventListener('resize',listenWindowSize)
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', listenWindowSize)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-screen h-screen overflow-hidden bg-transparent" ref="starRef">
|
||||
<div class="home-background w-screen h-screen overflow-hidden" v-if="homeBackground.url">
|
||||
<img :src="imgUrl" class="w-full h-full object-cover" alt="">
|
||||
</div>
|
||||
<div v-else class="w-screen h-screen overflow-hidden" ref="starRef">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped></style>
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
@@ -17,10 +18,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '24px',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
@@ -31,6 +33,7 @@ const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
||||
<use :xlink:href="symbolId" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 24px;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
</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>
|
||||
<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" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { default as Footer } from './Footer/index.vue'
|
||||
/**
|
||||
*title: 自动导出组件
|
||||
*/
|
||||
export { default as Header } from './Header/index.vue';
|
||||
export { default as Footer } from './Footer/index.vue';
|
||||
export { default as Header } from './Header/index.vue'
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import type { IPersonConfig } from '@/types/storeType'
|
||||
import { rgba } from '@/utils/color'
|
||||
import { IPersonConfig } from '@/types/storeType'
|
||||
|
||||
export const useElementStyle = (element: any, person: IPersonConfig, index: number, patternList: number[], patternColor: string, cardColor: string, cardSize: { width: number, height: number }, textSize: number, mod: 'default' | 'lucky'|'sphere' = 'default') => {
|
||||
if (patternList.includes(index+1)&&mod=='default') {
|
||||
export function useElementStyle(element: any, person: IPersonConfig, index: number, patternList: number[], patternColor: string, cardColor: string, cardSize: { width: number, height: number }, textSize: number, mod: 'default' | 'lucky' | 'sphere' = 'default', type: 'add' | 'change' = 'add') {
|
||||
if (patternList.includes(index + 1) && mod === 'default') {
|
||||
element.style.backgroundColor = rgba(patternColor, Math.random() * 0.2 + 0.8)
|
||||
}
|
||||
else if(mod=='sphere'||mod=='default') {
|
||||
else if (mod === 'sphere' || mod === 'default') {
|
||||
element.style.backgroundColor = rgba(cardColor, Math.random() * 0.5 + 0.25)
|
||||
}
|
||||
else if(mod=='lucky'){
|
||||
else if (mod === 'lucky') {
|
||||
element.style.backgroundColor = rgba(cardColor, 0.8)
|
||||
}
|
||||
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`;
|
||||
if (mod == 'lucky') {
|
||||
element.style.width = `${cardSize.width}px`
|
||||
element.style.height = `${cardSize.height}px`
|
||||
if (mod === 'lucky') {
|
||||
element.className = 'lucky-element-card'
|
||||
}
|
||||
else {
|
||||
element.className = 'element-card'
|
||||
}
|
||||
// 等比放大
|
||||
if (type === 'add') {
|
||||
element.addEventListener('mouseenter', (ev: MouseEvent) => {
|
||||
const target = ev.target as HTMLElement
|
||||
target.style.border = `1px solid ${rgba(cardColor, 0.75)}`
|
||||
@@ -32,17 +32,22 @@ export const useElementStyle = (element: any, person: IPersonConfig, index: numb
|
||||
target.style.border = `1px solid ${rgba(cardColor, 0.25)}`
|
||||
target.style.boxShadow = `0 0 12px ${rgba(cardColor, 0.5)}`
|
||||
})
|
||||
element.children[0].style.fontSize = textSize * 0.5 + 'px';
|
||||
}
|
||||
element.children[0].style.fontSize = `${textSize * 0.5}px`
|
||||
if (person.uid) {
|
||||
element.children[0].textContent = person.uid;
|
||||
element.children[0].textContent = person.uid
|
||||
}
|
||||
|
||||
element.children[1].style.fontSize = textSize + 'px'
|
||||
element.children[1].style.lineHeight = textSize * 3 + '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)}`
|
||||
if (person.name) {
|
||||
element.children[1].textContent = person.name
|
||||
}
|
||||
element.children[2].style.fontSize = `${textSize * 0.5}px`
|
||||
if (person.department || person.identity) {
|
||||
element.children[2].innerHTML = `${person.department ? person.department : ''}<br/>${person.identity ? person.identity : ''}`
|
||||
}
|
||||
|
||||
element.children[2].style.fontSize = textSize * 0.5 + 'px'
|
||||
if (person.department || person.identity) {
|
||||
@@ -52,33 +57,24 @@ export const useElementStyle = (element: any, person: IPersonConfig, index: numb
|
||||
return element
|
||||
}
|
||||
|
||||
// export const useElementPosition=(element:any,count:number,cardSize:{width:number,height:number},windowSize:{width:number,height:number},cardIndex:number)=>{
|
||||
// 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
|
||||
// }
|
||||
|
||||
export const useElementPosition = (element: any, count: number, cardSize: { width: number, height: number }, windowSize: { width: number, height: number }, cardIndex: number) => {
|
||||
/**
|
||||
* @description 设置抽中卡片的位置
|
||||
* 最少一个,最大十个
|
||||
*/
|
||||
// TODO:不超过5个时:单行排列;超过5个时,6:上3下3;7:上3下4;8:上3下5;9:上4下5;10:上5下5
|
||||
export function useElementPosition(element: any, count: number, totalCount: number, cardSize: { width: number, height: number }, windowSize: { width: number, height: number }, cardIndex: number) {
|
||||
let xTable = 0
|
||||
let yTable = 0
|
||||
const centerPosition = {
|
||||
x: 0,
|
||||
y: windowSize.height / 2 - cardSize.height / 2
|
||||
y: windowSize.height / 2 - cardSize.height / 2,
|
||||
}
|
||||
// 有一行为偶数的特殊数量
|
||||
const specialPosition = [2, 4, 7, 9]
|
||||
// 不包含特殊值的 和 分两行中第一行为奇数值的
|
||||
if (!specialPosition.includes(totalCount) || (totalCount > 5 && cardIndex < 5)) {
|
||||
const index = cardIndex % 5
|
||||
if (index == 0) {
|
||||
if (index === 0) {
|
||||
xTable = centerPosition.x
|
||||
yTable = centerPosition.y - Math.floor(cardIndex / 5) * (cardSize.height + 60)
|
||||
}
|
||||
@@ -86,6 +82,17 @@ export const useElementPosition = (element: any, count: number, cardSize: { widt
|
||||
xTable = index % 2 === 0 ? Math.ceil(index / 2) * (cardSize.width + 100) : -Math.ceil(index / 2) * (cardSize.width + 100)
|
||||
yTable = centerPosition.y - Math.floor(cardIndex / 5) * (cardSize.height + 60)
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
const index = cardIndex % 5
|
||||
if (index === 0) {
|
||||
xTable = centerPosition.x + (cardSize.width + 100) / 2
|
||||
yTable = centerPosition.y - Math.floor(cardIndex / 5) * (cardSize.height + 60)
|
||||
}
|
||||
else {
|
||||
xTable = index % 2 === 0 ? Math.ceil(index / 2) * (cardSize.width + 100) + (cardSize.width + 100) / 2 : -(Math.ceil(index / 2) * (cardSize.width + 100)) + (cardSize.width + 100) / 2
|
||||
yTable = centerPosition.y - Math.floor(cardIndex / 5) * (cardSize.height + 60)
|
||||
}
|
||||
}
|
||||
return { xTable, yTable }
|
||||
}
|
||||
|
||||
@@ -1 +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>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M572.235 205.282v600.365a30.118 30.118 0 1 1-60.235 0V205.282L292.382 438.633a28.913 28.913 0 0 1-42.646 0a33.43 33.43 0 0 1 0-45.236l271.058-288.045a28.913 28.913 0 0 1 42.647 0L834.5 393.397a33.43 33.43 0 0 1 0 45.176a28.913 28.913 0 0 1-42.647 0l-219.618-233.23z"/></svg>
|
||||
|
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 390 B |
@@ -7,4 +7,4 @@ export const footerList = {
|
||||
icon: 'github',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { footerList } from './config';
|
||||
const skip = (url: string) => {
|
||||
window.open(url);
|
||||
};
|
||||
import { footerList } from './config'
|
||||
|
||||
function skip(url: string) {
|
||||
window.open(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="footer-container">
|
||||
<ul class="flex justify-center">
|
||||
<li
|
||||
v-for="item in footerList.data"
|
||||
:key="item.id"
|
||||
@click="skip(item.url)"
|
||||
class="flex items-center gap-1 cursor-pointer"
|
||||
@click="skip(item.url)"
|
||||
>
|
||||
<svg-icon :name="item.icon"></svg-icon>
|
||||
<svg-icon :name="item.icon" />
|
||||
<p>{{ item.name }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { navList } from './config';
|
||||
const skip = (url: string) => {
|
||||
window.open(url, '_self');
|
||||
};
|
||||
import { navList } from './config'
|
||||
|
||||
function skip(url: string) {
|
||||
window.open(url, '_self')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -11,24 +12,34 @@ const skip = (url: string) => {
|
||||
<div class="navbar-start max-lg:w-full">
|
||||
<div class="dropdown">
|
||||
<label tabindex="0" class="btn btn-ghost lg:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h8m-8 6h16" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h8m-8 6h16"
|
||||
/>
|
||||
</svg>
|
||||
</label>
|
||||
<ul tabindex="0"
|
||||
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 text-lg flex flex-col gap-2">
|
||||
<li class="cursor-pointer hover:text-gray-100 hover:bg-base-200" v-for="item in navList" :key="item.id" @click="skip(item.url)">{{ item.name }}</li>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 text-lg flex flex-col gap-2"
|
||||
>
|
||||
<li v-for="item in navList" :key="item.id" class="cursor-pointer hover:text-gray-100 hover:bg-base-200" @click="skip(item.url)">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="text-xl lg:pl-12 max-lg:mx-auto" href="https://vitejs.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo">
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden navbar-center lg:flex">
|
||||
<ul class="flex gap-10 px-1 text-lg cursor-pointer menu menu-horizontal">
|
||||
<li class="hover:text-gray-100" v-for="item in navList" :key="item.id" @click="skip(item.url)">{{ item.name }}</li>
|
||||
<li v-for="item in navList" :key="item.id" class="hover:text-gray-100" @click="skip(item.url)">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-end max-lg:w-0">
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
// import Header from './Header/index.vue';
|
||||
// import Footer from './Footer/index.vue';
|
||||
import {ref} from 'vue';
|
||||
import ToTop from '@/components/ToTop/index.vue'
|
||||
import { useScroll } from '@vueuse/core'
|
||||
// import Header from './Header/index.vue';
|
||||
// import Footer from './Footer/index.vue';
|
||||
import { ref } from 'vue'
|
||||
|
||||
const mainContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const { y} = useScroll(mainContainer)
|
||||
const { y } = useScroll(mainContainer)
|
||||
|
||||
const scrollToTop=()=>{
|
||||
y.value=0
|
||||
function scrollToTop() {
|
||||
y.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -19,16 +19,16 @@ const scrollToTop=()=>{
|
||||
<!-- <header class="shadow-2xl head-container h-14">
|
||||
<Header></Header>
|
||||
</header> -->
|
||||
<ToTop @click="scrollToTop" v-if="y>400"></ToTop>
|
||||
<ToTop v-if="y > 400" @click="scrollToTop" />
|
||||
<main ref="mainContainer" class="box-content w-screen h-screen overflow-x-hidden overflow-y-auto main-container">
|
||||
<router-view class="h-full main-container-content"></router-view>
|
||||
|
||||
<router-view class="h-full main-container-content" />
|
||||
</main>
|
||||
<!-- <footer class="w-screen footer-container">
|
||||
<Footer></Footer>
|
||||
</footer> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
151
src/locales/en.ts
Normal file
151
src/locales/en.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
export default {
|
||||
button: {
|
||||
enterLottery: 'Enter Lottery',
|
||||
start: 'Start',
|
||||
selectLucky: 'Draw the Lucky',
|
||||
continue: 'Continue',
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
setting: 'Setting',
|
||||
delete: 'Delete',
|
||||
allDelete: 'Delete All',
|
||||
downloadTemplate: 'Download Template',
|
||||
importData: 'Import Data',
|
||||
resetData: 'Reset Data',
|
||||
exportResult: 'Export Result',
|
||||
add: 'Add',
|
||||
resetDefault: 'Reset Default',
|
||||
resetAllData: 'Reset All Data',
|
||||
clearPattern: 'Clear Pattern',
|
||||
DefaultPattern: 'Default Pattern',
|
||||
upload: 'Upload',
|
||||
reset: 'Reset',
|
||||
play: 'Play',
|
||||
setLayout: 'Set Layout',
|
||||
close: 'Close',
|
||||
noInfoAndImport: 'No Info and import it',
|
||||
useDefault: 'Use Default Data',
|
||||
},
|
||||
sidebar: {
|
||||
personConfiguration: 'Person Configuration',
|
||||
personList: 'Person List',
|
||||
winnerList: 'Winner List',
|
||||
prizeConfiguration: 'Prize Configuration',
|
||||
globalSetting: 'Global Configuration',
|
||||
viewSetting: 'View Setting',
|
||||
imagesManagement: 'Images Management',
|
||||
musicManagement: 'Music Management',
|
||||
operatingInstructions: 'Operating Instructions',
|
||||
},
|
||||
viewTitle: {
|
||||
personManagement: 'Person Management',
|
||||
winnerManagement: 'Winner Management',
|
||||
prizeManagement: 'Prize Management',
|
||||
globalSetting: 'Global Setting',
|
||||
operatingInstructions: 'Operating Instructions',
|
||||
},
|
||||
table: {
|
||||
// person configuration
|
||||
number: 'Number',
|
||||
name: 'Name',
|
||||
prizeName: 'Name',
|
||||
department: 'Department',
|
||||
identity: 'Identity',
|
||||
isLucky: 'Is Lucky',
|
||||
operation: 'Operation',
|
||||
setLuckyNumber: 'Set Lucky Number',
|
||||
luckyPeopleNumber: 'Lucky People Number',
|
||||
|
||||
detail: 'Detail',
|
||||
noneData: 'No Data',
|
||||
// prize configuration
|
||||
fullParticipation: 'FullParticipation',
|
||||
numberParticipants: 'NumberParticipants',
|
||||
isDone: 'is Done',
|
||||
image: 'Image',
|
||||
onceNumber: 'Once Number',
|
||||
time: 'Time',
|
||||
// view setting
|
||||
title: 'Main Title',
|
||||
columnNumber: 'Column Number',
|
||||
theme: 'Theme',
|
||||
language: 'Language',
|
||||
cardColor: 'Card Color',
|
||||
winnerColor: 'Winner Color',
|
||||
textColor: 'Text Color',
|
||||
cardWidth: 'Card Width',
|
||||
cardHeight: 'Card Height',
|
||||
textSize: 'Text Size',
|
||||
highlightColor: 'HighLight Color',
|
||||
patternSetting: 'Pattern Setting',
|
||||
alwaysDisplay: 'Always Display Prize List',
|
||||
selectPicture: 'Select a Picture',
|
||||
backgroundImage: 'Select Background Image',
|
||||
},
|
||||
dialog: {
|
||||
titleTip: 'Tip!',
|
||||
titleTemporary: 'Add Temporary Activity',
|
||||
dialogPCWeb: 'Please use a PC browser to access for optimal display performance',
|
||||
dialogDelAllPerson: 'This operation will delete all personnel list data. Do you want to continue?',
|
||||
dialogResetWinner: 'This operation will clear the winning information of personnel. Do you want to continue?',
|
||||
dialogResetAllData: 'This operation will reset all data. Do you want to continue?',
|
||||
dialogSingleDrawLimit: 'Only 10 characters can be extracted in a single draw',
|
||||
dialogLatestBrowser: 'Please use the latest version of Chrome or Edge browser',
|
||||
tipResetPrize: 'Performing operations may reset data, please proceed with caution',
|
||||
},
|
||||
tooltip: {
|
||||
settingConfiguration: 'Setting/Configuration',
|
||||
nextSong: 'Right Click to Next Song',
|
||||
noSongPlay: 'No Song to Play',
|
||||
prizeList: 'Prize List',
|
||||
addActivity: 'Add Activity',
|
||||
downloadTemplateTip: 'After downloading the file, please fill in the data in Excel and save it in xlsx format',
|
||||
uploadExcelTip: 'Upload the modified Excel file',
|
||||
leftClick: 'Left Click to Slice',
|
||||
toHome: 'to Home',
|
||||
resetLayout: 'This item is time-consuming and performance intensive',
|
||||
defaultLayout: 'The default pattern setting is valid for 17 columns, please set the number of other columns yourself',
|
||||
doneCount: 'Number of winners',
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
},
|
||||
error: {
|
||||
require: 'required field',
|
||||
requireNumber: 'please enter a number',
|
||||
minNumber1: 'the minimum is 1',
|
||||
maxNumber100: 'the maximum is 100',
|
||||
uploadSuccess: 'Upload Success',
|
||||
uploadFail: 'Upload Failed',
|
||||
notImage: 'Not Image',
|
||||
personIsAllDone: 'All Person Is Done',
|
||||
personNotEnough: 'Person Is Not Enough',
|
||||
startDraw: 'Now Draw {count} {leftover} people',
|
||||
completeInformation: 'Please provide complete information',
|
||||
},
|
||||
placeHolder: {
|
||||
enterTitle: 'Enter Title',
|
||||
name: 'Name',
|
||||
winnerCount: 'Lucky Person Count',
|
||||
},
|
||||
data: {
|
||||
yes: 'Yes',
|
||||
no: 'No',
|
||||
number: 'Number',
|
||||
isWin: 'isWin',
|
||||
department: 'Department',
|
||||
name: 'Name',
|
||||
identity: 'Identity',
|
||||
prizeName: 'Prize Name',
|
||||
prizeTime: 'Prize Time',
|
||||
operation: 'Operation',
|
||||
delete: 'Delete',
|
||||
removePerson: 'Remove the Person',
|
||||
defaultTitle: 'The Prelude to the Six Ministries of the Ming Dynasty Cabinet',
|
||||
xlsxName: 'personListTemplate-en.xlsx',
|
||||
readmeName: 'readme-en.md',
|
||||
},
|
||||
footer: {
|
||||
'self-reflection': 'Turn inward and examine yourself when you encounter difficulties in life.',
|
||||
'thiefEasy': 'Thief difficult mountain thief easily, breaking heart.',
|
||||
},
|
||||
}
|
||||
32
src/locales/i18n.ts
Normal file
32
src/locales/i18n.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// i18n配置
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import en from './en'
|
||||
import zhCn from './zhCn'
|
||||
|
||||
export type Language = 'en' | 'zhCn'
|
||||
|
||||
export const languageList = [
|
||||
{
|
||||
key: 'zhCn',
|
||||
name: '中文',
|
||||
flag: 'zh-cn',
|
||||
},
|
||||
{
|
||||
key: 'en',
|
||||
name: 'English',
|
||||
flag: 'en-us',
|
||||
},
|
||||
]
|
||||
export const browserLanguage = navigator.language.toLowerCase().includes('zh') ? 'zhCn' : 'en'
|
||||
const globalConfig = JSON.parse(localStorage.getItem('globalConfig') || '{}').globalConfig || {}
|
||||
// 创建i18n
|
||||
const i18n = createI18n({
|
||||
locale: globalConfig.language || browserLanguage,
|
||||
legacy: false,
|
||||
messages: {
|
||||
zhCn,
|
||||
en,
|
||||
},
|
||||
})
|
||||
|
||||
export default i18n
|
||||
151
src/locales/zhCn.ts
Normal file
151
src/locales/zhCn.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
export default {
|
||||
button: {
|
||||
enterLottery: '进入抽奖',
|
||||
start: '开始',
|
||||
selectLucky: '抽取幸运儿',
|
||||
continue: '继续',
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
setting: '设置',
|
||||
delete: '删除',
|
||||
allDelete: '删除全部',
|
||||
downloadTemplate: '下载模板',
|
||||
importData: '导入数据',
|
||||
resetData: '重置数据',
|
||||
exportResult: '导出结果',
|
||||
add: '添加',
|
||||
resetDefault: '重置为默认',
|
||||
resetAllData: '重置所有数据',
|
||||
clearPattern: '清除图案',
|
||||
DefaultPattern: '默认图案',
|
||||
upload: '上传',
|
||||
reset: '重置',
|
||||
play: '播放',
|
||||
setLayout: '重设布局',
|
||||
close: '关闭',
|
||||
noInfoAndImport: '暂无人员信息,前往导入',
|
||||
useDefault: '使用默认数据',
|
||||
},
|
||||
sidebar: {
|
||||
personConfiguration: '人员配置',
|
||||
personList: '人员列表',
|
||||
winnerList: '中奖人员',
|
||||
prizeConfiguration: '奖品配置',
|
||||
globalSetting: '全局配置',
|
||||
viewSetting: '界面设置',
|
||||
imagesManagement: '图片管理',
|
||||
musicManagement: '音乐管理',
|
||||
operatingInstructions: '操作说明',
|
||||
},
|
||||
viewTitle: {
|
||||
personManagement: '人员管理',
|
||||
winnerManagement: '已中奖人员管理',
|
||||
prizeManagement: '奖项配置',
|
||||
globalSetting: '全局配置',
|
||||
operatingInstructions: '操作说明',
|
||||
},
|
||||
table: {
|
||||
// person configuration
|
||||
number: '编号',
|
||||
name: '姓名',
|
||||
prizeName: '名称',
|
||||
department: '部门',
|
||||
identity: '身份',
|
||||
isLucky: '是否中奖',
|
||||
operation: '操作',
|
||||
setLuckyNumber: '设置中奖人数',
|
||||
luckyPeopleNumber: '中奖人数',
|
||||
|
||||
detail: '详细信息',
|
||||
noneData: '暂无数据',
|
||||
// prize configuration
|
||||
fullParticipation: '全员参加',
|
||||
numberParticipants: '抽奖人数',
|
||||
isDone: '已抽取',
|
||||
image: '图片',
|
||||
onceNumber: '单次抽取个数',
|
||||
time: '时间',
|
||||
// view setting
|
||||
title: '主标题',
|
||||
columnNumber: '列数',
|
||||
theme: '主题',
|
||||
language: '语言',
|
||||
cardColor: '卡片颜色',
|
||||
winnerColor: '中奖卡片颜色',
|
||||
textColor: '文字颜色',
|
||||
cardWidth: '卡片宽度',
|
||||
cardHeight: '卡片高度',
|
||||
textSize: '文字大小',
|
||||
highlightColor: '高亮颜色',
|
||||
patternSetting: '图案设置',
|
||||
alwaysDisplay: '常显奖项列表',
|
||||
selectPicture: '选择一张图片',
|
||||
backgroundImage: '选择背景图片',
|
||||
},
|
||||
dialog: {
|
||||
titleTip: '提示!',
|
||||
titleTemporary: '增加临时抽奖',
|
||||
dialogPCWeb: '请使用PC进行访问以获得最佳显示效果',
|
||||
dialogDelAllPerson: '该操作会删除所有人员数据,是否继续?',
|
||||
dialogResetWinner: '该操作会清空人员中奖信息,是否继续?',
|
||||
dialogResetAllData: '该操作会重置所有数据,是否继续?',
|
||||
dialogSingleDrawLimit: '单次抽取只能抽取10位',
|
||||
dialogLatestBrowser: '请使用最新版Chrome或者Edge浏览器',
|
||||
tipResetPrize: '进行操作可能会重置数据,请谨慎操作',
|
||||
},
|
||||
tooltip: {
|
||||
settingConfiguration: '设置/配置',
|
||||
nextSong: '右键点击下一首',
|
||||
noSongPlay: '没有音乐可以播放',
|
||||
prizeList: '奖项列表',
|
||||
addActivity: '添加抽奖',
|
||||
downloadTemplateTip: '下载文件后,请在excel中填写数据,并保存为xlsx格式',
|
||||
uploadExcelTip: '上传修改好的excel文件',
|
||||
leftClick: '左键切割',
|
||||
toHome: '主页',
|
||||
resetLayout: '该项比较耗费时间和性能',
|
||||
defaultLayout: '默认图案设置针对17列时有效,其他列数请自行设置',
|
||||
doneCount: '已抽取',
|
||||
edit: '编辑',
|
||||
delete: '删除',
|
||||
},
|
||||
error: {
|
||||
require: '必填项',
|
||||
requireNumber: '请输入数字',
|
||||
minNumber1: '最小为1',
|
||||
maxNumber100: '最大为100',
|
||||
uploadSuccess: '上传成功',
|
||||
uploadFail: '上传失败',
|
||||
notImage: '不是图片',
|
||||
personIsAllDone: '抽奖抽完了',
|
||||
personNotEnough: '抽奖人数不足',
|
||||
startDraw: '现在抽取{count}{leftover}人',
|
||||
completeInformation: '请填写完整信息',
|
||||
},
|
||||
placeHolder: {
|
||||
enterTitle: '输入标题',
|
||||
name: '名称',
|
||||
winnerCount: '中奖人数',
|
||||
},
|
||||
data: {
|
||||
yes: '是',
|
||||
no: '否',
|
||||
number: '编号',
|
||||
isWin: '是否中奖',
|
||||
department: '部门',
|
||||
name: '姓名',
|
||||
identity: '身份',
|
||||
prizeName: '获奖',
|
||||
prizeTime: '获奖时间',
|
||||
operation: '操作',
|
||||
delete: '删除',
|
||||
removePerson: '移入未中奖名单',
|
||||
defaultTitle: '大明内阁六部御前奏对',
|
||||
xlsxName: '人口登记表-zhCn.xlsx',
|
||||
readmeName: 'readme-zhCn.md',
|
||||
},
|
||||
footer: {
|
||||
'self-reflection': '行有不得,反求诸己',
|
||||
'thiefEasy': '破山中贼易,破心中贼难',
|
||||
},
|
||||
}
|
||||
35
src/main.ts
35
src/main.ts
@@ -1,24 +1,25 @@
|
||||
import { createApp } from 'vue';
|
||||
import './style.css';
|
||||
import svgIcon from '@/components/SvgIcon/index.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import * as THREE from 'three'
|
||||
import { createApp } from 'vue'
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html'
|
||||
import App from './App.vue'
|
||||
import './style.css'
|
||||
import './style/markdown.css'
|
||||
import './style/style.scss'
|
||||
import * as THREE from 'three';
|
||||
import App from './App.vue';
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html'
|
||||
|
||||
const app = createApp(App);
|
||||
// 全局svg组件
|
||||
import 'virtual:svg-icons-register';
|
||||
import svgIcon from '@/components/SvgIcon/index.vue';
|
||||
import 'virtual:svg-icons-register'
|
||||
// svg全局组件// 路由
|
||||
import router from '@/router';
|
||||
import router from '@/router'
|
||||
// pinia
|
||||
import { createPinia } from 'pinia';
|
||||
import { createPinia } from 'pinia'
|
||||
// pinia持久化
|
||||
import piniaPluginPersist from 'pinia-plugin-persist';
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersist);
|
||||
import piniaPluginPersist from 'pinia-plugin-persist'
|
||||
|
||||
app.config.globalProperties.$THREE = THREE; //挂载到原型
|
||||
app.component('svg-icon', svgIcon);
|
||||
app.use(router).use(VueDOMPurifyHTML).use(pinia).mount('#app');
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersist)
|
||||
|
||||
app.config.globalProperties.$THREE = THREE // 挂载到原型
|
||||
app.component('svg-icon', svgIcon)
|
||||
app.use(router).use(VueDOMPurifyHTML).use(pinia).use(i18n).mount('#app')
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import Layout from '@/layout/index.vue';
|
||||
import Home from '@/views/Home/index.vue';
|
||||
export const configRoutes={
|
||||
import Layout from '@/layout/index.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import Home from '@/views/Home/index.vue'
|
||||
import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
|
||||
|
||||
export const configRoutes = {
|
||||
path: '/log-lottery/config',
|
||||
name: 'Config',
|
||||
component: () => import('@/views/Config/index.vue'),
|
||||
@@ -15,31 +17,31 @@ export const configRoutes={
|
||||
name: 'PersonConfig',
|
||||
component: () => import('@/views/Config/Person/PersonConfig.vue'),
|
||||
meta: {
|
||||
title: '人员配置',
|
||||
title: i18n.global.t('sidebar.personConfiguration'),
|
||||
icon: 'person',
|
||||
},
|
||||
children:[
|
||||
children: [
|
||||
{
|
||||
path:'',
|
||||
path: '',
|
||||
redirect: '/log-lottery/config/person/all',
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/config/person/all',
|
||||
name:'AllPersonConfig',
|
||||
component:()=>import('@/views/Config/Person/PersonAll.vue'),
|
||||
meta:{
|
||||
title:'人员名单',
|
||||
icon:'all'
|
||||
}
|
||||
path: '/log-lottery/config/person/all',
|
||||
name: 'AllPersonConfig',
|
||||
component: () => import('@/views/Config/Person/PersonAll.vue'),
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.personList'),
|
||||
icon: 'all',
|
||||
},
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/config/person/already',
|
||||
name:'AlreadyPerson',
|
||||
component:()=>import('@/views/Config/Person/PersonAlready.vue'),
|
||||
meta:{
|
||||
title:'中奖名单人员',
|
||||
icon:'already'
|
||||
}
|
||||
path: '/log-lottery/config/person/already',
|
||||
name: 'AlreadyPerson',
|
||||
component: () => import('@/views/Config/Person/PersonAlready.vue'),
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.winnerList'),
|
||||
icon: 'already',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path:'other',
|
||||
@@ -50,66 +52,66 @@ export const configRoutes={
|
||||
// icon:'other'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/log-lottery/config/prize',
|
||||
name: 'PrizeConfig',
|
||||
component: () => import('@/views/Config/Prize/PrizeConfig.vue'),
|
||||
meta:{
|
||||
title: '奖品配置',
|
||||
icon: 'prize'
|
||||
}
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.prizeConfiguration'),
|
||||
icon: 'prize',
|
||||
},
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/config/global',
|
||||
name:'GlobalConfig',
|
||||
path: '/log-lottery/config/global',
|
||||
name: 'GlobalConfig',
|
||||
redirect: '/log-lottery/config/global/all',
|
||||
meta:{
|
||||
title:'全局配置',
|
||||
icon:'global'
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.globalSetting'),
|
||||
icon: 'global',
|
||||
},
|
||||
children:[
|
||||
children: [
|
||||
{
|
||||
path:'/log-lottery/config/global/face',
|
||||
name:'FaceConfig',
|
||||
component:()=>import('@/views/Config/Global/FaceConfig.vue'),
|
||||
meta:{
|
||||
title:'界面配置',
|
||||
icon:'face'
|
||||
}
|
||||
path: '/log-lottery/config/global/face',
|
||||
name: 'FaceConfig',
|
||||
component: () => import('@/views/Config/Global/FaceConfig.vue'),
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.viewSetting'),
|
||||
icon: 'face',
|
||||
},
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/config/global/image',
|
||||
name:'ImageConfig',
|
||||
component:()=>import('@/views/Config/Global/ImageConfig.vue'),
|
||||
meta:{
|
||||
title:'图片列表',
|
||||
icon:'image'
|
||||
}
|
||||
path: '/log-lottery/config/global/image',
|
||||
name: 'ImageConfig',
|
||||
component: () => import('@/views/Config/Global/ImageConfig.vue'),
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.imagesManagement'),
|
||||
icon: 'image',
|
||||
},
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/config/global/music',
|
||||
name:'MusicConfig',
|
||||
component:()=>import('@/views/Config/Global/MusicConfig.vue'),
|
||||
meta:{
|
||||
title:'音乐列表',
|
||||
icon:'music'
|
||||
}
|
||||
}
|
||||
]
|
||||
path: '/log-lottery/config/global/music',
|
||||
name: 'MusicConfig',
|
||||
component: () => import('@/views/Config/Global/MusicConfig.vue'),
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.musicManagement'),
|
||||
icon: 'music',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/log-lottery/config/readme',
|
||||
name: 'Readme',
|
||||
component: () => import('@/views/Config/Readme/index.vue'),
|
||||
meta:{
|
||||
title: '操作说明',
|
||||
icon: 'readme'
|
||||
}
|
||||
meta: {
|
||||
title: i18n.global.t('sidebar.operatingInstructions'),
|
||||
icon: 'readme',
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
const routes = [
|
||||
{
|
||||
path: '/log-lottery',
|
||||
@@ -122,18 +124,19 @@ const routes = [
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path:'/log-lottery/demo',
|
||||
name:'Demo',
|
||||
component:()=>import('@/views/Demo/index.vue')
|
||||
path: '/log-lottery/demo',
|
||||
name: 'Demo',
|
||||
component: () => import('@/views/Demo/index.vue'),
|
||||
},
|
||||
configRoutes,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const envMode=import.meta.env.MODE;
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
// 读取环境变量
|
||||
history: envMode==='file'?createWebHashHistory():createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
})
|
||||
|
||||
export default router;
|
||||
export default router
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,15 +1,17 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { defaultMusicList, defaultImageList, defaultPatternList } from './data'
|
||||
import { IMusic, IImage } from '@/types/storeType';
|
||||
import type { IImage, IMusic } from '@/types/storeType'
|
||||
import i18n, { browserLanguage } from '@/locales/i18n'
|
||||
import { defineStore } from 'pinia'
|
||||
import { defaultImageList, defaultMusicList, defaultPatternList } from './data'
|
||||
// import { IPrizeConfig } from '@/types/storeType';
|
||||
export const useGlobalConfig = defineStore('global', {
|
||||
isShowAvatar: true,
|
||||
state() {
|
||||
return {
|
||||
globalConfig: {
|
||||
rowCount: 17,
|
||||
isSHowPrizeList: true,
|
||||
isShowAvatar: true,
|
||||
topTitle: '大明内阁六部御前奏对',
|
||||
topTitle: i18n.global.t('data.defaultTitle'),
|
||||
language: browserLanguage,
|
||||
theme: {
|
||||
name: 'dracula',
|
||||
detail: { primary: '#0f5fd3' },
|
||||
@@ -21,6 +23,7 @@ export const useGlobalConfig = defineStore('global', {
|
||||
textSize: 30,
|
||||
patternColor: '#1b66c9',
|
||||
patternList: defaultPatternList as number[],
|
||||
background: {}, // 背景颜色或图片
|
||||
},
|
||||
musicList: defaultMusicList as IMusic[],
|
||||
imageList: defaultImageList as IImage[],
|
||||
@@ -29,141 +32,145 @@ export const useGlobalConfig = defineStore('global', {
|
||||
item: defaultMusicList[0],
|
||||
paused: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
// 获取全部配置
|
||||
getGlobalConfig(state) {
|
||||
return state.globalConfig;
|
||||
return state.globalConfig
|
||||
},
|
||||
// 获取标题
|
||||
getTopTitle(state) {
|
||||
return state.globalConfig.topTitle;
|
||||
return state.globalConfig.topTitle
|
||||
},
|
||||
// 获取行数
|
||||
getRowCount(state) {
|
||||
return state.globalConfig.rowCount;
|
||||
return state.globalConfig.rowCount
|
||||
},
|
||||
// 获取主题
|
||||
getTheme(state) {
|
||||
return state.globalConfig.theme;
|
||||
return state.globalConfig.theme
|
||||
},
|
||||
// 获取卡片颜色
|
||||
getCardColor(state) {
|
||||
return state.globalConfig.theme.cardColor;
|
||||
return state.globalConfig.theme.cardColor
|
||||
},
|
||||
// 获取中奖颜色
|
||||
getLuckyColor(state) {
|
||||
return state.globalConfig.theme.luckyCardColor;
|
||||
return state.globalConfig.theme.luckyCardColor
|
||||
},
|
||||
// 获取文字颜色
|
||||
getTextColor(state) {
|
||||
return state.globalConfig.theme.textColor;
|
||||
return state.globalConfig.theme.textColor
|
||||
},
|
||||
// 获取卡片宽高
|
||||
getCardSize(state) {
|
||||
return {
|
||||
width: state.globalConfig.theme.cardWidth,
|
||||
height: state.globalConfig.theme.cardHeight
|
||||
height: state.globalConfig.theme.cardHeight,
|
||||
}
|
||||
},
|
||||
// 获取文字大小
|
||||
getTextSize(state) {
|
||||
return state.globalConfig.theme.textSize;
|
||||
return state.globalConfig.theme.textSize
|
||||
},
|
||||
// 获取图案颜色
|
||||
getPatterColor(state) {
|
||||
return state.globalConfig.theme.patternColor;
|
||||
return state.globalConfig.theme.patternColor
|
||||
},
|
||||
// 获取图案列表
|
||||
getPatternList(state) {
|
||||
return state.globalConfig.theme.patternList;
|
||||
return state.globalConfig.theme.patternList
|
||||
},
|
||||
// 获取音乐列表
|
||||
getMusicList(state) {
|
||||
return state.globalConfig.musicList;
|
||||
return state.globalConfig.musicList
|
||||
},
|
||||
// 获取当前音乐
|
||||
getCurrentMusic(state) {
|
||||
return state.currentMusic;
|
||||
return state.currentMusic
|
||||
},
|
||||
// 获取图片列表
|
||||
getImageList(state) {
|
||||
return state.globalConfig.imageList;
|
||||
return state.globalConfig.imageList
|
||||
},
|
||||
// 获取是否显示奖品列表
|
||||
getIsShowPrizeList(state) {
|
||||
return state.globalConfig.isSHowPrizeList;
|
||||
return state.globalConfig.isSHowPrizeList
|
||||
},
|
||||
// 获取当前语言
|
||||
getLanguage(state) {
|
||||
return state.globalConfig.language
|
||||
},
|
||||
// 获取背景图片设置
|
||||
getBackground(state) {
|
||||
return state.globalConfig.theme.background
|
||||
},
|
||||
// 获取是否显示头像
|
||||
getIsShowAvatar(state) {
|
||||
return state.globalConfig.isShowAvatar;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 设置rowCount
|
||||
setRowCount(rowCount: number) {
|
||||
this.globalConfig.rowCount = rowCount;
|
||||
this.globalConfig.rowCount = rowCount
|
||||
},
|
||||
// 设置标题
|
||||
setTopTitle(topTitle: string) {
|
||||
this.globalConfig.topTitle = topTitle;
|
||||
this.globalConfig.topTitle = topTitle
|
||||
},
|
||||
// 设置主题
|
||||
setTheme(theme: any) {
|
||||
const { name, detail } = theme;
|
||||
this.globalConfig.theme.name = name;
|
||||
this.globalConfig.theme.detail = detail;
|
||||
const { name, detail } = theme
|
||||
this.globalConfig.theme.name = name
|
||||
this.globalConfig.theme.detail = detail
|
||||
},
|
||||
// 设置卡片颜色
|
||||
setCardColor(cardColor: string) {
|
||||
this.globalConfig.theme.cardColor = cardColor;
|
||||
this.globalConfig.theme.cardColor = cardColor
|
||||
},
|
||||
// 设置中奖颜色
|
||||
setLuckyCardColor(luckyCardColor: string) {
|
||||
this.globalConfig.theme.luckyCardColor = luckyCardColor;
|
||||
this.globalConfig.theme.luckyCardColor = luckyCardColor
|
||||
},
|
||||
// 设置文字颜色
|
||||
setTextColor(textColor: string) {
|
||||
this.globalConfig.theme.textColor = textColor;
|
||||
this.globalConfig.theme.textColor = textColor
|
||||
},
|
||||
// 设置卡片宽高
|
||||
setCardSize(cardSize: { width: number, height: number }) {
|
||||
this.globalConfig.theme.cardWidth = cardSize.width;
|
||||
this.globalConfig.theme.cardHeight = cardSize.height;
|
||||
this.globalConfig.theme.cardWidth = cardSize.width
|
||||
this.globalConfig.theme.cardHeight = cardSize.height
|
||||
},
|
||||
// 设置文字大小
|
||||
setTextSize(textSize: number) {
|
||||
this.globalConfig.theme.textSize = textSize;
|
||||
this.globalConfig.theme.textSize = textSize
|
||||
},
|
||||
// 设置图案颜色
|
||||
setPatterColor(patterColor: string) {
|
||||
this.globalConfig.theme.patternColor = patterColor;
|
||||
this.globalConfig.theme.patternColor = patterColor
|
||||
},
|
||||
// 设置图案列表
|
||||
setPatternList(patternList: number[]) {
|
||||
this.globalConfig.theme.patternList = patternList;
|
||||
this.globalConfig.theme.patternList = patternList
|
||||
},
|
||||
// 重置图案列表
|
||||
resetPatternList() {
|
||||
this.globalConfig.theme.patternList = defaultPatternList;
|
||||
this.globalConfig.theme.patternList = defaultPatternList
|
||||
},
|
||||
// 添加音乐
|
||||
addMusic(music: IMusic) {
|
||||
// 验证音乐是否已存在,看name字段
|
||||
for (let i = 0; i < this.globalConfig.musicList.length; i++) {
|
||||
if (this.globalConfig.musicList[i].name === music.name) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
}
|
||||
this.globalConfig.musicList.push(music);
|
||||
this.globalConfig.musicList.push(music)
|
||||
},
|
||||
// 删除音乐
|
||||
removeMusic(musicId: string) {
|
||||
for (let i = 0; i < this.globalConfig.musicList.length; i++) {
|
||||
if (this.globalConfig.musicList[i].id === musicId) {
|
||||
this.globalConfig.musicList.splice(i, 1);
|
||||
break;
|
||||
this.globalConfig.musicList.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -171,38 +178,38 @@ export const useGlobalConfig = defineStore('global', {
|
||||
setCurrentMusic(musicItem: IMusic, paused: boolean = true) {
|
||||
this.currentMusic = {
|
||||
item: musicItem,
|
||||
paused: paused,
|
||||
paused,
|
||||
}
|
||||
},
|
||||
// 重置音乐列表
|
||||
resetMusicList() {
|
||||
this.globalConfig.musicList = defaultMusicList as IMusic[];
|
||||
this.globalConfig.musicList =JSON.parse(JSON.stringify(defaultMusicList)) as IMusic[]
|
||||
},
|
||||
// 清空音乐列表
|
||||
clearMusicList() {
|
||||
this.globalConfig.musicList = [] as IMusic[];
|
||||
this.globalConfig.musicList = [] as IMusic[]
|
||||
},
|
||||
// 添加图片
|
||||
addImage(image: IImage) {
|
||||
for (let i = 0; i < this.globalConfig.imageList.length; i++) {
|
||||
if (this.globalConfig.imageList[i].name === image.name) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
}
|
||||
this.globalConfig.imageList.push(image);
|
||||
this.globalConfig.imageList.push(image)
|
||||
},
|
||||
// 删除图片
|
||||
removeImage(imageId: string) {
|
||||
for (let i = 0; i < this.globalConfig.imageList.length; i++) {
|
||||
if (this.globalConfig.imageList[i].id === imageId) {
|
||||
this.globalConfig.imageList.splice(i, 1);
|
||||
break;
|
||||
this.globalConfig.imageList.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
// 重置图片列表
|
||||
resetImageList() {
|
||||
this.globalConfig.imageList = defaultImageList as IImage[];
|
||||
this.globalConfig.imageList = defaultImageList as IImage[]
|
||||
},
|
||||
// 清空图片列表
|
||||
clearImageList() {
|
||||
@@ -210,19 +217,24 @@ export const useGlobalConfig = defineStore('global', {
|
||||
},
|
||||
// 设置是否显示奖品列表
|
||||
setIsShowPrizeList(isShowPrizeList: boolean) {
|
||||
this.globalConfig.isSHowPrizeList = isShowPrizeList;
|
||||
this.globalConfig.isSHowPrizeList = isShowPrizeList
|
||||
},
|
||||
// 设置是否显示头像
|
||||
setIsShowAvatar(isShowAvatar: boolean) {
|
||||
this.globalConfig.isShowAvatar = isShowAvatar;
|
||||
// 设置
|
||||
setLanguage(language: string) {
|
||||
this.globalConfig.language = language
|
||||
i18n.global.locale.value = language
|
||||
},
|
||||
// 设置背景图片
|
||||
setBackground(background: any) {
|
||||
this.globalConfig.theme.background = background
|
||||
},
|
||||
// 重置所有配置
|
||||
reset() {
|
||||
this.globalConfig = {
|
||||
rowCount: 17,
|
||||
isSHowPrizeList: true,
|
||||
isShowAvatar: false,
|
||||
topTitle: '大明内阁六部御前奏对',
|
||||
topTitle: i18n.global.t('data.defaultTitle'),
|
||||
language: browserLanguage,
|
||||
theme: {
|
||||
name: 'dracula',
|
||||
detail: { primary: '#0f5fd3' },
|
||||
@@ -234,15 +246,16 @@ export const useGlobalConfig = defineStore('global', {
|
||||
textSize: 30,
|
||||
patternColor: '#1b66c9',
|
||||
patternList: defaultPatternList as number[],
|
||||
background: {}, // 背景颜色或图片
|
||||
},
|
||||
musicList: defaultMusicList as IMusic[],
|
||||
imageList: defaultImageList as IImage[],
|
||||
},
|
||||
}
|
||||
this.currentMusic = {
|
||||
item: defaultMusicList[0],
|
||||
paused: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {usePersonConfig} from './personConfig';
|
||||
import { usePrizeConfig } from './prizeConfig';
|
||||
import {useGlobalConfig} from './globalConfig';
|
||||
import {useSystem} from './system';
|
||||
import { useGlobalConfig } from './globalConfig'
|
||||
import { usePersonConfig } from './personConfig'
|
||||
import { usePrizeConfig } from './prizeConfig'
|
||||
import { useSystem } from './system'
|
||||
|
||||
export default function useStore() {
|
||||
return {
|
||||
personConfig:usePersonConfig(),
|
||||
prizeConfig:usePrizeConfig(),
|
||||
globalConfig:useGlobalConfig(),
|
||||
system:useSystem(),
|
||||
};
|
||||
personConfig: usePersonConfig(),
|
||||
prizeConfig: usePrizeConfig(),
|
||||
globalConfig: useGlobalConfig(),
|
||||
system: useSystem(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { IPersonConfig } from '@/types/storeType';
|
||||
import { IPrizeConfig } from '@/types/storeType';
|
||||
import { defaultPersonList } from './data'
|
||||
import { usePrizeConfig } from './prizeConfig';
|
||||
import type { IPersonConfig, IPrizeConfig } from '@/types/storeType'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import { defineStore } from 'pinia'
|
||||
import { defaultPersonList } from './data'
|
||||
import { usePrizeConfig } from './prizeConfig'
|
||||
|
||||
export const usePersonConfig = defineStore('person', {
|
||||
state() {
|
||||
return {
|
||||
personConfig: {
|
||||
allPersonList: [] as IPersonConfig[],
|
||||
alreadyPersonList: [] as IPersonConfig[],
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
// 获取全部配置
|
||||
getPersonConfig(state) {
|
||||
return state.personConfig;
|
||||
return state.personConfig
|
||||
},
|
||||
// 获取全部人员名单
|
||||
getAllPersonList(state) {
|
||||
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
|
||||
return item
|
||||
});
|
||||
})
|
||||
},
|
||||
// 获取未获此奖的人员名单
|
||||
getNotThisPrizePersonList(state: any) {
|
||||
const currentPrize = usePrizeConfig().prizeConfig.currentPrize;
|
||||
const currentPrize = usePrizeConfig().prizeConfig.currentPrize
|
||||
const data = state.personConfig.allPersonList.filter((item: IPersonConfig) => {
|
||||
return !item.prizeId.includes(currentPrize.id as string);
|
||||
});
|
||||
return !item.prizeId.includes(currentPrize.id as string)
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
// 获取已中奖人员名单
|
||||
getAlreadyPersonList(state) {
|
||||
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
|
||||
return item.isWin === true;
|
||||
});
|
||||
return item.isWin === true
|
||||
})
|
||||
},
|
||||
// 获取中奖人员详情
|
||||
getAlreadyPersonDetail(state) {
|
||||
@@ -46,8 +47,8 @@ export const usePersonConfig = defineStore('person', {
|
||||
// 获取未中奖人员名单
|
||||
getNotPersonList(state) {
|
||||
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
|
||||
return item.isWin === false;
|
||||
});
|
||||
return item.isWin === false
|
||||
})
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
@@ -57,8 +58,8 @@ export const usePersonConfig = defineStore('person', {
|
||||
return
|
||||
}
|
||||
personList.forEach((item: IPersonConfig) => {
|
||||
this.personConfig.allPersonList.push(item);
|
||||
});
|
||||
this.personConfig.allPersonList.push(item)
|
||||
})
|
||||
},
|
||||
// 添加已中奖人员
|
||||
addAlreadyPersonList(personList: IPersonConfig[], prize: IPrizeConfig | null) {
|
||||
@@ -78,13 +79,13 @@ export const usePersonConfig = defineStore('person', {
|
||||
}
|
||||
|
||||
return item
|
||||
});
|
||||
this.personConfig.alreadyPersonList.push(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
|
||||
@@ -100,42 +101,42 @@ export const usePersonConfig = defineStore('person', {
|
||||
}
|
||||
for (let i = 0; i < alreadyPersonListLength; i++) {
|
||||
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) =>
|
||||
item.id !== person.id
|
||||
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);
|
||||
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 = [];
|
||||
this.personConfig.allPersonList = []
|
||||
this.personConfig.alreadyPersonList = []
|
||||
},
|
||||
|
||||
// 删除所有人员
|
||||
resetPerson() {
|
||||
this.personConfig.allPersonList = [];
|
||||
this.personConfig.alreadyPersonList = [];
|
||||
this.personConfig.allPersonList = []
|
||||
this.personConfig.alreadyPersonList = []
|
||||
},
|
||||
// 重置已中奖人员
|
||||
resetAlreadyPerson() {
|
||||
// 把已中奖人员合并到未中奖人员,要验证是否已存在
|
||||
this.personConfig.allPersonList.forEach((item: IPersonConfig) => {
|
||||
item.isWin = false;
|
||||
item.prizeName = [];
|
||||
item.prizeTime = [];
|
||||
item.isWin = false
|
||||
item.prizeName = []
|
||||
item.prizeTime = []
|
||||
item.prizeId = []
|
||||
});
|
||||
this.personConfig.alreadyPersonList = [];
|
||||
})
|
||||
this.personConfig.alreadyPersonList = []
|
||||
},
|
||||
setDefaultPersonList() {
|
||||
this.personConfig.allPersonList = defaultPersonList;
|
||||
this.personConfig.alreadyPersonList = [];
|
||||
this.personConfig.allPersonList = defaultPersonList
|
||||
this.personConfig.alreadyPersonList = []
|
||||
},
|
||||
// 重置所有配置
|
||||
reset() {
|
||||
@@ -155,4 +156,4 @@ export const usePersonConfig = defineStore('person', {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { IPrizeConfig } from '@/types/storeType';
|
||||
import { defaultPrizeList, defaultCurrentPrize } from './data';
|
||||
import type { IPrizeConfig } from '@/types/storeType'
|
||||
import { defineStore } from 'pinia'
|
||||
import { defaultCurrentPrize, defaultPrizeList } from './data'
|
||||
|
||||
export const usePrizeConfig = defineStore('prize', {
|
||||
state() {
|
||||
return {
|
||||
@@ -17,66 +18,66 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
picture: {
|
||||
id: '-1',
|
||||
name: '',
|
||||
url: ''
|
||||
url: '',
|
||||
},
|
||||
separateCount: {
|
||||
enable: true,
|
||||
countList: []
|
||||
countList: [],
|
||||
},
|
||||
desc: '',
|
||||
isShow: false,
|
||||
isUsed: false,
|
||||
frequency: 1,
|
||||
} as IPrizeConfig
|
||||
} as IPrizeConfig,
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
// 获取全部配置
|
||||
getPrizeConfigAll(state) {
|
||||
return state.prizeConfig;
|
||||
return state.prizeConfig
|
||||
},
|
||||
// 获取奖品列表
|
||||
getPrizeConfig(state) {
|
||||
return state.prizeConfig.prizeList;
|
||||
return state.prizeConfig.prizeList
|
||||
},
|
||||
// 根据id获取配置
|
||||
getPrizeConfigById(state) {
|
||||
return (id: number | string) => {
|
||||
return state.prizeConfig.prizeList.find(item => item.id === id);
|
||||
return state.prizeConfig.prizeList.find(item => item.id === id)
|
||||
}
|
||||
},
|
||||
// 获取当前奖项
|
||||
getCurrentPrize(state) {
|
||||
return state.prizeConfig.currentPrize;
|
||||
return state.prizeConfig.currentPrize
|
||||
},
|
||||
// 获取临时的奖项
|
||||
getTemporaryPrize(state) {
|
||||
return state.prizeConfig.temporaryPrize;
|
||||
return state.prizeConfig.temporaryPrize
|
||||
},
|
||||
|
||||
},
|
||||
actions: {
|
||||
// 设置奖项
|
||||
setPrizeConfig(prizeList: IPrizeConfig[]) {
|
||||
this.prizeConfig.prizeList = prizeList;
|
||||
this.prizeConfig.prizeList = prizeList
|
||||
},
|
||||
// 添加奖项
|
||||
addPrizeConfig(prizeConfigItem: IPrizeConfig) {
|
||||
this.prizeConfig.prizeList.push(prizeConfigItem);
|
||||
this.prizeConfig.prizeList.push(prizeConfigItem)
|
||||
},
|
||||
// 删除奖项
|
||||
deletePrizeConfig(prizeConfigItemId: number | string) {
|
||||
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId);
|
||||
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId)
|
||||
},
|
||||
// 更新奖项数据
|
||||
updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
|
||||
const prizeListLength = this.prizeConfig.prizeList.length;
|
||||
const prizeListLength = this.prizeConfig.prizeList.length
|
||||
if (prizeConfigItem.isUsed && prizeListLength) {
|
||||
for (let i = 0; i < prizeListLength; i++) {
|
||||
if (!this.prizeConfig.prizeList[i].isUsed) {
|
||||
this.setCurrentPrize(this.prizeConfig.prizeList[i]);
|
||||
break;
|
||||
this.setCurrentPrize(this.prizeConfig.prizeList[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
},
|
||||
// 删除全部奖项
|
||||
deleteAllPrizeConfig() {
|
||||
this.prizeConfig.prizeList = [] as IPrizeConfig[];
|
||||
this.prizeConfig.prizeList = [] as IPrizeConfig[]
|
||||
},
|
||||
// 设置当前奖项
|
||||
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
|
||||
@@ -95,10 +96,10 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
},
|
||||
// 设置临时奖项
|
||||
setTemporaryPrize(prizeItem: IPrizeConfig) {
|
||||
if (prizeItem.isShow == false) {
|
||||
if (prizeItem.isShow === false) {
|
||||
for (let i = 0; i < this.prizeConfig.prizeList.length; i++) {
|
||||
if (this.prizeConfig.prizeList[i].isUsed == false) {
|
||||
this.setCurrentPrize(this.prizeConfig.prizeList[i]);
|
||||
if (this.prizeConfig.prizeList[i].isUsed === false) {
|
||||
this.setCurrentPrize(this.prizeConfig.prizeList[i])
|
||||
|
||||
break
|
||||
}
|
||||
@@ -122,17 +123,17 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
picture: {
|
||||
id: '-1',
|
||||
name: '',
|
||||
url: ''
|
||||
url: '',
|
||||
},
|
||||
separateCount: {
|
||||
enable: true,
|
||||
countList: []
|
||||
countList: [],
|
||||
},
|
||||
desc: '',
|
||||
isShow: false,
|
||||
isUsed: false,
|
||||
frequency: 1,
|
||||
} as IPrizeConfig;
|
||||
} as IPrizeConfig
|
||||
},
|
||||
// 重置所有配置
|
||||
resetDefault() {
|
||||
@@ -149,20 +150,20 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
picture: {
|
||||
id: '-1',
|
||||
name: '',
|
||||
url: ''
|
||||
url: '',
|
||||
},
|
||||
separateCount: {
|
||||
enable: true,
|
||||
countList: []
|
||||
countList: [],
|
||||
},
|
||||
desc: '',
|
||||
isShow: false,
|
||||
isUsed: false,
|
||||
frequency: 1,
|
||||
} as IPrizeConfig
|
||||
}
|
||||
} as IPrizeConfig,
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
@@ -173,4 +174,4 @@ export const usePrizeConfig = defineStore('prize', {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { defineStore } from 'pinia'
|
||||
// import { IPrizeConfig } from '@/types/storeType';
|
||||
export const useSystem = defineStore('system', {
|
||||
state() {
|
||||
return {
|
||||
isMobile:false,
|
||||
isChrome:true
|
||||
};
|
||||
isMobile: false,
|
||||
isChrome: true,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getIsMobile(state) {
|
||||
return state.isMobile;
|
||||
return state.isMobile
|
||||
},
|
||||
getIsChrome(state) {
|
||||
return state.isChrome;
|
||||
return state.isChrome
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setIsMobile(isMobile: boolean) {
|
||||
this.isMobile = isMobile;
|
||||
this.isMobile = isMobile
|
||||
},
|
||||
setIsChrome(isChrome: boolean) {
|
||||
this.isChrome = isChrome;
|
||||
this.isChrome = isChrome
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
|
||||
@@ -14,40 +14,40 @@ export interface IPersonConfig {
|
||||
prizeId: string[];
|
||||
prizeTime: string[];
|
||||
}
|
||||
export type Separate = {
|
||||
id: string,
|
||||
count: number,
|
||||
isUsedCount: number,
|
||||
export interface Separate {
|
||||
id: string
|
||||
count: number
|
||||
isUsedCount: number
|
||||
}
|
||||
export interface IPrizeConfig {
|
||||
id: number | string;
|
||||
name: string;
|
||||
sort: number;
|
||||
isAll: boolean;
|
||||
count: number;
|
||||
isUsedCount: number,
|
||||
id: number | string
|
||||
name: string
|
||||
sort: number
|
||||
isAll: boolean
|
||||
count: number
|
||||
isUsedCount: number
|
||||
picture: {
|
||||
id: string | number,
|
||||
name: string,
|
||||
id: string | number
|
||||
name: string
|
||||
url: string
|
||||
};
|
||||
}
|
||||
separateCount: {
|
||||
enable: boolean,
|
||||
countList: Separate[],
|
||||
};
|
||||
desc: string;
|
||||
isShow: boolean;
|
||||
isUsed: boolean,
|
||||
frequency: number;
|
||||
enable: boolean
|
||||
countList: Separate[]
|
||||
}
|
||||
desc: string
|
||||
isShow: boolean
|
||||
isUsed: boolean
|
||||
frequency: number
|
||||
}
|
||||
export interface IMusic {
|
||||
id: string,
|
||||
name: string,
|
||||
url: string,
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface IImage {
|
||||
id: string,
|
||||
name: string,
|
||||
url: string,
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function getToken() {
|
||||
return window.localStorage.getItem('userToken');
|
||||
return window.localStorage.getItem('userToken')
|
||||
}
|
||||
|
||||
@@ -1,41 +1,71 @@
|
||||
// 判断颜色是否rgb或者rgba
|
||||
export function isRgbOrRgba(color: string) {
|
||||
return color.indexOf('rgb') > -1 || color.indexOf('rgba') > -1;
|
||||
return color.includes('rgb') || color.includes('rgba')
|
||||
}
|
||||
|
||||
// 判断是否hex形式
|
||||
export function isHex(color: string) {
|
||||
return color.indexOf('#') > -1;
|
||||
return color.includes('#')
|
||||
}
|
||||
|
||||
// 把hex颜色转成rgb数值类型
|
||||
export function hexToRgba(hex: string) {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
const r = Number.parseInt(hex.slice(1, 3), 16)
|
||||
const g = Number.parseInt(hex.slice(3, 5), 16)
|
||||
const b = Number.parseInt(hex.slice(5, 7), 16)
|
||||
|
||||
return {r,g,b}
|
||||
return { r, g, b }
|
||||
}
|
||||
// 把rgb数组转化成r g b 数值
|
||||
export function rgbToRgba(rgb: string) {
|
||||
const rgbArr = rgb.split('(')[1].split(')')[0].split(',');
|
||||
const rgbArr = rgb.split('(')[1].split(')')[0].split(',')
|
||||
|
||||
return {r:rgbArr[0],g:rgbArr[1],b:rgbArr[2]}
|
||||
return { r: rgbArr[0], g: rgbArr[1], b: rgbArr[2] }
|
||||
}
|
||||
|
||||
// 组成rgb颜色添加透明度
|
||||
export function rgba(color: string, opacity: number) {
|
||||
opacity = opacity || 1;
|
||||
let rgbaStr=''
|
||||
opacity = opacity || 1
|
||||
let rgbaStr = ''
|
||||
// 判断是否是hex颜色
|
||||
if (isHex(color)) {
|
||||
const {r,g,b} = hexToRgba(color);
|
||||
const { r, g, b } = hexToRgba(color)
|
||||
rgbaStr = `rgba(${r},${g},${b},${opacity})`
|
||||
}
|
||||
else{
|
||||
const {r,g,b} = rgbToRgba(color)
|
||||
else {
|
||||
const { r, g, b } = rgbToRgba(color)
|
||||
rgbaStr = `rgba(${r},${g},${b},${opacity})`
|
||||
}
|
||||
|
||||
return rgbaStr
|
||||
return rgbaStr
|
||||
}
|
||||
|
||||
export function rgbToHex(color:string) {
|
||||
// 去掉字符串中的空格
|
||||
color = color.replace(/\s+/g, '');
|
||||
|
||||
// 匹配rgba或rgb格式的字符串
|
||||
const rgbaMatch = color.match(/^rgba?\((\d+),(\d+),(\d+),?(\d*\.?\d+)?\)$/i);
|
||||
if (!rgbaMatch) {
|
||||
throw new Error('Invalid color format');
|
||||
}
|
||||
|
||||
const r = parseInt(rgbaMatch[1], 10);
|
||||
const g = parseInt(rgbaMatch[2], 10);
|
||||
const b = parseInt(rgbaMatch[3], 10);
|
||||
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : undefined;
|
||||
|
||||
// 将RGB值转换为十六进制
|
||||
let hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
||||
|
||||
// 如果提供了alpha值,则将其转换为十六进制并附加到结果中
|
||||
if (a !== undefined) {
|
||||
let alphaHex = Math.round(a * 255).toString(16).toUpperCase();
|
||||
if (alphaHex.length === 1) {
|
||||
alphaHex = "0" + alphaHex; // 确保alpha值是两位数
|
||||
}
|
||||
hex += alphaHex;
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export const readFileBinary = (file: any): Promise<any> => {
|
||||
return new Promise(resolve => {
|
||||
export function readFileBinary(file: any): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsBinaryString(file)
|
||||
reader.onload = (ev: any) => {
|
||||
@@ -8,12 +8,12 @@ export const readFileBinary = (file: any): Promise<any> => {
|
||||
})
|
||||
}
|
||||
|
||||
export const readFileData = (file: any): Promise<{dataUrl:string,fileName:string}> => {
|
||||
return new Promise(resolve => {
|
||||
export function readFileData(file: any): Promise<{ dataUrl: string, fileName: string }> {
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = (ev: any) => {
|
||||
resolve({dataUrl:ev.target.result,fileName:file.name})
|
||||
resolve({ dataUrl: ev.target.result, fileName: file.name })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
import dayjs from 'dayjs';
|
||||
import dayjs from 'dayjs'
|
||||
// 筛选人员数据
|
||||
export const filterData = (tableData: any[], localRowCount: number, startIndex = 0) => {
|
||||
export function filterData(tableData: any[], localRowCount: number) {
|
||||
const dataLength = tableData.length
|
||||
let j = 0;
|
||||
let j = 0
|
||||
for (let i = 0; i < dataLength; i++) {
|
||||
if (i % localRowCount === 0) {
|
||||
j++;
|
||||
j++
|
||||
}
|
||||
tableData[i].x = i % localRowCount + 1;
|
||||
tableData[i].y = j;
|
||||
tableData[i].id = i;
|
||||
tableData[i].x = i % localRowCount + 1
|
||||
tableData[i].y = j
|
||||
tableData[i].id = i
|
||||
// 是否中奖
|
||||
}
|
||||
|
||||
return tableData
|
||||
}
|
||||
|
||||
export const addOtherInfo = (personList: any[]) => {
|
||||
const len = personList.length;
|
||||
export function addOtherInfo(personList: any[]) {
|
||||
const len = personList.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
personList[i].id = i
|
||||
personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
||||
personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
|
||||
personList[i].prizeName = [] as string[];
|
||||
personList[i].prizeTime = [] as string[];
|
||||
personList[i].prizeId = [];
|
||||
personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
|
||||
personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
|
||||
personList[i].prizeName = [] as string[]
|
||||
personList[i].prizeTime = [] as string[]
|
||||
personList[i].prizeId = []
|
||||
personList[i].isWin = false
|
||||
}
|
||||
|
||||
return personList
|
||||
}
|
||||
|
||||
export const selectCard = (cardIndexArr: number[], tableLength: number, personId: number): number => {
|
||||
const cardIndex = Math.round(Math.random() * (tableLength - 1));
|
||||
export function selectCard(cardIndexArr: number[], tableLength: number, personId: number): number {
|
||||
const cardIndex = Math.round(Math.random() * (tableLength - 1))
|
||||
if (cardIndexArr.includes(cardIndex)) {
|
||||
return selectCard(cardIndexArr, tableLength, personId)
|
||||
}
|
||||
|
||||
return cardIndex
|
||||
}
|
||||
|
||||
export function themeChange(theme: string) {
|
||||
// 获取根html
|
||||
const html = document.querySelectorAll('html')
|
||||
if (html) {
|
||||
html[0].setAttribute('data-theme', theme)
|
||||
localStorage.setItem('theme', theme)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// 提取有哪些字段
|
||||
export const extractFields = (data: any) => {
|
||||
const item=data[0];
|
||||
export function extractFields(data: any) {
|
||||
const item = data[0]
|
||||
// 排除id x y,其他都加入数组
|
||||
const keys = Object.keys(item).filter(key => key!== 'id' && key!== 'x' && key!== 'y');
|
||||
if(keys.length>0){
|
||||
const keys = Object.keys(item).filter(key => key !== 'id' && key !== 'x' && key !== 'y')
|
||||
if (keys.length > 0) {
|
||||
// 返回数组key value
|
||||
return keys.map(key => ({label:key,value:true}));
|
||||
return keys.map(key => ({ label: key, value: true }))
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import useStore from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { themeChange } from 'theme-change';
|
||||
import zod from 'zod';
|
||||
import daisyuiThemes from 'daisyui/src/theming/themes'
|
||||
import { ColorPicker } from 'vue3-colorpicker';
|
||||
import 'vue3-colorpicker/style.css';
|
||||
import { isRgbOrRgba, isHex } from '@/utils/color'
|
||||
import PatternSetting from './components/PatternSetting.vue'
|
||||
import i18n, { languageList } from '@/locales/i18n'
|
||||
|
||||
import useStore from '@/store'
|
||||
import { themeChange } from '@/utils'
|
||||
import { isHex, isRgbOrRgba } from '@/utils/color'
|
||||
import daisyuiThemes from 'daisyui/src/theming/themes'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { ColorPicker } from 'vue3-colorpicker'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import zod from 'zod'
|
||||
import PatternSetting from './components/PatternSetting.vue'
|
||||
import 'vue3-colorpicker/style.css'
|
||||
|
||||
const { t } = useI18n()
|
||||
const globalConfig = useStore().globalConfig
|
||||
const personConfig = useStore().personConfig
|
||||
const prizeConfig= useStore().prizeConfig
|
||||
const { getTopTitle: topTitle, getTheme: localTheme, getPatterColor: patternColor, getPatternList: patternList, getCardColor: cardColor, getLuckyColor: luckyCardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getIsShowPrizeList: isShowPrizeList, getIsShowAvatar: isShowAvatar } = storeToRefs(globalConfig)
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const { getTopTitle: topTitle, getTheme: localTheme, getPatterColor: patternColor, getPatternList: patternList, getCardColor: cardColor, getLuckyColor: luckyCardColor, getTextColor: textColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getIsShowPrizeList: isShowPrizeList, getLanguage: userLanguage, getBackground: backgroundImage, getImageList: imageList,
|
||||
} = storeToRefs(globalConfig)
|
||||
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
|
||||
const colorPickerRef = ref()
|
||||
const resetDataDialogRef=ref()
|
||||
const resetDataDialogRef = ref()
|
||||
interface ThemeDaType {
|
||||
[key: string]: any
|
||||
}
|
||||
const isRowCountChange = ref(0) //0未改变,1改变,2加载中
|
||||
const isRowCountChange = ref(0) // 0未改变,1改变,2加载中
|
||||
const themeValue = ref(localTheme.value.name)
|
||||
const topTitleValue = ref(structuredClone(topTitle.value))
|
||||
const cardColorValue = ref(structuredClone(cardColor.value))
|
||||
@@ -29,38 +34,38 @@ const textColorValue = ref(structuredClone(textColor.value))
|
||||
const cardSizeValue = ref(structuredClone(cardSize.value))
|
||||
const textSizeValue = ref(structuredClone(textSize.value))
|
||||
const rowCountValue = ref(structuredClone(rowCount.value))
|
||||
const languageValue = ref(structuredClone(userLanguage.value))
|
||||
const isShowPrizeListValue = ref(structuredClone(isShowPrizeList.value))
|
||||
const isShowAvatarValue = ref(structuredClone(isShowAvatar.value))
|
||||
const patternColorValue = ref(structuredClone(patternColor.value))
|
||||
const themeList = ref(Object.keys(daisyuiThemes))
|
||||
const daisyuiThemeList = ref<ThemeDaType>(daisyuiThemes)
|
||||
const backgroundImageValue = ref(backgroundImage.value)
|
||||
const formData = ref({
|
||||
rowCount: rowCountValue,
|
||||
})
|
||||
const formErr = ref({
|
||||
rowCount: '',
|
||||
})
|
||||
|
||||
const schema = zod.object({
|
||||
rowCount: zod.number({
|
||||
required_error: '必填项',
|
||||
invalid_type_error: '必须填入数字',
|
||||
required_error: i18n.global.t('error.require'),
|
||||
invalid_type_error: i18n.global.t('error.requireNumber'),
|
||||
})
|
||||
.min(1, '最小为1')
|
||||
.max(100, '最大为100')
|
||||
.min(1, i18n.global.t('error.minNumber1'))
|
||||
.max(100, i18n.global.t('error.maxNumber100')),
|
||||
// 格式化
|
||||
|
||||
|
||||
})
|
||||
type ValidatePayload = zod.infer<typeof schema>
|
||||
const payload: ValidatePayload = {
|
||||
rowCount: formData.value.rowCount,
|
||||
}
|
||||
const parseSchema = (props: ValidatePayload) => {
|
||||
function parseSchema(props: ValidatePayload) {
|
||||
return schema.parseAsync(props)
|
||||
}
|
||||
|
||||
const resetPersonLayout = () => {
|
||||
function resetPersonLayout() {
|
||||
isRowCountChange.value = 2
|
||||
setTimeout(() => {
|
||||
const alreadyLen = alreadyPersonList.value.length
|
||||
@@ -79,17 +84,17 @@ const resetPersonLayout = () => {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const clearPattern = () => {
|
||||
function clearPattern() {
|
||||
globalConfig.setPatternList([] as number[])
|
||||
}
|
||||
const resetPattern = () => {
|
||||
function resetPattern() {
|
||||
globalConfig.resetPatternList()
|
||||
}
|
||||
|
||||
const resetData=()=>{
|
||||
globalConfig.reset();
|
||||
personConfig.reset();
|
||||
prizeConfig.resetDefault();
|
||||
function resetData() {
|
||||
globalConfig.reset()
|
||||
personConfig.reset()
|
||||
prizeConfig.resetDefault()
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
}
|
||||
@@ -104,28 +109,27 @@ const resetData=()=>{
|
||||
|
||||
watch(() => formData.value.rowCount, () => {
|
||||
payload.rowCount = formData.value.rowCount
|
||||
parseSchema(payload).then(res => {
|
||||
parseSchema(payload).then((res) => {
|
||||
if (res.rowCount) {
|
||||
isRowCountChange.value = 1
|
||||
globalConfig.setRowCount(res.rowCount)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
}).catch((err) => {
|
||||
formErr.value.rowCount = err.issues[0].message
|
||||
})
|
||||
})
|
||||
|
||||
watch(topTitleValue, (val) => {
|
||||
globalConfig.setTopTitle(val)
|
||||
}),
|
||||
|
||||
watch(themeValue, (val: any) => {
|
||||
})
|
||||
watch(themeValue, (val: any) => {
|
||||
const selectedThemeDetail = daisyuiThemeList.value[val]
|
||||
globalConfig.setTheme({ name: val, detail: selectedThemeDetail })
|
||||
themeChange(val)
|
||||
if (selectedThemeDetail.primary && (isHex(selectedThemeDetail.primary) || isRgbOrRgba(selectedThemeDetail.primary))) {
|
||||
globalConfig.setCardColor(selectedThemeDetail.primary)
|
||||
}
|
||||
}, { deep: true })
|
||||
}, { deep: true })
|
||||
|
||||
watch(cardColorValue, (val: string) => {
|
||||
globalConfig.setCardColor(val)
|
||||
@@ -140,12 +144,19 @@ watch(textColorValue, (val: string) => {
|
||||
globalConfig.setTextColor(val)
|
||||
}, { deep: true })
|
||||
|
||||
watch(cardSizeValue, (val: { width: number; height: number; }) => {
|
||||
watch(cardSizeValue, (val: { width: number, height: number }) => {
|
||||
globalConfig.setCardSize(val)
|
||||
}, { deep: true }),
|
||||
}, { deep: true })
|
||||
|
||||
watch(isShowPrizeListValue, () => {
|
||||
globalConfig.setIsShowPrizeList(isShowPrizeListValue.value)
|
||||
})
|
||||
watch(backgroundImageValue, (val) => {
|
||||
globalConfig.setBackground(val)
|
||||
})
|
||||
watch(languageValue, (val: string) => {
|
||||
globalConfig.setLanguage(val)
|
||||
})
|
||||
watch(isShowAvatarValue, () => {
|
||||
globalConfig.setIsShowAvatar(isShowAvatarValue.value)
|
||||
})
|
||||
@@ -156,150 +167,187 @@ onMounted(() => {
|
||||
<template>
|
||||
<dialog id="my_modal_1" ref="resetDataDialogRef" class="border-none modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">提示!</h3>
|
||||
<p class="py-4">该操作会重置所有数据,是否继续?</p>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ t('dialog.titleTip') }}
|
||||
</h3>
|
||||
<p class="py-4">
|
||||
{{ t('dialog.dialogResetAllData') }}
|
||||
</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="resetDataDialogRef.close()">取消</button>
|
||||
<button class="btn" @click="resetData">确定</button>
|
||||
<button class="btn" @click="resetDataDialogRef.close()">
|
||||
{{ t(`button.cancel`) }}
|
||||
</button>
|
||||
<button class="btn" @click="resetData">
|
||||
{{ t('button.confirm') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<div>
|
||||
<h2>全局配置</h2>
|
||||
<h2>{{ t('viewTitle.globalSetting') }}</h2>
|
||||
<div class="mb-8">
|
||||
<button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">重置所有数据</button>
|
||||
<button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">
|
||||
{{ t('button.resetAllData') }}
|
||||
</button>
|
||||
</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>
|
||||
<span class="label-text">{{ t('table.title') }}</span>
|
||||
</div>
|
||||
<input type="text" v-model="topTitleValue" placeholder="输入标题"
|
||||
class="w-full max-w-xs input input-bordered" />
|
||||
<input
|
||||
v-model="topTitleValue" type="text" :placeholder="t('placeHolder.enterTitle')"
|
||||
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">
|
||||
<div class="">
|
||||
<div class="label">
|
||||
<span class="label-text">列数</span>
|
||||
<span class="label-text">{{ t('table.columnNumber') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="formData.rowCount" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered" />
|
||||
<input
|
||||
v-model="formData.rowCount" type="number" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered"
|
||||
>
|
||||
<div class="help">
|
||||
<span class="text-sm text-red-400 help-text" v-if="formErr.rowCount">
|
||||
<span v-if="formErr.rowCount" class="text-sm text-red-400 help-text">
|
||||
{{ formErr.rowCount }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tooltip" data-tip="该项比较耗费时间和性能">
|
||||
<button class="mt-5 btn btn-info btn-sm" :disabled="isRowCountChange != 1" @click="resetPersonLayout">
|
||||
<span>重设布局</span>
|
||||
<span class="loading loading-ring loading-md" v-show="isRowCountChange == 2"></span>
|
||||
<div class="tooltip" :data-tip="t('tooltip.resetLayout')">
|
||||
<button class="mt-5 btn btn-info btn-sm" :disabled="isRowCountChange !== 1" @click="resetPersonLayout">
|
||||
<span>{{ t('button.setLayout') }}</span>
|
||||
<span v-show="isRowCountChange === 2" class="loading loading-ring loading-md" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">选择主题</span>
|
||||
<span class="label-text">{{ t('table.language') }}</span>
|
||||
</div>
|
||||
<select data-choose-theme class="w-full max-w-xs border-solid select border-1" v-model="themeValue">
|
||||
<option disabled selected>选取主题</option>
|
||||
<select v-model="languageValue" data-choose-theme class="w-full max-w-xs border-solid select border-1">
|
||||
<option disabled selected>{{ t('table.language') }}</option>
|
||||
<option v-for="item in languageList" :key="item.key" :value="item.key">{{ item.name }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">{{ t('table.theme') }}</span>
|
||||
</div>
|
||||
<select v-model="themeValue" data-choose-theme class="w-full max-w-xs border-solid select border-1">
|
||||
<option disabled selected>{{ t('table.theme') }}</option>
|
||||
<option v-for="(item, index) in themeList" :key="index" :value="item">{{ item }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">卡片颜色</span>
|
||||
<span class="label-text">{{ t('table.backgroundImage') }}</span>
|
||||
</div>
|
||||
<ColorPicker ref="colorPickerRef" v-model="cardColorValue" v-model:pure-color="cardColorValue"></ColorPicker>
|
||||
<select
|
||||
v-model="backgroundImageValue" data-choose-theme
|
||||
class="w-full max-w-xs border-solid select border-1"
|
||||
>
|
||||
<option disabled selected>{{ t('table.backgroundImage') }}</option>
|
||||
<option
|
||||
v-for="(item, index) in [{ name: '❌', url: '', id: '' }, ...imageList]" :key="index"
|
||||
:value="item"
|
||||
>{{ item.name }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">中奖卡片颜色</span>
|
||||
<span class="label-text">{{ t('table.cardColor') }}</span>
|
||||
</div>
|
||||
<ColorPicker ref="colorPickerRef" v-model="luckyCardColorValue" v-model:pure-color="luckyCardColorValue">
|
||||
</ColorPicker>
|
||||
<ColorPicker ref="colorPickerRef" v-model="cardColorValue" v-model:pure-color="cardColorValue" />
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">{{ t('table.winnerColor') }}</span>
|
||||
</div>
|
||||
<ColorPicker ref="colorPickerRef" v-model="luckyCardColorValue" v-model:pure-color="luckyCardColorValue" />
|
||||
</label>
|
||||
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">文字颜色</span>
|
||||
<span class="label-text">{{ t('table.textColor') }}</span>
|
||||
</div>
|
||||
<ColorPicker ref="colorPickerRef" v-model="textColorValue" v-model:pure-color="textColorValue"></ColorPicker>
|
||||
<ColorPicker ref="colorPickerRef" v-model="textColorValue" v-model:pure-color="textColorValue" />
|
||||
</label>
|
||||
<label class="flex flex-row w-full max-w-xs gap-10 mb-10 form-control">
|
||||
<div>
|
||||
<div class="label">
|
||||
<span class="label-text">卡片宽度</span>
|
||||
<span class="label-text">{{ t('table.cardWidth') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="cardSizeValue.width" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered" />
|
||||
<input
|
||||
v-model="cardSizeValue.width" type="number" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">
|
||||
<span class="label-text">卡片高度</span>
|
||||
<span class="label-text">{{ t('table.cardHeight') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="cardSizeValue.height" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered" />
|
||||
<input
|
||||
v-model="cardSizeValue.height" type="number" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered"
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
<label class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">文字大小</span>
|
||||
<span class="label-text">{{ t('table.textSize') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="textSizeValue" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered" />
|
||||
<input
|
||||
v-model="textSizeValue" type="number" placeholder="Type here"
|
||||
class="w-full max-w-xs input input-bordered"
|
||||
>
|
||||
</label>
|
||||
<label class="w-full max-w-xs form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">高亮颜色</span>
|
||||
<span class="label-text">{{ t('table.highlightColor') }}</span>
|
||||
</div>
|
||||
<ColorPicker ref="colorPickerRef" v-model="patternColorValue" v-model:pure-color="patternColorValue">
|
||||
</ColorPicker>
|
||||
<ColorPicker ref="colorPickerRef" v-model="patternColorValue" v-model:pure-color="patternColorValue" />
|
||||
</label>
|
||||
<label class="flex flex-row items-center w-full gap-24 mb-0 form-control">
|
||||
<div>
|
||||
<div class="label">
|
||||
<span class="label-text">图案设置</span>
|
||||
<span class="label-text">{{ t('table.patternSetting') }}</span>
|
||||
</div>
|
||||
<div class="h-auto">
|
||||
<PatternSetting :rowCount="rowCount" :cardColor="cardColor" :patternColor="patternColor"
|
||||
:patternList="patternList"></PatternSetting>
|
||||
<PatternSetting
|
||||
:row-count="rowCount" :card-color="cardColor" :pattern-color="patternColor"
|
||||
:pattern-list="patternList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<div class="flex w-full h-24 gap-3 m-0">
|
||||
<button class="mt-5 btn btn-info btn-sm" @click.stop="clearPattern">
|
||||
<span>清空图案设置</span>
|
||||
<span>{{ t('button.clearPattern') }}</span>
|
||||
</button>
|
||||
<div class="tooltip" data-tip="默认图案设置针对17列时有效,其他列数请自行设置">
|
||||
<div class="tooltip" :data-tip="t('tooltip.defaultLayout')">
|
||||
<button class="mt-5 btn btn-info btn-sm" @click="resetPattern">
|
||||
<span>默认图案设置</span>
|
||||
<span>{{ t('button.DefaultPattern') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">是否常显奖品列表</span>
|
||||
<span class="label-text">{{ t('table.alwaysDisplay') }}</span>
|
||||
</div>
|
||||
<input type="checkbox" :checked="isShowPrizeListValue" @change="isShowPrizeListValue = !isShowPrizeListValue"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
|
||||
<input
|
||||
type="checkbox" :checked="isShowPrizeListValue" class="mt-2 border-solid checkbox checkbox-secondary border-1"
|
||||
@change="isShowPrizeListValue = !isShowPrizeListValue"
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">是否显示头像</span>
|
||||
</div>
|
||||
<input type="checkbox" :checked="isShowAvatarValue" @change="isShowAvatarValue = !isShowAvatarValue"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { IImage } from '@/types/storeType'
|
||||
import type { IImage } from '@/types/storeType'
|
||||
import ImageSync from '@/components/ImageSync/index.vue'
|
||||
import useStore from '@/store'
|
||||
import { readFileData } from '@/utils/file'
|
||||
import localforage from 'localforage'
|
||||
import useStore from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import ImageSync from '@/components/ImageSync/index.vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
const globalConfig= useStore().globalConfig
|
||||
const { getImageList:localImageList} = storeToRefs(globalConfig)
|
||||
const { t } = useI18n()
|
||||
const globalConfig = useStore().globalConfig
|
||||
const { getImageList: localImageList } = storeToRefs(globalConfig)
|
||||
const limitType = ref('image/*')
|
||||
const imgUploadToast = ref(0) //0是不显示,1是成功,2是失败,3是不是图片
|
||||
const imgUploadToast = ref(0) // 0是不显示,1是成功,2是失败,3是不是图片
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore'
|
||||
name: 'imgStore',
|
||||
})
|
||||
const handleFileChange = async (e: Event) => {
|
||||
const isImage= /image*/.test(((e.target as HTMLInputElement).files as FileList)[0].type)
|
||||
async function handleFileChange(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 readFileData(((e.target as HTMLInputElement).files as FileList)[0])
|
||||
imageDbStore.setItem(new Date().getTime().toString() + '+' + fileName, dataUrl)
|
||||
const { dataUrl, fileName } = await readFileData(((e.target as HTMLInputElement).files as FileList)[0])
|
||||
imageDbStore.setItem(`${new Date().getTime().toString()}+${fileName}`, dataUrl)
|
||||
.then(() => {
|
||||
imgUploadToast.value = 1
|
||||
getImageDbStore()
|
||||
@@ -33,21 +34,21 @@ const handleFileChange = async (e: Event) => {
|
||||
})
|
||||
}
|
||||
|
||||
const getImageDbStore =async () => {
|
||||
const keys =await imageDbStore.keys()
|
||||
if(keys.length>0){
|
||||
async function getImageDbStore() {
|
||||
const keys = await imageDbStore.keys()
|
||||
if (keys.length > 0) {
|
||||
imageDbStore.iterate((value, key) => {
|
||||
globalConfig.addImage({
|
||||
id:key,
|
||||
name:key,
|
||||
url:'Storage'
|
||||
id: key,
|
||||
name: key,
|
||||
url: 'Storage',
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeImage=(item:IImage)=>{
|
||||
if(item.url=='Storage'){
|
||||
function removeImage(item: IImage) {
|
||||
if (item.url === 'Storage') {
|
||||
imageDbStore.removeItem(item.id).then(() => {
|
||||
globalConfig.removeImage(item.id)
|
||||
})
|
||||
@@ -68,23 +69,25 @@ watch(() => imgUploadToast.value, (val) => {
|
||||
|
||||
<template>
|
||||
<div class="toast toast-top toast-end">
|
||||
<div class="alert alert-error" v-if="imgUploadToast == 2">
|
||||
<span>上传失败</span>
|
||||
<div v-if="imgUploadToast === 2" class="alert alert-error">
|
||||
<span>{{ t('error.uploadFail') }}</span>
|
||||
</div>
|
||||
<div class="alert alert-success" v-if="imgUploadToast == 1">
|
||||
<span>上传成功</span>
|
||||
<div v-if="imgUploadToast === 1" class="alert alert-success">
|
||||
<span>{{ t('error.uploadSuccess') }}</span>
|
||||
</div>
|
||||
<div class="alert alert-error" v-if="imgUploadToast == 3">
|
||||
<span>不是图片</span>
|
||||
<div v-if="imgUploadToast === 3" class="alert alert-error">
|
||||
<span>{{ t('error.notImage') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="">
|
||||
<label for="explore">
|
||||
<input type="file" class="" id="explore" style="display: none" @change="handleFileChange"
|
||||
:accept="limitType" />
|
||||
<span class="btn btn-primary btn-sm">上传图片</span>
|
||||
<input
|
||||
id="explore" type="file" class="" style="display: none" :accept="limitType"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<span class="btn btn-primary btn-sm">{{ t('button.upload') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<ul class="p-0">
|
||||
@@ -93,14 +96,18 @@ watch(() => imgUploadToast.value, (val) => {
|
||||
<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 :imgItem="item"></ImageSync>
|
||||
<ImageSync :img-item="item" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-64">
|
||||
<div class="overflow-hidden font-bold whitespace-nowrap text-ellipsis">{{ item.name}}</div>
|
||||
<div class="overflow-hidden font-bold whitespace-nowrap text-ellipsis">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-error btn-xs" @click="removeImage(item)">删除</button>
|
||||
<button class="btn btn-error btn-xs" @click="removeImage(item)">
|
||||
{{ t('button.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {storeToRefs } from 'pinia'
|
||||
import { IMusic } from '@/types/storeType';
|
||||
import type { IMusic } from '@/types/storeType'
|
||||
import useStore from '@/store'
|
||||
import { readFileData } from '@/utils/file'
|
||||
import useStore from '@/store';
|
||||
|
||||
import localforage from 'localforage'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const audioUploadToast = ref(0) //0是不显示,1是成功,2是失败,3是不是图片
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const audioUploadToast = ref(0) // 0是不显示,1是成功,2是失败,3是不是图片
|
||||
const audioDbStore = localforage.createInstance({
|
||||
name: 'audioStore'
|
||||
name: 'audioStore',
|
||||
})
|
||||
const globalConfig = useStore().globalConfig
|
||||
|
||||
const { getMusicList: localMusicList } = storeToRefs(globalConfig);
|
||||
const { getMusicList: localMusicList } = storeToRefs(globalConfig)
|
||||
const limitType = ref('audio/*')
|
||||
const localMusicListValue = ref(localMusicList)
|
||||
const play = async (item: IMusic) => {
|
||||
globalConfig.setCurrentMusic(item,false)
|
||||
async function play(item: IMusic) {
|
||||
globalConfig.setCurrentMusic(item, false)
|
||||
}
|
||||
|
||||
const deleteMusic = (item: IMusic) => {
|
||||
function deleteMusic(item: IMusic) {
|
||||
globalConfig.removeMusic(item.id)
|
||||
audioDbStore.removeItem(item.name)
|
||||
// setTimeout(()=>{
|
||||
// localMusicListValue.value=localMusicList
|
||||
// },100)
|
||||
}
|
||||
const resetMusic = () => {
|
||||
function resetMusic() {
|
||||
globalConfig.resetMusicList()
|
||||
audioDbStore.clear()
|
||||
}
|
||||
const deleteAll = () => {
|
||||
function deleteAll() {
|
||||
globalConfig.clearMusicList()
|
||||
audioDbStore.clear()
|
||||
}
|
||||
const getMusicDbStore = async () => {
|
||||
async function getMusicDbStore() {
|
||||
const keys = await audioDbStore.keys()
|
||||
if (keys.length > 0) {
|
||||
audioDbStore.iterate((value: string, key: string) => {
|
||||
@@ -47,15 +49,15 @@ const getMusicDbStore = async () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleFileChange = async (e: Event) => {
|
||||
async function handleFileChange(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 readFileData(((e.target as HTMLInputElement).files as FileList)[0])
|
||||
audioDbStore.setItem(new Date().getTime().toString() + '+' + fileName, dataUrl)
|
||||
const { dataUrl, fileName } = await readFileData(((e.target as HTMLInputElement).files as FileList)[0])
|
||||
audioDbStore.setItem(`${new Date().getTime().toString()}+${fileName}`, dataUrl)
|
||||
.then(() => {
|
||||
audioUploadToast.value = 1
|
||||
getMusicDbStore()
|
||||
@@ -65,7 +67,6 @@ const handleFileChange = async (e: Event) => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getMusicDbStore()
|
||||
})
|
||||
@@ -74,13 +75,19 @@ onMounted(() => {
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex gap-3">
|
||||
<button class="btn btn-primary btn-sm" @click="resetMusic">重置音乐列表</button>
|
||||
<button class="btn btn-primary btn-sm" @click="resetMusic">
|
||||
{{ t('button.reset') }}
|
||||
</button>
|
||||
<label for="explore">
|
||||
<input type="file" class="" id="explore" style="display: none" @change="handleFileChange"
|
||||
:accept="limitType" />
|
||||
<span class="btn btn-primary btn-sm">上传音乐</span>
|
||||
<input
|
||||
id="explore" type="file" class="" style="display: none" :accept="limitType"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<span class="btn btn-primary btn-sm">{{ t('button.upload') }}</span>
|
||||
</label>
|
||||
<button class="btn btn-error btn-sm" @click="deleteAll">删除所有</button>
|
||||
<button class="btn btn-error btn-sm" @click="deleteAll">
|
||||
{{ t('button.allDelete') }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="p-0">
|
||||
@@ -90,8 +97,12 @@ onMounted(() => {
|
||||
{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button class="btn btn-primary btn-xs" @click="play(item)">播放</button>
|
||||
<button class="btn btn-error btn-xs" @click="deleteMusic(item)">删除</button>
|
||||
<button class="btn btn-primary btn-xs" @click="play(item)">
|
||||
{{ t('button.play') }}
|
||||
</button>
|
||||
<button class="btn btn-error btn-xs" @click="deleteMusic(item)">
|
||||
{{ t('button.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
<script setup lang='ts'>
|
||||
import {computed} from 'vue';
|
||||
const props=defineProps({
|
||||
rowCount:{
|
||||
type:Number,
|
||||
default:17
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
rowCount: {
|
||||
type: Number,
|
||||
default: 17,
|
||||
},
|
||||
cardColor:{
|
||||
type:String,
|
||||
default:'#fff'
|
||||
cardColor: {
|
||||
type: String,
|
||||
default: '#fff',
|
||||
},
|
||||
patternColor:{
|
||||
type:String,
|
||||
default:'#000'
|
||||
patternColor: {
|
||||
type: String,
|
||||
default: '#000',
|
||||
},
|
||||
patternList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
patternList:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
})
|
||||
const data=computed(()=>{
|
||||
const data = computed(() => {
|
||||
return props
|
||||
})
|
||||
|
||||
const updatePatternList=(event:Event,item:number)=>{
|
||||
if(data.value.patternList.includes(item)){
|
||||
const index=data.value.patternList.indexOf(item)
|
||||
data.value.patternList.splice(index,1)
|
||||
}else{
|
||||
function updatePatternList(event: Event, item: number) {
|
||||
if (data.value.patternList.includes(item)) {
|
||||
const index = data.value.patternList.indexOf(item)
|
||||
data.value.patternList.splice(index, 1)
|
||||
}
|
||||
else {
|
||||
data.value.patternList.push(item)
|
||||
}
|
||||
// emits
|
||||
@@ -34,12 +36,11 @@ const updatePatternList=(event:Event,item:number)=>{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-auto" >
|
||||
<ul class="pattern-list" :style="{gridTemplateColumns:'repeat('+data.rowCount+',1fr)'}">
|
||||
<li @click.stop="(event)=>updatePatternList(event,item)" class="w-5 h-5" v-for="item in data.rowCount*7" :key="item" :style="{backgroundColor:data.patternList.includes(item)?data.patternColor:data.cardColor}">
|
||||
</li>
|
||||
<div class="w-full h-auto">
|
||||
<ul class="pattern-list" :style="{ gridTemplateColumns: `repeat(${data.rowCount},1fr)` }">
|
||||
<li v-for="item in data.rowCount * 7" :key="item" class="w-5 h-5" :style="{ backgroundColor: data.patternList.includes(item) ? data.patternColor : data.cardColor }" @click.stop="(event) => updatePatternList(event, item)" />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<!-- eslint-disable vue/no-parsing-error -->
|
||||
<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 { readFileBinary } from '@/utils/file'
|
||||
import { addOtherInfo } from '@/utils'
|
||||
import type { IPersonConfig } from '@/types/storeType'
|
||||
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
import { addOtherInfo } from '@/utils'
|
||||
import { readFileBinary } from '@/utils/file'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import * as XLSX from 'xlsx'
|
||||
|
||||
const { t } = useI18n()
|
||||
const personConfig = useStore().personConfig
|
||||
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
|
||||
const limitType = '.xlsx,.xls'
|
||||
@@ -17,16 +20,16 @@ const limitType = '.xlsx,.xls'
|
||||
const resetDataDialog = ref()
|
||||
const delAllDataDialog = ref()
|
||||
|
||||
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]]
|
||||
async function handleFileChange(e: Event) {
|
||||
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
|
||||
const workBook = XLSX.read(dataBinary, { type: 'binary', cellDates: true })
|
||||
const workSheet = workBook.Sheets[workBook.SheetNames[0]]
|
||||
const excelData = XLSX.utils.sheet_to_json(workSheet)
|
||||
const allData = addOtherInfo(excelData);
|
||||
const allData = addOtherInfo(excelData)
|
||||
personConfig.resetPerson()
|
||||
personConfig.addNotPersonList(allData)
|
||||
}
|
||||
const exportData = () => {
|
||||
function exportData() {
|
||||
let data = JSON.parse(JSON.stringify(allPersonList.value))
|
||||
// 排除一些字段
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
@@ -38,9 +41,10 @@ const exportData = () => {
|
||||
delete data[i].prizeId
|
||||
// 修改字段名称
|
||||
if (data[i].isWin) {
|
||||
data[i].isWin = '是'
|
||||
} else {
|
||||
data[i].isWin = '否'
|
||||
data[i].isWin = i18n.global.t('data.yes')
|
||||
}
|
||||
else {
|
||||
data[i].isWin = i18n.global.t('data.no')
|
||||
}
|
||||
// 格式化数组为
|
||||
data[i].prizeTime = data[i].prizeTime.join(',')
|
||||
@@ -48,13 +52,13 @@ const exportData = () => {
|
||||
}
|
||||
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, '获奖时间')
|
||||
.replaceAll(/uid/g, i18n.global.t('data.number'))
|
||||
.replaceAll(/isWin/g, i18n.global.t('data.isWin'))
|
||||
.replaceAll(/department/g, i18n.global.t('data.department'))
|
||||
.replaceAll(/name/g, i18n.global.t('data.name'))
|
||||
.replaceAll(/identity/g, i18n.global.t('data.identity'))
|
||||
.replaceAll(/prizeName/g, i18n.global.t('data.prizeName'))
|
||||
.replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime'))
|
||||
|
||||
data = JSON.parse(dataString)
|
||||
|
||||
@@ -66,51 +70,44 @@ const exportData = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const resetData = () => {
|
||||
function resetData() {
|
||||
personConfig.resetAlreadyPerson()
|
||||
}
|
||||
|
||||
const deleteAll = () => {
|
||||
function deleteAll() {
|
||||
personConfig.deleteAllPerson()
|
||||
}
|
||||
|
||||
const delPersonItem = (row: IPersonConfig) => {
|
||||
function delPersonItem(row: IPersonConfig) {
|
||||
personConfig.deletePerson(row)
|
||||
}
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
label: '编号',
|
||||
label: i18n.global.t('data.number'),
|
||||
props: 'uid',
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
label: i18n.global.t('data.name'),
|
||||
props: 'name',
|
||||
},
|
||||
{
|
||||
label: '头像',
|
||||
props: 'avatar',
|
||||
formatValue(row: any) {
|
||||
return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '部门',
|
||||
label: i18n.global.t('data.department'),
|
||||
props: 'department',
|
||||
},
|
||||
{
|
||||
label: '身份',
|
||||
label: i18n.global.t('data.identity'),
|
||||
props: 'identity',
|
||||
},
|
||||
{
|
||||
label: '是否已中奖',
|
||||
label: i18n.global.t('data.isWin'),
|
||||
props: 'isWin',
|
||||
formatValue(row: IPersonConfig) {
|
||||
return row.isWin ? '是' : '否'
|
||||
}
|
||||
return row.isWin ? i18n.global.t('data.yes') : i18n.global.t('data.no')
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
label: i18n.global.t('data.operation'),
|
||||
actions: [
|
||||
// {
|
||||
// label: '编辑',
|
||||
@@ -120,14 +117,14 @@ const tableColumns = [
|
||||
// }
|
||||
// },
|
||||
{
|
||||
label: '删除',
|
||||
label: i18n.global.t('data.delete'),
|
||||
type: 'btn-error',
|
||||
onClick: (row: IPersonConfig) => {
|
||||
delPersonItem(row)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
]
|
||||
],
|
||||
},
|
||||
]
|
||||
onMounted(() => {
|
||||
@@ -137,62 +134,85 @@ onMounted(() => {
|
||||
<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>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ t('dialog.titleTip') }}
|
||||
</h3>
|
||||
<p class="py-4">
|
||||
{{ t('dialog.dialogResetWinner') }}
|
||||
</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>
|
||||
<button class="btn" @click="resetDataDialog.close()">
|
||||
{{ t('button.cancel') }}
|
||||
</button>
|
||||
<button class="btn" @click="resetData">
|
||||
{{ t('button.confirm') }}
|
||||
</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>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ t('dialog.titleTip') }}
|
||||
</h3>
|
||||
<p class="py-4">
|
||||
{{ t('dialog.dialogDelAllPerson') }}
|
||||
</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>
|
||||
<button class="btn" @click="delAllDataDialog.close()">
|
||||
{{ t('button.cancel') }}
|
||||
</button>
|
||||
<button class="btn" @click="deleteAll">
|
||||
{{ t('button.confirm') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="min-w-1000px">
|
||||
|
||||
<h2>人员管理</h2>
|
||||
<h2>{{ t('viewTitle.personManagement') }}</h2>
|
||||
<div class="flex gap-3">
|
||||
<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>
|
||||
<button class="btn btn-error btn-sm" @click="delAllDataDialog.showModal()">
|
||||
{{ t('button.allDelete') }}
|
||||
</button>
|
||||
<div class="tooltip tooltip-bottom" :data-tip="t('tooltip.downloadTemplateTip')">
|
||||
<a
|
||||
class="no-underline btn btn-secondary btn-sm" :download="t('data.xlsxName')" target="_blank"
|
||||
:href="`/log-lottery/${t('data.xlsxName')}`"
|
||||
>{{ t('button.downloadTemplate') }}</a>
|
||||
</div>
|
||||
<div class="">
|
||||
<label for="explore">
|
||||
|
||||
<div class="tooltip tooltip-bottom" data-tip="上传修改好的excel文件">
|
||||
<input type="file" class="" id="explore" style="display: none" @change="handleFileChange"
|
||||
:accept="limitType" />
|
||||
<div class="tooltip tooltip-bottom" :data-tip="t('tooltip.uploadExcelTip')">
|
||||
<input
|
||||
id="explore" type="file" class="" style="display: none" :accept="limitType"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
|
||||
<span class="btn btn-primary btn-sm">导入人员数据</span>
|
||||
<span class="btn btn-primary btn-sm">{{ t('button.importData') }}</span>
|
||||
</div>
|
||||
</label>
|
||||
<!-- <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>
|
||||
<button class="btn btn-error btn-sm" @click="resetDataDialog.showModal()">
|
||||
{{ t('button.resetData') }}
|
||||
</button>
|
||||
<button class="btn btn-accent btn-sm" @click="exportData">
|
||||
{{ t('button.exportResult') }}
|
||||
</button>
|
||||
<div>
|
||||
<span>中奖人数:</span>
|
||||
<span>{{ t('table.luckyPeopleNumber') }}:</span>
|
||||
<span>{{ alreadyPersonList.length }}</span>
|
||||
<span> / </span>
|
||||
<span>{{ allPersonList.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<DaiysuiTable :tableColumns="tableColumns" :data="allPersonList"></DaiysuiTable>
|
||||
<DaiysuiTable :table-columns="tableColumns" :data="allPersonList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<!-- eslint-disable vue/no-parsing-error -->
|
||||
<script setup lang='ts'>
|
||||
import { ref } from 'vue';
|
||||
import useStore from '@/store'
|
||||
import { IPersonConfig } from '@/types/storeType';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { IPersonConfig } from '@/types/storeType'
|
||||
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const personConfig = useStore().personConfig
|
||||
|
||||
const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: alreadyPersonDetail } = storeToRefs(personConfig)
|
||||
@@ -13,132 +16,115 @@ const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: already
|
||||
// alreadyPersonList
|
||||
// )
|
||||
|
||||
|
||||
// const deleteAll = () => {
|
||||
// personConfig.deleteAllPerson()
|
||||
// }
|
||||
|
||||
const isDetail = ref(false)
|
||||
const handleMoveNotPerson = (row: IPersonConfig) => {
|
||||
function handleMoveNotPerson(row: IPersonConfig) {
|
||||
personConfig.moveAlreadyToNot(row)
|
||||
}
|
||||
|
||||
const tableColumnsList = [
|
||||
{
|
||||
label: '编号',
|
||||
label: i18n.global.t('data.number'),
|
||||
props: 'uid',
|
||||
sort: true
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
label: i18n.global.t('data.name'),
|
||||
props: 'name',
|
||||
},
|
||||
{
|
||||
label: '头像',
|
||||
props: 'avatar',
|
||||
formatValue(row: any) {
|
||||
return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '部门',
|
||||
label: i18n.global.t('data.department'),
|
||||
props: 'department',
|
||||
},
|
||||
{
|
||||
label: '身份',
|
||||
label: i18n.global.t('data.identity'),
|
||||
props: 'identity',
|
||||
},
|
||||
{
|
||||
label: '奖品',
|
||||
label: i18n.global.t('data.prizeName'),
|
||||
props: 'prizeName',
|
||||
sort: true
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
label: i18n.global.t('data.operation'),
|
||||
actions: [
|
||||
{
|
||||
label: '移入未中奖名单',
|
||||
label: i18n.global.t('data.removePerson'),
|
||||
type: 'btn-info',
|
||||
onClick: (row: IPersonConfig) => {
|
||||
handleMoveNotPerson(row)
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
const tableColumnsDetail = [
|
||||
{
|
||||
label: '编号',
|
||||
label: i18n.global.t('data.number'),
|
||||
props: 'uid',
|
||||
sort: true
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
label: i18n.global.t('data.number'),
|
||||
props: 'name',
|
||||
},
|
||||
{
|
||||
label: '头像',
|
||||
props: 'avatar',
|
||||
formatValue(row: any) {
|
||||
return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '部门',
|
||||
label: i18n.global.t('data.department'),
|
||||
props: 'department',
|
||||
},
|
||||
{
|
||||
label: '身份',
|
||||
label: i18n.global.t('data.identity'),
|
||||
props: 'identity',
|
||||
},
|
||||
{
|
||||
label: '奖品',
|
||||
label: i18n.global.t('data.prizeName'),
|
||||
props: 'prizeName',
|
||||
sort: true
|
||||
sort: true,
|
||||
},
|
||||
{
|
||||
label: '中奖时间',
|
||||
label: i18n.global.t('data.prizeTime'),
|
||||
props: 'prizeTime',
|
||||
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
label: i18n.global.t('data.operation'),
|
||||
actions: [
|
||||
{
|
||||
label: '移入未中奖名单',
|
||||
label: i18n.global.t('data.removePerson'),
|
||||
type: 'btn-info',
|
||||
onClick: (row: IPersonConfig) => {
|
||||
handleMoveNotPerson(row)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
]
|
||||
],
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="overflow-y-auto">
|
||||
|
||||
<h2>已中奖人员管理</h2>
|
||||
<h2>{{ t('viewTitle.winnerManagement') }}</h2>
|
||||
<div class="flex items-center justify-start gap-10">
|
||||
<!-- <button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button> -->
|
||||
<div>
|
||||
<span>中奖人数:</span>
|
||||
<span>{{ t('table.luckyPeopleNumber') }}:</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" />
|
||||
<span class="label-text">{{ t('table.detail') }}:</span>
|
||||
<input v-model="isDetail" type="checkbox" class="border-solid toggle toggle-primary border-1">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DaiysuiTable v-if="!isDetail" :tableColumns="tableColumnsList" :data="alreadyPersonList"></DaiysuiTable>
|
||||
<DaiysuiTable v-if="!isDetail" :table-columns="tableColumnsList" :data="alreadyPersonList" />
|
||||
|
||||
<DaiysuiTable v-if="isDetail" :tableColumns="tableColumnsDetail" :data="alreadyPersonDetail"></DaiysuiTable>
|
||||
<DaiysuiTable v-if="isDetail" :table-columns="tableColumnsDetail" :data="alreadyPersonDetail" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<script setup lang='ts'>
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<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 type { IPrizeConfig } from '@/types/storeType'
|
||||
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
import localforage from 'localforage'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore'
|
||||
name: 'imgStore',
|
||||
})
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const globalConfig = useStore().globalConfig
|
||||
@@ -19,10 +22,10 @@ const imgList = ref<any[]>([])
|
||||
|
||||
const selectedPrize = ref<IPrizeConfig | null>()
|
||||
|
||||
const addPrize = () => {
|
||||
function addPrize() {
|
||||
const defaultPrizeCOnfig: IPrizeConfig = {
|
||||
id: new Date().getTime().toString(),
|
||||
name: '奖项',
|
||||
name: i18n.global.t('data.prizeName'),
|
||||
sort: 0,
|
||||
isAll: false,
|
||||
count: 1,
|
||||
@@ -30,11 +33,11 @@ const addPrize = () => {
|
||||
picture: {
|
||||
id: '',
|
||||
name: '',
|
||||
url: ''
|
||||
url: '',
|
||||
},
|
||||
separateCount: {
|
||||
enable: false,
|
||||
countList: []
|
||||
countList: [],
|
||||
},
|
||||
desc: '',
|
||||
isUsed: false,
|
||||
@@ -44,7 +47,7 @@ const addPrize = () => {
|
||||
prizeConfig.addPrizeConfig(defaultPrizeCOnfig)
|
||||
}
|
||||
|
||||
const selectPrize = (item: IPrizeConfig) => {
|
||||
function selectPrize(item: IPrizeConfig) {
|
||||
selectedPrize.value = item
|
||||
selectedPrize.value.isUsedCount = 0
|
||||
selectedPrize.value.isUsed = false
|
||||
@@ -59,12 +62,12 @@ const selectPrize = (item: IPrizeConfig) => {
|
||||
id: '0',
|
||||
count: item.count,
|
||||
isUsedCount: 0,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const changePrizeStatus = (item: IPrizeConfig) => {
|
||||
function changePrizeStatus(item: IPrizeConfig) {
|
||||
// if (item.isUsed == true) {
|
||||
// item.isUsedCount = 0;
|
||||
// if (item.separateCount && item.separateCount.countList.length) {
|
||||
@@ -81,58 +84,59 @@ const changePrizeStatus = (item: IPrizeConfig) => {
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
item.isUsed?item.isUsedCount=0:item.isUsedCount=item.count;
|
||||
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
|
||||
item.separateCount.countList = []
|
||||
item.isUsed = !item.isUsed
|
||||
}
|
||||
|
||||
const changePrizePerson = (item: IPrizeConfig) => {
|
||||
let indexPrize = -1;
|
||||
function changePrizePerson(item: IPrizeConfig) {
|
||||
let indexPrize = -1
|
||||
for (let i = 0; i < prizeList.value.length; i++) {
|
||||
if (prizeList.value[i].id == item.id) {
|
||||
indexPrize = i;
|
||||
break;
|
||||
if (prizeList.value[i].id === item.id) {
|
||||
indexPrize = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (indexPrize > -1) {
|
||||
prizeList.value[indexPrize].separateCount.countList = []
|
||||
prizeList.value[indexPrize].isUsed?prizeList.value[indexPrize].isUsedCount=prizeList.value[indexPrize].count:prizeList.value[indexPrize].isUsedCount=0
|
||||
prizeList.value[indexPrize].isUsed ? prizeList.value[indexPrize].isUsedCount = prizeList.value[indexPrize].count : prizeList.value[indexPrize].isUsedCount = 0
|
||||
}
|
||||
}
|
||||
const submitData = (value: any) => {
|
||||
selectedPrize.value!.separateCount.countList = value;
|
||||
function submitData(value: any) {
|
||||
selectedPrize.value!.separateCount.countList = value
|
||||
selectedPrize.value = null
|
||||
}
|
||||
const resetDefault = () => {
|
||||
function resetDefault() {
|
||||
prizeConfig.resetDefault()
|
||||
}
|
||||
|
||||
const getImageDbStore = async () => {
|
||||
async function getImageDbStore() {
|
||||
const keys = await imageDbStore.keys()
|
||||
if (keys.length > 0) {
|
||||
imageDbStore.iterate((value, key) => {
|
||||
imgList.value.push({
|
||||
key,
|
||||
value
|
||||
value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const sort = (item: IPrizeConfig, isUp: number) => {
|
||||
function sort(item: IPrizeConfig, isUp: number) {
|
||||
const itemIndex = prizeList.value.indexOf(item)
|
||||
if (isUp == 1) {
|
||||
if (isUp === 1) {
|
||||
prizeList.value.splice(itemIndex, 1)
|
||||
prizeList.value.splice(itemIndex - 1, 0, item)
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
prizeList.value.splice(itemIndex, 1)
|
||||
prizeList.value.splice(itemIndex + 1, 0, item)
|
||||
}
|
||||
}
|
||||
const delItem = (item: IPrizeConfig) => {
|
||||
function delItem(item: IPrizeConfig) {
|
||||
prizeConfig.deletePrizeConfig(item.id)
|
||||
}
|
||||
const delAll = async () => {
|
||||
async function delAll() {
|
||||
await prizeConfig.deleteAllPrizeConfig()
|
||||
}
|
||||
onMounted(() => {
|
||||
@@ -141,120 +145,142 @@ onMounted(() => {
|
||||
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
|
||||
prizeConfig.setPrizeConfig(val)
|
||||
}, { deep: true })
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>奖项配置</h2>
|
||||
<h2>{{ t('viewTitle.prizeManagement') }}</h2>
|
||||
<div class="flex w-full gap-3">
|
||||
<button class="btn btn-info btn-sm" @click="addPrize">添加</button>
|
||||
<button class="btn btn-info btn-sm" @click="resetDefault">默认列表</button>
|
||||
<button class="btn btn-error btn-sm" @click="delAll">全部删除</button>
|
||||
|
||||
<button class="btn btn-info btn-sm" @click="addPrize">
|
||||
{{ t('button.add') }}
|
||||
</button>
|
||||
<button class="btn btn-info btn-sm" @click="resetDefault">
|
||||
{{ t('button.resetDefault') }}
|
||||
</button>
|
||||
<button class="btn btn-error btn-sm" @click="delAll">
|
||||
{{ t('button.allDelete') }}
|
||||
</button>
|
||||
</div>
|
||||
<div role="alert" class="w-full my-4 alert alert-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-6 h-6 stroke-current shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<path
|
||||
stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>进行操作可能会重置数据,请谨慎操作</span>
|
||||
<span>{{ t('dialog.tipResetPrize') }}</span>
|
||||
</div>
|
||||
<ul class="p-0 m-0">
|
||||
<li v-for="item in prizeList" :key="item.id" class="flex gap-10"
|
||||
:class="currentPrize.id == item.id ? 'border-1 border-dotted rounded-xl' : null">
|
||||
<li
|
||||
v-for="item in prizeList" :key="item.id" class="flex gap-10"
|
||||
:class="currentPrize.id === item.id ? 'border-1 border-dotted rounded-xl' : null"
|
||||
>
|
||||
<label class="max-w-xs mb-10 form-control">
|
||||
<!-- 向上向下 -->
|
||||
<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" name="down" @click="sort(item, 0)"
|
||||
:class="prizeList.indexOf(item) == prizeList.length - 1 ? 'opacity-0 cursor-default' : ''"></svg-icon>
|
||||
<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
|
||||
class="cursor-pointer hover:text-blue-400" name="down" :class="prizeList.indexOf(item) === prizeList.length - 1 ? 'opacity-0 cursor-default' : ''"
|
||||
@click="sort(item, 0)"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<label class="w-1/2 max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">名称</span>
|
||||
<span class="label-text">{{ t('table.prizeName') }}</span>
|
||||
</div>
|
||||
<input type="text" v-model="item.name" placeholder="名称"
|
||||
class="w-full max-w-xs input-sm input input-bordered" />
|
||||
<input
|
||||
v-model="item.name" type="text" :placeholder="t('placeHolder.name')"
|
||||
class="w-full max-w-xs input-sm input input-bordered"
|
||||
>
|
||||
</label>
|
||||
<label class="w-1/2 max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">全员参加</span>
|
||||
<span class="label-text">{{ t('table.fullParticipation') }}</span>
|
||||
</div>
|
||||
<input type="checkbox" :checked="item.isAll" @change="item.isAll = !item.isAll"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
|
||||
<input
|
||||
type="checkbox" :checked="item.isAll" class="mt-2 border-solid checkbox checkbox-secondary border-1"
|
||||
@change="item.isAll = !item.isAll"
|
||||
>
|
||||
</label>
|
||||
<label class="w-1/2 max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">抽奖人数</span>
|
||||
<span class="label-text">{{ t('table.numberParticipants') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="item.count" placeholder="获奖人数" @change="changePrizePerson(item)"
|
||||
class="w-full max-w-xs p-0 m-0 input-sm input input-bordered" />
|
||||
<div class="tooltip tooltip-bottom" :data-tip="'已抽取:' + item.isUsedCount + '/' + item.count">
|
||||
<progress class="w-full progress" :value="item.isUsedCount" :max="item.count"></progress>
|
||||
<input
|
||||
v-model="item.count" type="number" :placeholder="t('placeHolder.winnerCount')" class="w-full max-w-xs p-0 m-0 input-sm input input-bordered"
|
||||
@change="changePrizePerson(item)"
|
||||
>
|
||||
<div class="tooltip tooltip-bottom" :data-tip="`${t('table.isDone') + item.isUsedCount}/${item.count}`">
|
||||
<progress class="w-full progress" :value="item.isUsedCount" :max="item.count" />
|
||||
</div>
|
||||
</label>
|
||||
<!-- <label class="w-1/2 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-1/2 max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">已抽取</span>
|
||||
<span class="label-text">{{ t('table.isDone') }}</span>
|
||||
</div>
|
||||
<input type="checkbox" :checked="item.isUsed" @change="changePrizeStatus(item)"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
|
||||
<input
|
||||
type="checkbox" :checked="item.isUsed" class="mt-2 border-solid checkbox checkbox-secondary border-1"
|
||||
@change="changePrizeStatus(item)"
|
||||
>
|
||||
</label>
|
||||
<label class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">图片</span>
|
||||
<span class="label-text">{{ t('table.image') }}</span>
|
||||
</div>
|
||||
<select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture">
|
||||
<select v-model="item.picture" class="w-full max-w-xs select select-warning select-sm">
|
||||
<option v-if="item.picture.id" :value="{ id: '', name: '', url: '' }">❌</option>
|
||||
<option disabled selected>选择一张图片</option>
|
||||
<option disabled selected>{{ t('table.selectPicture') }}</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" v-if="item.separateCount">
|
||||
<label v-if="item.separateCount" class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">单次抽取个数</span>
|
||||
<span class="label-text">{{ t('table.onceNumber') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-start w-full h-full" @click="selectPrize(item)">
|
||||
<ul class="flex flex-wrap w-full h-full gap-1 p-0 pt-1 m-0 cursor-pointer"
|
||||
v-if="item.separateCount.countList.length">
|
||||
<li class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated"
|
||||
v-for="se in item.separateCount.countList" :key="se.id">
|
||||
<div class="flex items-center justify-center w-full h-full tooltip"
|
||||
:data-tip="'已抽取:' + se.isUsedCount + '/' + se.count">
|
||||
<div class="absolute left-0 z-50 h-full bg-blue-300/80"
|
||||
:style="`width:${se.isUsedCount * 100 / se.count}%`"></div>
|
||||
<ul
|
||||
v-if="item.separateCount.countList.length"
|
||||
class="flex flex-wrap w-full h-full gap-1 p-0 pt-1 m-0 cursor-pointer"
|
||||
>
|
||||
<li
|
||||
v-for="se in item.separateCount.countList"
|
||||
:key="se.id" class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center w-full h-full tooltip"
|
||||
:data-tip="`${t('tooltip.doneCount') + se.isUsedCount}/${se.count}`"
|
||||
>
|
||||
<div
|
||||
class="absolute left-0 z-50 h-full bg-blue-300/80"
|
||||
:style="`width:${se.isUsedCount * 100 / se.count}%`"
|
||||
/>
|
||||
<span>{{ se.count }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button v-else class="btn btn-secondary btn-xs">设置</button>
|
||||
<button v-else class="btn btn-secondary btn-xs">{{ t('button.setting') }}</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="w-full max-w-xs mb-10 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">操作</span>
|
||||
<span class="label-text">{{ t('table.operation') }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-error btn-sm" @click="delItem(item)">删除</button>
|
||||
<button class="btn btn-error btn-sm" @click="delItem(item)">{{ t('button.delete') }}</button>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<EditSeparateDialog :totalNumber="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
|
||||
@submitData="submitData" />
|
||||
<EditSeparateDialog
|
||||
:total-number="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
|
||||
@submit-data="submitData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang='ts'>
|
||||
import {ref,onMounted} from 'vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import markdownit from 'markdown-it'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const md = markdownit()
|
||||
const readmeHtml=ref('')
|
||||
const readMd=()=>{
|
||||
fetch('/log-lottery/readme.md')
|
||||
.then(res=>res.text())
|
||||
.then(res=>{
|
||||
const readmeHtml = ref('')
|
||||
function readMd() {
|
||||
fetch(`/log-lottery/${i18n.global.t('data.readmeName')}`)
|
||||
.then(res => res.text())
|
||||
.then((res) => {
|
||||
readmeHtml.value = md.render(res)
|
||||
})
|
||||
}
|
||||
@@ -17,9 +19,9 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-3/4 mb-10 ml-3">
|
||||
<div class="markdown-body" v-dompurify-html="readmeHtml"></div>
|
||||
</div>
|
||||
<div class="w-3/4 mb-10 ml-3">
|
||||
<div v-dompurify-html="readmeHtml" class="markdown-body" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,88 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { configRoutes } from '../../router';
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { configRoutes } from '../../router'
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const menuList = ref<any[]>(configRoutes.children)
|
||||
|
||||
const cleanMenuList = (menu: any) => {
|
||||
const newList = menu;
|
||||
function cleanMenuList(menu: any) {
|
||||
const newList = menu
|
||||
for (let i = 0; i < newList.length; i++) {
|
||||
if (newList[i].children) {
|
||||
cleanMenuList(newList[i].children);
|
||||
cleanMenuList(newList[i].children)
|
||||
}
|
||||
if (!newList[i].meta) {
|
||||
newList.splice(i, 1);
|
||||
i--;
|
||||
newList.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
return newList;
|
||||
return newList
|
||||
}
|
||||
|
||||
menuList.value = cleanMenuList(menuList.value);
|
||||
menuList.value = cleanMenuList(menuList.value)
|
||||
|
||||
const skip = (path: string) => {
|
||||
router.push(path);
|
||||
function skip(path: string) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-[calc(100%-280px)]">
|
||||
<ul class="w-56 m-0 mr-3 menu bg-base-200 pt-14">
|
||||
<ul class="w-56 m-0 mr-3 min-w-56 menu bg-base-200 pt-14">
|
||||
<li v-for="item in menuList" :key="item.name">
|
||||
<details open v-if="item.children">
|
||||
<details v-if="item.children" open>
|
||||
<summary>{{ item.meta.title }}</summary>
|
||||
<ul>
|
||||
<li v-for="subItem in item.children" :key="subItem.name">
|
||||
<details open v-if="subItem.children">
|
||||
<details v-if="subItem.children" open>
|
||||
<summary>{{ subItem.meta!.title }}</summary>
|
||||
<ul>
|
||||
<li v-for="subSubItem in subItem.children" :key="subSubItem.name">
|
||||
<a @click="skip(subItem.path)"
|
||||
:style="subSubItem.name == route.name ? 'background-color:rgba(12,12,12,0.2)' : ''">{{
|
||||
<a
|
||||
:style="subSubItem.name === route.name ? 'background-color:rgba(12,12,12,0.2)' : ''"
|
||||
@click="skip(subItem.path)"
|
||||
>{{
|
||||
subSubItem.meta!.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<a v-else @click="skip(subItem.path)"
|
||||
:style="subItem.name == route.name ? 'background-color:rgba(12,12,12,0.2)' : ''">{{
|
||||
<a
|
||||
v-else :style="subItem.name === route.name ? 'background-color:rgba(12,12,12,0.2)' : ''"
|
||||
@click="skip(subItem.path)"
|
||||
>{{
|
||||
subItem.meta!.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<a v-else @click="skip(item.path)"
|
||||
:style="item.name == route.name ? 'background-color:rgba(12,12,12,0.2)' : ''">{{ item.meta!.title }}</a>
|
||||
<a
|
||||
v-else :style="item.name === route.name ? 'background-color:rgba(12,12,12,0.2)' : ''"
|
||||
@click="skip(item.path)"
|
||||
>{{ item.meta!.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<router-view class="mt-5"></router-view>
|
||||
<router-view class="flex-1 mt-5" />
|
||||
</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="cursor-pointer link link-hover text-inherit" target="_blank" href="https://1kw20.fun">行有不得,反求诸己</a>
|
||||
<a class="cursor-pointer link link-hover text-inherit" target="_blank" href="https://1kw20.fun">{{ t('footer.self-reflection') }}</a>
|
||||
</nav>
|
||||
<nav>
|
||||
<a class="cursor-pointer link link-hover text-inherit" target="_blank" href="https://1kw20.fun">破山中贼易,破心中贼难</a>
|
||||
<a class="cursor-pointer link link-hover text-inherit" target="_blank" href="https://1kw20.fun">{{ t('footer.thiefEasy') }}</a>
|
||||
</nav>
|
||||
<nav>
|
||||
<div class="grid grid-flow-col gap-4">
|
||||
<a href="https://github.com/LOG1997/log-lottery" target="_blank" class="cursor-pointer text-inherit">
|
||||
<svg-icon name="github"></svg-icon>
|
||||
<svg-icon name="github" />
|
||||
</a>
|
||||
<a href="https://twitter.com/TaborSwift" target="_blank" class="cursor-pointer "><svg-icon name="twitter"></svg-icon></a>
|
||||
<a href="https://twitter.com/TaborSwift" target="_blank" class="cursor-pointer "><svg-icon name="twitter" /></a>
|
||||
<a href="https://www.instagram.com/log.z1997/" target="_blank" class="cursor-pointer ">
|
||||
<svg-icon name="instagram"></svg-icon>
|
||||
<svg-icon name="instagram" />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<aside>
|
||||
|
||||
<p class="p-0 m-0">蜀ICP备2021028666号</p>
|
||||
<p class="p-0 m-0">
|
||||
蜀ICP备2021028666号
|
||||
</p>
|
||||
<p>Copyright © 2024 - All right reserved by Log1997</p>
|
||||
</aside>
|
||||
</footer>
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button class="btn btn-error">打印</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-error">
|
||||
打印
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script setup lang='ts'>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import useStore from '@/store'
|
||||
|
||||
import ImageSync from '@/components/ImageSync/index.vue'
|
||||
import type { IPrizeConfig } from '../../types/storeType'
|
||||
import defaultPrizeImage from '@/assets/images/龙.png'
|
||||
import { IPrizeConfig } from '../../types/storeType';
|
||||
import ImageSync from '@/components/ImageSync/index.vue'
|
||||
|
||||
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
|
||||
import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const globalConfig = useStore().globalConfig
|
||||
const system = useStore().system
|
||||
@@ -21,8 +25,8 @@ const prizeListContainerRef = ref()
|
||||
const temporaryPrizeRef = ref()
|
||||
const selectedPrize = ref<IPrizeConfig | null>()
|
||||
// 获取prizeListRef高度
|
||||
const getPrizeListHeight = () => {
|
||||
let height = 200;
|
||||
function getPrizeListHeight() {
|
||||
let height = 200
|
||||
if (prizeListRef.value) {
|
||||
height = (prizeListRef.value as HTMLElement).offsetHeight
|
||||
}
|
||||
@@ -31,25 +35,25 @@ const getPrizeListHeight = () => {
|
||||
}
|
||||
const prizeShow = ref(structuredClone(isShowPrizeList.value))
|
||||
|
||||
const addTemporaryPrize = () => {
|
||||
function addTemporaryPrize() {
|
||||
temporaryPrizeRef.value.showModal()
|
||||
}
|
||||
|
||||
const deleteTemporaryPrize = () => {
|
||||
function deleteTemporaryPrize() {
|
||||
temporaryPrize.value.isShow = false
|
||||
prizeConfig.setTemporaryPrize(temporaryPrize.value)
|
||||
}
|
||||
const submitTemporaryPrize = () => {
|
||||
function submitTemporaryPrize() {
|
||||
if (!temporaryPrize.value.name || !temporaryPrize.value.count) {
|
||||
alert('请填写完整信息')
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(i18n.global.t('error.completeInformation'))
|
||||
return
|
||||
}
|
||||
temporaryPrize.value.isShow = true
|
||||
temporaryPrize.value.id=new Date().getTime().toString()
|
||||
temporaryPrize.value.id = new Date().getTime().toString()
|
||||
prizeConfig.setCurrentPrize(temporaryPrize.value)
|
||||
}
|
||||
const selectPrize = (item: IPrizeConfig) => {
|
||||
function selectPrize(item: IPrizeConfig) {
|
||||
selectedPrize.value = item
|
||||
selectedPrize.value.isUsedCount = 0
|
||||
selectedPrize.value.isUsed = false
|
||||
@@ -64,28 +68,28 @@ const selectPrize = (item: IPrizeConfig) => {
|
||||
id: '0',
|
||||
count: item.count,
|
||||
isUsedCount: 0,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
const submitData = (value: any) => {
|
||||
selectedPrize.value!.separateCount.countList = value;
|
||||
function submitData(value: any) {
|
||||
selectedPrize.value!.separateCount.countList = value
|
||||
selectedPrize.value = null
|
||||
}
|
||||
const changePersonCount=()=>{
|
||||
temporaryPrize.value.separateCount.countList=[]
|
||||
function changePersonCount() {
|
||||
temporaryPrize.value.separateCount.countList = []
|
||||
}
|
||||
const setCurrentPrize=()=>{
|
||||
for(let i=0;i<localPrizeList.value.length;i++){
|
||||
if(localPrizeList.value[i].isUsedCount<localPrizeList.value[i].count){
|
||||
function setCurrentPrize() {
|
||||
for (let i = 0; i < localPrizeList.value.length; i++) {
|
||||
if (localPrizeList.value[i].isUsedCount < localPrizeList.value[i].count) {
|
||||
prizeConfig.setCurrentPrize(localPrizeList.value[i])
|
||||
|
||||
return
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
prizeListContainerRef.value.style.height = getPrizeListHeight() + 'px'
|
||||
prizeListContainerRef.value.style.height = `${getPrizeListHeight()}px`
|
||||
setCurrentPrize()
|
||||
})
|
||||
</script>
|
||||
@@ -94,66 +98,84 @@ onMounted(() => {
|
||||
<div class="flex items-center">
|
||||
<dialog id="my_modal_1" ref="temporaryPrizeRef" class="border-none modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">增加临时抽奖</h3>
|
||||
<h3 class="text-lg font-bold">
|
||||
{{ t('dialog.titleTemporary') }}
|
||||
</h3>
|
||||
<div class="flex flex-col gap-3">
|
||||
<label class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">名称:</span>
|
||||
<span class="label-text">{{ t('table.name') }}:</span>
|
||||
</div>
|
||||
<input type="text" v-model="temporaryPrize.name" placeholder="名称"
|
||||
class="max-w-xs input-sm input input-bordered" />
|
||||
<input
|
||||
v-model="temporaryPrize.name" type="text" :placeholder="t('placeHolder.name')"
|
||||
class="max-w-xs input-sm input input-bordered"
|
||||
>
|
||||
</label>
|
||||
<label class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">是否全员参加</span>
|
||||
<span class="label-text">{{ t('table.fullParticipation') }}</span>
|
||||
</div>
|
||||
<input type="checkbox" :checked="temporaryPrize.isAll"
|
||||
<input
|
||||
type="checkbox" :checked="temporaryPrize.isAll"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1"
|
||||
@change="temporaryPrize.isAll = !temporaryPrize.isAll"
|
||||
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
|
||||
>
|
||||
</label>
|
||||
<label class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">获奖人数</span>
|
||||
<span class="label-text">{{ t('table.setLuckyNumber') }}</span>
|
||||
</div>
|
||||
<input type="number" v-model="temporaryPrize.count" @change="changePersonCount" placeholder="获奖人数"
|
||||
class="max-w-xs input-sm input input-bordered" />
|
||||
<input
|
||||
v-model="temporaryPrize.count" type="number" :placeholder="t('placeHolder.winnerCount')" class="max-w-xs input-sm input input-bordered"
|
||||
@change="changePersonCount"
|
||||
>
|
||||
</label>
|
||||
<label class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">已获奖人数</span>
|
||||
<span class="label-text">{{ t('table.luckyPeopleNumber') }}</span>
|
||||
</div>
|
||||
<input disabled type="number" v-model="temporaryPrize.isUsedCount" placeholder="获奖人数"
|
||||
class="max-w-xs input-sm input input-bordered" />
|
||||
<input
|
||||
v-model="temporaryPrize.isUsedCount" disabled type="number" :placeholder="t('placeHolder.winnerCount')"
|
||||
class="max-w-xs input-sm input input-bordered"
|
||||
>
|
||||
</label>
|
||||
<label class="flex w-full max-w-xs" v-if="temporaryPrize.separateCount">
|
||||
<label v-if="temporaryPrize.separateCount" class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">单次抽取个数</span>
|
||||
<span class="label-text">{{ t('table.onceNumber') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-start h-full" @click="selectPrize(temporaryPrize)">
|
||||
<ul class="flex flex-wrap w-full h-full gap-1 p-0 pt-1 m-0 cursor-pointer"
|
||||
v-if="temporaryPrize.separateCount.countList.length">
|
||||
<li class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated"
|
||||
v-for="se in temporaryPrize.separateCount.countList" :key="se.id">
|
||||
<div class="flex items-center justify-center w-full h-full tooltip"
|
||||
:data-tip="'已抽取:' + se.isUsedCount + '/' + se.count">
|
||||
<div class="absolute left-0 z-50 h-full bg-blue-300/80"
|
||||
:style="`width:${se.isUsedCount * 100 / se.count}%`"></div>
|
||||
<ul
|
||||
v-if="temporaryPrize.separateCount.countList.length"
|
||||
class="flex flex-wrap w-full h-full gap-1 p-0 pt-1 m-0 cursor-pointer"
|
||||
>
|
||||
<li
|
||||
v-for="se in temporaryPrize.separateCount.countList"
|
||||
:key="se.id" class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center w-full h-full tooltip"
|
||||
:data-tip="`${t('tooltip.doneCount') + se.isUsedCount}/${se.count}`"
|
||||
>
|
||||
<div
|
||||
class="absolute left-0 z-50 h-full bg-blue-300/80"
|
||||
:style="`width:${se.isUsedCount * 100 / se.count}%`"
|
||||
/>
|
||||
<span>{{ se.count }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button v-else class="btn btn-secondary btn-xs">设置</button>
|
||||
<button v-else class="btn btn-secondary btn-xs">{{ t('button.setting') }}</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">图片</span>
|
||||
<span class="label-text">{{ t('table.image') }}</span>
|
||||
</div>
|
||||
<select class="flex-1 w-12 select select-warning select-sm" v-model="temporaryPrize.picture">
|
||||
<select v-model="temporaryPrize.picture" class="flex-1 w-12 select select-warning select-sm">
|
||||
<option v-if="temporaryPrize.picture.id" :value="{ id: '', name: '', url: '' }">❌
|
||||
</option>
|
||||
<option disabled selected>选择一张图片</option>
|
||||
<option class="w-auto" v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{
|
||||
<option disabled selected>{{ t('table.selectPicture') }}</option>
|
||||
<option v-for="picItem in localImageList" :key="picItem.id" class="w-auto" :value="picItem">{{
|
||||
picItem.name }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -161,89 +183,120 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<form method="dialog" class="flex gap-3">
|
||||
<button class="btn btn-sm" @click="submitTemporaryPrize">确定</button>
|
||||
<button class="btn btn-sm">取消</button>
|
||||
<button class="btn btn-sm" @click="submitTemporaryPrize">
|
||||
{{ t('button.confirm') }}
|
||||
</button>
|
||||
<button class="btn btn-sm">
|
||||
{{ t('button.cancel') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<EditSeparateDialog :totalNumber="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
|
||||
@submitData="submitData" />
|
||||
<EditSeparateDialog
|
||||
:total-number="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
|
||||
@submit-data="submitData"
|
||||
/>
|
||||
<div ref="prizeListContainerRef">
|
||||
<div class="h-20 w-72" :class="temporaryPrize.isShow ? 'current-prize' : ''" v-if="temporaryPrize.isShow">
|
||||
<div v-if="temporaryPrize.isShow" class="h-20 w-72" :class="temporaryPrize.isShow ? 'current-prize' : ''">
|
||||
<div class="relative flex flex-row items-center justify-between w-full h-full shadow-xl card bg-base-100">
|
||||
<div v-if="temporaryPrize.isUsed"
|
||||
class="absolute z-50 w-full h-full bg-gray-800/70 item-mask rounded-xl"></div>
|
||||
<div
|
||||
v-if="temporaryPrize.isUsed"
|
||||
class="absolute z-50 w-full h-full bg-gray-800/70 item-mask rounded-xl"
|
||||
/>
|
||||
<figure class="w-10 h-10 rounded-xl">
|
||||
<ImageSync v-if="temporaryPrize.picture.url" :imgItem="temporaryPrize.picture"></ImageSync>
|
||||
<img v-else :src="defaultPrizeImage" alt="Prize" class="object-cover h-full rounded-xl" />
|
||||
<ImageSync v-if="temporaryPrize.picture.url" :img-item="temporaryPrize.picture" />
|
||||
<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">
|
||||
<div class="tooltip tooltip-left" :data-tip="temporaryPrize.name">
|
||||
<h2 class="p-0 m-0 overflow-hidden w-28 card-title whitespace-nowrap text-ellipsis">{{
|
||||
temporaryPrize.name }}</h2>
|
||||
<h2 class="p-0 m-0 overflow-hidden w-28 card-title whitespace-nowrap text-ellipsis">
|
||||
{{
|
||||
temporaryPrize.name }}
|
||||
</h2>
|
||||
</div>
|
||||
<p class="absolute z-40 p-0 m-0 text-gray-300/80 mt-9">{{ temporaryPrize.isUsedCount }}/{{
|
||||
temporaryPrize.count }}</p>
|
||||
<progress class="w-3/4 h-6 progress progress-primary" :value="temporaryPrize.isUsedCount"
|
||||
:max="temporaryPrize.count"></progress>
|
||||
<p class="absolute z-40 p-0 m-0 text-gray-300/80 mt-9">
|
||||
{{ temporaryPrize.isUsedCount }}/{{
|
||||
temporaryPrize.count }}
|
||||
</p>
|
||||
<progress
|
||||
class="w-3/4 h-6 progress progress-primary" :value="temporaryPrize.isUsedCount"
|
||||
:max="temporaryPrize.count"
|
||||
/>
|
||||
<!-- <p class="p-0 m-0">{{ item.isUsedCount }}/{{ item.count }}</p> -->
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 mr-2">
|
||||
<div class="tooltip tooltip-left" data-tip="编辑">
|
||||
<div class="tooltip tooltip-left" :data-tip="t('tooltip.edit')">
|
||||
<div class="cursor-pointer hover:text-blue-400" @click="addTemporaryPrize">
|
||||
<svg-icon name="edit"></svg-icon>
|
||||
<svg-icon name="edit" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tooltip tooltip-left" data-tip="删除">
|
||||
<div class="tooltip tooltip-left" :data-tip="t('tooltip.delete')">
|
||||
<div class="cursor-pointer hover:text-blue-400" @click="deleteTemporaryPrize">
|
||||
<svg-icon name="delete"></svg-icon>
|
||||
<svg-icon name="delete" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<transition name="prize-list" :appear="true">
|
||||
<div v-if="prizeShow && !isMobile && !temporaryPrize.isShow" class="flex items-center">
|
||||
<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"
|
||||
:class="currentPrize.id == item.id ? 'current-prize' : ''">
|
||||
<div 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>
|
||||
<ul ref="prizeListRef" class="flex flex-col gap-1 p-2 rounded-xl bg-slate-500/50">
|
||||
<li
|
||||
v-for="item in localPrizeList" :key="item.id"
|
||||
:class="currentPrize.id === item.id ? 'current-prize' : ''"
|
||||
>
|
||||
<div
|
||||
v-if="item.isShow"
|
||||
class="relative flex flex-row items-center justify-between w-64 h-20 shadow-xl card bg-base-100"
|
||||
>
|
||||
<div
|
||||
v-if="item.isUsed"
|
||||
class="absolute z-50 w-full h-full bg-gray-800/70 item-mask rounded-xl"
|
||||
/>
|
||||
<figure class="w-10 h-10 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" />
|
||||
<ImageSync v-if="item.picture.url" :img-item="item.picture" />
|
||||
<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">
|
||||
<div class="tooltip tooltip-left" :data-tip="item.name">
|
||||
<h2
|
||||
class="w-24 p-0 m-0 overflow-hidden text-center card-title whitespace-nowrap text-ellipsis">
|
||||
{{ item.name }}</h2>
|
||||
class="w-24 p-0 m-0 overflow-hidden text-center card-title whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ item.name }}
|
||||
</h2>
|
||||
</div>
|
||||
<p class="absolute z-40 p-0 m-0 text-gray-300/80 mt-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="absolute z-40 p-0 m-0 text-gray-300/80 mt-9">
|
||||
{{ item.isUsedCount }}/{{
|
||||
item.count }}
|
||||
</p>
|
||||
<progress
|
||||
class="w-3/4 h-6 progress progress-primary" :value="item.isUsedCount"
|
||||
:max="item.count"
|
||||
/>
|
||||
<!-- <p class="p-0 m-0">{{ item.isUsedCount }}/{{ item.count }}</p> -->
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="tooltip tooltip-right" data-tip="奖项列表">
|
||||
<div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
|
||||
@click="prizeShow = !prizeShow">
|
||||
<svg-icon name="arrow_left" class="w-full h-full"></svg-icon>
|
||||
<div class="tooltip tooltip-right" :data-tip="t('tooltip.prizeList')">
|
||||
<div
|
||||
class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
|
||||
@click="prizeShow = !prizeShow"
|
||||
>
|
||||
<svg-icon name="arrow_left" class="w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tooltip tooltip-right" data-tip="添加抽奖">
|
||||
<div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
|
||||
@click="addTemporaryPrize">
|
||||
<svg-icon name="add" class="w-full h-full"></svg-icon>
|
||||
<div class="tooltip tooltip-right" :data-tip="t('tooltip.addActivity')">
|
||||
<div
|
||||
class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
|
||||
@click="addTemporaryPrize"
|
||||
>
|
||||
<svg-icon name="add" class="w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,10 +305,12 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<transition name="prize-operate" :appear="true">
|
||||
<div class="tooltip tooltip-right" 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"
|
||||
@click="prizeShow = !prizeShow">
|
||||
<svg-icon name="arrow_right" class="w-full h-full"></svg-icon>
|
||||
<div v-show="!prizeShow" class="tooltip tooltip-right" :data-tip="t('tooltip.prizeList')">
|
||||
<div
|
||||
class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
|
||||
@click="prizeShow = !prizeShow"
|
||||
>
|
||||
<svg-icon name="arrow_right" class="w-full h-full" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -311,7 +366,6 @@ onMounted(() => {
|
||||
translate: 0% 0%;
|
||||
}
|
||||
|
||||
|
||||
.current-prize::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -401,4 +455,5 @@ onMounted(() => {
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}</style>
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, } from 'vue'
|
||||
import PrizeList from './PrizeList.vue'
|
||||
import { useElementStyle, useElementPosition } from '@/hooks/useElement'
|
||||
import type { IPersonConfig } from '@/types/storeType'
|
||||
import type { Material } from 'three'
|
||||
import StarsBackground from '@/components/StarsBackground/index.vue'
|
||||
import confetti from 'canvas-confetti'
|
||||
import { useElementPosition, useElementStyle } from '@/hooks/useElement'
|
||||
import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
import { filterData, selectCard } from '@/utils'
|
||||
import { rgba } from '@/utils/color'
|
||||
import { IPersonConfig } from '@/types/storeType'
|
||||
// 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 confetti from 'canvas-confetti'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { Object3D, PerspectiveCamera, Scene, Vector3 } from 'three'
|
||||
import { CSS3DObject, CSS3DRenderer } from 'three-css3d'
|
||||
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'vue-toast-notification';
|
||||
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import PrizeList from './PrizeList.vue'
|
||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||
|
||||
const toast = useToast();
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const personConfig = useStore().personConfig
|
||||
const globalConfig = useStore().globalConfig
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
|
||||
const { getAllPersonList: allPersonList, getNotPersonList: notPersonList, getNotThisPrizePersonList: notThisPrizePersonList } = storeToRefs(personConfig)
|
||||
const { getAllPersonList: allPersonList, getNotPersonList: notPersonList, getNotThisPrizePersonList: notThisPrizePersonList,
|
||||
} = storeToRefs(personConfig)
|
||||
const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
||||
const { getTopTitle: topTitle, getCardColor: cardColor, getPatterColor: patternColor, getPatternList: patternList, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getIsShowAvatar: isShowAvatar } = storeToRefs(globalConfig)
|
||||
const { getTopTitle: topTitle, getCardColor: cardColor, getPatterColor: patternColor, getPatternList: patternList, getTextColor: textColor, getLuckyColor: luckyColor, getCardSize: cardSize, getTextSize: textSize, getRowCount: rowCount, getBackground: homeBackground } = 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>()
|
||||
// const LuckyViewRef= ref()
|
||||
const canOperate = ref(true)
|
||||
const cameraZ = ref(3000)
|
||||
|
||||
@@ -46,200 +43,189 @@ const camera = ref()
|
||||
const renderer = ref()
|
||||
const controls = ref()
|
||||
const objects = ref<any[]>([])
|
||||
|
||||
const targets = {
|
||||
grid: <any[]>[],
|
||||
helix: <any[]>[],
|
||||
table: <any[]>[],
|
||||
sphere: <any[]>[]
|
||||
};
|
||||
interface TargetType {
|
||||
grid: any[]
|
||||
helix: any[]
|
||||
table: any[]
|
||||
sphere: any[]
|
||||
}
|
||||
const targets: TargetType = {
|
||||
grid: [],
|
||||
helix: [],
|
||||
table: [],
|
||||
sphere: [],
|
||||
}
|
||||
|
||||
const luckyTargets = ref<any[]>([])
|
||||
const luckyCardList = ref<number[]>([])
|
||||
let luckyCount = ref(10)
|
||||
const luckyCount = ref(10)
|
||||
const personPool = ref<IPersonConfig[]>([])
|
||||
|
||||
const intervalTimer = ref<any>(null)
|
||||
// const currentPrizeValue = ref(JSON.parse(JSON.stringify(currentPrize.value)))
|
||||
|
||||
// 填充数据,填满七行
|
||||
function initTableData() {
|
||||
if (allPersonList.value.length <= 0) {
|
||||
return
|
||||
}
|
||||
const totalCount = rowCount.value * 7
|
||||
const orginPersonData = JSON.parse(JSON.stringify(allPersonList.value))
|
||||
const orginPersonLength = orginPersonData.length
|
||||
if (orginPersonLength < totalCount) {
|
||||
const repeatCount = Math.ceil(totalCount / orginPersonLength)
|
||||
const originPersonData = JSON.parse(JSON.stringify(allPersonList.value))
|
||||
const originPersonLength = originPersonData.length
|
||||
if (originPersonLength < totalCount) {
|
||||
const repeatCount = Math.ceil(totalCount / originPersonLength)
|
||||
// 复制数据
|
||||
for (let i = 0; i < repeatCount; i++) {
|
||||
tableData.value = tableData.value.concat(JSON.parse(JSON.stringify(orginPersonData)))
|
||||
tableData.value = tableData.value.concat(JSON.parse(JSON.stringify(originPersonData)))
|
||||
}
|
||||
}
|
||||
else{
|
||||
tableData.value=orginPersonData.slice(0, totalCount)
|
||||
else {
|
||||
tableData.value = originPersonData.slice(0, totalCount)
|
||||
}
|
||||
tableData.value = filterData(tableData.value.slice(0, totalCount), rowCount.value)
|
||||
}
|
||||
const init = () => {
|
||||
const felidView = 40;
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
const aspect = width / height;
|
||||
const nearPlane = 1;
|
||||
const farPlane = 10000;
|
||||
function init() {
|
||||
const felidView = 40
|
||||
const width = window.innerWidth
|
||||
const height = window.innerHeight
|
||||
const aspect = width / height
|
||||
const nearPlane = 1
|
||||
const farPlane = 10000
|
||||
const WebGLoutput = containerRef.value
|
||||
|
||||
scene.value = new Scene();
|
||||
camera.value = new PerspectiveCamera(felidView, aspect, nearPlane, farPlane);
|
||||
scene.value = new Scene()
|
||||
camera.value = new PerspectiveCamera(felidView, aspect, nearPlane, farPlane)
|
||||
camera.value.position.z = cameraZ.value
|
||||
renderer.value = new CSS3DRenderer()
|
||||
renderer.value.setSize(width, height * 0.9)
|
||||
renderer.value.domElement.style.position = 'absolute';
|
||||
renderer.value.domElement.style.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);
|
||||
renderer.value.domElement.style.top = '50%'
|
||||
renderer.value.domElement.style.left = '50%'
|
||||
renderer.value.domElement.style.transform = 'translate(-50%, -50%)'
|
||||
WebGLoutput!.appendChild(renderer.value.domElement)
|
||||
|
||||
controls.value = new TrackballControls(camera.value, renderer.value.domElement);
|
||||
controls.value.rotateSpeed = 1;
|
||||
controls.value.staticMoving = true;
|
||||
controls.value.minDistance = 500;
|
||||
controls.value.maxDistance = 6000;
|
||||
controls.value.addEventListener('change', render);
|
||||
controls.value = new TrackballControls(camera.value, renderer.value.domElement)
|
||||
controls.value.rotateSpeed = 1
|
||||
controls.value.staticMoving = true
|
||||
controls.value.minDistance = 500
|
||||
controls.value.maxDistance = 6000
|
||||
controls.value.addEventListener('change', render)
|
||||
|
||||
const tableLen = tableData.value.length
|
||||
for (let i = 0; i < tableLen; i++) {
|
||||
let element = document.createElement('div');
|
||||
element.className = 'element-card';
|
||||
let element = document.createElement('div')
|
||||
element.className = 'element-card'
|
||||
|
||||
const number = document.createElement('div');
|
||||
number.className = 'card-id';
|
||||
number.textContent = tableData.value[i].uid;
|
||||
if(isShowAvatar.value) number.style.display = 'none'
|
||||
element.appendChild(number);
|
||||
const number = document.createElement('div')
|
||||
number.className = 'card-id'
|
||||
number.textContent = tableData.value[i].uid
|
||||
element.appendChild(number)
|
||||
|
||||
const symbol = document.createElement('div');
|
||||
symbol.className = 'card-name';
|
||||
symbol.textContent = tableData.value[i].name;
|
||||
if(isShowAvatar.value) symbol.className = 'card-name card-avatar-name'
|
||||
element.appendChild(symbol);
|
||||
const symbol = document.createElement('div')
|
||||
symbol.className = 'card-name'
|
||||
symbol.textContent = tableData.value[i].name
|
||||
element.appendChild(symbol)
|
||||
|
||||
const detail = document.createElement('div');
|
||||
detail.className = 'card-detail';
|
||||
detail.innerHTML = `${tableData.value[i].department}<br/>${tableData.value[i].identity}`;
|
||||
if(isShowAvatar.value) detail.style.display = 'none'
|
||||
element.appendChild(detail);
|
||||
|
||||
const avatar = document.createElement('img');
|
||||
avatar.className = 'card-avatar';
|
||||
avatar.src = tableData.value[i].avatar;
|
||||
avatar.alt = 'avatar';
|
||||
avatar.style.width = '140px';
|
||||
avatar.style.height = '140px';
|
||||
if(!isShowAvatar.value) avatar.style.display = 'none'
|
||||
element.appendChild(avatar);
|
||||
const detail = document.createElement('div')
|
||||
detail.className = 'card-detail'
|
||||
detail.innerHTML = `${tableData.value[i].department}<br/>${tableData.value[i].identity}`
|
||||
element.appendChild(detail)
|
||||
|
||||
element = useElementStyle(element, tableData.value[i], i, patternList.value, patternColor.value, cardColor.value, cardSize.value, textSize.value)
|
||||
const object = new CSS3DObject(element);
|
||||
object.position.x = Math.random() * 4000 - 2000;
|
||||
object.position.y = Math.random() * 4000 - 2000;
|
||||
object.position.z = Math.random() * 4000 - 2000;
|
||||
scene.value.add(object);
|
||||
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);
|
||||
objects.value.push(object)
|
||||
}
|
||||
|
||||
createTableVertices();
|
||||
createSphereVertices();
|
||||
createHelixVertices();
|
||||
createTableVertices()
|
||||
createSphereVertices()
|
||||
createHelixVertices()
|
||||
|
||||
function createTableVertices() {
|
||||
const tableLen = tableData.value.length;
|
||||
const tableLen = tableData.value.length
|
||||
|
||||
for (let i = 0; i < tableLen; i++) {
|
||||
const object = new 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;
|
||||
object.position.z = 0;
|
||||
object.position.x = tableData.value[i].x * (cardSize.value.width + 40) - rowCount.value * 90
|
||||
object.position.y = -tableData.value[i].y * (cardSize.value.height + 20) + 1000
|
||||
object.position.z = 0
|
||||
|
||||
targets.table.push(object);
|
||||
targets.table.push(object)
|
||||
}
|
||||
}
|
||||
|
||||
function createSphereVertices() {
|
||||
let i = 0;
|
||||
const objLength = objects.value.length;
|
||||
const vector = new Vector3();
|
||||
let i = 0
|
||||
const objLength = objects.value.length
|
||||
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 Object3D();
|
||||
const phi = Math.acos(-1 + (2 * i) / objLength)
|
||||
const theta = Math.sqrt(objLength * Math.PI) * phi
|
||||
const object = new Object3D()
|
||||
|
||||
object.position.x = 800 * Math.cos(theta) * Math.sin(phi);
|
||||
object.position.y = 800 * Math.sin(theta) * Math.sin(phi);
|
||||
object.position.z = -800 * Math.cos(phi);
|
||||
object.position.x = 800 * Math.cos(theta) * Math.sin(phi)
|
||||
object.position.y = 800 * Math.sin(theta) * Math.sin(phi)
|
||||
object.position.z = -800 * Math.cos(phi)
|
||||
|
||||
// rotation object
|
||||
|
||||
vector.copy(object.position).multiplyScalar(2);
|
||||
object.lookAt(vector);
|
||||
targets.sphere.push(object);
|
||||
vector.copy(object.position).multiplyScalar(2)
|
||||
object.lookAt(vector)
|
||||
targets.sphere.push(object)
|
||||
}
|
||||
}
|
||||
function createHelixVertices() {
|
||||
let i = 0;
|
||||
const vector = new Vector3();
|
||||
const objLength = objects.value.length;
|
||||
let i = 0
|
||||
const vector = new Vector3()
|
||||
const objLength = objects.value.length
|
||||
for (; i < objLength; ++i) {
|
||||
let phi = i * 0.213 + Math.PI;
|
||||
const phi = i * 0.213 + Math.PI
|
||||
|
||||
const object = new Object3D();
|
||||
const object = new Object3D()
|
||||
|
||||
object.position.x = 800 * Math.sin(phi);
|
||||
object.position.y = -(i * 8) + 450;
|
||||
object.position.z = 800 * Math.cos(phi + Math.PI);
|
||||
object.position.x = 800 * Math.sin(phi)
|
||||
object.position.y = -(i * 8) + 450
|
||||
object.position.z = 800 * Math.cos(phi + Math.PI)
|
||||
|
||||
object.scale.set(1.1, 1.1, 1.1);
|
||||
object.scale.set(1.1, 1.1, 1.1)
|
||||
|
||||
vector.x = object.position.x * 2;
|
||||
vector.y = object.position.y;
|
||||
vector.z = object.position.z * 2;
|
||||
vector.x = object.position.x * 2
|
||||
vector.y = object.position.y
|
||||
vector.z = object.position.z * 2
|
||||
|
||||
object.lookAt(vector);
|
||||
object.lookAt(vector)
|
||||
|
||||
targets.helix.push(object);
|
||||
targets.helix.push(object)
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', onWindowResize, false);
|
||||
window.addEventListener('resize', onWindowResize, false)
|
||||
transform(targets.table, 1000)
|
||||
render();
|
||||
render()
|
||||
}
|
||||
|
||||
const transform = (targets: any[], duration: number) => {
|
||||
TWEEN.removeAll();
|
||||
function transform(targets: any[], duration: number) {
|
||||
TWEEN.removeAll()
|
||||
if (intervalTimer.value) {
|
||||
clearInterval(intervalTimer.value);
|
||||
clearInterval(intervalTimer.value)
|
||||
intervalTimer.value = null
|
||||
randomBallData('sphere')
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const objLength = objects.value.length;
|
||||
const objLength = objects.value.length
|
||||
for (let i = 0; i < objLength; ++i) {
|
||||
let object = objects.value[i];
|
||||
let target = targets[i];
|
||||
const object = objects.value[i]
|
||||
const 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)
|
||||
.to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration)
|
||||
.easing(TWEEN.Easing.Exponential.InOut)
|
||||
.start();
|
||||
|
||||
.start()
|
||||
|
||||
new TWEEN.Tween(object.rotation)
|
||||
.to({ x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration)
|
||||
@@ -252,11 +238,11 @@ const transform = (targets: any[], duration: number) => {
|
||||
useElementStyle(item.element, {} as any, i, patternList.value, patternColor.value, cardColor.value, cardSize.value, textSize.value, 'sphere')
|
||||
})
|
||||
}
|
||||
luckyTargets.value = [];
|
||||
luckyCardList.value = [];
|
||||
luckyTargets.value = []
|
||||
luckyCardList.value = []
|
||||
|
||||
canOperate.value = true
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 这个补间用来在位置与旋转补间同步执行,通过onUpdate在每次更新数据后渲染scene和camera
|
||||
@@ -267,36 +253,38 @@ const transform = (targets: any[], duration: number) => {
|
||||
.onComplete(() => {
|
||||
canOperate.value = true
|
||||
resolve('')
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
function onWindowResize() {
|
||||
camera.value.aspect = window.innerWidth / window.innerHeight
|
||||
camera.value.updateProjectionMatrix();
|
||||
camera.value.updateProjectionMatrix()
|
||||
|
||||
renderer.value.setSize(window.innerWidth, window.innerHeight);
|
||||
render();
|
||||
renderer.value.setSize(window.innerWidth, window.innerHeight)
|
||||
render()
|
||||
}
|
||||
|
||||
/**
|
||||
* [animation update all tween && controls]
|
||||
*/
|
||||
* [animation update all tween && controls]
|
||||
*/
|
||||
function animation() {
|
||||
TWEEN.update();
|
||||
controls.value.update();
|
||||
TWEEN.update()
|
||||
if (controls.value) {
|
||||
controls.value.update()
|
||||
}
|
||||
// 设置自动旋转
|
||||
// 设置相机位置
|
||||
requestAnimationFrame(animation);
|
||||
requestAnimationFrame(animation)
|
||||
}
|
||||
|
||||
// // 旋转的动画
|
||||
function rollBall(rotateY: number, duration: number) {
|
||||
TWEEN.removeAll();
|
||||
TWEEN.removeAll()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
scene.value.rotation.y = 0;
|
||||
scene.value.rotation.y = 0
|
||||
ballRotationY.value = Math.PI * rotateY * 1000
|
||||
const rotateObj = new TWEEN.Tween(scene.value.rotation);
|
||||
const rotateObj = new TWEEN.Tween(scene.value.rotation)
|
||||
rotateObj
|
||||
.to(
|
||||
{
|
||||
@@ -304,9 +292,9 @@ function rollBall(rotateY: number, duration: number) {
|
||||
x: 0,
|
||||
y: ballRotationY.value,
|
||||
// z: Math.PI * rotateZ * 1000
|
||||
z: 0
|
||||
z: 0,
|
||||
},
|
||||
duration * 1000
|
||||
duration * 1000,
|
||||
)
|
||||
.onUpdate(render)
|
||||
.start()
|
||||
@@ -325,9 +313,9 @@ function resetCamera() {
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 3000
|
||||
z: 3000,
|
||||
},
|
||||
1000
|
||||
1000,
|
||||
)
|
||||
.onUpdate(render)
|
||||
.start()
|
||||
@@ -337,9 +325,9 @@ function resetCamera() {
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
z: 0,
|
||||
},
|
||||
1000
|
||||
1000,
|
||||
)
|
||||
.onUpdate(render)
|
||||
.start()
|
||||
@@ -358,9 +346,11 @@ function resetCamera() {
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderer.value.render(scene.value, camera.value);
|
||||
if (renderer.value) {
|
||||
renderer.value.render(scene.value, camera.value)
|
||||
}
|
||||
}
|
||||
const enterLottery = async () => {
|
||||
async function enterLottery() {
|
||||
if (!canOperate.value) {
|
||||
return
|
||||
}
|
||||
@@ -368,9 +358,9 @@ const enterLottery = async () => {
|
||||
randomBallData()
|
||||
}
|
||||
if (patternList.value.length) {
|
||||
for(let i=0;i<patternList.value.length;i++){
|
||||
if(i<rowCount.value*7){
|
||||
objects.value[patternList.value[i]-1].element.style.backgroundColor = rgba(cardColor.value, Math.random() * 0.5 + 0.25)
|
||||
for (let i = 0; i < patternList.value.length; i++) {
|
||||
if (i < rowCount.value * 7) {
|
||||
objects.value[patternList.value[i] - 1].element.style.backgroundColor = rgba(cardColor.value, Math.random() * 0.5 + 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,17 +370,17 @@ const enterLottery = async () => {
|
||||
rollBall(0.1, 2000)
|
||||
}
|
||||
// 开始抽奖
|
||||
const startLottery = () => {
|
||||
function startLottery() {
|
||||
if (!canOperate.value) {
|
||||
return
|
||||
}
|
||||
// 验证是否已抽完全部奖项
|
||||
if (currentPrize.value.isUsed || !currentPrize.value) {
|
||||
toast.open({
|
||||
message: '抽奖抽完了',
|
||||
message: i18n.global.t('error.personIsAllDone'),
|
||||
type: 'warning',
|
||||
position: 'top-right',
|
||||
duration: 10000
|
||||
duration: 10000,
|
||||
})
|
||||
|
||||
return
|
||||
@@ -399,13 +389,13 @@ const startLottery = () => {
|
||||
// 验证抽奖人数是否还够
|
||||
if (personPool.value.length < currentPrize.value.count - currentPrize.value.isUsedCount) {
|
||||
toast.open({
|
||||
message: '抽奖人数不够',
|
||||
message: i18n.global.t('error.personNotEnough'),
|
||||
type: 'warning',
|
||||
position: 'top-right',
|
||||
duration: 10000
|
||||
duration: 10000,
|
||||
})
|
||||
|
||||
return;
|
||||
return
|
||||
}
|
||||
luckyCount.value = 10
|
||||
// 自定义抽奖个数
|
||||
@@ -416,11 +406,11 @@ const startLottery = () => {
|
||||
for (let i = 0; i < customCount.countList.length; i++) {
|
||||
if (customCount.countList[i].isUsedCount < customCount.countList[i].count) {
|
||||
leftover = customCount.countList[i].count - customCount.countList[i].isUsedCount
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
leftover < luckyCount.value ? luckyCount.value = leftover : luckyCount
|
||||
luckyCount.value = leftover < luckyCount.value ? leftover : luckyCount.value
|
||||
for (let i = 0; i < luckyCount.value; i++) {
|
||||
if (personPool.value.length > 0) {
|
||||
const randomIndex = Math.round(Math.random() * (personPool.value.length - 1))
|
||||
@@ -428,36 +418,39 @@ const startLottery = () => {
|
||||
personPool.value.splice(randomIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
toast.open({
|
||||
message: `现在抽取${currentPrize.value.name} ${leftover}人`,
|
||||
type:'default',
|
||||
// message: `现在抽取${currentPrize.value.name} ${leftover}人`,
|
||||
message: i18n.global.t('error.startDraw', { count: currentPrize.value.name, leftover }),
|
||||
type: 'default',
|
||||
position: 'top-right',
|
||||
duration: 8000
|
||||
duration: 8000,
|
||||
})
|
||||
currentStatus.value = 2
|
||||
rollBall(10, 3000)
|
||||
}
|
||||
|
||||
const stopLottery = async () => {
|
||||
async function stopLottery() {
|
||||
if (!canOperate.value) {
|
||||
return
|
||||
}
|
||||
clearInterval(intervalTimer.value)
|
||||
intervalTimer.value = null
|
||||
// clearInterval(intervalTimer.value)
|
||||
// intervalTimer.value = null
|
||||
canOperate.value = false
|
||||
rollBall(0, 1)
|
||||
|
||||
const windowSize = { width: window.innerWidth, height: window.innerHeight }
|
||||
luckyTargets.value.forEach((person: IPersonConfig, index: number) => {
|
||||
let cardIndex = selectCard(luckyCardList.value, tableData.value.length, person.id)
|
||||
const cardIndex = selectCard(luckyCardList.value, tableData.value.length, person.id)
|
||||
luckyCardList.value.push(cardIndex)
|
||||
let item = objects.value[cardIndex]
|
||||
const { xTable, yTable } = useElementPosition(item, rowCount.value, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, windowSize, index)
|
||||
const totalLuckyCount = luckyTargets.value.length
|
||||
const item = objects.value[cardIndex]
|
||||
const { xTable, yTable } = useElementPosition(item, rowCount.value, totalLuckyCount, { width: cardSize.value.width * 2, height: cardSize.value.height * 2 }, windowSize, index)
|
||||
new TWEEN.Tween(item.position)
|
||||
.to({
|
||||
x: xTable,
|
||||
y: yTable,
|
||||
z: 1000
|
||||
z: 1000,
|
||||
}, 1200)
|
||||
.easing(TWEEN.Easing.Exponential.InOut)
|
||||
.onStart(() => {
|
||||
@@ -472,7 +465,7 @@ const stopLottery = async () => {
|
||||
.to({
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
z: 0,
|
||||
}, 900)
|
||||
.easing(TWEEN.Easing.Exponential.InOut)
|
||||
.start()
|
||||
@@ -483,7 +476,7 @@ const stopLottery = async () => {
|
||||
})
|
||||
}
|
||||
// 继续
|
||||
const continueLottery = async () => {
|
||||
async function continueLottery() {
|
||||
if (!canOperate.value) {
|
||||
return
|
||||
}
|
||||
@@ -493,7 +486,7 @@ const continueLottery = async () => {
|
||||
for (let i = 0; i < customCount.countList.length; i++) {
|
||||
if (customCount.countList[i].isUsedCount < customCount.countList[i].count) {
|
||||
customCount.countList[i].isUsedCount += luckyCount.value
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,13 +500,13 @@ const continueLottery = async () => {
|
||||
prizeConfig.updatePrizeConfig(currentPrize.value)
|
||||
await enterLottery()
|
||||
}
|
||||
const quitLottery = () => {
|
||||
function quitLottery() {
|
||||
enterLottery()
|
||||
currentStatus.value = 0
|
||||
}
|
||||
// 庆祝动画
|
||||
const confettiFire = () => {
|
||||
const duration = 3 * 1000;
|
||||
function confettiFire() {
|
||||
const duration = 3 * 1000
|
||||
const end = Date.now() + duration;
|
||||
(function frame() {
|
||||
// launch a few confetti from the left edge
|
||||
@@ -521,60 +514,60 @@ const confettiFire = () => {
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
origin: { x: 0 }
|
||||
});
|
||||
origin: { x: 0 },
|
||||
})
|
||||
// and launch a few from the right edge
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
origin: { x: 1 }
|
||||
});
|
||||
origin: { x: 1 },
|
||||
})
|
||||
|
||||
// keep going until we are out of time
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame);
|
||||
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
|
||||
});
|
||||
scalar: 0.8,
|
||||
})
|
||||
centerFire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 25,
|
||||
decay: 0.92,
|
||||
scalar: 1.2
|
||||
});
|
||||
scalar: 1.2,
|
||||
})
|
||||
centerFire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 45,
|
||||
});
|
||||
})
|
||||
}
|
||||
const centerFire = (particleRatio: number, opts: any) => {
|
||||
function centerFire(particleRatio: number, opts: any) {
|
||||
const count = 200
|
||||
confetti({
|
||||
origin: { y: 0.7 },
|
||||
...opts,
|
||||
particleCount: Math.floor(count * particleRatio)
|
||||
});
|
||||
particleCount: Math.floor(count * particleRatio),
|
||||
})
|
||||
}
|
||||
|
||||
const setDefaultPersonList = () => {
|
||||
function setDefaultPersonList() {
|
||||
personConfig.setDefaultPersonList()
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
}
|
||||
// 随机替换数据
|
||||
const randomBallData = (mod: 'default' | 'lucky' | 'sphere' = 'default') => {
|
||||
function randomBallData(mod: 'default' | 'lucky' | 'sphere' = 'default') {
|
||||
// 两秒执行一次
|
||||
intervalTimer.value = setInterval(() => {
|
||||
// 产生随机数数组
|
||||
@@ -582,20 +575,24 @@ const randomBallData = (mod: 'default' | 'lucky' | 'sphere' = 'default') => {
|
||||
const cardRandomIndexArr: number[] = []
|
||||
const personRandomIndexArr: number[] = []
|
||||
for (let i = 0; i < indexLength; i++) {
|
||||
cardRandomIndexArr.push(Math.round(Math.random() * (tableData.value.length - 1)))
|
||||
personRandomIndexArr.push(Math.round(Math.random() * (allPersonList.value.length - 1)))
|
||||
const randomCardIndex = Math.round(Math.random() * (tableData.value.length - 1))
|
||||
const randomPersonIndex = Math.round(Math.random() * (allPersonList.value.length - 1))
|
||||
if (luckyCardList.value.includes(randomCardIndex)) {
|
||||
continue
|
||||
}
|
||||
cardRandomIndexArr.push(randomCardIndex)
|
||||
personRandomIndexArr.push(randomPersonIndex)
|
||||
}
|
||||
for (let i = 0; i < cardRandomIndexArr.length; i++) {
|
||||
if (!objects.value[cardRandomIndexArr[i]]) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
objects.value[cardRandomIndexArr[i]].element = useElementStyle(objects.value[cardRandomIndexArr[i]].element, allPersonList.value[personRandomIndexArr[i]], cardRandomIndexArr[i], patternList.value, patternColor.value, cardColor.value, { width: cardSize.value.width, height: cardSize.value.height }, textSize.value, mod)
|
||||
objects.value[cardRandomIndexArr[i]].element = useElementStyle(objects.value[cardRandomIndexArr[i]].element, allPersonList.value[personRandomIndexArr[i]], cardRandomIndexArr[i], patternList.value, patternColor.value, cardColor.value, { width: cardSize.value.width, height: cardSize.value.height }, textSize.value, mod, 'change')
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
// 监听键盘
|
||||
const listenKeyboard = () => {
|
||||
window.addEventListener('keydown', (e: any) => {
|
||||
function listenKeyboard(e: any) {
|
||||
if ((e.keyCode !== 32 || e.keyCode !== 27) && !canOperate.value) {
|
||||
return
|
||||
}
|
||||
@@ -608,111 +605,167 @@ const listenKeyboard = () => {
|
||||
switch (currentStatus.value) {
|
||||
case 0:
|
||||
enterLottery()
|
||||
break;
|
||||
break
|
||||
case 1:
|
||||
startLottery()
|
||||
break;
|
||||
break
|
||||
case 2:
|
||||
stopLottery()
|
||||
break;
|
||||
break
|
||||
case 3:
|
||||
continueLottery()
|
||||
break;
|
||||
break
|
||||
default:
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
// animationRunning.value = false
|
||||
clearInterval(intervalTimer.value)
|
||||
intervalTimer.value = null
|
||||
if (scene.value) {
|
||||
scene.value.traverse((object: Object3D) => {
|
||||
if ((object as any).material) {
|
||||
if (Array.isArray((object as any).material)) {
|
||||
(object as any).material.forEach((material: Material) => {
|
||||
material.dispose()
|
||||
})
|
||||
}
|
||||
else {
|
||||
(object as any).material.dispose()
|
||||
}
|
||||
}
|
||||
if ((object as any).geometry) {
|
||||
(object as any).geometry.dispose()
|
||||
}
|
||||
if ((object as any).texture) {
|
||||
(object as any).texture.dispose()
|
||||
}
|
||||
})
|
||||
scene.value.clear()
|
||||
}
|
||||
|
||||
if (objects.value) {
|
||||
objects.value.forEach((object) => {
|
||||
if (object.element) {
|
||||
object.element.remove()
|
||||
}
|
||||
})
|
||||
objects.value = []
|
||||
}
|
||||
|
||||
if (controls.value) {
|
||||
controls.value.removeEventListener('change')
|
||||
controls.value.dispose()
|
||||
}
|
||||
// 移除所有事件监听
|
||||
window.removeEventListener('resize', onWindowResize)
|
||||
scene.value = null
|
||||
camera.value = null
|
||||
renderer.value = null
|
||||
controls.value = null
|
||||
}
|
||||
onMounted(() => {
|
||||
initTableData();
|
||||
init();
|
||||
animation();
|
||||
initTableData()
|
||||
init()
|
||||
animation()
|
||||
containerRef.value!.style.color = `${textColor}`
|
||||
randomBallData()
|
||||
listenKeyboard()
|
||||
});
|
||||
window.addEventListener('keydown', listenKeyboard)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
nextTick(() => {
|
||||
cleanup()
|
||||
})
|
||||
clearInterval(intervalTimer.value)
|
||||
intervalTimer.value = null
|
||||
window.removeEventListener('keydown', listenKeyboard)
|
||||
})
|
||||
// watch(() => currentPrize.value.isUsed, (val) => {
|
||||
// if (val) {
|
||||
// currentPrize.value = JSON.parse(JSON.stringify(currentPrize.value))
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute z-10 flex flex-col items-center justify-center -translate-x-1/2 left-1/2">
|
||||
<h2 class="pt-12 m-0 mb-12 font-mono tracking-wide text-center leading-12 header-title"
|
||||
:style="{ fontSize: textSize * 1.5 + 'px', color: textColor }">{{ topTitle }}</h2>
|
||||
<h2
|
||||
class="pt-12 m-0 mb-12 font-mono tracking-wide text-center leading-12 header-title"
|
||||
:style="{ fontSize: `${textSize * 1.5}px`, color: textColor }"
|
||||
>
|
||||
{{ topTitle }}
|
||||
</h2>
|
||||
<div class="flex gap-3">
|
||||
<button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
||||
@click="router.push('config')">暂无人员信息,前往导入</button>
|
||||
<button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
||||
@click="setDefaultPersonList">使用默认数据</button>
|
||||
<button
|
||||
v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
||||
@click="router.push('config')"
|
||||
>
|
||||
{{ t('button.noInfoAndImport') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
||||
@click="setDefaultPersonList"
|
||||
>
|
||||
{{ t('button.useDefault') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="container" ref="containerRef" class="3dContainer">
|
||||
|
||||
<!-- 选中菜单结构 start-->
|
||||
<!-- 选中菜单结构 start -->
|
||||
<div id="menu">
|
||||
<button class="btn-end " @click="enterLottery" v-if="currentStatus == 0 && tableData.length > 0">进入抽奖</button>
|
||||
<button v-if="currentStatus === 0 && tableData.length > 0" class="btn-end " @click="enterLottery">
|
||||
{{ t('button.enterLottery') }}
|
||||
</button>
|
||||
|
||||
<div class="start" v-if="currentStatus == 1">
|
||||
<button class="btn-start" @click="startLottery"><strong>开始</strong>
|
||||
<div v-if="currentStatus === 1" class="start">
|
||||
<button class="btn-start" @click="startLottery">
|
||||
<strong>{{ t('button.start') }}</strong>
|
||||
<div id="container-stars">
|
||||
<div id="stars"></div>
|
||||
<div id="stars" />
|
||||
</div>
|
||||
|
||||
<div id="glow">
|
||||
<div class="circle"></div>
|
||||
<div class="circle"></div>
|
||||
<div class="circle" />
|
||||
<div class="circle" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="btn-end btn glass btn-lg" @click="stopLottery" v-if="currentStatus == 2">抽取幸运儿</button>
|
||||
<button v-if="currentStatus === 2" class="btn-end btn glass btn-lg" @click="stopLottery">
|
||||
{{ t('button.selectLucky') }}
|
||||
</button>
|
||||
|
||||
<div v-if="currentStatus == 3" class="flex justify-center gap-6 enStop">
|
||||
<div v-if="currentStatus === 3" class="flex justify-center gap-6 enStop">
|
||||
<div class="start">
|
||||
<button class="btn-start" @click="continueLottery"><strong>继续!</strong>
|
||||
<button class="btn-start" @click="continueLottery">
|
||||
<strong>{{ t('button.continue') }}</strong>
|
||||
<div id="container-stars">
|
||||
<div id="stars"></div>
|
||||
<div id="stars" />
|
||||
</div>
|
||||
|
||||
<div id="glow">
|
||||
<div class="circle"></div>
|
||||
<div class="circle"></div>
|
||||
<div class="circle" />
|
||||
<div class="circle" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="start">
|
||||
<button class="btn-cancel" @click="quitLottery"><strong>取消</strong>
|
||||
<button class="btn-cancel" @click="quitLottery">
|
||||
<strong>{{ t('button.cancel') }}</strong>
|
||||
<div id="container-stars">
|
||||
<div id="stars"></div>
|
||||
<div id="stars" />
|
||||
</div>
|
||||
|
||||
<div id="glow">
|
||||
<div class="circle"></div>
|
||||
<div class="circle"></div>
|
||||
<div class="circle" />
|
||||
<div class="circle" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <button id="table" @click="transform(targets.table, 2000)">TABLE</button> -->
|
||||
<!-- <button id="helix" @click="transform(targets.helix, 2000)">HELIX</button> -->
|
||||
|
||||
</div>
|
||||
<!-- end -->
|
||||
</div>
|
||||
<StarsBackground></StarsBackground>
|
||||
|
||||
<!-- <LuckyView :luckyPersonList="luckyTargets" ref="LuckyViewRef"></LuckyView> -->
|
||||
<!-- <PlayMusic class="absolute right-0 bottom-1/2"></PlayMusic> -->
|
||||
<PrizeList class="absolute left-0 top-32"></PrizeList>
|
||||
<StarsBackground :home-background="homeBackground" />
|
||||
<PrizeList class="absolute left-0 top-32" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
7
src/vite-env.d.ts
vendored
7
src/vite-env.d.ts
vendored
@@ -1,9 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
import type { DefineComponent } from 'vue'
|
||||
|
||||
const component: DefineComponent<object, object, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare module 'sparticles'
|
||||
|
||||
BIN
static/images/lottery.png
Normal file
BIN
static/images/lottery.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
@@ -19,6 +19,6 @@
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"include": ["src/**/*.ts","src/**/*.d.ts","src/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
/// <reference types="vitest" />
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import path from 'path';
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import IconsResolver from 'unplugin-icons/resolver';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
// import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
// https://vitejs.dev/config/
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const process = require('node:process')
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, __dirname);
|
||||
const chunkName = mode == 'prebuild' ? '[name]' : 'chunk';
|
||||
const env = loadEnv(mode, __dirname)
|
||||
const chunkName = mode === 'prebuild' ? '[name]' : 'chunk'
|
||||
|
||||
return {
|
||||
base:'/log-lottery/',
|
||||
base: mode === 'file' ? './' : '/log-lottery/',
|
||||
plugins: [
|
||||
vue(),
|
||||
mode === 'file'
|
||||
? legacy({
|
||||
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
|
||||
})
|
||||
: null,
|
||||
// vueDevTools(),
|
||||
viteCompression({
|
||||
verbose: true,
|
||||
@@ -29,11 +40,11 @@ export default defineConfig(({ mode }) => {
|
||||
ext: '.gz',
|
||||
}),
|
||||
visualizer({
|
||||
emitFile: true, //是否被触摸
|
||||
filename: 'test.html', //生成分析网页文件名
|
||||
open: true, //在默认用户代理中打开生成的文件
|
||||
gzipSize: true, //从源代码中收集 gzip 大小并将其显示在图表中
|
||||
brotliSize: true, //从源代码中收集 brotli 大小并将其显示在图表中
|
||||
emitFile: true, // 是否被触摸
|
||||
filename: 'test.html', // 生成分析网页文件名
|
||||
open: true, // 在默认用户代理中打开生成的文件
|
||||
gzipSize: true, // 从源代码中收集 gzip 大小并将其显示在图表中
|
||||
brotliSize: true, // 从源代码中收集 brotli 大小并将其显示在图表中
|
||||
}),
|
||||
|
||||
createSvgIconsPlugin({
|
||||
@@ -86,7 +97,7 @@ export default defineConfig(({ mode }) => {
|
||||
// 是否跨域
|
||||
changeOrigin: true,
|
||||
// 路径重写
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
rewrite: path => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -99,7 +110,7 @@ export default defineConfig(({ mode }) => {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
//生产环境时移除console
|
||||
// 生产环境时移除console
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
},
|
||||
@@ -119,7 +130,7 @@ export default defineConfig(({ mode }) => {
|
||||
.toString()
|
||||
.split('node_modules/')[1]
|
||||
.split('/')[0]
|
||||
.toString();
|
||||
.toString()
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -135,5 +146,5 @@ export default defineConfig(({ mode }) => {
|
||||
web: [/\.[jt]sx$/],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user