Feature action (#149)
* ci: 👷 整合github action配置文件 * docs: 📝 贡献文档修改 * style: 💄 更新版本 * style: 💄 cargo.lock版本更新 * feat(husky): 增强Git标签版本校验脚本 添加了对Git标签指向提交与release分支一致性的校验功能。 脚本现在会检查tag指向的提交是否与当前或任何release分支的最新提交一致, 确保发布流程的准确性。如果当前在release分支上,直接比较分支HEAD与tag指向的提交; 如果不在release分支上,则遍历所有release分支查找匹配的提交。 * feat: ✨ 国际化
This commit is contained in:
@@ -3,6 +3,7 @@ import { refDebounced } from '@vueuse/core'
|
||||
import { ChevronRight, ChevronsUpDownIcon } from 'lucide-vue-next'
|
||||
import { PopoverArrow } from 'reka-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Command,
|
||||
@@ -31,7 +32,7 @@ const { getFonts, disabled: browserDisabled, fonts } = useLocalFonts()
|
||||
const open = ref(false)
|
||||
const activeKey = ref('')
|
||||
const debouncedActiveKey = refDebounced(activeKey, 20)
|
||||
|
||||
const { t } = useI18n()
|
||||
function selectFont(selectedValue: any) {
|
||||
open.value = false
|
||||
activeKey.value = ''
|
||||
@@ -67,7 +68,7 @@ const disabledStyle = computed(() => {
|
||||
@click="getFonts"
|
||||
>
|
||||
<span class="w-7/8 text-left truncate" :style="{ fontFamily: `${selectedFont}` }">
|
||||
{{ selectedFont || "选择字体..." }}
|
||||
{{ selectedFont || t('placeHolder.selectFont') }}
|
||||
</span>
|
||||
<ChevronsUpDownIcon class="opacity-50" />
|
||||
</Button>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang='ts'>
|
||||
import type { IFileData } from '@/components/FileUpload/type'
|
||||
import localforage from 'localforage'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import CustomDialog from '@/components/Dialog/index.vue'
|
||||
import FileUpload from '@/components/FileUpload/index.vue'
|
||||
@@ -19,7 +18,7 @@ const visible = defineModel('visible', {
|
||||
required: true,
|
||||
})
|
||||
const jsonFileData = ref<IFileData | null>(null)
|
||||
|
||||
const { t } = useI18n()
|
||||
const uploadDialogRef = ref()
|
||||
|
||||
async function uploadFile(fileData: IFileData | null) {
|
||||
@@ -30,7 +29,7 @@ async function uploadFile(fileData: IFileData | null) {
|
||||
const isJson = /application\/json/.test(fileData?.type || '')
|
||||
if (!isJson) {
|
||||
toast.open({
|
||||
message: '不是json文件,请检查',
|
||||
message: t('error.notJsonFile'),
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
})
|
||||
@@ -57,7 +56,7 @@ watch(visible, (newVal) => {
|
||||
<CustomDialog
|
||||
ref="uploadDialogRef"
|
||||
v-model:visible="visible"
|
||||
title="设置文件上传"
|
||||
:title="t('dialog.uploadFileTitle')"
|
||||
:submit-func="submitUpload"
|
||||
class=""
|
||||
>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script setup lang='ts'>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const definiteTime = defineModel<number | null>('definiteTime', { required: true })
|
||||
const winMusic = defineModel<boolean>('winMusic', { required: true })
|
||||
</script>
|
||||
@@ -6,22 +9,22 @@ const winMusic = defineModel<boolean>('winMusic', { required: true })
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
功能设置
|
||||
{{ t('table.abilitySetting') }}
|
||||
</legend>
|
||||
|
||||
<label class="flex flex-row items-center form-control">
|
||||
<div class="">
|
||||
<div class="label flex flex-col justify-start items-start">
|
||||
<label class="label">
|
||||
<span class="label-text text-left">定时停止</span>
|
||||
<div class="tooltip" data-tip="开始抽奖过后定时停止,默认为0,单位为秒,0为关闭定时停止功能">
|
||||
<span class="label-text text-left">{{ t('table.timedStop') }}</span>
|
||||
<div class="tooltip" :data-tip="t('tooltip.timedStop')">
|
||||
<button class="btn btn-circle h-4 hover:bg-base-300">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
v-model="definiteTime" type="number" placeholder="开始后定时抽取"
|
||||
v-model="definiteTime" type="number" :placeholder="t('placeHolder.timedStop')"
|
||||
class="w-full max-w-xs input input-bordered"
|
||||
>
|
||||
</div>
|
||||
@@ -29,7 +32,7 @@ const winMusic = defineModel<boolean>('winMusic', { required: true })
|
||||
</label>
|
||||
<div class="flex items-center justify-between w-full max-w-xs gap-2 mb-3 form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">播放获奖音乐</span>
|
||||
<span class="label-text">{{ t('table.playWinMusic') }}</span>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox" :checked="winMusic" class="border-solid checkbox checkbox-secondary border"
|
||||
|
||||
@@ -19,7 +19,7 @@ const uploadVisible = ref(false)
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
数据操作
|
||||
{{ t('table.DataSetting') }}
|
||||
</legend>
|
||||
<dialog id="my_modal_1" ref="resetDataDialogRef" class="border-none modal">
|
||||
<div class="modal-box">
|
||||
@@ -46,7 +46,7 @@ const uploadVisible = ref(false)
|
||||
<label class="flex flex-row items-center form-control">
|
||||
<div class="">
|
||||
<div class="label flex flex-col justify-start items-start">
|
||||
<span class="label-text text-left">重置数据</span>
|
||||
<span class="label-text text-left">{{ t('table.resetAllData') }}</span>
|
||||
<div class="help">
|
||||
<button class="btn btn-sm btn-primary" @click="resetDataDialogRef.showModal()">
|
||||
{{ t('button.resetAllData') }}
|
||||
|
||||
@@ -18,7 +18,7 @@ const isShowAvatarValue = defineModel<boolean>('isShowAvatarValue', { required:
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
布局设置
|
||||
{{ t('table.layoutSetting') }}
|
||||
</legend>
|
||||
<label class="flex flex-row items-center form-control">
|
||||
<div class="">
|
||||
|
||||
@@ -17,7 +17,7 @@ const { t } = useI18n()
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
图案设置
|
||||
{{ t('table.patternSetting') }}
|
||||
</legend>
|
||||
<div class="items-center gap-24 mb-0 form-control">
|
||||
<div>
|
||||
|
||||
@@ -15,7 +15,7 @@ const titleFontSyncGlobalValue = defineModel<boolean>('titleFontSyncGlobalValue'
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
文本设置
|
||||
{{ t('table.textSetting') }}
|
||||
</legend>
|
||||
<label class="label">
|
||||
<div class="label">
|
||||
|
||||
@@ -26,7 +26,7 @@ const patternColorValue = defineModel<string>('patternColorValue')
|
||||
<template>
|
||||
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
|
||||
<legend class="fieldset-legend">
|
||||
主题设置
|
||||
{{ t('table.themeSetting') }}
|
||||
</legend>
|
||||
|
||||
<div class="w-full max-w-xs form-control">
|
||||
@@ -62,11 +62,12 @@ const patternColorValue = defineModel<string>('patternColorValue')
|
||||
<span class="truncate w-option-xs">{{ item.name }}</span>
|
||||
</option>
|
||||
</select>
|
||||
<span class="label">请先前往
|
||||
<span class="label">
|
||||
{{ t('tooltip.pleaseGoto') }}
|
||||
<a class="link link-info" @click="() => { router.push('image') }">
|
||||
图片管理
|
||||
{{ t('sidebar.imagesManagement') }}
|
||||
</a>
|
||||
上传图片</span>
|
||||
{{ t('tooltip.uploadImage') }}</span>
|
||||
</div>
|
||||
<div class="grid w-full grid-cols-2 gap-4">
|
||||
<div class="flex flex-col items-center max-w-xs gap-1 form-control">
|
||||
|
||||
@@ -96,14 +96,14 @@ watch(visible, (newVal) => {
|
||||
<CustomDialog
|
||||
ref="uploadDialogRef"
|
||||
v-model:visible="visible"
|
||||
title="图片上传"
|
||||
:title="t('dialog.uploadImageTitle')"
|
||||
:submit-func="submitUpload"
|
||||
class=""
|
||||
>
|
||||
<template #content>
|
||||
<div class="flex flex-col items-center gap-6 w-full px-12">
|
||||
<FileUpload v-if="visible" :limit-type="limitType" @upload-file="uploadFile" />
|
||||
<input v-model="fileName" :disabled="imageData === null" type="text" placeholder="图片名称" class="input w-full">
|
||||
<input v-model="fileName" :disabled="imageData === null" type="text" :placeholder="t('placeHolder.imageName')" class="input w-full">
|
||||
</div>
|
||||
</template>
|
||||
</CustomDialog>
|
||||
|
||||
@@ -9,6 +9,7 @@ import CustomDialog from '@/components/Dialog/index.vue'
|
||||
import FileUpload from '@/components/FileUpload/index.vue'
|
||||
import useStore from '@/store'
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const limitType = ref('audio/*')
|
||||
const visible = defineModel('visible', {
|
||||
@@ -41,7 +42,7 @@ async function uploadFile(fileData: IFileData | null) {
|
||||
const isAudio = /audio*/.test(fileData?.type || '')
|
||||
if (!isAudio) {
|
||||
toast.open({
|
||||
message: '不是音频文件',
|
||||
message: t('error.notAudioFile'),
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
})
|
||||
@@ -71,7 +72,7 @@ function submitUpload() {
|
||||
})
|
||||
.then(() => {
|
||||
toast.open({
|
||||
message: '上传成功',
|
||||
message: t('error.uploadSuccess'),
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
})
|
||||
@@ -79,7 +80,7 @@ function submitUpload() {
|
||||
})
|
||||
.catch(() => {
|
||||
toast.open({
|
||||
message: '上传失败',
|
||||
message: t('error.uploadFail'),
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
})
|
||||
@@ -97,14 +98,14 @@ watch(visible, (newVal) => {
|
||||
<CustomDialog
|
||||
ref="uploadDialogRef"
|
||||
v-model:visible="visible"
|
||||
title="音乐上传"
|
||||
:title="t('dialog.uploadAudioTitle')"
|
||||
:submit-func="submitUpload"
|
||||
class=""
|
||||
>
|
||||
<template #content>
|
||||
<div class="flex flex-col items-center gap-6 w-full px-12">
|
||||
<FileUpload v-if="visible" :limit-type="limitType" @upload-file="uploadFile" />
|
||||
<input v-model="fileName" :disabled="audioData === null" type="text" placeholder="图片名称" class="input w-full">
|
||||
<input v-model="fileName" :disabled="audioData === null" type="text" class="input w-full">
|
||||
</div>
|
||||
</template>
|
||||
</CustomDialog>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PageHeader from '@/components/PageHeader/index.vue'
|
||||
import { sidebar } from '@/locales/modules'
|
||||
import useStore from '@/store'
|
||||
import UploadDialog from './components/UploadDialog.vue'
|
||||
|
||||
@@ -41,7 +42,7 @@ function deleteAll() {
|
||||
<template>
|
||||
<UploadDialog v-model:visible="uploadVisible" />
|
||||
<div>
|
||||
<PageHeader title="音乐管理">
|
||||
<PageHeader :title="t('sidebar.musicManagement')">
|
||||
<template #buttons>
|
||||
<div class="flex gap-3">
|
||||
<button class="btn btn-primary btn-sm" @click="resetMusic">
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
<script setup lang='ts'>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{
|
||||
addOnePersonDrawerRef: any
|
||||
addOnePerson: (addOnePersonDrawerRef: any, event: any) => void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const singlePersonData = defineModel<any>('singlePersonData', { required: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="fieldset rounded-box w-xs p-4" @submit="(e) => addOnePerson(addOnePersonDrawerRef, e)">
|
||||
<label class="fieldset">
|
||||
<span class="label">编号</span>
|
||||
<input v-model="singlePersonData.uid" type="text" class="input validator" placeholder="编号">
|
||||
<span class="label">{{ t('table.number') }}</span>
|
||||
<input v-model="singlePersonData.uid" type="text" class="input validator" :placeholder="t('placeHolder.number')">
|
||||
</label>
|
||||
<fieldset class="fieldset">
|
||||
<label class="label" required>姓名<span class="text-red-500">*</span></label>
|
||||
<input v-model="singlePersonData.name" type="text" class="input validator" placeholder="姓名" required minlength="1">
|
||||
<label class="label" required>{{ t('table.name') }}<span class="text-red-500">*</span></label>
|
||||
<input v-model="singlePersonData.name" type="text" class="input validator" :placeholder="t('placeHolder.name')" required minlength="1">
|
||||
<p class="validator-hint hidden">
|
||||
请填写姓名
|
||||
{{ t('error.personNameEmpty') }}
|
||||
</p>
|
||||
</fieldset>
|
||||
<label class="fieldset">
|
||||
<span class="label">部门</span>
|
||||
<input v-model="singlePersonData.department" type="text" class="input validator" placeholder="部门">
|
||||
<span class="label">{{ t('table.department') }}</span>
|
||||
<input v-model="singlePersonData.department" type="text" class="input validator" :placeholder="t('placeHolder.department')">
|
||||
</label>
|
||||
<label class="fieldset">
|
||||
<span class="label">头像</span>
|
||||
<input v-model="singlePersonData.avatar" type="text" class="input validator" placeholder="头像">
|
||||
<span class="label">{{ t('table.avatar') }}</span>
|
||||
<input v-model="singlePersonData.avatar" type="text" class="input validator" :placeholder="t('placeHolder.avatar')">
|
||||
</label>
|
||||
<label class="fieldset">
|
||||
<span class="label">身份</span>
|
||||
<input v-model="singlePersonData.identity" type="text" class="input validator" placeholder="身份">
|
||||
<span class="label">{{ t('table.identity') }}</span>
|
||||
<input v-model="singlePersonData.identity" type="text" class="input validator" :placeholder="t('placeHolder.identity')">
|
||||
</label>
|
||||
<button class="btn btn-neutral mt-4" type="submit">
|
||||
确定
|
||||
{{ t('button.submit') }}
|
||||
</button>
|
||||
<button class="btn btn-ghost mt-1" type="reset" @click="addOnePersonDrawerRef.closeDrawer()">
|
||||
取消
|
||||
{{ t('button.cancel') }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
import i18n from '@/locales/i18n'
|
||||
import { addOtherInfo } from '@/utils'
|
||||
// 定义消息类型
|
||||
interface WorkerMessage {
|
||||
type: 'start' | 'stop' | 'reset'
|
||||
data: any
|
||||
templateData: any
|
||||
type: 'start' | 'stop' | 'reset'
|
||||
data: any
|
||||
templateData: any
|
||||
}
|
||||
|
||||
let allData: any[] = []
|
||||
|
||||
function headersEqual(template: string[], actual: string[]): boolean {
|
||||
return template.length >= actual.length
|
||||
&& actual.some(item => template.includes(item))
|
||||
return template.length >= actual.length
|
||||
&& actual.some(item => template.includes(item))
|
||||
}
|
||||
|
||||
// 接收主线程消息
|
||||
globalThis.onmessage = async (e: MessageEvent<WorkerMessage>) => {
|
||||
switch (e.data.type) {
|
||||
case 'start':
|
||||
{
|
||||
const fileData = e.data.data
|
||||
const templateData = e.data.templateData
|
||||
switch (e.data.type) {
|
||||
case 'start':
|
||||
{
|
||||
const fileData = e.data.data
|
||||
const templateData = e.data.templateData
|
||||
|
||||
const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true })
|
||||
const workSheet = workBook.Sheets[workBook.SheetNames[0]]
|
||||
const excelData: object[] = XLSX.utils.sheet_to_json(workSheet)
|
||||
const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true })
|
||||
const workSheet = workBook.Sheets[workBook.SheetNames[0]]
|
||||
const excelData: object[] = XLSX.utils.sheet_to_json(workSheet)
|
||||
|
||||
const templateWorkBook = XLSX.read(templateData, { type: 'array', cellDates: true })
|
||||
const templateWorkSheet = templateWorkBook.Sheets[templateWorkBook.SheetNames[0]]
|
||||
const templateExcelData: object[] = XLSX.utils.sheet_to_json(templateWorkSheet)
|
||||
const templateWorkBook = XLSX.read(templateData, { type: 'array', cellDates: true })
|
||||
const templateWorkSheet = templateWorkBook.Sheets[templateWorkBook.SheetNames[0]]
|
||||
const templateExcelData: object[] = XLSX.utils.sheet_to_json(templateWorkSheet)
|
||||
|
||||
const templateHeader = Object.keys(templateExcelData[0])
|
||||
const header = Object.keys(excelData[0])
|
||||
const templateHeader = Object.keys(templateExcelData[0])
|
||||
const header = Object.keys(excelData[0])
|
||||
|
||||
if (!headersEqual(templateHeader, header)) {
|
||||
globalThis.postMessage({
|
||||
type: 'error',
|
||||
data: null,
|
||||
message: '表头不一致,请先下载模板然后修改',
|
||||
})
|
||||
return
|
||||
}
|
||||
allData = addOtherInfo(excelData)
|
||||
globalThis.postMessage({
|
||||
type: 'done',
|
||||
data: allData,
|
||||
message: '读取完成',
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
globalThis.postMessage({
|
||||
type: 'fail',
|
||||
data: null,
|
||||
message: '读取失败',
|
||||
})
|
||||
break
|
||||
if (!headersEqual(templateHeader, header)) {
|
||||
globalThis.postMessage({
|
||||
type: 'error',
|
||||
data: null,
|
||||
message: i18n.global.t('error.excelFileError'),
|
||||
})
|
||||
return
|
||||
}
|
||||
allData = addOtherInfo(excelData)
|
||||
globalThis.postMessage({
|
||||
type: 'done',
|
||||
data: allData,
|
||||
message: '读取完成',
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
globalThis.postMessage({
|
||||
type: 'fail',
|
||||
data: null,
|
||||
message: '读取失败',
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ const limitType = '.xlsx,.xls'
|
||||
{{ t('button.exportResult') }}
|
||||
</button>
|
||||
<button class="btn btn-neutral btn-sm" @click="addOnePersonDrawerRef.showDrawer()">
|
||||
添加
|
||||
{{ t('button.add') }}
|
||||
</button>
|
||||
<div>
|
||||
<span>{{ t('table.luckyPeopleNumber') }}:</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { IPersonConfig } from '@/types/storeType'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { inject, ref, toRaw } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import * as XLSX from 'xlsx'
|
||||
import { loadingKey } from '@/components/Loading'
|
||||
@@ -16,176 +17,177 @@ import ImportExcelWorker from './importExcel.worker?worker'
|
||||
type IBasePersonConfig = Pick<IPersonConfig, 'uid' | 'name' | 'department' | 'identity' | 'avatar'>
|
||||
|
||||
export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref<HTMLInputElement> }) {
|
||||
const baseUrl = import.meta.env.BASE_URL
|
||||
const toast = useToast()
|
||||
const worker: Worker | null = new ImportExcelWorker()
|
||||
const loading = inject(loadingKey)
|
||||
const personConfig = useStore().personConfig
|
||||
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
|
||||
const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem })
|
||||
const addPersonModalVisible = ref(false)
|
||||
const singlePersonData = ref<IBasePersonConfig>({
|
||||
uid: '',
|
||||
name: '',
|
||||
department: '',
|
||||
avatar: '',
|
||||
identity: '',
|
||||
})
|
||||
async function getExcelTemplateContent() {
|
||||
const locale = i18n.global.locale.value
|
||||
if (locale === 'zhCn') {
|
||||
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}人口登记表-zhCn.xlsx`)
|
||||
return templateData
|
||||
}
|
||||
else {
|
||||
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}personListTemplate-en.xlsx`)
|
||||
return templateData
|
||||
}
|
||||
const { t } = useI18n()
|
||||
const baseUrl = import.meta.env.BASE_URL
|
||||
const toast = useToast()
|
||||
const worker: Worker | null = new ImportExcelWorker()
|
||||
const loading = inject(loadingKey)
|
||||
const personConfig = useStore().personConfig
|
||||
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
|
||||
const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem })
|
||||
const addPersonModalVisible = ref(false)
|
||||
const singlePersonData = ref<IBasePersonConfig>({
|
||||
uid: '',
|
||||
name: '',
|
||||
department: '',
|
||||
avatar: '',
|
||||
identity: '',
|
||||
})
|
||||
async function getExcelTemplateContent() {
|
||||
const locale = i18n.global.locale.value
|
||||
if (locale === 'zhCn') {
|
||||
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}人口登记表-zhCn.xlsx`)
|
||||
return templateData
|
||||
}
|
||||
/// 向worker发送消息
|
||||
function sendWorkerMessage(message: any) {
|
||||
if (worker) {
|
||||
worker.postMessage(message)
|
||||
else {
|
||||
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}personListTemplate-en.xlsx`)
|
||||
return templateData
|
||||
}
|
||||
}
|
||||
/// 向worker发送消息
|
||||
function sendWorkerMessage(message: any) {
|
||||
if (worker) {
|
||||
worker.postMessage(message)
|
||||
}
|
||||
}
|
||||
/// 开始导入
|
||||
async function startWorker(data: string) {
|
||||
loading?.show()
|
||||
getExcelTemplateContent()
|
||||
sendWorkerMessage({ type: 'start', data, templateData: await getExcelTemplateContent() })
|
||||
}
|
||||
/**
|
||||
* 获取用户数据
|
||||
*/
|
||||
async function handleFileChange(e: Event) {
|
||||
if (worker) {
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data.type === 'done') {
|
||||
personConfig.resetPerson()
|
||||
personConfig.addNotPersonList(e.data.data)
|
||||
// 提示导入成功
|
||||
toast.open({
|
||||
message: t('error.importSuccess'),
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
})
|
||||
// 导入成功后清空file input
|
||||
clearFileInput()
|
||||
}
|
||||
}
|
||||
/// 开始导入
|
||||
async function startWorker(data: string) {
|
||||
loading?.show()
|
||||
getExcelTemplateContent()
|
||||
sendWorkerMessage({ type: 'start', data, templateData: await getExcelTemplateContent() })
|
||||
}
|
||||
/**
|
||||
* 获取用户数据
|
||||
*/
|
||||
async function handleFileChange(e: Event) {
|
||||
if (worker) {
|
||||
worker.onmessage = (e) => {
|
||||
if (e.data.type === 'done') {
|
||||
personConfig.resetPerson()
|
||||
personConfig.addNotPersonList(e.data.data)
|
||||
// 提示导入成功
|
||||
toast.open({
|
||||
message: '导入成功',
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
})
|
||||
// 导入成功后清空file input
|
||||
clearFileInput()
|
||||
}
|
||||
if (e.data.type === 'error') {
|
||||
toast.open({
|
||||
message: e.data.message || '导入错误',
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
})
|
||||
// toast.warning(e.data.message || '导入错误')
|
||||
}
|
||||
loading?.hide()
|
||||
}
|
||||
if (e.data.type === 'error') {
|
||||
toast.open({
|
||||
message: e.data.message || t('error.importFail'),
|
||||
type: 'error',
|
||||
position: 'top-right',
|
||||
})
|
||||
// toast.warning(e.data.message || '导入错误')
|
||||
}
|
||||
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
|
||||
startWorker(dataBinary)
|
||||
loading?.hide()
|
||||
}
|
||||
}
|
||||
// 清空file input
|
||||
function clearFileInput() {
|
||||
if (exportInputFileRef.value) {
|
||||
exportInputFileRef.value.value = ''
|
||||
}
|
||||
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
|
||||
startWorker(dataBinary)
|
||||
}
|
||||
// 清空file input
|
||||
function clearFileInput() {
|
||||
if (exportInputFileRef.value) {
|
||||
exportInputFileRef.value.value = ''
|
||||
}
|
||||
function downloadTemplate() {
|
||||
// 下载
|
||||
const templateFileName = i18n.global.t('data.xlsxName')
|
||||
const fileUrl = `${baseUrl}${templateFileName}`
|
||||
fetch(fileUrl)
|
||||
.then(res => res.blob())
|
||||
.then((blob) => {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = templateFileName
|
||||
a.click()
|
||||
toast.open({
|
||||
message: '下载成功',
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
})
|
||||
})
|
||||
}
|
||||
function downloadTemplate() {
|
||||
// 下载
|
||||
const templateFileName = i18n.global.t('data.xlsxName')
|
||||
const fileUrl = `${baseUrl}${templateFileName}`
|
||||
fetch(fileUrl)
|
||||
.then(res => res.blob())
|
||||
.then((blob) => {
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = templateFileName
|
||||
a.click()
|
||||
toast.open({
|
||||
message: t('error.downloadSuccess'),
|
||||
type: 'success',
|
||||
position: 'top-right',
|
||||
})
|
||||
})
|
||||
}
|
||||
// 导出数据
|
||||
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')
|
||||
}
|
||||
else {
|
||||
data[i].isWin = i18n.global.t('data.no')
|
||||
}
|
||||
// 格式化数组为
|
||||
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')
|
||||
}
|
||||
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'))
|
||||
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)
|
||||
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')
|
||||
}
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
function resetData() {
|
||||
personConfig.resetAlreadyPerson()
|
||||
}
|
||||
function resetData() {
|
||||
personConfig.resetAlreadyPerson()
|
||||
}
|
||||
|
||||
function deleteAll() {
|
||||
personConfig.deleteAllPerson()
|
||||
}
|
||||
function deleteAll() {
|
||||
personConfig.deleteAllPerson()
|
||||
}
|
||||
|
||||
function delPersonItem(row: IPersonConfig) {
|
||||
personConfig.deletePerson(row)
|
||||
}
|
||||
function addOnePerson(addOnePersonDrawerRef: any, event: any) {
|
||||
event.preventDefault()
|
||||
// 表单中的验证信息清除
|
||||
function delPersonItem(row: IPersonConfig) {
|
||||
personConfig.deletePerson(row)
|
||||
}
|
||||
function addOnePerson(addOnePersonDrawerRef: any, event: any) {
|
||||
event.preventDefault()
|
||||
// 表单中的验证信息清除
|
||||
|
||||
const personData = addOtherInfo([toRaw(singlePersonData.value)])
|
||||
personData[0].id = uuidv4()
|
||||
personConfig.addOnePerson(personData)
|
||||
// singlePersonData.value = {} as IBasePersonConfig
|
||||
addOnePersonDrawerRef.closeDrawer()
|
||||
singlePersonData.value = {} as IBasePersonConfig
|
||||
}
|
||||
return {
|
||||
resetData,
|
||||
deleteAll,
|
||||
handleFileChange,
|
||||
exportData,
|
||||
alreadyPersonList,
|
||||
allPersonList,
|
||||
tableColumnList,
|
||||
addOnePerson,
|
||||
addPersonModalVisible,
|
||||
singlePersonData,
|
||||
downloadTemplate,
|
||||
}
|
||||
const personData = addOtherInfo([toRaw(singlePersonData.value)])
|
||||
personData[0].id = uuidv4()
|
||||
personConfig.addOnePerson(personData)
|
||||
// singlePersonData.value = {} as IBasePersonConfig
|
||||
addOnePersonDrawerRef.closeDrawer()
|
||||
singlePersonData.value = {} as IBasePersonConfig
|
||||
}
|
||||
return {
|
||||
resetData,
|
||||
deleteAll,
|
||||
handleFileChange,
|
||||
exportData,
|
||||
alreadyPersonList,
|
||||
allPersonList,
|
||||
tableColumnList,
|
||||
addOnePerson,
|
||||
addPersonModalVisible,
|
||||
singlePersonData,
|
||||
downloadTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
|
||||
import PageHeader from '@/components/PageHeader/index.vue'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useViewModel } from './useViewModel'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -8,133 +8,133 @@ import i18n from '@/locales/i18n'
|
||||
import useStore from '@/store'
|
||||
|
||||
export function usePrizeConfig() {
|
||||
const toast = useToast()
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore',
|
||||
})
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const globalConfig = useStore().globalConfig
|
||||
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
||||
const toast = useToast()
|
||||
const imageDbStore = localforage.createInstance({
|
||||
name: 'imgStore',
|
||||
})
|
||||
const prizeConfig = useStore().prizeConfig
|
||||
const globalConfig = useStore().globalConfig
|
||||
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
||||
|
||||
const { getImageList: localImageList } = storeToRefs(globalConfig)
|
||||
const imgList = ref<any[]>([])
|
||||
const { getImageList: localImageList } = storeToRefs(globalConfig)
|
||||
const imgList = ref<any[]>([])
|
||||
|
||||
const prizeList = ref(cloneDeep(localPrizeList.value))
|
||||
const selectedPrize = ref<IPrizeConfig | null>()
|
||||
const prizeList = ref(cloneDeep(localPrizeList.value))
|
||||
const selectedPrize = ref<IPrizeConfig | null>()
|
||||
|
||||
function selectPrize(item: IPrizeConfig) {
|
||||
selectedPrize.value = item
|
||||
selectedPrize.value.isUsedCount = 0
|
||||
selectedPrize.value.isUsed = false
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
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) {
|
||||
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
|
||||
item.separateCount.countList = []
|
||||
item.isUsed = !item.isUsed
|
||||
}
|
||||
function changePrizeStatus(item: IPrizeConfig) {
|
||||
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 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
|
||||
}
|
||||
}
|
||||
function submitData(value: any) {
|
||||
selectedPrize.value!.separateCount.countList = value
|
||||
selectedPrize.value = null
|
||||
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
|
||||
}
|
||||
|
||||
async function getImageDbStore() {
|
||||
const keys = await imageDbStore.keys()
|
||||
if (keys.length > 0) {
|
||||
imageDbStore.iterate((value, key) => {
|
||||
imgList.value.push({
|
||||
key,
|
||||
value,
|
||||
})
|
||||
})
|
||||
}
|
||||
async function getImageDbStore() {
|
||||
const keys = await imageDbStore.keys()
|
||||
if (keys.length > 0) {
|
||||
imageDbStore.iterate((value, key) => {
|
||||
imgList.value.push({
|
||||
key,
|
||||
value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function delItem(item: IPrizeConfig) {
|
||||
prizeConfig.deletePrizeConfig(item.id)
|
||||
toast.success('删除成功')
|
||||
function delItem(item: IPrizeConfig) {
|
||||
prizeConfig.deletePrizeConfig(item.id)
|
||||
toast.success(i18n.global.t('error.deleteSuccess'))
|
||||
}
|
||||
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,
|
||||
}
|
||||
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,
|
||||
}
|
||||
prizeList.value.push(defaultPrizeCOnfig)
|
||||
toast.success('添加成功')
|
||||
}
|
||||
function resetDefault() {
|
||||
prizeConfig.resetDefault()
|
||||
prizeList.value = cloneDeep(localPrizeList.value)
|
||||
toast.success('重置成功')
|
||||
}
|
||||
async function delAll() {
|
||||
prizeList.value = []
|
||||
toast.success('删除成功')
|
||||
}
|
||||
onMounted(() => {
|
||||
getImageDbStore()
|
||||
})
|
||||
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
|
||||
prizeConfig.setPrizeConfig(val)
|
||||
}, { deep: true })
|
||||
prizeList.value.push(defaultPrizeCOnfig)
|
||||
toast.success(i18n.global.t('error.success'))
|
||||
}
|
||||
function resetDefault() {
|
||||
prizeConfig.resetDefault()
|
||||
prizeList.value = cloneDeep(localPrizeList.value)
|
||||
toast.success(i18n.global.t('error.success'))
|
||||
}
|
||||
async function delAll() {
|
||||
prizeList.value = []
|
||||
toast.success(i18n.global.t('error.success'))
|
||||
}
|
||||
onMounted(() => {
|
||||
getImageDbStore()
|
||||
})
|
||||
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
|
||||
prizeConfig.setPrizeConfig(val)
|
||||
}, { deep: true })
|
||||
|
||||
return {
|
||||
addPrize,
|
||||
resetDefault,
|
||||
delAll,
|
||||
delItem,
|
||||
prizeList,
|
||||
currentPrize,
|
||||
selectedPrize,
|
||||
submitData,
|
||||
changePrizePerson,
|
||||
changePrizeStatus,
|
||||
selectPrize,
|
||||
localImageList,
|
||||
}
|
||||
return {
|
||||
addPrize,
|
||||
resetDefault,
|
||||
delAll,
|
||||
delItem,
|
||||
prizeList,
|
||||
currentPrize,
|
||||
selectedPrize,
|
||||
submitData,
|
||||
changePrizePerson,
|
||||
changePrizeStatus,
|
||||
selectPrize,
|
||||
localImageList,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ function skip(path: string) {
|
||||
</ul>
|
||||
<router-view class="flex-1 mt-5" />
|
||||
</div>
|
||||
<footer class="p-10 rounded footer footer-center bg-base-200 h-[280px] flex flex-col gap-4 text-base-content">
|
||||
<footer class="p-10 rounded footer footer-center bg-base-200 h-70 flex flex-col gap-4 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>
|
||||
|
||||
@@ -64,7 +64,7 @@ const { t } = useI18n()
|
||||
<!-- 加载中 -->
|
||||
<div v-else class="flex gap-3 items-center">
|
||||
<span class="loading loading-spinner loading-xl" />
|
||||
<span>加载中</span>
|
||||
<span>{{ t('button.loading') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user