feat: i18n,add en and zh-cn language.

This commit is contained in:
ex_zhangwenlei@exiot.cmcc
2024-11-22 14:53:36 +08:00
parent 8e6eff54f4
commit 391142223f
18 changed files with 189 additions and 116 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -35,7 +35,7 @@ const actionsColumns = computed<any[]>(() => {
<tr> <tr>
<th></th> <th></th>
<th v-for="(item, index) in dataColumns" :key="index">{{ item.label }}</th> <th v-for="(item, index) in dataColumns" :key="index">{{ item.label }}</th>
<th v-for="(item, index) in actionsColumns" :key="index">操作</th> <th v-for="(item, index) in actionsColumns" :key="index">{{ $t('table.operation') }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@@ -56,7 +56,7 @@ const actionsColumns = computed<any[]>(() => {
</tbody> </tbody>
<tbody v-else> <tbody v-else>
<tr> <tr>
<td colspan="5" class="text-center">暂无数据</td> <td colspan="5" class="text-center">{{ $t('table.noneData') }}</td>
</tr> </tr>
</tbody> </tbody>
<!-- foot --> <!-- foot -->

View File

@@ -74,12 +74,12 @@ onMounted(() => {
<template> <template>
<dialog id="my_modal_1" ref="separatedNumberRef" class="z-50 overflow-hidden border-none modal"> <dialog id="my_modal_1" ref="separatedNumberRef" class="z-50 overflow-hidden border-none modal">
<div class="overflow-hidden modal-box"> <div class="overflow-hidden modal-box">
<h3 class="pb-6 text-lg font-bold">提示!</h3> <h3 class="pb-6 text-lg font-bold">{{ $t('dialog.titleTip') }}</h3>
<p class="pb-8">单次抽取只能抽取10位</p> <p class="pb-8">{{ $t('dialog.dialogSingleDrawLimit') }}</p>
<div class="flex justify-between px-3 text-center separated-number"> <div class="flex justify-between px-3 text-center separated-number">
<div v-for="item in props.totalNumber" :key="item" <div v-for="item in props.totalNumber" :key="item"
class="relative flex flex-col items-center cursor-pointer"> class="relative flex flex-col items-center cursor-pointer">
<div class="absolute mb-12 text-center tooltip -top-5 hover:text-lg" data-tip="左键切割" <div class="absolute mb-12 text-center tooltip -top-5 hover:text-lg" :data-tip="$t('tooltip.leftClick')"
@click.left="editScale(item)"> @click.left="editScale(item)">
<span> {{ item }}</span> <span> {{ item }}</span>
</div> </div>
@@ -89,7 +89,7 @@ onMounted(() => {
<div class="modal-action"> <div class="modal-action">
<form method="dialog"> <form method="dialog">
<!-- if there is a button in form, it will close the modal --> <!-- if there is a button in form, it will close the modal -->
<button class="btn" @click="clearData">关闭</button> <button class="btn" @click="clearData">{{ $t('button.close') }} }}</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -100,20 +100,19 @@ watch(currentMusic, (val: any) => {
<svg-icon name="home"></svg-icon> <svg-icon name="home"></svg-icon>
</div> </div>
</div> </div>
<div v-else class="tooltip tooltip-left" data-tip="设置/配置"> <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" <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"> @click="enterConfig">
<svg-icon name="setting"></svg-icon> <svg-icon name="setting"></svg-icon>
</div> </div>
</div> </div>
<div class="tooltip tooltip-left" :data-tip="currentMusic.item ? currentMusic.item.name+'\n\r &nbsp; 右键下一曲' : '没有音乐可以播放'"> <div class="tooltip tooltip-left" :data-tip="currentMusic.item ? currentMusic.item.name+'\n\r &nbsp;'+$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" <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"> @click="playMusic(currentMusic.item)" @click.right.prevent="nextPlay">
<svg-icon :name="currentMusic.paused ? 'play' : 'pause'"></svg-icon> <svg-icon :name="currentMusic.paused ? 'play' : 'pause'"></svg-icon>
</div> </div>
</div> </div>
<!-- <div class="bg-blue-300 cursor-pointer" @click="nextPlay">下一首</div> -->
</div> </div>
</template> </template>

View File

@@ -51,9 +51,11 @@ export default {
identity:'Identity', identity:'Identity',
isLucky:'Is Lucky', isLucky:'Is Lucky',
operation:'Operation', operation:'Operation',
setLuckyNumber:'Set Lucky Number',
luckyPeopleNumber:'Lucky People Number', luckyPeopleNumber:'Lucky People Number',
detail:'Detail', detail:'Detail',
noneData:'No Data',
// prize configuration // prize configuration
fullParticipation:'FullParticipation', fullParticipation:'FullParticipation',
numberParticipants:'NumberParticipants', numberParticipants:'NumberParticipants',
@@ -75,6 +77,7 @@ export default {
highlightColor:'HighLight Color', highlightColor:'HighLight Color',
patternSetting:'Pattern Setting', patternSetting:'Pattern Setting',
alwaysDisplay:'Always Display Prize List', alwaysDisplay:'Always Display Prize List',
selectPicture:'Select a Picture'
}, },
dialog:{ dialog:{
titleTip:'Tip!', titleTip:'Tip!',
@@ -103,7 +106,42 @@ export default {
edit:'Edit', edit:'Edit',
delete:'Delete' 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',
noInfoAndImport:'No Info and import it',
useDefault:'Use Default Data',
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:{ footer:{
'self-reflection':'Turn inward and examine yourself when you encounter difficulties in life.', 'self-reflection':'Turn inward and examine yourself when you encounter difficulties in life.',
'thiefEasy':'Thief difficult mountain thief easily, breaking heart.' 'thiefEasy':'Thief difficult mountain thief easily, breaking heart.'

View File

@@ -7,18 +7,21 @@ export type Language='en'|'zhCn'
export const languageList=[ export const languageList=[
{ {
key:'zhCn', key:'zhCn',
name:'中文' name:'中文',
flag:'zh-cn'
}, },
{ {
key:'en', key:'en',
name:'English' name:'English',
flag:'en-us'
} }
] ]
export const browserLanguage=navigator.language.toLowerCase().indexOf('zh')>=0?'zhCn':'en';
const globalConfig=JSON.parse(localStorage.getItem('globalConfig')||'{}').globalConfig||{}
// 创建i18n // 创建i18n
const i18n = createI18n({ const i18n = createI18n({
locale: JSON.parse(localStorage.getItem("globalConfig")?localStorage.getItem("globalConfig") as string:"{globalConfig:{language:'zhCn'}}").globalConfig.language || "zhCn", // 语言标识 locale: globalConfig.language||browserLanguage,
globalInjection: true, // 全局注入,可以直接使用$t globalInjection: true, // 全局注入,可以直接使用$t
// 处理报错: Uncaught (in promise) SyntaxError: Not available in legacy mode (at message-compiler.esm-bundler.js:54:19)
legacy:false, legacy:false,
messages: { messages: {
zhCn, zhCn,

View File

@@ -22,7 +22,9 @@ export default {
reset: '重置', reset: '重置',
play: '播放', play: '播放',
setLayout:'重设布局', setLayout:'重设布局',
close:'关闭' close:'关闭',
noInfoAndImport:'暂无人员信息,前往导入',
useDefault:'使用默认数据'
}, },
sidebar:{ sidebar:{
personConfiguration:'人员配置', personConfiguration:'人员配置',
@@ -51,9 +53,11 @@ export default {
identity:'身份', identity:'身份',
isLucky:'是否中奖', isLucky:'是否中奖',
operation:'操作', operation:'操作',
setLuckyNumber:'设置中奖人数',
luckyPeopleNumber:'中奖人数', luckyPeopleNumber:'中奖人数',
detail:'详细信息', detail:'详细信息',
noneData:'暂无数据',
// prize configuration // prize configuration
fullParticipation:'全员参加', fullParticipation:'全员参加',
numberParticipants:'抽奖人数', numberParticipants:'抽奖人数',
@@ -75,6 +79,7 @@ export default {
highlightColor:'高亮颜色', highlightColor:'高亮颜色',
patternSetting:'图案设置', patternSetting:'图案设置',
alwaysDisplay:'常显奖项列表', alwaysDisplay:'常显奖项列表',
selectPicture:'选择一张图片'
}, },
dialog:{ dialog:{
titleTip:'提示!', titleTip:'提示!',
@@ -103,7 +108,41 @@ export default {
edit:'编辑', edit:'编辑',
delete:'删除' 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:{ footer:{
'self-reflection':'行有不得,反求诸己', 'self-reflection':'行有不得,反求诸己',
'thiefEasy':'破山中贼易,破心中贼难' 'thiefEasy':'破山中贼易,破心中贼难'

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { defaultMusicList, defaultImageList, defaultPatternList } from './data' import { defaultMusicList, defaultImageList, defaultPatternList } from './data'
import { IMusic, IImage } from '@/types/storeType'; import { IMusic, IImage } from '@/types/storeType';
import i18n,{Language} from '@/locales/i18n' import i18n,{browserLanguage} from '@/locales/i18n'
// import { IPrizeConfig } from '@/types/storeType'; // import { IPrizeConfig } from '@/types/storeType';
export const useGlobalConfig = defineStore('global', { export const useGlobalConfig = defineStore('global', {
state() { state() {
@@ -9,8 +9,8 @@ export const useGlobalConfig = defineStore('global', {
globalConfig: { globalConfig: {
rowCount: 17, rowCount: 17,
isSHowPrizeList: true, isSHowPrizeList: true,
topTitle: '大明内阁六部御前奏对', topTitle: i18n.global.t('data.defaultTitle'),
language:'zhCn', language:browserLanguage,
theme: { theme: {
name: 'dracula', name: 'dracula',
detail: { primary: '#0f5fd3' }, detail: { primary: '#0f5fd3' },
@@ -223,8 +223,8 @@ export const useGlobalConfig = defineStore('global', {
this.globalConfig = { this.globalConfig = {
rowCount: 17, rowCount: 17,
isSHowPrizeList: true, isSHowPrizeList: true,
topTitle: '大明内阁六部御前奏对', topTitle: i18n.global.t('data.defaultTitle'),
language: 'zhCn', language: browserLanguage,
theme: { theme: {
name: 'dracula', name: 'dracula',
detail: { primary: '#0f5fd3' }, detail: { primary: '#0f5fd3' },

View File

@@ -10,7 +10,7 @@ import 'vue3-colorpicker/style.css';
import { isRgbOrRgba, isHex } from '@/utils/color' import { isRgbOrRgba, isHex } from '@/utils/color'
import PatternSetting from './components/PatternSetting.vue' import PatternSetting from './components/PatternSetting.vue'
import {languageList} from '@/locales/i18n' import {languageList} from '@/locales/i18n'
import {Language} from '@/locales/i18n' import i18n from '@/locales/i18n'
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const prizeConfig= useStore().prizeConfig const prizeConfig= useStore().prizeConfig
@@ -44,11 +44,11 @@ const formErr = ref({
const schema = zod.object({ const schema = zod.object({
rowCount: zod.number({ rowCount: zod.number({
required_error: '必填项', required_error: i18n.global.t('error.require'),
invalid_type_error: '必须填入数字', invalid_type_error: i18n.global.t('error.requireNumber'),
}) })
.min(1, '最小为1') .min(1, i18n.global.t('error.minNumber1'))
.max(100, '最大为100') .max(100, i18n.global.t('error.maxNumber100'))
// 格式化 // 格式化
@@ -169,7 +169,7 @@ onMounted(() => {
</div> </div>
</dialog> </dialog>
<div> <div>
<h2>全局配置</h2> <h2>{{$t('viewTitle.globalSetting')}}</h2>
<div class="mb-8"> <div class="mb-8">
<button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">{{$t('button.resetAllData')}}</button> <button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">{{$t('button.resetAllData')}}</button>
</div> </div>
@@ -178,7 +178,7 @@ onMounted(() => {
<div class="label"> <div class="label">
<span class="label-text">{{$t('table.title')}}</span> <span class="label-text">{{$t('table.title')}}</span>
</div> </div>
<input type="text" v-model="topTitleValue" placeholder="输入标题" <input type="text" v-model="topTitleValue" :placeholder="$t('placeHolder.enterTitle')"
class="w-full max-w-xs input input-bordered" /> class="w-full max-w-xs input input-bordered" />
</div> </div>
</label> </label>

View File

@@ -69,13 +69,13 @@ watch(() => imgUploadToast.value, (val) => {
<template> <template>
<div class="toast toast-top toast-end"> <div class="toast toast-top toast-end">
<div class="alert alert-error" v-if="imgUploadToast == 2"> <div class="alert alert-error" v-if="imgUploadToast == 2">
<span>上传失败</span> <span>{{ $t('error.uploadFail') }}</span>
</div> </div>
<div class="alert alert-success" v-if="imgUploadToast == 1"> <div class="alert alert-success" v-if="imgUploadToast == 1">
<span>上传成功</span> <span>{{ $t('error.uploadSuccess') }}</span>
</div> </div>
<div class="alert alert-error" v-if="imgUploadToast == 3"> <div class="alert alert-error" v-if="imgUploadToast == 3">
<span>不是图片</span> <span>{{ $t('error.notImage') }}</span>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ import * as XLSX from 'xlsx'
import { readFileBinary } from '@/utils/file' import { readFileBinary } from '@/utils/file'
import { addOtherInfo } from '@/utils' import { addOtherInfo } from '@/utils'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue' import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
import i18n from '@/locales/i18n'
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig) const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const limitType = '.xlsx,.xls' const limitType = '.xlsx,.xls'
@@ -38,9 +38,9 @@ const exportData = () => {
delete data[i].prizeId delete data[i].prizeId
// 修改字段名称 // 修改字段名称
if (data[i].isWin) { if (data[i].isWin) {
data[i].isWin = '是' data[i].isWin = i18n.global.t('data.yes')
} else { } else {
data[i].isWin = '否' data[i].isWin = i18n.global.t('data.no')
} }
// 格式化数组为 // 格式化数组为
data[i].prizeTime = data[i].prizeTime.join(',') data[i].prizeTime = data[i].prizeTime.join(',')
@@ -48,13 +48,13 @@ const exportData = () => {
} }
let dataString = JSON.stringify(data) let dataString = JSON.stringify(data)
dataString = dataString dataString = dataString
.replaceAll(/uid/g, '编号') .replaceAll(/uid/g, i18n.global.t('data.number'))
.replaceAll(/isWin/g, '是否中奖') .replaceAll(/isWin/g, i18n.global.t('data.isWin'))
.replaceAll(/department/g, '部门') .replaceAll(/department/g, i18n.global.t('data.department'))
.replaceAll(/name/g, '姓名') .replaceAll(/name/g, i18n.global.t('data.name'))
.replaceAll(/identity/g, '身份') .replaceAll(/identity/g, i18n.global.t('data.identity'))
.replaceAll(/prizeName/g, '获奖') .replaceAll(/prizeName/g, i18n.global.t('data.prizeName'))
.replaceAll(/prizeTime/g, '获奖时间') .replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime'))
data = JSON.parse(dataString) data = JSON.parse(dataString)
@@ -80,30 +80,30 @@ const delPersonItem = (row: IPersonConfig) => {
const tableColumns = [ const tableColumns = [
{ {
label: '编号', label: i18n.global.t('data.number'),
props: 'uid', props: 'uid',
}, },
{ {
label: '姓名', label: i18n.global.t('data.name'),
props: 'name', props: 'name',
}, },
{ {
label: '部门', label: i18n.global.t('data.department'),
props: 'department', props: 'department',
}, },
{ {
label: '身份', label: i18n.global.t('data.identity'),
props: 'identity', props: 'identity',
}, },
{ {
label: '是否已中奖', label: i18n.global.t('data.isWin'),
props: 'isWin', props: 'isWin',
formatValue(row: IPersonConfig) { formatValue(row: IPersonConfig) {
return row.isWin ? '是' : '否' return row.isWin ? i18n.global.t('data.yes') : i18n.global.t('data.no')
} }
}, },
{ {
label: '操作', label: i18n.global.t('data.operation'),
actions: [ actions: [
// { // {
// label: '编辑', // label: '编辑',
@@ -113,7 +113,7 @@ const tableColumns = [
// } // }
// }, // },
{ {
label: '删除', label: i18n.global.t('data.delete'),
type: 'btn-error', type: 'btn-error',
onClick: (row: IPersonConfig) => { onClick: (row: IPersonConfig) => {
delPersonItem(row) delPersonItem(row)
@@ -156,12 +156,12 @@ onMounted(() => {
</dialog> </dialog>
<div class="min-w-1000px"> <div class="min-w-1000px">
<h2>人员管理</h2> <h2>{{ $t('viewTitle.personManagement') }}</h2>
<div class="flex gap-3"> <div class="flex gap-3">
<button class="btn btn-error btn-sm" @click="delAllDataDialog.showModal()">{{ $t('button.allDelete') }}</button> <button class="btn btn-error btn-sm" @click="delAllDataDialog.showModal()">{{ $t('button.allDelete') }}</button>
<div class="tooltip tooltip-bottom" :data-tip="$t('tooltip.downloadTemplateTip')"> <div class="tooltip tooltip-bottom" :data-tip="$t('tooltip.downloadTemplateTip')">
<a class="no-underline btn btn-secondary btn-sm" download="人口登记表.xlsx" target="_blank" <a class="no-underline btn btn-secondary btn-sm" :download="$t('data.xlsxName')" target="_blank"
href="/log-lottery/人口登记表.xlsx">{{ $t('button.downloadTemplate') }}</a> :href="'/log-lottery/'+$t('data.xlsxName')">{{ $t('button.downloadTemplate') }}</a>
</div> </div>
<div class=""> <div class="">
<label for="explore"> <label for="explore">
@@ -173,8 +173,6 @@ onMounted(() => {
<span class="btn btn-primary btn-sm">{{ $t('button.importData') }}</span> <span class="btn btn-primary btn-sm">{{ $t('button.importData') }}</span>
</div> </div>
</label> </label>
<!-- <button class="btn btn-primary btn-sm">上传excel</button> -->
</div> </div>
<button class="btn btn-error btn-sm" @click="resetDataDialog.showModal()">{{ $t('button.resetData') }}</button> <button class="btn btn-error btn-sm" @click="resetDataDialog.showModal()">{{ $t('button.resetData') }}</button>
<button class="btn btn-accent btn-sm" @click="exportData">{{ $t('button.exportResult') }}</button> <button class="btn btn-accent btn-sm" @click="exportData">{{ $t('button.exportResult') }}</button>

View File

@@ -5,7 +5,7 @@ import useStore from '@/store'
import { IPersonConfig } from '@/types/storeType'; import { IPersonConfig } from '@/types/storeType';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import DaiysuiTable from '@/components/DaiysuiTable/index.vue' import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
import i18n from '@/locales/i18n'
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: alreadyPersonDetail } = storeToRefs(personConfig) const { getAlreadyPersonList: alreadyPersonList, getAlreadyPersonDetail: alreadyPersonDetail } = storeToRefs(personConfig)
@@ -25,32 +25,32 @@ const handleMoveNotPerson = (row: IPersonConfig) => {
const tableColumnsList = [ const tableColumnsList = [
{ {
label: '编号', label: i18n.global.t('data.number'),
props: 'uid', props: 'uid',
sort: true sort: true
}, },
{ {
label: '姓名', label: i18n.global.t('data.number'),
props: 'name', props: 'name',
}, },
{ {
label: '部门', label: i18n.global.t('data.department'),
props: 'department', props: 'department',
}, },
{ {
label: '身份', label: i18n.global.t('data.identity'),
props: 'identity', props: 'identity',
}, },
{ {
label: '奖品', label: i18n.global.t('data.prizeName'),
props: 'prizeName', props: 'prizeName',
sort: true sort: true
}, },
{ {
label: '操作', label: i18n.global.t('data.operation'),
actions: [ actions: [
{ {
label: '移入未中奖名单', label: i18n.global.t('data.removePerson'),
type: 'btn-info', type: 'btn-info',
onClick: (row: IPersonConfig) => { onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row) handleMoveNotPerson(row)
@@ -61,37 +61,37 @@ const tableColumnsList = [
] ]
const tableColumnsDetail = [ const tableColumnsDetail = [
{ {
label: '编号', label: i18n.global.t('data.number'),
props: 'uid', props: 'uid',
sort: true sort: true
}, },
{ {
label: '姓名', label: i18n.global.t('data.number'),
props: 'name', props: 'name',
}, },
{ {
label: '部门', label: i18n.global.t('data.department'),
props: 'department', props: 'department',
}, },
{ {
label: '身份', label: i18n.global.t('data.identity'),
props: 'identity', props: 'identity',
}, },
{ {
label: '奖品', label: i18n.global.t('data.prizeName'),
props: 'prizeName', props: 'prizeName',
sort: true sort: true
}, },
{ {
label: '中奖时间', label: i18n.global.t('data.prizeTime'),
props: 'prizeTime', props: 'prizeTime',
}, },
{ {
label: '操作', label: i18n.global.t('data.operation'),
actions: [ actions: [
{ {
label: '移入未中奖名单', label: i18n.global.t('data.removePerson'),
type: 'btn-info', type: 'btn-info',
onClick: (row: IPersonConfig) => { onClick: (row: IPersonConfig) => {
handleMoveNotPerson(row) handleMoveNotPerson(row)
@@ -108,7 +108,6 @@ const tableColumnsDetail = [
<h2>{{ $t('viewTitle.winnerManagement') }}</h2> <h2>{{ $t('viewTitle.winnerManagement') }}</h2>
<div class="flex items-center justify-start gap-10"> <div class="flex items-center justify-start gap-10">
<!-- <button class="btn btn-error btn-sm" @click="deleteAll">全部删除</button> -->
<div> <div>
<span>{{$t('table.luckyPeopleNumber')}}</span> <span>{{$t('table.luckyPeopleNumber')}}</span>
<span>{{ alreadyPersonList.length }}</span> <span>{{ alreadyPersonList.length }}</span>

View File

@@ -5,7 +5,7 @@ import { IPrizeConfig } from '@/types/storeType'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import localforage from 'localforage' import localforage from 'localforage'
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue' import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
import i18n from '@/locales/i18n'
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
name: 'imgStore' name: 'imgStore'
}) })
@@ -22,7 +22,7 @@ const selectedPrize = ref<IPrizeConfig | null>()
const addPrize = () => { const addPrize = () => {
const defaultPrizeCOnfig: IPrizeConfig = { const defaultPrizeCOnfig: IPrizeConfig = {
id: new Date().getTime().toString(), id: new Date().getTime().toString(),
name: '奖项', name: i18n.global.t('data.prizeName'),
sort: 0, sort: 0,
isAll: false, isAll: false,
count: 1, count: 1,
@@ -178,7 +178,7 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
<div class="label"> <div class="label">
<span class="label-text">{{ $t('table.prizeName') }}</span> <span class="label-text">{{ $t('table.prizeName') }}</span>
</div> </div>
<input type="text" v-model="item.name" placeholder="名称" <input type="text" v-model="item.name" :placeholder="$t('placeHolder.name')"
class="w-full max-w-xs input-sm input input-bordered" /> class="w-full max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="w-1/2 max-w-xs mb-10 form-control"> <label class="w-1/2 max-w-xs mb-10 form-control">
@@ -192,19 +192,12 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
<div class="label"> <div class="label">
<span class="label-text">{{ $t('table.numberParticipants') }}</span> <span class="label-text">{{ $t('table.numberParticipants') }}</span>
</div> </div>
<input type="number" v-model="item.count" placeholder="获奖人数" @change="changePrizePerson(item)" <input type="number" v-model="item.count" :placeholder="$t('placeHolder.winnerCount')" @change="changePrizePerson(item)"
class="w-full max-w-xs p-0 m-0 input-sm input input-bordered" /> class="w-full max-w-xs p-0 m-0 input-sm input input-bordered" />
<div class="tooltip tooltip-bottom" :data-tip="$t('table.isDone') + item.isUsedCount + '/' + item.count"> <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"></progress> <progress class="w-full progress" :value="item.isUsedCount" :max="item.count"></progress>
</div> </div>
</label> </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"> <label class="w-1/2 max-w-xs mb-10 form-control">
<div class="label"> <div class="label">
<span class="label-text">{{ $t('table.isDone') }}</span> <span class="label-text">{{ $t('table.isDone') }}</span>
@@ -218,7 +211,7 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
</div> </div>
<select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture"> <select class="w-full max-w-xs select select-warning select-sm" v-model="item.picture">
<option v-if="item.picture.id" :value="{ id: '', name: '', url: '' }"><span></span></option> <option v-if="item.picture.id" :value="{ id: '', name: '', url: '' }"><span></span></option>
<option disabled selected>选择一张图片</option> <option disabled selected>{{ $t('table.selectPicture') }}</option>
<option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }} <option v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ picItem.name }}
</option> </option>
</select> </select>
@@ -233,7 +226,7 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
<li class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated" <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"> v-for="se in item.separateCount.countList" :key="se.id">
<div class="flex items-center justify-center w-full h-full tooltip" <div class="flex items-center justify-center w-full h-full tooltip"
:data-tip="'已抽取:' + se.isUsedCount + '/' + se.count"> :data-tip="$t('tooltip.doneCount') + se.isUsedCount + '/' + se.count">
<div class="absolute left-0 z-50 h-full bg-blue-300/80" <div class="absolute left-0 z-50 h-full bg-blue-300/80"
:style="`width:${se.isUsedCount * 100 / se.count}%`"></div> :style="`width:${se.isUsedCount * 100 / se.count}%`"></div>
<span>{{ se.count }}</span> <span>{{ se.count }}</span>

View File

@@ -1,10 +1,11 @@
<script setup lang='ts'> <script setup lang='ts'>
import {ref,onMounted} from 'vue' import {ref,onMounted} from 'vue'
import markdownit from 'markdown-it' import markdownit from 'markdown-it'
import i18n from '@/locales/i18n'
const md = markdownit() const md = markdownit()
const readmeHtml=ref('') const readmeHtml=ref('')
const readMd=()=>{ const readMd=()=>{
fetch('/log-lottery/readme.md') fetch('/log-lottery/'+i18n.global.t('data.readmeName'))
.then(res=>res.text()) .then(res=>res.text())
.then(res=>{ .then(res=>{
readmeHtml.value = md.render(res) readmeHtml.value = md.render(res)

View File

@@ -9,6 +9,7 @@ import { IPrizeConfig } from '../../types/storeType';
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue' import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
import i18n from '@/locales/i18n'
const prizeConfig = useStore().prizeConfig const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const system = useStore().system const system = useStore().system
@@ -41,7 +42,7 @@ const deleteTemporaryPrize = () => {
} }
const submitTemporaryPrize = () => { const submitTemporaryPrize = () => {
if (!temporaryPrize.value.name || !temporaryPrize.value.count) { if (!temporaryPrize.value.name || !temporaryPrize.value.count) {
alert('请填写完整信息') alert(i18n.global.t('error.completeInformation'))
return return
} }
@@ -94,18 +95,18 @@ onMounted(() => {
<div class="flex items-center"> <div class="flex items-center">
<dialog id="my_modal_1" ref="temporaryPrizeRef" class="border-none modal"> <dialog id="my_modal_1" ref="temporaryPrizeRef" class="border-none modal">
<div class="modal-box"> <div class="modal-box">
<h3 class="text-lg font-bold">增加临时抽奖</h3> <h3 class="text-lg font-bold">{{$t('dialog.titleTemporary')}}</h3>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<label class="flex w-full max-w-xs"> <label class="flex w-full max-w-xs">
<div class="label"> <div class="label">
<span class="label-text">名称:</span> <span class="label-text">{{$t('table.name')}}:</span>
</div> </div>
<input type="text" v-model="temporaryPrize.name" placeholder="名称" <input type="text" v-model="temporaryPrize.name" :placeholder="$t('placeHolder.name')"
class="max-w-xs input-sm input input-bordered" /> class="max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="flex w-full max-w-xs"> <label class="flex w-full max-w-xs">
<div class="label"> <div class="label">
<span class="label-text">是否全员参加</span> <span class="label-text">{{$t('table.fullParticipation')}}</span>
</div> </div>
<input type="checkbox" :checked="temporaryPrize.isAll" <input type="checkbox" :checked="temporaryPrize.isAll"
@change="temporaryPrize.isAll = !temporaryPrize.isAll" @change="temporaryPrize.isAll = !temporaryPrize.isAll"
@@ -113,21 +114,21 @@ onMounted(() => {
</label> </label>
<label class="flex w-full max-w-xs"> <label class="flex w-full max-w-xs">
<div class="label"> <div class="label">
<span class="label-text">获奖人数</span> <span class="label-text">{{$t('table.setLuckyNumber')}}</span>
</div> </div>
<input type="number" v-model="temporaryPrize.count" @change="changePersonCount" placeholder="获奖人数" <input type="number" v-model="temporaryPrize.count" @change="changePersonCount" :placeholder="$t('placeHolder.winnerCount')"
class="max-w-xs input-sm input input-bordered" /> class="max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="flex w-full max-w-xs"> <label class="flex w-full max-w-xs">
<div class="label"> <div class="label">
<span class="label-text">已获奖人数</span> <span class="label-text">{{$t('table.luckyPeopleNumber')}}</span>
</div> </div>
<input disabled type="number" v-model="temporaryPrize.isUsedCount" placeholder="获奖人数" <input disabled type="number" v-model="temporaryPrize.isUsedCount" :placeholder="$t('placeHolder.winnerCount')"
class="max-w-xs input-sm input input-bordered" /> class="max-w-xs input-sm input input-bordered" />
</label> </label>
<label class="flex w-full max-w-xs" v-if="temporaryPrize.separateCount"> <label class="flex w-full max-w-xs" v-if="temporaryPrize.separateCount">
<div class="label"> <div class="label">
<span class="label-text">单次抽取个数</span> <span class="label-text">{{$t('table.onceNumber ')}}</span>
</div> </div>
<div class="flex justify-start h-full" @click="selectPrize(temporaryPrize)"> <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" <ul class="flex flex-wrap w-full h-full gap-1 p-0 pt-1 m-0 cursor-pointer"
@@ -135,24 +136,24 @@ onMounted(() => {
<li class="relative flex items-center justify-center w-8 h-8 bg-slate-600/60 separated" <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"> v-for="se in temporaryPrize.separateCount.countList" :key="se.id">
<div class="flex items-center justify-center w-full h-full tooltip" <div class="flex items-center justify-center w-full h-full tooltip"
:data-tip="'已抽取:' + se.isUsedCount + '/' + se.count"> :data-tip="$t('tooltip.doneCount') + se.isUsedCount + '/' + se.count">
<div class="absolute left-0 z-50 h-full bg-blue-300/80" <div class="absolute left-0 z-50 h-full bg-blue-300/80"
:style="`width:${se.isUsedCount * 100 / se.count}%`"></div> :style="`width:${se.isUsedCount * 100 / se.count}%`"></div>
<span>{{ se.count }}</span> <span>{{ se.count }}</span>
</div> </div>
</li> </li>
</ul> </ul>
<button v-else class="btn btn-secondary btn-xs">设置</button> <button v-else class="btn btn-secondary btn-xs">{{$t('button.setting')}}</button>
</div> </div>
</label> </label>
<label class="flex w-full max-w-xs"> <label class="flex w-full max-w-xs">
<div class="label"> <div class="label">
<span class="label-text">图片</span> <span class="label-text">{{$t('table.image')}}</span>
</div> </div>
<select class="flex-1 w-12 select select-warning select-sm" v-model="temporaryPrize.picture"> <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: '' }"><span></span> <option v-if="temporaryPrize.picture.id" :value="{ id: '', name: '', url: '' }"><span></span>
</option> </option>
<option disabled selected>选择一张图片</option> <option disabled selected>{{$t('table.selectPicture')}}</option>
<option class="w-auto" v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{ <option class="w-auto" v-for="picItem in localImageList" :key="picItem.id" :value="picItem">{{
picItem.name }} picItem.name }}
</option> </option>
@@ -161,8 +162,8 @@ onMounted(() => {
</div> </div>
<div class="modal-action"> <div class="modal-action">
<form method="dialog" class="flex gap-3"> <form method="dialog" class="flex gap-3">
<button class="btn btn-sm" @click="submitTemporaryPrize">确定</button> <button class="btn btn-sm" @click="submitTemporaryPrize">{{ $t('button.confirm') }}</button>
<button class="btn btn-sm">取消</button> <button class="btn btn-sm">{{ $t('button.cancel') }}</button>
</form> </form>
</div> </div>
</div> </div>
@@ -190,12 +191,12 @@ onMounted(() => {
<!-- <p class="p-0 m-0">{{ item.isUsedCount }}/{{ item.count }}</p> --> <!-- <p class="p-0 m-0">{{ item.isUsedCount }}/{{ item.count }}</p> -->
</div> </div>
<div class="flex flex-col gap-1 mr-2"> <div class="flex flex-col gap-1 mr-2">
<div class="tooltip tooltip-left" data-tip="编辑"> <div class="tooltip tooltip-left" :data-tip="$t('tooltip.edit')">
<div class="cursor-pointer hover:text-blue-400" @click="addTemporaryPrize"> <div class="cursor-pointer hover:text-blue-400" @click="addTemporaryPrize">
<svg-icon name="edit"></svg-icon> <svg-icon name="edit"></svg-icon>
</div> </div>
</div> </div>
<div class="tooltip tooltip-left" data-tip="删除"> <div class="tooltip tooltip-left" :data-tip="$t('tooltip.delete')">
<div class="cursor-pointer hover:text-blue-400" @click="deleteTemporaryPrize"> <div class="cursor-pointer hover:text-blue-400" @click="deleteTemporaryPrize">
<svg-icon name="delete"></svg-icon> <svg-icon name="delete"></svg-icon>
</div> </div>
@@ -234,13 +235,13 @@ onMounted(() => {
</li> </li>
</ul> </ul>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="tooltip tooltip-right" data-tip="奖项列表"> <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" <div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
@click="prizeShow = !prizeShow"> @click="prizeShow = !prizeShow">
<svg-icon name="arrow_left" class="w-full h-full"></svg-icon> <svg-icon name="arrow_left" class="w-full h-full"></svg-icon>
</div> </div>
</div> </div>
<div class="tooltip tooltip-right" data-tip="添加抽奖"> <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" <div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
@click="addTemporaryPrize"> @click="addTemporaryPrize">
<svg-icon name="add" class="w-full h-full"></svg-icon> <svg-icon name="add" class="w-full h-full"></svg-icon>
@@ -252,7 +253,7 @@ onMounted(() => {
</div> </div>
<transition name="prize-operate" :appear="true"> <transition name="prize-operate" :appear="true">
<div class="tooltip tooltip-right" data-tip="奖项列表" v-show="!prizeShow"> <div class="tooltip tooltip-right" :data-tip="$t('tooltip.prizeList')" v-show="!prizeShow">
<div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50" <div class="flex items-center w-6 h-8 rounded-r-lg cursor-pointer prize-option bg-slate-500/50"
@click="prizeShow = !prizeShow"> @click="prizeShow = !prizeShow">
<svg-icon name="arrow_right" class="w-full h-full"></svg-icon> <svg-icon name="arrow_right" class="w-full h-full"></svg-icon>

View File

@@ -5,6 +5,7 @@ import { useElementStyle, useElementPosition } from '@/hooks/useElement'
import StarsBackground from '@/components/StarsBackground/index.vue' import StarsBackground from '@/components/StarsBackground/index.vue'
import confetti from 'canvas-confetti' import confetti from 'canvas-confetti'
import { filterData, selectCard } from '@/utils' import { filterData, selectCard } from '@/utils'
import i18n from '@/locales/i18n'
import { rgba } from '@/utils/color' import { rgba } from '@/utils/color'
import { IPersonConfig } from '@/types/storeType' import { IPersonConfig } from '@/types/storeType'
// import * as THREE from 'three' // import * as THREE from 'three'
@@ -375,7 +376,7 @@ const startLottery = () => {
// 验证是否已抽完全部奖项 // 验证是否已抽完全部奖项
if (currentPrize.value.isUsed || !currentPrize.value) { if (currentPrize.value.isUsed || !currentPrize.value) {
toast.open({ toast.open({
message: '抽奖抽完了', message: i18n.global.t('error.personIsAllDone'),
type: 'warning', type: 'warning',
position: 'top-right', position: 'top-right',
duration: 10000 duration: 10000
@@ -387,7 +388,7 @@ const startLottery = () => {
// 验证抽奖人数是否还够 // 验证抽奖人数是否还够
if (personPool.value.length < currentPrize.value.count - currentPrize.value.isUsedCount) { if (personPool.value.length < currentPrize.value.count - currentPrize.value.isUsedCount) {
toast.open({ toast.open({
message: '抽奖人数不够', message: i18n.global.t('error.personNotEnough'),
type: 'warning', type: 'warning',
position: 'top-right', position: 'top-right',
duration: 10000 duration: 10000
@@ -417,7 +418,8 @@ const startLottery = () => {
} }
} }
toast.open({ toast.open({
message: `现在抽取${currentPrize.value.name} ${leftover}`, // message: `现在抽取${currentPrize.value.name} ${leftover}人`,
message:i18n.global.t('error.startDraw',{count:currentPrize.value.name,leftover:leftover}),
type:'default', type:'default',
position: 'top-right', position: 'top-right',
duration: 8000 duration: 8000
@@ -637,19 +639,19 @@ onUnmounted(() => {
:style="{ fontSize: textSize * 1.5 + 'px', color: textColor }">{{ topTitle }}</h2> :style="{ fontSize: textSize * 1.5 + 'px', color: textColor }">{{ topTitle }}</h2>
<div class="flex gap-3"> <div class="flex gap-3">
<button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg" <button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
@click="router.push('config')">暂无人员信息前往导入</button> @click="router.push('config')">{{$t('button.noInfoAndImport')}}</button>
<button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg" <button v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
@click="setDefaultPersonList">使用默认数据</button> @click="setDefaultPersonList">{{$t('button.useDefault')}}</button>
</div> </div>
</div> </div>
<div id="container" ref="containerRef" class="3dContainer"> <div id="container" ref="containerRef" class="3dContainer">
<!-- 选中菜单结构 start--> <!-- 选中菜单结构 start-->
<div id="menu"> <div id="menu">
<button class="btn-end " @click="enterLottery" v-if="currentStatus == 0 && tableData.length > 0">进入抽奖</button> <button class="btn-end " @click="enterLottery" v-if="currentStatus == 0 && tableData.length > 0">{{$t('button.enterLottery')}}</button>
<div class="start" v-if="currentStatus == 1"> <div class="start" v-if="currentStatus == 1">
<button class="btn-start" @click="startLottery"><strong>开始</strong> <button class="btn-start" @click="startLottery"><strong>{{$t('button.start')}}</strong>
<div id="container-stars"> <div id="container-stars">
<div id="stars"></div> <div id="stars"></div>
</div> </div>
@@ -661,11 +663,11 @@ onUnmounted(() => {
</button> </button>
</div> </div>
<button class="btn-end btn glass btn-lg" @click="stopLottery" v-if="currentStatus == 2">抽取幸运儿</button> <button class="btn-end btn glass btn-lg" @click="stopLottery" v-if="currentStatus == 2">{{$t('button.selectLucky')}}</button>
<div v-if="currentStatus == 3" class="flex justify-center gap-6 enStop"> <div v-if="currentStatus == 3" class="flex justify-center gap-6 enStop">
<div class="start"> <div class="start">
<button class="btn-start" @click="continueLottery"><strong>继续</strong> <button class="btn-start" @click="continueLottery"><strong>{{$t('button.continue')}}</strong>
<div id="container-stars"> <div id="container-stars">
<div id="stars"></div> <div id="stars"></div>
</div> </div>
@@ -678,7 +680,7 @@ onUnmounted(() => {
</div> </div>
<div class="start"> <div class="start">
<button class="btn-cancel" @click="quitLottery"><strong>取消</strong> <button class="btn-cancel" @click="quitLottery"><strong>{{$t('button.cancel')}}</strong>
<div id="container-stars"> <div id="container-stars">
<div id="stars"></div> <div id="stars"></div>
</div> </div>