feat: 国际化

This commit is contained in:
LOG1997
2025-01-08 00:19:29 +08:00
64 changed files with 5792 additions and 3520 deletions

View File

@@ -1,12 +0,0 @@
.md
node_modules
public
package.json
*.yaml
.gitignore
.eslintrc*
.babelrc
.eslintignore
.commitlintrc*
.env*
tsconfig*

View File

@@ -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"],
},
};

View File

@@ -4,8 +4,6 @@
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

3
.gitignore vendored
View File

@@ -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

View File

@@ -46,6 +46,7 @@ or
- [x] 🧨 国际化多语言
- [x] 🍃 更换背景图片
- [x] 🚅 添加docker构建
- [x] 😘 弹幕(开发中)
- [ ] 🧵 卡片组成多种形状
...

7
eslint.config.js Normal file
View 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*'],
},
)

View File

@@ -12,7 +12,8 @@
"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",
@@ -31,6 +32,7 @@
"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",
@@ -38,6 +40,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",
@@ -56,6 +61,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",

1707
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -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

Binary file not shown.

31
public/readme-en.md Normal file
View 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 |

View File

@@ -1,84 +1,90 @@
<script setup lang="ts">
import { onMounted,ref } from 'vue'
import PlayMusic from '@/components/PlayMusic/index.vue'
import useStore from '@/store'
import { storeToRefs } from 'pinia'
import PlayMusic from '@/components/PlayMusic/index.vue'
import { themeChange } from 'theme-change'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
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 tipDialog = ref()
// const isMobileValue = ref(structuredClone(isMobile.value))
const setLocalTheme = (theme: any) => {
themeChange(theme.name)
function setLocalTheme(theme: any) {
themeChange(theme.name)
}
// 设置当前奖列表
const setCurrentPrize = () => {
if (prizeList.value.length <= 0) {
return
}
for (let i = 0; i < prizeList.value.length; i++) {
if (!prizeList.value[i].isUsed) {
prizeConfig.setCurrentPrize(prizeList.value[i])
break
}
}
function setCurrentPrize() {
if (prizeList.value.length <= 0) {
return
}
for (let i = 0; i < prizeList.value.length; i++) {
if (!prizeList.value[i].isUsed) {
prizeConfig.setCurrentPrize(prizeList.value[i])
break
}
}
}
// 判断是否手机端访问
const 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/)
function judgeMobile() {
const ua = navigator.userAgent
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=()=>{
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome') > -1
const isEdge = ua.indexOf('Edg') > -1
function judgeChromeOrEdge() {
const ua = navigator.userAgent
const isChrome = ua.includes('Chrome')
const isEdge = ua.includes('Edg')
system.setIsChrome(isChrome)
system.setIsChrome(isChrome)
return isChrome||isEdge
return isChrome || isEdge
}
onMounted(() => {
setLocalTheme(localTheme.value)
setCurrentPrize()
if(judgeMobile()||!judgeChromeOrEdge()){
tipDialog.value.showModal()
}
setLocalTheme(localTheme.value)
setCurrentPrize()
if (judgeMobile() || !judgeChromeOrEdge()) {
tipDialog.value.showModal()
}
})
</script>
<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>
<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>
</form>
</div>
</div>
</dialog>
<router-view></router-view>
<PlayMusic class="absolute right-0 bottom-1/2"></PlayMusic>
<dialog id="my_modal_1" ref="tipDialog" class="border-none modal">
<div class="modal-box">
<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">
{{ t('button.confirm') }}
</button>
</form>
</div>
</div>
</dialog>
<router-view />
<PlayMusic class="absolute right-0 bottom-1/2" />
</template>
<style scoped lang="scss"></style>

View File

@@ -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

View File

@@ -1,69 +1,78 @@
<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[]
},
tableColumns: {
type: Array,
default: [] as any[]
},
data: {
type: Array as any,
default: [] as any[],
},
tableColumns: {
type: Array,
default: [] as any[],
},
})
const { t } = useI18n()
const dataColumns = computed<any[]>(() => {
// 不带有actions的列
const columns = props.tableColumns.filter((item: any) => !item.actions)
// 不带有actions的列
const columns = props.tableColumns.filter((item: any) => !item.actions)
return columns
return columns
})
const actionsColumns = computed<any[]>(() => {
// 带有actions的列
const columns = props.tableColumns.filter((item: any) => item.actions)
// 带有actions的列
const columns = props.tableColumns.filter((item: any) => item.actions)
return columns
return columns
})
</script>
<template>
<div class="overflow-x-auto">
<table class="table min-w-[600px]">
<!-- 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>
</tr>
</thead>
<tbody v-if="data.length > 0">
<!-- row -->
<tr class="hover" v-for="item in data" :key="item.id">
<th>{{ item.id }}</th>
<td v-for="(column, index) in dataColumns" :key="index">
<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>
</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5" class="text-center">暂无数据</td>
</tr>
</tbody>
<!-- foot -->
</table>
</div>
<div class="overflow-x-auto">
<table class="table min-w-[600px]">
<!-- head -->
<thead>
<tr>
<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 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">{{ 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
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">
{{ t('table.noneData') }}
</td>
</tr>
</tbody>
<!-- foot -->
</table>
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -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>

View File

@@ -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('')
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
}
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
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>

View File

