diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 99fa5f1..123fe4c 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -15,7 +15,7 @@ jobs: contents: read strategy: matrix: - node-version: [20.x] + node-version: [22.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/package.json b/package.json index 5d5a7b7..482e7f0 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "axios": "^1.7.8", "canvas-confetti": "^1.9.3", "dayjs": "^1.11.13", + "dexie": "^4.2.1", "github-markdown-css": "^5.8.0", "localforage": "^1.10.0", "markdown-it": "^14.1.0", @@ -58,6 +59,7 @@ "@vitest/ui": "^3.2.4", "@vue/test-utils": "^2.4.6", "autoprefixer": "^10.4.20", + "baseline-browser-mapping": "^2.8.32", "daisyui": "^5.1.13", "eslint": "^9.15.0", "eslint-plugin-vue": "^10.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9cda78..8799787 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + dexie: + specifier: ^4.2.1 + version: 4.2.1 github-markdown-css: specifier: ^5.8.0 version: 5.8.0 @@ -129,6 +132,9 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + baseline-browser-mapping: + specifier: ^2.8.32 + version: 2.8.32 daisyui: specifier: ^5.1.13 version: 5.1.13 @@ -1363,42 +1369,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -1469,67 +1469,56 @@ packages: resolution: {integrity: sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.0': resolution: {integrity: sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.0': resolution: {integrity: sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.0': resolution: {integrity: sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.0': resolution: {integrity: sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.0': resolution: {integrity: sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.0': resolution: {integrity: sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.0': resolution: {integrity: sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.0': resolution: {integrity: sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.0': resolution: {integrity: sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.0': resolution: {integrity: sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.0': resolution: {integrity: sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==} @@ -1607,28 +1596,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.13': resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.13': resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.13': resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.13': resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} @@ -2281,8 +2266,8 @@ packages: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} - baseline-browser-mapping@2.8.6: - resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true bidi-js@1.0.3: @@ -2701,6 +2686,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dexie@4.2.1: + resolution: {integrity: sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -3747,28 +3735,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -7823,7 +7807,7 @@ snapshots: mixin-deep: 1.3.2 pascalcase: 0.1.1 - baseline-browser-mapping@2.8.6: {} + baseline-browser-mapping@2.8.32: {} bidi-js@1.0.3: dependencies: @@ -7886,7 +7870,7 @@ snapshots: browserslist@4.26.2: dependencies: - baseline-browser-mapping: 2.8.6 + baseline-browser-mapping: 2.8.32 caniuse-lite: 1.0.30001743 electron-to-chromium: 1.5.222 node-releases: 2.0.21 @@ -8238,6 +8222,8 @@ snapshots: dependencies: dequal: 2.0.3 + dexie@4.2.1: {} + dom-accessibility-api@0.5.16: {} dom-serializer@0.2.2: diff --git a/src/App.vue b/src/App.vue index 0aee9b9..96991db 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,13 @@ + + + + diff --git a/src/components/Loading/index.ts b/src/components/Loading/index.ts new file mode 100644 index 0000000..aa1cc9f --- /dev/null +++ b/src/components/Loading/index.ts @@ -0,0 +1,5 @@ +import type { LoadingOptions } from './loading-context' +import Loading from './index.vue' +import { loadingKey, loadingState } from './loading-context' + +export { Loading, loadingKey, LoadingOptions, loadingState } diff --git a/src/components/Loading/index.vue b/src/components/Loading/index.vue new file mode 100644 index 0000000..62a2781 --- /dev/null +++ b/src/components/Loading/index.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/components/Loading/loading-context.ts b/src/components/Loading/loading-context.ts new file mode 100644 index 0000000..6093acf --- /dev/null +++ b/src/components/Loading/loading-context.ts @@ -0,0 +1,61 @@ +// src/contexts/loading-context.ts +import type { InjectionKey, Ref } from 'vue' +import { ref } from 'vue' + +// 定义 Loading 配置类型 +export interface LoadingOptions { + visible: Ref + text: Ref + fullscreen: Ref + zIndex: Ref + count: Ref + show: (options?: Partial<{ text: string, fullscreen: boolean, zIndex: number }>) => void + hide: () => void +} + +// 注入密钥(Symbol 确保唯一性) +export const loadingKey: InjectionKey = Symbol('loading') + +// 全局状态(单例) +const visible = ref(false) +const text = ref('') +const fullscreen = ref(true) +const zIndex = ref(9999) +const count = ref(0) + +// 显示 Loading +function show(options?: Partial<{ text: string, fullscreen: boolean, zIndex: number }>) { + count.value++ + if (count.value > 1) + return + visible.value = true + if (options) { + text.value = options.text || '' + fullscreen.value = options.fullscreen ?? true + zIndex.value = options.zIndex || 9999 + } +} + +// 隐藏 Loading +function hide() { + if (count.value <= 0) + return + count.value-- + if (count.value === 0) { + visible.value = false + text.value = '' + fullscreen.value = true + zIndex.value = 9999 + } +} + +// 导出全局状态(供根组件提供) +export const loadingState: LoadingOptions = { + visible, + text, + fullscreen, + zIndex, + count, + show, + hide, +} diff --git a/src/components/PageHeader/index.vue b/src/components/PageHeader/index.vue new file mode 100644 index 0000000..fd14914 --- /dev/null +++ b/src/components/PageHeader/index.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/StarsBackground/index.vue b/src/components/StarsBackground/index.vue deleted file mode 100644 index 85a4ac5..0000000 --- a/src/components/StarsBackground/index.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/src/layout/index.vue b/src/layout/index.vue index acd521c..10cc77e 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -1,12 +1,12 @@ + + + + diff --git a/src/views/Config/Person/PersonAll/useViewModel.ts b/src/views/Config/Person/PersonAll/useViewModel.ts new file mode 100644 index 0000000..9464db9 --- /dev/null +++ b/src/views/Config/Person/PersonAll/useViewModel.ts @@ -0,0 +1,167 @@ +import type { Ref } from 'vue' +import type { IPersonConfig } from '@/types/storeType' +import { storeToRefs } from 'pinia' +import { inject } from 'vue' +import * as XLSX from 'xlsx' +import { loadingKey } from '@/components/Loading' +import i18n from '@/locales/i18n' +import useStore from '@/store' +import { readFileBinary } from '@/utils/file' +import ImportExcelWorker from './importExcel.worker?worker' + +export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref }) { + const worker: Worker | null = new ImportExcelWorker() + const loading = inject(loadingKey) + const personConfig = useStore().personConfig + const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig) + const tableColumns = [ + { + 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.avatar'), + props: 'avatar', + formatValue(row: any) { + return row.avatar ? `avatar` : '-' + }, + }, + { + 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: 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) + }, + }, + + ], + }, + ] + /// 向worker发送消息 + function sendWorkerMessage(message: any) { + if (worker) { + worker.postMessage(message) + } + } + /// 开始导入 + function startWorker(data: Event) { + loading?.show() + sendWorkerMessage({ type: 'start', data }) + } + /** + * 获取用户数据 + */ + async function handleFileChange(e: Event) { + // worker = new ImportExcelWorker() + if (worker) { + worker.onmessage = (e) => { + if (e.data.type === 'done') { + personConfig.resetPerson() + personConfig.addNotPersonList(e.data.data) + // 导入成功后清空file input + clearFileInput() + } + loading?.hide() + } + } + 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 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')) + + 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') + } + } + + function resetData() { + personConfig.resetAlreadyPerson() + } + + function deleteAll() { + personConfig.deleteAllPerson() + } + + function delPersonItem(row: IPersonConfig) { + personConfig.deletePerson(row) + } + return { + resetData, + deleteAll, + handleFileChange, + exportData, + alreadyPersonList, + allPersonList, + tableColumns, + } +} diff --git a/src/views/Config/Person/PersonAlready.vue b/src/views/Config/Person/PersonAlready.vue index 04335d3..cb290c9 100644 --- a/src/views/Config/Person/PersonAlready.vue +++ b/src/views/Config/Person/PersonAlready.vue @@ -1,12 +1,13 @@