@@ -1,99 +1,111 @@
<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
},
separatedNumber: {
type: Array<Separate>,
default: []
}
totalNumber: {
type: Number,
default: 0,
},
separatedNumber: {
type: Array<Separate>,
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) {
return
}
if (scaleList.value.includes(item)) {
const index = scaleList.value.indexOf(item)
scaleList.value.splice(index, 1)
separatedNumber.value.splice(index, 1)
}
else {
scaleList.value.push(item)
scaleList.value.sort((a, b) => a - b)
}
function editScale(item: number) {
if (item === totalNumber.value) {
return
}
if (scaleList.value.includes(item)) {
const index = scaleList.value.indexOf(item)
scaleList.value.splice(index, 1)
separatedNumber.value.splice(index, 1)
}
else {
scaleList.value.push(item)
scaleList.value.sort((a, b) => a - b)
}
}
const clearData = () => {
emits('submitData', separatedNumber.value)
separatedNumberRef.value.close()
function clearData() {
emits('submitData', separatedNumber.value)
separatedNumberRef.value.close()
}
watch(scaleList, (val: number[]) => {
separatedNumber.value.length=0
for (let i = 1; i < scaleList.value.length; i++) {
separatedNumber.value[i - 1] = {
id: i.toString(),
count: val[i] - val[i - 1],
isUsedCount: 0,
}
separatedNumber.value.length = 0
for (let i = 1; i < scaleList.value.length; i++) {
separatedNumber.value[i - 1] = {
id: i.toString(),
count: val[i] - val[i - 1],
isUsedCount: 0,
}
}
}, { deep: true })
watch(totalNumber, (val) => {
if (val <= 0) {
return
}
separatedNumberRef.value.showModal()
// scaleList.value = [0, val]
scaleList.value = new Array(separatedNumber.value.length + 1).fill(totalNumber.value)
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){
scaleList.value.unshift(0)
}
if (val <= 0) {
return
}
separatedNumberRef.value.showModal()
// scaleList.value = [0, val]
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) {
scaleList.value.unshift(0)
}
})
onMounted(() => {
// 阻止esc事件
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault()
}
})
// 阻止esc事件
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault()
}
})
})
</script>
<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>
<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)">
<span> {{ item }}</span>
</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>
</form>
</div>
<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">
{{ 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="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>
</dialog>
</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">
{{ t('button.close') }}
</button>
</form>
</div>
</div>
</dialog>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,120 +1,127 @@
<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){
return
}
// if (!audio.value.paused && !skip) {
// audio.value.pause()
async function play(item: any) {
if (!item) {
return
}
// if (!audio.value.paused && !skip) {
// audio.value.pause()
// return
// }
let audioUrl = ''
if (!item.url) {
return
}
if (item.url == 'Storage') {
audioUrl = await audioDbStore.getItem(item.name) as string
}
else {
audioUrl = item.url
}
audio.value.pause()
audio.value.src = audioUrl
audio.value.play()
// return
// }
let audioUrl = ''
if (!item.url) {
return
}
if (item.url === 'Storage') {
audioUrl = await audioDbStore.getItem(item.name) as string
}
else {
audioUrl = item.url
}
audio.value.pause()
audio.value.src = audioUrl
audio.value.play()
}
const playMusic=(item:any,skip = false)=>{
if(!item){
return
}
if(!currentMusic.value.paused&&!skip){
globalConfig.setCurrentMusic(item,true)
function playMusic(item: any, skip = false) {
if (!item) {
return
}
if (!currentMusic.value.paused && !skip) {
globalConfig.setCurrentMusic(item, true)
return
}
globalConfig.setCurrentMusic(item,false)
return
}
globalConfig.setCurrentMusic(item, false)
}
const nextPlay = () => {
// 播放下一首
if (localMusicList.value.length >= 1) {
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)
function nextPlay() {
// 播放下一首
if (localMusicList.value.length >= 1) {
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)
}
}
// 监听播放成后开始下一首
const onPlayEnd = () => {
audio.value.addEventListener('ended', nextPlay)
function onPlayEnd() {
audio.value.addEventListener('ended', nextPlay)
}
const enterConfig = () => {
router.push('/log-lottery/config')
function enterConfig() {
router.push('/log-lottery/config')
}
const enterHome = () => {
router.push('/log-lottery')
function enterHome() {
router.push('/log-lottery')
}
onMounted(() => {
globalConfig.setCurrentMusic(localMusicList.value[0],true)
onPlayEnd()
// 不使用空格控制audio
globalConfig.setCurrentMusic(localMusicList.value[0], true)
onPlayEnd()
// 不使用空格控制audio
})
onUnmounted(() => {
audio.value.removeEventListener('ended', nextPlay)
audio.value.removeEventListener('ended', nextPlay)
})
watch(currentMusic, (val: any) => {
if(!val.paused&&audio.value){
play(val.item)
}
else{
audio.value.pause()
}
},{deep:true})
if (!val.paused && audio.value) {
play(val.item)
}
else {
audio.value.pause()
}
}, { 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>
</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>
</div>
<div class="tooltip tooltip-left" :data-tip="currentMusic.item ? currentMusic.item.name+'\n\r &nbsp; 右键下一曲' : '没有音乐可以播放'">
<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>
</div>
<!-- <div class="bg-blue-300 cursor-pointer" @click="nextPlay">下一首</div> -->
<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="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 ${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>
</template>
<style lang='scss' scoped>

View File

@@ -56,7 +56,6 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('resize', listenWindowSize)
})
</script>
<template>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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'

View File

@@ -1,5 +1,5 @@
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') {
@@ -37,19 +37,19 @@ export const useElementStyle = (element: any, person: IPersonConfig, index: numb
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.textShadow = `0 0 12px ${rgba(cardColor, 0.95)}`
if (person.name) {
element.children[1].textContent = person.name
}
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) {
element.children[2].innerHTML = `${person.department ? person.department : ''}<br/>${person.identity ? person.identity : ''}`
}
return element
return element
}
/**

View File

@@ -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

View File

@@ -7,4 +7,4 @@ export const footerList = {
icon: 'github',
},
],
};
}

View File

@@ -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>

View File

@@ -1,17 +1,17 @@
export const navList = [
{
id: 0,
name: '首页',
url: 'home',
},
{
id: 1,
name: '项目',
url: 'project',
},
{
id: 2,
name: '关于',
url: 'about',
},
{
id: 0,
name: '首页',
url: 'home',
},
{
id: 1,
name: '项目',
url: 'project',
},
{
id: 2,
name: '关于',
url: 'about',
},
]

View File

@@ -1,41 +1,52 @@
<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>
<div class="h-full header-container">
<div class="p-0 navbar bg-base-100">
<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>
</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>
</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" />
</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>
</ul>
</div>
<div class="navbar-end max-lg:w-0">
<a class="btn">Button</a>
<div class="h-full header-container">
<div class="p-0 navbar bg-base-100">
<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>
</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 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">
</a>
</div>
<div class="hidden navbar-center lg:flex">
<ul class="flex gap-10 px-1 text-lg cursor-pointer menu menu-horizontal">
<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">
<a class="btn">Button</a>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -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
View 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
View 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
View 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': '破山中贼易,破心中贼难',
},
}

View File

@@ -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')

View File

@@ -1,115 +1,117 @@
import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router';
import Layout from '@/layout/index.vue';
import Home from '@/views/Home/index.vue';
export const configRoutes={
path: '/log-lottery/config',
name: 'Config',
component: () => import('@/views/Config/index.vue'),
children: [
import Layout from '@/layout/index.vue'
import i18n from '@/locales/i18n'
import Home from '@/views/Home/index.vue'
import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
export const configRoutes = {
path: '/log-lottery/config',
name: 'Config',
component: () => import('@/views/Config/index.vue'),
children: [
{
path: '',
redirect: '/log-lottery/config/person',
},
{
path: '/log-lottery/config/person',
name: 'PersonConfig',
component: () => import('@/views/Config/Person/PersonConfig.vue'),
meta: {
title: i18n.global.t('sidebar.personConfiguration'),
icon: 'person',
},
children: [
{
path: '',
redirect: '/log-lottery/config/person',
},
{
path: '/log-lottery/config/person',
name: 'PersonConfig',
component: () => import('@/views/Config/Person/PersonConfig.vue'),
meta: {
title: '人员配置',
icon: 'person',
path: '',
redirect: '/log-lottery/config/person/all',
},
children:[
{
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/already',
name:'AlreadyPerson',
component:()=>import('@/views/Config/Person/PersonAlready.vue'),
meta:{
title:'中奖名单人员',
icon:'already'
}
},
// {
// path:'other',
// name:'OtherPersonConfig',
// component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'),
// meta:{
// title:'其他配置',
// icon:'other'
// }
// }
]
},
{
path: '/log-lottery/config/prize',
name: 'PrizeConfig',
component: () => import('@/views/Config/Prize/PrizeConfig.vue'),
meta:{
title: '奖品配置',
icon: 'prize'
}
},
{
path:'/log-lottery/config/global',
name:'GlobalConfig',
redirect: '/log-lottery/config/global/all',
meta:{
title:'全局配置',
icon:'global'
{
path: '/log-lottery/config/person/all',
name: 'AllPersonConfig',
component: () => import('@/views/Config/Person/PersonAll.vue'),
meta: {
title: i18n.global.t('sidebar.personList'),
icon: 'all',
},
},
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/image',
name:'ImageConfig',
component:()=>import('@/views/Config/Global/ImageConfig.vue'),
meta:{
title:'图片列表',
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/person/already',
name: 'AlreadyPerson',
component: () => import('@/views/Config/Person/PersonAlready.vue'),
meta: {
title: i18n.global.t('sidebar.winnerList'),
icon: 'already',
},
},
// {
// path:'other',
// name:'OtherPersonConfig',
// component:()=>import('@/views/Config/Person/OtherPersonConfig.vue'),
// meta:{
// title:'其他配置',
// icon:'other'
// }
// }
],
},
{
path: '/log-lottery/config/prize',
name: 'PrizeConfig',
component: () => import('@/views/Config/Prize/PrizeConfig.vue'),
meta: {
title: i18n.global.t('sidebar.prizeConfiguration'),
icon: 'prize',
},
{
path: '/log-lottery/config/readme',
name: 'Readme',
component: () => import('@/views/Config/Readme/index.vue'),
meta:{
title: '操作说明',
icon: 'readme'
}
},
{
path: '/log-lottery/config/global',
name: 'GlobalConfig',
redirect: '/log-lottery/config/global/all',
meta: {
title: i18n.global.t('sidebar.globalSetting'),
icon: 'global',
},
]
}
children: [
{
path: '/log-lottery/config/global/face',
name: 'FaceConfig',
component: () => import('@/views/Config/Global/FaceConfig.vue'),
meta: {
title: i18n.global.t('sidebar.viewSetting'),
icon: 'face',
},
},
{
path: '/log-lottery/config/global/image',
name: 'ImageConfig',
component: () => import('@/views/Config/Global/ImageConfig.vue'),
meta: {
title: i18n.global.t('sidebar.imagesManagement'),
icon: 'image',
},
},
{
path: '/log-lottery/config/global/music',
name: 'MusicConfig',
component: () => import('@/views/Config/Global/MusicConfig.vue'),
meta: {
title: i18n.global.t('sidebar.musicManagement'),
icon: 'music',
},
},
],
},
{
path: '/log-lottery/config/readme',
name: 'Readme',
component: () => import('@/views/Config/Readme/index.vue'),
meta: {
title: i18n.global.t('sidebar.operatingInstructions'),
icon: 'readme',
},
},
],
}
const routes = [
{
path: '/log-lottery',
@@ -122,9 +124,9 @@ 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,
],
@@ -135,6 +137,6 @@ const router = createRouter({
// 读取环境变量
history: envMode==='file'?createWebHashHistory():createWebHistory(),
routes,
});
})
export default router;
export default router

File diff suppressed because one or more lines are too long

View File

@@ -1,258 +1,270 @@
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', {
state() {
return {
globalConfig: {
rowCount: 17,
isSHowPrizeList: true,
topTitle: '大明内阁六部御前奏对',
theme: {
name: 'dracula',
detail: { primary: '#0f5fd3' },
cardColor: '#ff79c6',
cardWidth: 140,
cardHeight: 200,
textColor: '#ffffff',
luckyCardColor: '#ECB1AC',
textSize: 30,
patternColor: '#1b66c9',
patternList: defaultPatternList as number[],
background:{}, // 背景颜色或图片
},
musicList: defaultMusicList as IMusic[],
imageList: defaultImageList as IImage[],
},
currentMusic: {
item: defaultMusicList[0],
paused: true,
},
};
state() {
return {
globalConfig: {
rowCount: 17,
isSHowPrizeList: true,
topTitle: i18n.global.t('data.defaultTitle'),
language: browserLanguage,
theme: {
name: 'dracula',
detail: { primary: '#0f5fd3' },
cardColor: '#ff79c6',
cardWidth: 140,
cardHeight: 200,
textColor: '#ffffff',
luckyCardColor: '#ECB1AC',
textSize: 30,
patternColor: '#1b66c9',
patternList: defaultPatternList as number[],
background: {}, // 背景颜色或图片
},
musicList: defaultMusicList as IMusic[],
imageList: defaultImageList as IImage[],
},
currentMusic: {
item: defaultMusicList[0],
paused: true,
},
}
},
getters: {
// 获取全部配置
getGlobalConfig(state) {
return state.globalConfig
},
getters: {
// 获取全部配置
getGlobalConfig(state) {
return state.globalConfig;
},
// 获取标题
getTopTitle(state) {
return state.globalConfig.topTitle;
},
// 获取行数
getRowCount(state) {
return state.globalConfig.rowCount;
},
// 获取主题
getTheme(state) {
return state.globalConfig.theme;
},
// 获取卡片颜色
getCardColor(state) {
return state.globalConfig.theme.cardColor;
},
// 获取中奖颜色
getLuckyColor(state) {
return state.globalConfig.theme.luckyCardColor;
},
// 获取文字颜色
getTextColor(state) {
return state.globalConfig.theme.textColor;
},
// 获取卡片宽高
getCardSize(state) {
return {
width: state.globalConfig.theme.cardWidth,
height: state.globalConfig.theme.cardHeight
}
},
// 获取文字大小
getTextSize(state) {
return state.globalConfig.theme.textSize;
},
// 获取图案颜色
getPatterColor(state) {
return state.globalConfig.theme.patternColor;
},
// 获取图案列表
getPatternList(state) {
return state.globalConfig.theme.patternList;
},
// 获取音乐列表
getMusicList(state) {
return state.globalConfig.musicList;
},
// 获取当前音乐
getCurrentMusic(state) {
return state.currentMusic;
},
// 获取图片列表
getImageList(state) {
return state.globalConfig.imageList;
},
// 获取是否显示奖品列表
getIsShowPrizeList(state) {
return state.globalConfig.isSHowPrizeList;
},
// 获取背景图片设置
getBackground(state){
return state.globalConfig.theme.background
},
// 获取标题
getTopTitle(state) {
return state.globalConfig.topTitle
},
actions: {
// 设置rowCount
setRowCount(rowCount: number) {
this.globalConfig.rowCount = rowCount;
},
// 设置标题
setTopTitle(topTitle: string) {
this.globalConfig.topTitle = topTitle;
},
// 设置主题
setTheme(theme: any) {
const { name, detail } = theme;
this.globalConfig.theme.name = name;
this.globalConfig.theme.detail = detail;
},
// 设置卡片颜色
setCardColor(cardColor: string) {
this.globalConfig.theme.cardColor = cardColor;
},
// 设置中奖颜色
setLuckyCardColor(luckyCardColor: string) {
this.globalConfig.theme.luckyCardColor = luckyCardColor;
},
// 设置文字颜色
setTextColor(textColor: string) {
this.globalConfig.theme.textColor = textColor;
},
// 设置卡片宽高
setCardSize(cardSize: { width: number, height: number }) {
this.globalConfig.theme.cardWidth = cardSize.width;
this.globalConfig.theme.cardHeight = cardSize.height;
},
// 设置文字大小
setTextSize(textSize: number) {
this.globalConfig.theme.textSize = textSize;
},
// 设置图案颜色
setPatterColor(patterColor: string) {
this.globalConfig.theme.patternColor = patterColor;
},
// 设置图案列表
setPatternList(patternList: number[]) {
this.globalConfig.theme.patternList = patternList;
},
// 重置图案列表
resetPatternList() {
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;
}
}
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;
}
}
},
// 设置当前播放音乐
setCurrentMusic(musicItem: IMusic, paused: boolean = true) {
this.currentMusic = {
item: musicItem,
paused: paused,
}
},
// 重置音乐列表
resetMusicList() {
this.globalConfig.musicList = defaultMusicList as IMusic[];
},
// 清空音乐列表
clearMusicList() {
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;
}
}
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;
}
}
},
// 置图片列表
resetImageList() {
this.globalConfig.imageList = defaultImageList as IImage[];
},
// 清空图片列表
clearImageList() {
this.globalConfig.imageList = [] as IImage[]
},
// 设置是否显示奖品列表
setIsShowPrizeList(isShowPrizeList: boolean) {
this.globalConfig.isSHowPrizeList = isShowPrizeList;
},
// 设置背景图片
setBackground(background:{}){
this.globalConfig.theme.background = background
},
// 重置所有配置
reset() {
this.globalConfig = {
rowCount: 17,
isSHowPrizeList: true,
topTitle: '大明内阁六部御前奏对',
theme: {
name: 'dracula',
detail: { primary: '#0f5fd3' },
cardColor: '#ff79c6',
cardWidth: 140,
cardHeight: 200,
textColor: '#ffffff',
luckyCardColor: '#ECB1AC',
textSize: 30,
patternColor: '#1b66c9',
patternList: defaultPatternList as number[],
background:{}, // 背景图片
},
musicList: defaultMusicList as IMusic[],
imageList: defaultImageList as IImage[],
},
this.currentMusic = {
item: defaultMusicList[0],
paused: true,
}
// 获取行数
getRowCount(state) {
return state.globalConfig.rowCount
},
// 获取主题
getTheme(state) {
return state.globalConfig.theme
},
// 获取卡片颜色
getCardColor(state) {
return state.globalConfig.theme.cardColor
},
// 获取中奖颜色
getLuckyColor(state) {
return state.globalConfig.theme.luckyCardColor
},
// 获取文字颜色
getTextColor(state) {
return state.globalConfig.theme.textColor
},
// 获取卡片宽高
getCardSize(state) {
return {
width: state.globalConfig.theme.cardWidth,
height: state.globalConfig.theme.cardHeight,
}
},
// 获取文字大小
getTextSize(state) {
return state.globalConfig.theme.textSize
},
// 获取图案颜色
getPatterColor(state) {
return state.globalConfig.theme.patternColor
},
// 获取图案列表
getPatternList(state) {
return state.globalConfig.theme.patternList
},
// 获取音乐列表
getMusicList(state) {
return state.globalConfig.musicList
},
// 获取当前音乐
getCurrentMusic(state) {
return state.currentMusic
},
// 获取图片列表
getImageList(state) {
return state.globalConfig.imageList
},
// 获取是否显示奖品列表
getIsShowPrizeList(state) {
return state.globalConfig.isSHowPrizeList
},
// 获取当前语言
getLanguage(state) {
return state.globalConfig.language
},
// 获取背景图片设置
getBackground(state) {
return state.globalConfig.theme.background
},
},
actions: {
// 设置rowCount
setRowCount(rowCount: number) {
this.globalConfig.rowCount = rowCount
},
// 设置标题
setTopTitle(topTitle: string) {
this.globalConfig.topTitle = topTitle
},
// 设置主题
setTheme(theme: any) {
const { name, detail } = theme
this.globalConfig.theme.name = name
this.globalConfig.theme.detail = detail
},
// 设置卡片颜色
setCardColor(cardColor: string) {
this.globalConfig.theme.cardColor = cardColor
},
// 设置中奖颜色
setLuckyCardColor(luckyCardColor: string) {
this.globalConfig.theme.luckyCardColor = luckyCardColor
},
// 设置文字颜色
setTextColor(textColor: string) {
this.globalConfig.theme.textColor = textColor
},
// 设置卡片宽高
setCardSize(cardSize: { width: number, height: number }) {
this.globalConfig.theme.cardWidth = cardSize.width
this.globalConfig.theme.cardHeight = cardSize.height
},
// 设置文字大小
setTextSize(textSize: number) {
this.globalConfig.theme.textSize = textSize
},
// 置图案颜色
setPatterColor(patterColor: string) {
this.globalConfig.theme.patternColor = patterColor
},
// 设置图案列表
setPatternList(patternList: number[]) {
this.globalConfig.theme.patternList = patternList
},
// 重置图案列表
resetPatternList() {
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
}
}
this.globalConfig.musicList.push(music)
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'globalConfig',
paths: ['globalConfig'],
},
],
// 删除音乐
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
}
}
},
// 设置当前播放音乐
setCurrentMusic(musicItem: IMusic, paused: boolean = true) {
this.currentMusic = {
item: musicItem,
paused,
}
},
// 重置音乐列表
resetMusicList() {
this.globalConfig.musicList = defaultMusicList as IMusic[]
},
// 清空音乐列表
clearMusicList() {
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
}
}
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
}
}
},
// 重置图片列表
resetImageList() {
this.globalConfig.imageList = defaultImageList as IImage[]
},
// 清空图片列表
clearImageList() {
this.globalConfig.imageList = [] as IImage[]
},
// 设置是否显示奖品列表
setIsShowPrizeList(isShowPrizeList: boolean) {
this.globalConfig.isSHowPrizeList = isShowPrizeList
},
// 设置
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,
topTitle: i18n.global.t('data.defaultTitle'),
language: browserLanguage,
theme: {
name: 'dracula',
detail: { primary: '#0f5fd3' },
cardColor: '#ff79c6',
cardWidth: 140,
cardHeight: 200,
textColor: '#ffffff',
luckyCardColor: '#ECB1AC',
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,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'globalConfig',
paths: ['globalConfig'],
},
],
},
})

View File

@@ -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(),
}
}

View File

@@ -1,158 +1,159 @@
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[],
}
};
state() {
return {
personConfig: {
allPersonList: [] as IPersonConfig[],
alreadyPersonList: [] as IPersonConfig[],
},
}
},
getters: {
// 获取全部配置
getPersonConfig(state) {
return state.personConfig
},
getters: {
// 获取全部配置
getPersonConfig(state) {
return state.personConfig;
},
// 获取全部人员名单
getAllPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item
});
},
// 获取未获此奖的人员名单
getNotThisPrizePersonList(state: any) {
const currentPrize = usePrizeConfig().prizeConfig.currentPrize;
const data = state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return !item.prizeId.includes(currentPrize.id as string);
});
return data
},
// 获取已中奖人员名单
getAlreadyPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item.isWin === true;
});
},
// 获取中奖人员详情
getAlreadyPersonDetail(state) {
return state.personConfig.alreadyPersonList
},
// 获取未中奖人员名单
getNotPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item.isWin === false;
});
},
// 获取全部人员名单
getAllPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item
})
},
actions: {
// 添加未中奖人员
addNotPersonList(personList: IPersonConfig[]) {
if (personList.length <= 0) {
return
}
personList.forEach((item: IPersonConfig) => {
this.personConfig.allPersonList.push(item);
});
},
// 添加已中奖人员
addAlreadyPersonList(personList: IPersonConfig[], prize: IPrizeConfig | null) {
if (personList.length <= 0) {
return
}
personList.forEach((person: IPersonConfig) => {
this.personConfig.allPersonList.map((item: IPersonConfig) => {
if (item.id === person.id && prize != null) {
item.isWin = true
// person.isWin = true
item.prizeName.push(prize.name)
// person.prizeName += prize.name
item.prizeTime.push(dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'))
// person.prizeTime = new Date().toString()
item.prizeId.push(prize.id as string)
}
// 获取未获此奖的人员名单
getNotThisPrizePersonList(state: any) {
const currentPrize = usePrizeConfig().prizeConfig.currentPrize
const data = state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return !item.prizeId.includes(currentPrize.id as string)
})
return item
});
this.personConfig.alreadyPersonList.push(person);
});
},
// 从已中奖移动到未中奖
moveAlreadyToNot(person: IPersonConfig) {
if (person.id == undefined || person.id == null) {
return
}
const alreadyPersonListLength = this.personConfig.alreadyPersonList.length
for (let i = 0; i < this.personConfig.allPersonList.length; i++) {
if (person.id === this.personConfig.allPersonList[i].id) {
this.personConfig.allPersonList[i].isWin = false
this.personConfig.allPersonList[i].prizeName = []
this.personConfig.allPersonList[i].prizeTime = []
this.personConfig.allPersonList[i].prizeId = []
break
}
}
for (let i = 0; i < alreadyPersonListLength; i++) {
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) =>
item.id !== person.id
)
}
},
// 删除指定人员
deletePerson(person: IPersonConfig) {
if (person.id != undefined || person.id != null) {
this.personConfig.allPersonList = this.personConfig.allPersonList.filter((item: IPersonConfig) => item.id !== person.id);
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) => item.id !== person.id);
}
},
// 删除所有人员
deleteAllPerson() {
this.personConfig.allPersonList = [];
this.personConfig.alreadyPersonList = [];
},
// 删除所有人员
resetPerson() {
this.personConfig.allPersonList = [];
this.personConfig.alreadyPersonList = [];
},
// 重置已中奖人员
resetAlreadyPerson() {
// 把已中奖人员合并到未中奖人员,要验证是否已存在
this.personConfig.allPersonList.forEach((item: IPersonConfig) => {
item.isWin = false;
item.prizeName = [];
item.prizeTime = [];
item.prizeId = []
});
this.personConfig.alreadyPersonList = [];
},
setDefaultPersonList() {
this.personConfig.allPersonList = defaultPersonList;
this.personConfig.alreadyPersonList = [];
},
// 重置所有配置
reset() {
this.personConfig = {
allPersonList: [] as IPersonConfig[],
alreadyPersonList: [] as IPersonConfig[],
}
},
return data
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'personConfig',
},
],
// 获取已中奖人员名单
getAlreadyPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item.isWin === true
})
},
});
// 获取中奖人员详情
getAlreadyPersonDetail(state) {
return state.personConfig.alreadyPersonList
},
// 获取未中奖人员名单
getNotPersonList(state) {
return state.personConfig.allPersonList.filter((item: IPersonConfig) => {
return item.isWin === false
})
},
},
actions: {
// 添加未中奖人员
addNotPersonList(personList: IPersonConfig[]) {
if (personList.length <= 0) {
return
}
personList.forEach((item: IPersonConfig) => {
this.personConfig.allPersonList.push(item)
})
},
// 添加已中奖人员
addAlreadyPersonList(personList: IPersonConfig[], prize: IPrizeConfig | null) {
if (personList.length <= 0) {
return
}
personList.forEach((person: IPersonConfig) => {
this.personConfig.allPersonList.map((item: IPersonConfig) => {
if (item.id === person.id && prize != null) {
item.isWin = true
// person.isWin = true
item.prizeName.push(prize.name)
// person.prizeName += prize.name
item.prizeTime.push(dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'))
// person.prizeTime = new Date().toString()
item.prizeId.push(prize.id as string)
}
return item
})
this.personConfig.alreadyPersonList.push(person)
})
},
// 从已中奖移动到未中奖
moveAlreadyToNot(person: IPersonConfig) {
if (person.id === undefined || person.id == null) {
return
}
const alreadyPersonListLength = this.personConfig.alreadyPersonList.length
for (let i = 0; i < this.personConfig.allPersonList.length; i++) {
if (person.id === this.personConfig.allPersonList[i].id) {
this.personConfig.allPersonList[i].isWin = false
this.personConfig.allPersonList[i].prizeName = []
this.personConfig.allPersonList[i].prizeTime = []
this.personConfig.allPersonList[i].prizeId = []
break
}
}
for (let i = 0; i < alreadyPersonListLength; i++) {
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) =>
item.id !== person.id,
)
}
},
// 删除指定人员
deletePerson(person: IPersonConfig) {
if (person.id !== undefined || person.id != null) {
this.personConfig.allPersonList = this.personConfig.allPersonList.filter((item: IPersonConfig) => item.id !== person.id)
this.personConfig.alreadyPersonList = this.personConfig.alreadyPersonList.filter((item: IPersonConfig) => item.id !== person.id)
}
},
// 删除所有人员
deleteAllPerson() {
this.personConfig.allPersonList = []
this.personConfig.alreadyPersonList = []
},
// 删除所有人员
resetPerson() {
this.personConfig.allPersonList = []
this.personConfig.alreadyPersonList = []
},
// 重置已中奖人员
resetAlreadyPerson() {
// 把已中奖人员合并到未中奖人员,要验证是否已存在
this.personConfig.allPersonList.forEach((item: IPersonConfig) => {
item.isWin = false
item.prizeName = []
item.prizeTime = []
item.prizeId = []
})
this.personConfig.alreadyPersonList = []
},
setDefaultPersonList() {
this.personConfig.allPersonList = defaultPersonList
this.personConfig.alreadyPersonList = []
},
// 重置所有配置
reset() {
this.personConfig = {
allPersonList: [] as IPersonConfig[],
alreadyPersonList: [] as IPersonConfig[],
}
},
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'personConfig',
},
],
},
})

View File

@@ -1,176 +1,177 @@
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 {
prizeConfig: {
prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize,
temporaryPrize: {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: ''
},
separateCount: {
enable: true,
countList: []
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig
}
};
state() {
return {
prizeConfig: {
prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize,
temporaryPrize: {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig,
},
}
},
getters: {
// 获取全部配置
getPrizeConfigAll(state) {
return state.prizeConfig
},
getters: {
// 获取全部配置
getPrizeConfigAll(state) {
return state.prizeConfig;
},
// 获取奖品列表
getPrizeConfig(state) {
return state.prizeConfig.prizeList;
},
// 根据id获取配置
getPrizeConfigById(state) {
return (id: number | string) => {
return state.prizeConfig.prizeList.find(item => item.id === id);
}
},
// 获取当前奖项
getCurrentPrize(state) {
return state.prizeConfig.currentPrize;
},
// 获取临时的奖项
getTemporaryPrize(state) {
return state.prizeConfig.temporaryPrize;
},
// 获取奖品列表
getPrizeConfig(state) {
return state.prizeConfig.prizeList
},
// 根据id获取配置
getPrizeConfigById(state) {
return (id: number | string) => {
return state.prizeConfig.prizeList.find(item => item.id === id)
}
},
// 获取当前奖项
getCurrentPrize(state) {
return state.prizeConfig.currentPrize
},
// 获取临时的奖项
getTemporaryPrize(state) {
return state.prizeConfig.temporaryPrize
},
actions: {
// 设置奖项
setPrizeConfig(prizeList: IPrizeConfig[]) {
this.prizeConfig.prizeList = prizeList;
},
// 添加奖项
addPrizeConfig(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.prizeList.push(prizeConfigItem);
},
// 删除奖项
deletePrizeConfig(prizeConfigItemId: number | string) {
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId);
},
// 更新奖项数据
updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
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;
}
}
}
else {
return
}
this.resetTemporaryPrize()
},
// 删除全部奖项
deleteAllPrizeConfig() {
this.prizeConfig.prizeList = [] as IPrizeConfig[];
},
// 设置当前奖项
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.currentPrize = prizeConfigItem
},
// 设置临时奖项
setTemporaryPrize(prizeItem: IPrizeConfig) {
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]);
break
}
}
this.resetTemporaryPrize()
return
}
this.prizeConfig.temporaryPrize = prizeItem
},
// 重置临时奖项
resetTemporaryPrize() {
this.prizeConfig.temporaryPrize = {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: ''
},
separateCount: {
enable: true,
countList: []
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig;
},
// 重置所有配置
resetDefault() {
this.prizeConfig = {
prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize,
temporaryPrize: {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: ''
},
separateCount: {
enable: true,
countList: []
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig
}
},
actions: {
// 设置奖项
setPrizeConfig(prizeList: IPrizeConfig[]) {
this.prizeConfig.prizeList = prizeList
},
// 添加奖项
addPrizeConfig(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.prizeList.push(prizeConfigItem)
},
// 删除奖项
deletePrizeConfig(prizeConfigItemId: number | string) {
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId)
},
// 更新奖项数据
updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
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
}
}
}
else {
return
}
this.resetTemporaryPrize()
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'prizeConfig',
},
],
// 删除全部奖项
deleteAllPrizeConfig() {
this.prizeConfig.prizeList = [] as IPrizeConfig[]
},
});
// 设置当前奖项
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.currentPrize = prizeConfigItem
},
// 设置临时奖项
setTemporaryPrize(prizeItem: IPrizeConfig) {
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])
break
}
}
this.resetTemporaryPrize()
return
}
this.prizeConfig.temporaryPrize = prizeItem
},
// 重置临时奖项
resetTemporaryPrize() {
this.prizeConfig.temporaryPrize = {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig
},
// 重置所有配置
resetDefault() {
this.prizeConfig = {
prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize,
temporaryPrize: {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig,
}
},
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'prizeConfig',
},
],
},
})

View File

@@ -1,37 +1,37 @@
import { defineStore } from 'pinia';
import { defineStore } from 'pinia'
// import { IPrizeConfig } from '@/types/storeType';
export const useSystem = defineStore('system', {
state() {
return {
isMobile:false,
isChrome:true
};
state() {
return {
isMobile: false,
isChrome: true,
}
},
getters: {
getIsMobile(state) {
return state.isMobile
},
getters: {
getIsMobile(state) {
return state.isMobile;
},
getIsChrome(state) {
return state.isChrome;
},
getIsChrome(state) {
return state.isChrome
},
actions: {
setIsMobile(isMobile: boolean) {
this.isMobile = isMobile;
},
setIsChrome(isChrome: boolean) {
this.isChrome = isChrome;
},
},
actions: {
setIsMobile(isMobile: boolean) {
this.isMobile = isMobile
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
// storage: localStorage,
// key: 'globalConfig',
// paths: ['globalConfig'],
},
],
setIsChrome(isChrome: boolean) {
this.isChrome = isChrome
},
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
// storage: localStorage,
// key: 'globalConfig',
// paths: ['globalConfig'],
},
],
},
})

View File

@@ -1,52 +1,52 @@
export interface IPersonConfig {
id: number;
uid: string;
name: string;
department: string;
identity: string;
isWin: boolean;
x: number;
y: number
createTime: string;
updateTime: string;
prizeName: string[];
prizeId: string[];
prizeTime: string[];
id: number
uid: string
name: string
department: string
identity: string
isWin: boolean
x: number
y: number
createTime: string
updateTime: string
prizeName: string[]
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,
picture: {
id: string | number,
name: string,
url: string
};
separateCount: {
enable: boolean,
countList: Separate[],
};
desc: string;
isShow: boolean;
isUsed: boolean,
frequency: number;
id: number | string
name: string
sort: number
isAll: boolean
count: number
isUsedCount: number
picture: {
id: string | number
name: string
url: string
}
separateCount: {
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
}

View File

@@ -1,3 +1,3 @@
export function getToken() {
return window.localStorage.getItem('userToken');
return window.localStorage.getItem('userToken')
}

View File

@@ -1,43 +1,43 @@
// 判断颜色是否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=''
// 判断是否是hex颜色
if (isHex(color)) {
const {r,g,b} = hexToRgba(color);
rgbaStr = `rgba(${r},${g},${b},${opacity})`
}
else{
const {r,g,b} = rgbToRgba(color)
rgbaStr = `rgba(${r},${g},${b},${opacity})`
}
opacity = opacity || 1
let rgbaStr = ''
// 判断是否是hex颜色
if (isHex(color)) {
const { r, g, b } = hexToRgba(color)
rgbaStr = `rgba(${r},${g},${b},${opacity})`
}
else {
const { r, g, b } = rgbToRgba(color)
rgbaStr = `rgba(${r},${g},${b},${opacity})`
}
return rgbaStr
return rgbaStr
}
export function rgbToHex(color:string) {

View File

@@ -1,19 +1,19 @@
export const readFileBinary = (file: any): Promise<any> => {
return new Promise(resolve => {
const reader = new FileReader()
reader.readAsBinaryString(file)
reader.onload = (ev: any) => {
resolve(ev.target.result)
}
})
export function readFileBinary(file: any): Promise<any> {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsBinaryString(file)
reader.onload = (ev: any) => {
resolve(ev.target.result)
}
})
}
export const 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})
}
})
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 })
}
})
}

View File

@@ -1,41 +1,41 @@
import dayjs from 'dayjs';
import dayjs from 'dayjs'
// 筛选人员数据
export const filterData = (tableData: any[], localRowCount: number, startIndex = 0) => {
const dataLength = tableData.length
let j = 0;
for (let i = 0; i < dataLength; i++) {
if (i % localRowCount === 0) {
j++;
}
tableData[i].x = i % localRowCount + 1;
tableData[i].y = j;
tableData[i].id = i;
// 是否中奖
export function filterData(tableData: any[], localRowCount: number) {
const dataLength = tableData.length
let j = 0
for (let i = 0; i < dataLength; i++) {
if (i % localRowCount === 0) {
j++
}
tableData[i].x = i % localRowCount + 1
tableData[i].y = j
tableData[i].id = i
// 是否中奖
}
return tableData
return tableData
}
export const addOtherInfo = (personList: any[]) => {
const len = personList.length;
for (let i = 0; i < len; i++) {
personList[i].id = i
personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
personList[i].prizeName = [] as string[];
personList[i].prizeTime = [] as string[];
personList[i].prizeId = [];
personList[i].isWin = false
}
export function addOtherInfo(personList: any[]) {
const len = personList.length
for (let i = 0; i < len; i++) {
personList[i].id = i
personList[i].createTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
personList[i].updateTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
personList[i].prizeName = [] as string[]
personList[i].prizeTime = [] as string[]
personList[i].prizeId = []
personList[i].isWin = false
}
return personList
return personList
}
export const 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)
}
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
return cardIndex
}

View File

@@ -1,11 +1,10 @@
// 提取有哪些字段
export const 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){
// 返回数组key value
return keys.map(key => ({label:key,value:true}));
}
};
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) {
// 返回数组key value
return keys.map(key => ({ label: key, value: true }))
}
}

View File

@@ -1,39 +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 { isHex, isRgbOrRgba } from '@/utils/color'
import daisyuiThemes from 'daisyui/src/theming/themes'
import { storeToRefs } from 'pinia'
import { themeChange } from 'theme-change'
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,
getBackground: backgroundImage,
getImageList: imageList
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()
interface ThemeDaType {
[key: string]: any
[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))
@@ -42,68 +34,68 @@ 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 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,
rowCount: rowCountValue,
})
const formErr = ref({
rowCount: '',
rowCount: '',
})
const schema = zod.object({
rowCount: zod.number({
required_error: '必填项',
invalid_type_error: '必须填入数字',
})
.min(1, '最小为1')
.max(100, '最大为100')
// 格式化
rowCount: zod.number({
required_error: i18n.global.t('error.require'),
invalid_type_error: i18n.global.t('error.requireNumber'),
})
.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,
rowCount: formData.value.rowCount,
}
const parseSchema = (props: ValidatePayload) => {
return schema.parseAsync(props)
function parseSchema(props: ValidatePayload) {
return schema.parseAsync(props)
}
const resetPersonLayout = () => {
isRowCountChange.value = 2
setTimeout(() => {
const alreadyLen = alreadyPersonList.value.length
const notLen = notPersonList.value.length
if (alreadyLen <= 0 && notLen <= 0) {
return
}
const allPersonList = alreadyPersonList.value.concat(notPersonList.value)
const newAlreadyPersonList = allPersonList.slice(0, alreadyLen)
const newNotPersonList = allPersonList.slice(alreadyLen, notLen + alreadyLen)
personConfig.deleteAllPerson()
personConfig.addNotPersonList(newNotPersonList)
personConfig.addAlreadyPersonList(newAlreadyPersonList, null)
function resetPersonLayout() {
isRowCountChange.value = 2
setTimeout(() => {
const alreadyLen = alreadyPersonList.value.length
const notLen = notPersonList.value.length
if (alreadyLen <= 0 && notLen <= 0) {
return
}
const allPersonList = alreadyPersonList.value.concat(notPersonList.value)
const newAlreadyPersonList = allPersonList.slice(0, alreadyLen)
const newNotPersonList = allPersonList.slice(alreadyLen, notLen + alreadyLen)
personConfig.deleteAllPerson()
personConfig.addNotPersonList(newNotPersonList)
personConfig.addAlreadyPersonList(newAlreadyPersonList, null)
isRowCountChange.value = 0
}, 1000)
isRowCountChange.value = 0
}, 1000)
}
const clearPattern = () => {
globalConfig.setPatternList([] as number[])
function clearPattern() {
globalConfig.setPatternList([] as number[])
}
const resetPattern = () => {
globalConfig.resetPatternList()
function resetPattern() {
globalConfig.resetPatternList()
}
const resetData = () => {
globalConfig.reset();
personConfig.reset();
prizeConfig.resetDefault();
// 刷新页面
window.location.reload()
function resetData() {
globalConfig.reset()
personConfig.reset()
prizeConfig.resetDefault()
// 刷新页面
window.location.reload()
}
// const handleChangeShowFields = (fieldItem: any) => {
@@ -115,210 +107,244 @@ const resetData = () => {
// }
watch(() => formData.value.rowCount, () => {
payload.rowCount = formData.value.rowCount
parseSchema(payload).then(res => {
if (res.rowCount) {
isRowCountChange.value = 1
globalConfig.setRowCount(res.rowCount)
}
})
.catch(err => {
formErr.value.rowCount = err.issues[0].message
})
payload.rowCount = formData.value.rowCount
parseSchema(payload).then((res) => {
if (res.rowCount) {
isRowCountChange.value = 1
globalConfig.setRowCount(res.rowCount)
}
}).catch((err) => {
formErr.value.rowCount = err.issues[0].message
})
})
watch(topTitleValue, (val) => {
globalConfig.setTopTitle(val)
}),
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 })
watch(topTitleValue, (val) => {
globalConfig.setTopTitle(val)
})
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 })
watch(cardColorValue, (val: string) => {
globalConfig.setCardColor(val)
globalConfig.setCardColor(val)
}, { deep: true })
watch(luckyCardColorValue, (val: string) => {
globalConfig.setLuckyCardColor(val)
globalConfig.setLuckyCardColor(val)
}, { deep: true })
watch(patternColorValue, (val: string) => {
globalConfig.setPatterColor(val)
globalConfig.setPatterColor(val)
})
watch(textColorValue, (val: string) => {
globalConfig.setTextColor(val)
globalConfig.setTextColor(val)
}, { deep: true })
watch(cardSizeValue, (val: { width: number; height: number; }) => {
globalConfig.setCardSize(val)
}, { deep: true }),
watch(isShowPrizeListValue, () => {
globalConfig.setIsShowPrizeList(isShowPrizeListValue.value)
})
watch(backgroundImageValue, (val: {}) => {
globalConfig.setBackground(val)
watch(cardSizeValue, (val: { width: number, height: number }) => {
globalConfig.setCardSize(val)
}, { deep: true })
watch(isShowPrizeListValue, () => {
globalConfig.setIsShowPrizeList(isShowPrizeListValue.value)
})
watch(backgroundImageValue, (val) => {
globalConfig.setBackground(val)
})
watch(languageValue, (val: string) => {
globalConfig.setLanguage(val)
})
onMounted(() => {
})
</script>
<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>
<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>
</form>
</div>
</div>
</dialog>
<div>
<h2>全局配置</h2>
<div class="mb-8">
<button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">重置所有数据</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>
</div>
<input type="text" v-model="topTitleValue" placeholder="输入标题"
class="w-full max-w-xs input input-bordered" />
</div>
</label>
<label class="flex flex-row items-center w-full gap-24 mb-10 form-control">
<div class="">
<div class="label">
<span class="label-text">列数</span>
</div>
<input type="number" v-model="formData.rowCount" 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">
{{ 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>
</button>
</div>
</div>
</label>
<label class="w-full max-w-xs form-control">
<div class="label">
<span class="label-text">选择主题</span>
</div>
<select data-choose-theme class="w-full max-w-xs border-solid select border-1" v-model="themeValue">
<option disabled selected>选取主题</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>
</div>
<select data-choose-theme class="w-full max-w-xs border-solid select border-1"
v-model="backgroundImageValue">
<option disabled selected>选取背景图片</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>
</div>
<ColorPicker ref="colorPickerRef" v-model="cardColorValue" v-model:pure-color="cardColorValue">
</ColorPicker>
</label>
<label class="w-full max-w-xs form-control">
<div class="label">
<span class="label-text">中奖卡片颜色</span>
</div>
<ColorPicker ref="colorPickerRef" v-model="luckyCardColorValue" v-model:pure-color="luckyCardColorValue">
</ColorPicker>
</label>
<label class="w-full max-w-xs form-control">
<div class="label">
<span class="label-text">文字颜色</span>
</div>
<ColorPicker ref="colorPickerRef" v-model="textColorValue" v-model:pure-color="textColorValue">
</ColorPicker>
</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>
</div>
<input type="number" v-model="cardSizeValue.width" placeholder="Type here"
class="w-full max-w-xs input input-bordered" />
</div>
<div>
<div class="label">
<span class="label-text">卡片高度</span>
</div>
<input type="number" v-model="cardSizeValue.height" 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>
</div>
<input type="number" v-model="textSizeValue" 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>
</div>
<ColorPicker ref="colorPickerRef" v-model="patternColorValue" v-model:pure-color="patternColorValue">
</ColorPicker>
</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>
</div>
<div class="h-auto">
<PatternSetting :rowCount="rowCount" :cardColor="cardColor" :patternColor="patternColor"
:patternList="patternList"></PatternSetting>
</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>
</button>
<div class="tooltip" data-tip="默认图案设置针对17列时有效其他列数请自行设置">
<button class="mt-5 btn btn-info btn-sm" @click="resetPattern">
<span>默认图案设置</span>
</button>
</div>
</div>
<label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">是否常显奖品列表</span>
</div>
<input type="checkbox" :checked="isShowPrizeListValue"
@change="isShowPrizeListValue = !isShowPrizeListValue"
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
</label>
<dialog id="my_modal_1" ref="resetDataDialogRef" class="border-none modal">
<div class="modal-box">
<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()">
{{ t(`button.cancel`) }}
</button>
<button class="btn" @click="resetData">
{{ t('button.confirm') }}
</button>
</form>
</div>
</div>
</dialog>
<div>
<h2>{{ t('viewTitle.globalSetting') }}</h2>
<div class="mb-8">
<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">{{ t('table.title') }}</span>
</div>
<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">{{ t('table.columnNumber') }}</span>
</div>
<input
v-model="formData.rowCount" type="number" placeholder="Type here"
class="w-full max-w-xs input input-bordered"
>
<div class="help">
<span v-if="formErr.rowCount" class="text-sm text-red-400 help-text">
{{ formErr.rowCount }}
</span>
</div>
</div>
<div>
<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">{{ t('table.language') }}</span>
</div>
<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">{{ t('table.backgroundImage') }}</span>
</div>
<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">{{ t('table.cardColor') }}</span>
</div>
<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">{{ t('table.textColor') }}</span>
</div>
<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">{{ t('table.cardWidth') }}</span>
</div>
<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">{{ t('table.cardHeight') }}</span>
</div>
<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">{{ t('table.textSize') }}</span>
</div>
<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">{{ t('table.highlightColor') }}</span>
</div>
<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">{{ t('table.patternSetting') }}</span>
</div>
<div class="h-auto">
<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>{{ t('button.clearPattern') }}</span>
</button>
<div class="tooltip" :data-tip="t('tooltip.defaultLayout')">
<button class="mt-5 btn btn-info btn-sm" @click="resetPattern">
<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">{{ t('table.alwaysDisplay') }}</span>
</div>
<input
type="checkbox" :checked="isShowPrizeListValue" class="mt-2 border-solid checkbox checkbox-secondary border-1"
@change="isShowPrizeListValue = !isShowPrizeListValue"
>
</label>
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,111 +1,118 @@
<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)
if (!isImage) {
imgUploadToast.value = 3
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)
.then(() => {
imgUploadToast.value = 1
getImageDbStore()
})
.catch(() => {
imgUploadToast.value = 2
})
return
}
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()
})
.catch(() => {
imgUploadToast.value = 2
})
}
const getImageDbStore =async () => {
const keys =await imageDbStore.keys()
if(keys.length>0){
imageDbStore.iterate((value, key) => {
globalConfig.addImage({
id:key,
name:key,
url:'Storage'
})
})
}
async function getImageDbStore() {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value, key) => {
globalConfig.addImage({
id: key,
name: key,
url: 'Storage',
})
})
}
}
const removeImage=(item:IImage)=>{
if(item.url=='Storage'){
imageDbStore.removeItem(item.id).then(() => {
globalConfig.removeImage(item.id)
})
}
globalConfig.removeImage(item.id)
function removeImage(item: IImage) {
if (item.url === 'Storage') {
imageDbStore.removeItem(item.id).then(() => {
globalConfig.removeImage(item.id)
})
}
globalConfig.removeImage(item.id)
}
onMounted(() => {
// getImageDbStore()
// getImageDbStore()
})
watch(() => imgUploadToast.value, (val) => {
if (val !== 0) {
setTimeout(() => {
imgUploadToast.value = 0
}, 2000)
}
if (val !== 0) {
setTimeout(() => {
imgUploadToast.value = 0
}, 2000)
}
})
</script>
<template>
<div class="toast toast-top toast-end">
<div class="alert alert-error" v-if="imgUploadToast == 2">
<span>上传失败</span>
</div>
<div class="alert alert-success" v-if="imgUploadToast == 1">
<span>上传成功</span>
</div>
<div class="alert alert-error" v-if="imgUploadToast == 3">
<span>不是图片</span>
</div>
<div class="toast toast-top toast-end">
<div v-if="imgUploadToast === 2" class="alert alert-error">
<span>{{ t('error.uploadFail') }}</span>
</div>
<div v-if="imgUploadToast === 1" class="alert alert-success">
<span>{{ t('error.uploadSuccess') }}</span>
</div>
<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>
</label>
</div>
<ul class="p-0">
<li v-for="item in localImageList" :key="item.id" class="mb-3">
<div class="flex items-center gap-8">
<div class="avatar h-14">
<div class="w-12 h-12 mask mask-squircle hover:w-14 hover:h-14">
<!-- <img v-if="item.url!=='Storage'" :src="item.url" alt="Avatar Tailwind CSS Component" /> -->
<ImageSync :imgItem="item"></ImageSync>
</div>
</div>
<div class="w-64">
<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>
</div>
</div>
</li>
</ul>
<div>
<div class="">
<label for="explore">
<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">
<li v-for="item in localImageList" :key="item.id" class="mb-3">
<div class="flex items-center gap-8">
<div class="avatar h-14">
<div class="w-12 h-12 mask mask-squircle hover:w-14 hover:h-14">
<!-- <img v-if="item.url!=='Storage'" :src="item.url" alt="Avatar Tailwind CSS Component" /> -->
<ImageSync :img-item="item" />
</div>
</div>
<div class="w-64">
<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)">
{{ t('button.upload') }}
</button>
</div>
</div>
</li>
</ul>
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,102 +1,113 @@
<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) => {
globalConfig.removeMusic(item.id)
audioDbStore.removeItem(item.name)
// setTimeout(()=>{
// localMusicListValue.value=localMusicList
// },100)
function deleteMusic(item: IMusic) {
globalConfig.removeMusic(item.id)
audioDbStore.removeItem(item.name)
// setTimeout(()=>{
// localMusicListValue.value=localMusicList
// },100)
}
const resetMusic = () => {
globalConfig.resetMusicList()
audioDbStore.clear()
function resetMusic() {
globalConfig.resetMusicList()
audioDbStore.clear()
}
const deleteAll = () => {
globalConfig.clearMusicList()
audioDbStore.clear()
function deleteAll() {
globalConfig.clearMusicList()
audioDbStore.clear()
}
const getMusicDbStore = async () => {
const keys = await audioDbStore.keys()
if (keys.length > 0) {
audioDbStore.iterate((value: string, key: string) => {
globalConfig.addMusic({
id: key + new Date().getTime().toString(),
name: key,
url: 'Storage',
})
})
}
async function getMusicDbStore() {
const keys = await audioDbStore.keys()
if (keys.length > 0) {
audioDbStore.iterate((value: string, key: string) => {
globalConfig.addMusic({
id: key + new Date().getTime().toString(),
name: key,
url: 'Storage',
})
})
}
}
const handleFileChange = async (e: Event) => {
const isAudio = /audio*/.test(((e.target as HTMLInputElement).files as FileList)[0].type)
if (!isAudio) {
audioUploadToast.value = 3
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)
.then(() => {
audioUploadToast.value = 1
getMusicDbStore()
})
.catch(() => {
audioUploadToast.value = 2
})
return
}
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()
})
.catch(() => {
audioUploadToast.value = 2
})
}
onMounted(() => {
getMusicDbStore()
getMusicDbStore()
})
</script>
<template>
<div>
<div class="flex gap-3">
<button class="btn btn-primary btn-sm" @click="resetMusic">重置音乐列表</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>
</label>
<button class="btn btn-error btn-sm" @click="deleteAll">删除所有</button>
</div>
<div>
<ul class="p-0">
<li v-for="item in localMusicListValue" :key="item.id" class="flex items-center gap-6 pb-2 mb-3 divide-y">
<div class="mr-12 overflow-hidden w-72 whitespace-nowrap text-ellipsis">
<span>
{{ 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>
</div>
</li>
</ul>
</div>
<div>
<div class="flex gap-3">
<button class="btn btn-primary btn-sm" @click="resetMusic">
{{ t('button.reset') }}
</button>
<label for="explore">
<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">
{{ t('button.allDelete') }}
</button>
</div>
<div>
<ul class="p-0">
<li v-for="item in localMusicListValue" :key="item.id" class="flex items-center gap-6 pb-2 mb-3 divide-y">
<div class="mr-12 overflow-hidden w-72 whitespace-nowrap text-ellipsis">
<span>
{{ item.name }}</span>
</div>
<div class="flex gap-3">
<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>
</div>
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,45 +1,46 @@
<script setup lang='ts'>
import {computed} from 'vue';
const props=defineProps({
rowCount:{
type:Number,
default:17
},
cardColor:{
type:String,
default:'#fff'
},
patternColor:{
type:String,
default:'#000'
},
patternList:{
type:Array,
default:()=>[]
}
import { computed } from 'vue'
const props = defineProps({
rowCount: {
type: Number,
default: 17,
},
cardColor: {
type: String,
default: '#fff',
},
patternColor: {
type: String,
default: '#000',
},
patternList: {
type: Array,
default: () => [],
},
})
const data=computed(()=>{
return props
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{
data.value.patternList.push(item)
}
// emits
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
}
</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>

View File

@@ -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,176 +20,200 @@ 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]]
const excelData = XLSX.utils.sheet_to_json(workSheet)
const allData = addOtherInfo(excelData);
personConfig.resetPerson()
personConfig.addNotPersonList(allData)
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)
personConfig.resetPerson()
personConfig.addNotPersonList(allData)
}
const exportData = () => {
let data = JSON.parse(JSON.stringify(allPersonList.value))
// 排除一些字段
for (let i = 0; i < data.length; i++) {
delete data[i].x
delete data[i].y
delete data[i].id
delete data[i].createTime
delete data[i].updateTime
delete data[i].prizeId
// 修改字段名称
if (data[i].isWin) {
data[i].isWin = '是'
} else {
data[i].isWin = '否'
}
// 格式化数组为
data[i].prizeTime = data[i].prizeTime.join(',')
data[i].prizeName = data[i].prizeName.join(',')
function exportData() {
let data = JSON.parse(JSON.stringify(allPersonList.value))
// 排除一些字段
for (let i = 0; i < data.length; i++) {
delete data[i].x
delete data[i].y
delete data[i].id
delete data[i].createTime
delete data[i].updateTime
delete data[i].prizeId
// 修改字段名称
if (data[i].isWin) {
data[i].isWin = i18n.global.t('data.yes')
}
let dataString = JSON.stringify(data)
dataString = dataString
.replaceAll(/uid/g, '编号')
.replaceAll(/isWin/g, '是否中奖')
.replaceAll(/department/g, '部门')
.replaceAll(/name/g, '姓名')
.replaceAll(/identity/g, '身份')
.replaceAll(/prizeName/g, '获奖')
.replaceAll(/prizeTime/g, '获奖时间')
data = JSON.parse(dataString)
if (data.length > 0) {
const dataBinary = XLSX.utils.json_to_sheet(data)
const dataBinaryBinary = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1')
XLSX.writeFile(dataBinaryBinary, 'data.xlsx')
else {
data[i].isWin = i18n.global.t('data.no')
}
// 格式化数组为
data[i].prizeTime = data[i].prizeTime.join(',')
data[i].prizeName = data[i].prizeName.join(',')
}
let dataString = JSON.stringify(data)
dataString = dataString
.replaceAll(/uid/g, i18n.global.t('data.number'))
.replaceAll(/isWin/g, i18n.global.t('data.isWin'))
.replaceAll(/department/g, i18n.global.t('data.department'))
.replaceAll(/name/g, i18n.global.t('data.name'))
.replaceAll(/identity/g, i18n.global.t('data.identity'))
.replaceAll(/prizeName/g, i18n.global.t('data.prizeName'))
.replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime'))
data = JSON.parse(dataString)
if (data.length > 0) {
const dataBinary = XLSX.utils.json_to_sheet(data)
const dataBinaryBinary = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1')
XLSX.writeFile(dataBinaryBinary, 'data.xlsx')
}
}
const resetData = () => {
personConfig.resetAlreadyPerson()
function resetData() {
personConfig.resetAlreadyPerson()
}
const deleteAll = () => {
personConfig.deleteAllPerson()
function deleteAll() {
personConfig.deleteAllPerson()
}
const delPersonItem = (row: IPersonConfig) => {
personConfig.deletePerson(row)
function delPersonItem(row: IPersonConfig) {
personConfig.deletePerson(row)
}
const tableColumns = [
{
label: '编号',
props: 'uid',
{
label: i18n.global.t('data.number'),
props: 'uid',
},
{
label: i18n.global.t('data.name'),
props: 'name',
},
{
label: i18n.global.t('data.department'),
props: 'department',
},
{
label: i18n.global.t('data.identity'),
props: 'identity',
},
{
label: i18n.global.t('data.isWin'),
props: 'isWin',
formatValue(row: IPersonConfig) {
return row.isWin ? i18n.global.t('data.yes') : i18n.global.t('data.no')
},
{
label: '姓名',
props: 'name',
},
{
label: '部门',
props: 'department',
},
{
label: '身份',
props: 'identity',
},
{
label: '是否已中奖',
props: 'isWin',
formatValue(row: IPersonConfig) {
return row.isWin ? '是' : '否'
}
},
{
label: '操作',
actions: [
// {
// label: '编辑',
// type: 'btn-info',
// onClick: (row: any) => {
// delPersonItem(row)
// }
// },
{
label: '删除',
type: 'btn-error',
onClick: (row: IPersonConfig) => {
delPersonItem(row)
}
},
},
{
label: i18n.global.t('data.operation'),
actions: [
// {
// label: '编辑',
// type: 'btn-info',
// onClick: (row: any) => {
// delPersonItem(row)
// }
// },
{
label: i18n.global.t('data.delete'),
type: 'btn-error',
onClick: (row: IPersonConfig) => {
delPersonItem(row)
},
},
]
},
],
},
]
onMounted(() => {
})
</script>
<template>
<dialog id="my_modal_1" ref="resetDataDialog" class="border-none modal">
<div class="modal-box">
<h3 class="text-lg font-bold">提示!</h3>
<p class="py-4">该操作会清空人员中奖信息是否继续</p>
<div class="modal-action">
<form method="dialog" class="flex gap-3">
<!-- if there is a button in form, it will close the modal -->
<button class="btn" @click="resetDataDialog.close()">取消</button>
<button class="btn" @click="resetData">确定</button>
</form>
</div>
</div>
</dialog>
<dialog id="my_modal_1" ref="delAllDataDialog" class="border-none modal">
<div class="modal-box">
<h3 class="text-lg font-bold">提示!</h3>
<p class="py-4">该操作会删除所有人员数据是否继续</p>
<div class="modal-action">
<form method="dialog" class="flex gap-3">
<!-- if there is a button in form, it will close the modal -->
<button class="btn" @click="delAllDataDialog.close()">取消</button>
<button class="btn" @click="deleteAll">确定</button>
</form>
</div>
</div>
</dialog>
<div class="min-w-1000px">
<h2>人员管理</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>
</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" />
<span class="btn btn-primary btn-sm">导入人员数据</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>
<div>
<span>中奖人数</span>
<span>{{ alreadyPersonList.length }}</span>
<span>&nbsp;/&nbsp;</span>
<span>{{ allPersonList.length }}</span>
</div>
</div>
<DaiysuiTable :tableColumns="tableColumns" :data="allPersonList"></DaiysuiTable>
<dialog id="my_modal_1" ref="resetDataDialog" class="border-none modal">
<div class="modal-box">
<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()">
{{ 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">
{{ 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()">
{{ t('button.cancel') }}
</button>
<button class="btn" @click="deleteAll">
{{ t('button.confirm') }}
</button>
</form>
</div>
</div>
</dialog>
<div class="min-w-1000px">
<h2>{{ t('viewTitle.personManagement') }}</h2>
<div class="flex gap-3">
<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="t('tooltip.uploadExcelTip')">
<input
id="explore" type="file" class="" style="display: none" :accept="limitType"
@change="handleFileChange"
>
<span class="btn btn-primary btn-sm">{{ t('button.importData') }}</span>
</div>
</label>
</div>
<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>{{ t('table.luckyPeopleNumber') }}:</span>
<span>{{ alreadyPersonList.length }}</span>
<span>&nbsp;/&nbsp;</span>
<span>{{ allPersonList.length }}</span>
</div>
</div>
<DaiysuiTable :table-columns="tableColumns" :data="allPersonList" />
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -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,119 +16,116 @@ const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: already
// alreadyPersonList
// )
// const deleteAll = () => {
// personConfig.deleteAllPerson()
// }
const isDetail = ref(false)
const handleMoveNotPerson = (row: IPersonConfig) => {
personConfig.moveAlreadyToNot(row)
function handleMoveNotPerson(row: IPersonConfig) {
personConfig.moveAlreadyToNot(row)
}
const tableColumnsList = [
{
label: '编号',
props: 'uid',
sort: true
},
{
label: '姓名',
props: 'name',
},
{
label: '部门',
props: 'department',
},
{
label: '身份',
props: 'identity',
},
{
label: '奖品',
props: 'prizeName',
sort: true
},
{
label: '操作',
actions: [
{
label: '移入未中奖名单',
type: 'btn-info',
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
}
},
]
},
{
label: i18n.global.t('data.number'),
props: 'uid',
sort: true,
},
{
label: i18n.global.t('data.number'),
props: 'name',
},
{
label: i18n.global.t('data.department'),
props: 'department',
},
{
label: i18n.global.t('data.identity'),
props: 'identity',
},
{
label: i18n.global.t('data.prizeName'),
props: 'prizeName',
sort: true,
},
{
label: i18n.global.t('data.operation'),
actions: [
{
label: i18n.global.t('data.removePerson'),
type: 'btn-info',
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
},
},
],
},
]
const tableColumnsDetail = [
{
label: '编号',
props: 'uid',
sort: true
},
{
label: '姓名',
props: 'name',
},
{
label: '部门',
props: 'department',
},
{
label: '身份',
props: 'identity',
},
{
label: '奖品',
props: 'prizeName',
sort: true
},
{
label: '中奖时间',
props: 'prizeTime',
{
label: i18n.global.t('data.number'),
props: 'uid',
sort: true,
},
{
label: i18n.global.t('data.number'),
props: 'name',
},
{
label: i18n.global.t('data.department'),
props: 'department',
},
{
label: i18n.global.t('data.identity'),
props: 'identity',
},
{
label: i18n.global.t('data.prizeName'),
props: 'prizeName',
sort: true,
},
{
label: i18n.global.t('data.prizeTime'),
props: 'prizeTime',
},
{
label: '操作',
actions: [
{
label: '移入未中奖名单',
type: 'btn-info',
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
}
},
},
{
label: i18n.global.t('data.operation'),
actions: [
{
label: i18n.global.t('data.removePerson'),
type: 'btn-info',
onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row)
},
},
]
},
],
},
]
</script>
<template>
<div class="overflow-y-auto">
<h2>已中奖人员管理</h2>
<div class="flex items-center justify-start gap-10">
<!-- <button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button> -->
<div>
<span>中奖人数</span>
<span>{{ alreadyPersonList.length }}</span>
</div>
<div class="flex flex-col">
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">详细信息:</span>
<input type="checkbox" class="border-solid toggle toggle-primary border-1" v-model="isDetail" />
</label>
</div>
</div>
<div class="overflow-y-auto">
<h2>{{ t('viewTitle.winnerManagement') }}</h2>
<div class="flex items-center justify-start gap-10">
<div>
<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">{{ t('table.detail') }}:</span>
<input v-model="isDetail" type="checkbox" class="border-solid toggle toggle-primary border-1">
</label>
</div>
<DaiysuiTable v-if="!isDetail" :tableColumns="tableColumnsList" :data="alreadyPersonList"></DaiysuiTable>
<DaiysuiTable v-if="isDetail" :tableColumns="tableColumnsDetail" :data="alreadyPersonDetail"></DaiysuiTable>
</div>
</div>
<DaiysuiTable v-if="!isDetail" :table-columns="tableColumnsList" :data="alreadyPersonList" />
<DaiysuiTable v-if="isDetail" :table-columns="tableColumnsDetail" :data="alreadyPersonDetail" />
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,11 +1,9 @@
<script setup lang='ts'>
</script>
<template>
<router-view></router-view>
<router-view />
</template>
<style lang='scss' scoped>

View File

@@ -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,243 +22,266 @@ const imgList = ref<any[]>([])
const selectedPrize = ref<IPrizeConfig | null>()
const addPrize = () => {
const defaultPrizeCOnfig: IPrizeConfig = {
id: new Date().getTime().toString(),
name: '奖项',
sort: 0,
isAll: false,
count: 1,
function addPrize() {
const defaultPrizeCOnfig: IPrizeConfig = {
id: new Date().getTime().toString(),
name: i18n.global.t('data.prizeName'),
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '',
name: '',
url: '',
},
separateCount: {
enable: false,
countList: [],
},
desc: '',
isUsed: false,
isShow: true,
frequency: 1,
}
prizeConfig.addPrizeConfig(defaultPrizeCOnfig)
}
function selectPrize(item: IPrizeConfig) {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
if (selectedPrize.value.separateCount.countList.length > 1) {
return
}
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
picture: {
id: '',
name: '',
url: ''
},
separateCount: {
enable: false,
countList: []
},
desc: '',
isUsed: false,
isShow: true,
frequency: 1,
}
prizeConfig.addPrizeConfig(defaultPrizeCOnfig)
},
],
}
}
const selectPrize = (item: IPrizeConfig) => {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
if (selectedPrize.value.separateCount.countList.length > 1) {
return
}
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
}
]
}
function changePrizeStatus(item: IPrizeConfig) {
// if (item.isUsed == true) {
// item.isUsedCount = 0;
// if (item.separateCount && item.separateCount.countList.length) {
// item.separateCount.countList.forEach((countItem: any) => {
// countItem.isUsedCount = 0;
// })
// }
// }
// else {
// item.isUsedCount = item.count;
// if (item.separateCount && item.separateCount.countList.length) {
// item.separateCount.countList.forEach((countItem: any) => {
// countItem.isUsedCount = countItem.count;
// })
// }
// }
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
item.separateCount.countList = []
item.isUsed = !item.isUsed
}
const changePrizeStatus = (item: IPrizeConfig) => {
// if (item.isUsed == true) {
// item.isUsedCount = 0;
// if (item.separateCount && item.separateCount.countList.length) {
// item.separateCount.countList.forEach((countItem: any) => {
// countItem.isUsedCount = 0;
// })
// }
// }
// else {
// item.isUsedCount = item.count;
// if (item.separateCount && item.separateCount.countList.length) {
// item.separateCount.countList.forEach((countItem: any) => {
// countItem.isUsedCount = countItem.count;
// })
// }
// }
item.isUsed?item.isUsedCount=0:item.isUsedCount=item.count;
item.separateCount.countList = []
item.isUsed = !item.isUsed
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 (indexPrize > -1) {
prizeList.value[indexPrize].separateCount.countList = []
prizeList.value[indexPrize].isUsed ? prizeList.value[indexPrize].isUsedCount = prizeList.value[indexPrize].count : prizeList.value[indexPrize].isUsedCount = 0
}
}
function submitData(value: any) {
selectedPrize.value!.separateCount.countList = value
selectedPrize.value = null
}
function resetDefault() {
prizeConfig.resetDefault()
}
const 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 (indexPrize > -1) {
prizeList.value[indexPrize].separateCount.countList = []
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;
selectedPrize.value = null
}
const resetDefault = () => {
prizeConfig.resetDefault()
async function getImageDbStore() {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value, key) => {
imgList.value.push({
key,
value,
})
})
}
}
const getImageDbStore = async () => {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value, key) => {
imgList.value.push({
key,
value
})
})
}
function sort(item: IPrizeConfig, isUp: number) {
const itemIndex = prizeList.value.indexOf(item)
if (isUp === 1) {
prizeList.value.splice(itemIndex, 1)
prizeList.value.splice(itemIndex - 1, 0, item)
}
else {
prizeList.value.splice(itemIndex, 1)
prizeList.value.splice(itemIndex + 1, 0, item)
}
}
const sort = (item: IPrizeConfig, isUp: number) => {
const itemIndex = prizeList.value.indexOf(item)
if (isUp == 1) {
prizeList.value.splice(itemIndex, 1)
prizeList.value.splice(itemIndex - 1, 0, item)
} else {
prizeList.value.splice(itemIndex, 1)
prizeList.value.splice(itemIndex + 1, 0, item)
}
function delItem(item: IPrizeConfig) {
prizeConfig.deletePrizeConfig(item.id)
}
const delItem = (item: IPrizeConfig) => {
prizeConfig.deletePrizeConfig(item.id)
}
const delAll = async () => {
await prizeConfig.deleteAllPrizeConfig()
async function delAll() {
await prizeConfig.deleteAllPrizeConfig()
}
onMounted(() => {
getImageDbStore()
getImageDbStore()
})
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
prizeConfig.setPrizeConfig(val)
prizeConfig.setPrizeConfig(val)
}, { deep: true })
</script>
<template>
<div>
<h2>奖项配置</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>
</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>
</svg>
<span>进行操作可能会重置数据请谨慎操作</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">
<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>
</div>
</label>
<label class="w-1/2 max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">名称</span>
</div>
<input type="text" v-model="item.name" 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>
</div>
<input type="checkbox" :checked="item.isAll" @change="item.isAll = !item.isAll"
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
</label>
<label class="w-1/2 max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">抽奖人数</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>
</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>
</div>
<input type="checkbox" :checked="item.isUsed" @change="changePrizeStatus(item)"
class="mt-2 border-solid checkbox checkbox-secondary border-1" />
</label>
<label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">图片</span>
</div>
<select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture">
<option v-if="item.picture.id" :value="{ id: '', name: '', url: '' }"></option>
<option disabled selected>选择一张图片</option>
<option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}
</option>
</select>
</label>
<label class="w-full max-w-xs mb-10 form-control" v-if="item.separateCount">
<div class="label">
<span class="label-text">单次抽取个数</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>
<span>{{ se.count }}</span>
</div>
</li>
</ul>
<button v-else class="btn btn-secondary btn-xs">设置</button>
</div>
</label>
<label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">操作</span>
</div>
<div class="flex gap-2">
<button class="btn btn-error btn-sm" @click="delItem(item)">删除</button>
</div>
</label>
</li>
</ul>
<EditSeparateDialog :totalNumber="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
@submitData="submitData" />
<div>
<h2>{{ t('viewTitle.prizeManagement') }}</h2>
<div class="flex w-full gap-3">
<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"
/>
</svg>
<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"
>
<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
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">{{ t('table.prizeName') }}</span>
</div>
<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">{{ t('table.fullParticipation') }}</span>
</div>
<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">{{ t('table.numberParticipants') }}</span>
</div>
<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">{{ t('table.isDone') }}</span>
</div>
<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">{{ t('table.image') }}</span>
</div>
<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>{{ t('table.selectPicture') }}</option>
<option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}
</option>
</select>
</label>
<label v-if="item.separateCount" class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">{{ t('table.onceNumber') }}</span>
</div>
<div class="flex justify-start w-full h-full" @click="selectPrize(item)">
<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">{{ t('button.setting') }}</button>
</div>
</label>
<label class="w-full max-w-xs mb-10 form-control">
<div class="label">
<span class="label-text">{{ t('table.operation') }}</span>
</div>
<div class="flex gap-2">
<button class="btn btn-error btn-sm" @click="delItem(item)">{{ t('button.delete') }}</button>
</div>
</label>
</li>
</ul>
<EditSeparateDialog
:total-number="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
@submit-data="submitData"
/>
</div>
</template>
<style lang='scss' scoped></style>

View File

@@ -1,25 +1,27 @@
<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=>{
readmeHtml.value = md.render(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)
})
}
onMounted(() => {
readMd()
readMd()
})
</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>

View File

@@ -1,91 +1,97 @@
<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;
for (let i = 0; i < newList.length; i++) {
if (newList[i].children) {
cleanMenuList(newList[i].children);
}
if (!newList[i].meta) {
newList.splice(i, 1);
i--;
}
function cleanMenuList(menu: any) {
const newList = menu
for (let i = 0; i < newList.length; i++) {
if (newList[i].children) {
cleanMenuList(newList[i].children)
}
if (!newList[i].meta) {
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">
<li v-for="item in menuList" :key="item.name">
<details open v-if="item.children">
<summary>{{ item.meta.title }}</summary>
<ul>
<li v-for="subItem in item.children" :key="subItem.name">
<details open v-if="subItem.children">
<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)' : ''">{{
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)' : ''">{{
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>
<div class="flex min-h-[calc(100%-280px)]">
<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 v-if="item.children" open>
<summary>{{ item.meta.title }}</summary>
<ul>
<li v-for="subItem in item.children" :key="subItem.name">
<details v-if="subItem.children" open>
<summary>{{ subItem.meta!.title }}</summary>
<ul>
<li v-for="subSubItem in subItem.children" :key="subSubItem.name">
<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 :style="subItem.name === route.name ? 'background-color:rgba(12,12,12,0.2)' : ''"
@click="skip(subItem.path)"
>{{
subItem.meta!.title }}</a>
</li>
</ul>
<router-view class="mt-5"></router-view>
</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>
</nav>
<nav>
<a class="cursor-pointer link link-hover text-inherit" target="_blank" href="https://1kw20.fun">破山中贼易破心中贼难</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>
</a>
<a href="https://twitter.com/TaborSwift" target="_blank" class="cursor-pointer "><svg-icon name="twitter"></svg-icon></a>
<a href="https://www.instagram.com/log.z1997/" target="_blank" class="cursor-pointer ">
<svg-icon name="instagram"></svg-icon>
</a>
</div>
</nav>
<aside>
<p class="p-0 m-0">蜀ICP备2021028666号</p>
<p>Copyright © 2024 - All right reserved by Log1997</p>
</aside>
</footer>
</ul>
</details>
<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="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">{{ t('footer.self-reflection') }}</a>
</nav>
<nav>
<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" />
</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" />
</a>
</div>
</nav>
<aside>
<p class="p-0 m-0">
蜀ICP备2021028666号
</p>
<p>Copyright © 2024 - All right reserved by Log1997</p>
</aside>
</footer>
</template>
<style scoped></style>

View File

@@ -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>

View File

@@ -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,245 +25,296 @@ const prizeListContainerRef = ref()
const temporaryPrizeRef = ref()
const selectedPrize = ref<IPrizeConfig | null>()
// 获取prizeListRef高度
const getPrizeListHeight = () => {
let height = 200;
if (prizeListRef.value) {
height = (prizeListRef.value as HTMLElement).offsetHeight
}
function getPrizeListHeight() {
let height = 200
if (prizeListRef.value) {
height = (prizeListRef.value as HTMLElement).offsetHeight
}
return height
return height
}
const prizeShow = ref(structuredClone(isShowPrizeList.value))
const addTemporaryPrize = () => {
temporaryPrizeRef.value.showModal()
function addTemporaryPrize() {
temporaryPrizeRef.value.showModal()
}
const deleteTemporaryPrize = () => {
temporaryPrize.value.isShow = false
prizeConfig.setTemporaryPrize(temporaryPrize.value)
function deleteTemporaryPrize() {
temporaryPrize.value.isShow = false
prizeConfig.setTemporaryPrize(temporaryPrize.value)
}
const submitTemporaryPrize = () => {
if (!temporaryPrize.value.name || !temporaryPrize.value.count) {
alert('请填写完整信息')
function submitTemporaryPrize() {
if (!temporaryPrize.value.name || !temporaryPrize.value.count) {
// eslint-disable-next-line no-alert
alert(i18n.global.t('error.completeInformation'))
return
}
temporaryPrize.value.isShow = true
temporaryPrize.value.id = new Date().getTime().toString()
prizeConfig.setCurrentPrize(temporaryPrize.value)
}
function selectPrize(item: IPrizeConfig) {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
return
}
temporaryPrize.value.isShow = true
temporaryPrize.value.id=new Date().getTime().toString()
prizeConfig.setCurrentPrize(temporaryPrize.value)
if (selectedPrize.value.separateCount.countList.length > 1) {
return
}
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
},
],
}
}
const selectPrize = (item: IPrizeConfig) => {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
function submitData(value: any) {
selectedPrize.value!.separateCount.countList = value
selectedPrize.value = null
}
function changePersonCount() {
temporaryPrize.value.separateCount.countList = []
}
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])
if (selectedPrize.value.separateCount.countList.length > 1) {
return
return
}
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
}
]
}
}
const submitData = (value: any) => {
selectedPrize.value!.separateCount.countList = value;
selectedPrize.value = null
}
const 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){
prizeConfig.setCurrentPrize(localPrizeList.value[i])
return
}
}
}
}
onMounted(() => {
prizeListContainerRef.value.style.height = getPrizeListHeight() + 'px'
setCurrentPrize()
prizeListContainerRef.value.style.height = `${getPrizeListHeight()}px`
setCurrentPrize()
})
</script>
<template>
<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>
<div class="flex flex-col gap-3">
<label class="flex w-full max-w-xs">
<div class="label">
<span class="label-text">名称:</span>
</div>
<input type="text" v-model="temporaryPrize.name" placeholder="名称"
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>
</div>
<input type="checkbox" :checked="temporaryPrize.isAll"
@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>
</div>
<input type="number" v-model="temporaryPrize.count" @change="changePersonCount" placeholder="获奖人数"
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>
</div>
<input disabled type="number" v-model="temporaryPrize.isUsedCount" placeholder="获奖人数"
class="max-w-xs input-sm input input-bordered" />
</label>
<label class="flex w-full max-w-xs" v-if="temporaryPrize.separateCount">
<div class="label">
<span class="label-text">单次抽取个数</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>
<span>{{ se.count }}</span>
</div>
</li>
</ul>
<button v-else class="btn btn-secondary btn-xs">设置</button>
</div>
</label>
<label class="flex w-full max-w-xs">
<div class="label">
<span class="label-text">图片</span>
</div>
<select class="flex-1 w-12 select select-warning select-sm" v-model="temporaryPrize.picture">
<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">{{
picItem.name }}
</option>
</select>
</label>
</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>
</form>
</div>
<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">
{{ 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">{{ t('table.name') }}:</span>
</div>
</dialog>
<EditSeparateDialog :totalNumber="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
@submitData="submitData" />
<div ref="prizeListContainerRef">
<div class="h-20 w-72" :class="temporaryPrize.isShow ? 'current-prize' : ''" v-if="temporaryPrize.isShow">
<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>
<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" />
</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>
</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="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="cursor-pointer hover:text-blue-400" @click="addTemporaryPrize">
<svg-icon name="edit"></svg-icon>
</div>
</div>
<div class="tooltip tooltip-left" data-tip="删除">
<div class="cursor-pointer hover:text-blue-400" @click="deleteTemporaryPrize">
<svg-icon name="delete"></svg-icon>
</div>
</div>
</div>
</div>
<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">{{ t('table.fullParticipation') }}</span>
</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>
<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" />
</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>
</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="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>
</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>
</div>
</div>
</div>
</transition>
<input
type="checkbox" :checked="temporaryPrize.isAll"
class="mt-2 border-solid checkbox checkbox-secondary border-1"
@change="temporaryPrize.isAll = !temporaryPrize.isAll"
>
</label>
<label class="flex w-full max-w-xs">
<div class="label">
<span class="label-text">{{ t('table.setLuckyNumber') }}</span>
</div>
<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">{{ t('table.luckyPeopleNumber') }}</span>
</div>
<input
v-model="temporaryPrize.isUsedCount" disabled type="number" :placeholder="t('placeHolder.winnerCount')"
class="max-w-xs input-sm input input-bordered"
>
</label>
<label v-if="temporaryPrize.separateCount" class="flex w-full max-w-xs">
<div class="label">
<span class="label-text">{{ t('table.onceNumber') }}</span>
</div>
<div class="flex justify-start h-full" @click="selectPrize(temporaryPrize)">
<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">{{ t('button.setting') }}</button>
</div>
</label>
<label class="flex w-full max-w-xs">
<div class="label">
<span class="label-text">{{ t('table.image') }}</span>
</div>
<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>{{ t('table.selectPicture') }}</option>
<option v-for="picItem in localImageList" :key="picItem.id" class="w-auto" :value="picItem">{{
picItem.name }}
</option>
</select>
</label>
</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>
<div class="modal-action">
<form method="dialog" class="flex gap-3">
<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
:total-number="selectedPrize?.count" :separated-number="selectedPrize?.separateCount.countList"
@submit-data="submitData"
/>
<div ref="prizeListContainerRef">
<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"
/>
<figure class="w-10 h-10 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>
</div>
</transition>
<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="t('tooltip.edit')">
<div class="cursor-pointer hover:text-blue-400" @click="addTemporaryPrize">
<svg-icon name="edit" />
</div>
</div>
<div class="tooltip tooltip-left" :data-tip="t('tooltip.delete')">
<div class="cursor-pointer hover:text-blue-400" @click="deleteTemporaryPrize">
<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 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" :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>
</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"
/>
<!-- <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="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="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>
</div>
</transition>
</div>
<transition name="prize-operate" :appear="true">
<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>
</div>
</template>
<style lang='scss' scoped>
@@ -311,7 +366,6 @@ onMounted(() => {
translate: 0% 0%;
}
.current-prize::after {
content: "";
position: absolute;
@@ -401,4 +455,5 @@ onMounted(() => {
100% {
opacity: 1;
}
}</style>
}
</style>

File diff suppressed because it is too large Load Diff

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

@@ -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'

View File

@@ -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" }]
}

View File

@@ -1,143 +1,150 @@
/// <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 legacy from '@vitejs/plugin-legacy';
// 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:mode == 'file'?'./':'/log-lottery/',
plugins: [
vue(),
mode == 'file'?legacy({
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
}):null,
// vueDevTools(),
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
visualizer({
emitFile: true, //是否被触摸
filename: 'test.html', //生成分析网页文件名
open: true, //在默认用户代理中打开生成的文件
gzipSize: true, //从源代码中收集 gzip 大小并将其显示在图表中
brotliSize: true, //从源代码中收集 brotli 大小并将其显示在图表中
}),
return {
base: mode === 'file' ? './' : '/log-lottery/',
plugins: [
vue(),
mode === 'file'
? legacy({
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
})
: null,
// vueDevTools(),
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
visualizer({
emitFile: true, // 是否被触摸
filename: 'test.html', // 生成分析网页文件名
open: true, // 在默认用户代理中打开生成的文件
gzipSize: true, // 从源代码中收集 gzip 大小并将其显示在图表中
brotliSize: true, // 从源代码中收集 brotli 大小并将其显示在图表中
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
}),
AutoImport({
resolvers: [
// 自动导入图标组件
IconsResolver({
prefix: 'Icon',
}),
],
dts: path.resolve(path.resolve(__dirname, 'src'), 'auto-imports.d.ts'),
}),
Components({
resolvers: [
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep'],
}),
],
dts: path.resolve(path.resolve(__dirname, 'src'), 'components.d.ts'),
}),
Icons({
autoInstall: true,
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
}),
AutoImport({
resolvers: [
// 自动导入图标组件
IconsResolver({
prefix: 'Icon',
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/style/global.scss" as *;',
},
},
// postcss: {
// plugins: [
// require('tailwindcss'),
// require('autoprefixer'),
// ]
// }
dts: path.resolve(path.resolve(__dirname, 'src'), 'auto-imports.d.ts'),
}),
Components({
resolvers: [
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep'],
}),
],
dts: path.resolve(path.resolve(__dirname, 'src'), 'components.d.ts'),
}),
Icons({
autoInstall: true,
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "@/style/global.scss" as *;',
},
server: {
host: 'localhost',
port: 6719,
proxy: {
'/api': {
target: env.VITE_BASE_URL,
// 是否跨域
changeOrigin: true,
// 路径重写
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
// postcss: {
// plugins: [
// require('tailwindcss'),
// require('autoprefixer'),
// ]
// }
},
server: {
host: 'localhost',
port: 6719,
proxy: {
'/api': {
target: env.VITE_BASE_URL,
// 是否跨域
changeOrigin: true,
// 路径重写
rewrite: path => path.replace(/^\/api/, ''),
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
build: {
minify: 'terser',
terserOptions: {
compress: {
// 生产环境时移除console
drop_console: true,
drop_debugger: true,
},
build: {
minify: 'terser',
terserOptions: {
compress: {
//生产环境时移除console
drop_console: true,
drop_debugger: true,
},
},
// 关闭文件计算
reportCompressedSize: false,
// 关闭生成map文件 可以达到缩小打包体积
sourcemap: false, // 这个生产环境一定要关闭,不然打包的产物会很大
rollupOptions: {
output: {
chunkFileNames: `js/${chunkName}-[hash].js`, // 引入文件名的名称
entryFileNames: `js/${chunkName}-[hash].js`, // 包的入口文件名称
assetFileNames: `[ext]/${chunkName}-[hash].[ext]`, // 资源文件像 字体,图片等
manualChunks(id: any): string {
if (id.includes('node_modules')) {
return id
.toString()
.split('node_modules/')[1]
.split('/')[0]
.toString();
}
},
},
},
},
// 关闭文件计算
reportCompressedSize: false,
// 关闭生成map文件 可以达到缩小打包体积
sourcemap: false, // 这个生产环境一定要关闭,不然打包的产物会很大
rollupOptions: {
output: {
chunkFileNames: `js/${chunkName}-[hash].js`, // 引入文件名的名称
entryFileNames: `js/${chunkName}-[hash].js`, // 包的入口文件名称
assetFileNames: `[ext]/${chunkName}-[hash].[ext]`, // 资源文件像 字体,图片等
manualChunks(id: any): string {
if (id.includes('node_modules')) {
return id
.toString()
.split('node_modules/')[1]
.split('/')[0]
.toString()
}
},
},
// 使用这个必须在上面加/// <reference types="vitest" /> 不然会有类型报错
test: {
globals: true, // --> 0.8.1+ 请修改成globals
environment: 'jsdom',
// include: ['**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
// passWithNoTests: true,
transformMode: {
web: [/\.[jt]sx$/],
},
},
};
});
},
},
// 使用这个必须在上面加/// <reference types="vitest" /> 不然会有类型报错
test: {
globals: true, // --> 0.8.1+ 请修改成globals
environment: 'jsdom',
// include: ['**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
// passWithNoTests: true,
transformMode: {
web: [/\.[jt]sx$/],
},
},
}
})