* feat: 更新依赖,解决启动报错

* feat: 依赖升级后的样式修改

* refactor: 首页提取样式至单独的文件

* refactor: 重构首页代码,提取处理函数

* feat: 使用web worker来进行文件导入计算

* refactor: 提取组件,优化代码

* refactor: 人员列表代码结构重组

* feat:  修复问题

* feat: 添加Loading效果

* feat: indexdb存储人员名单,使用dexie

* feat(config): 重构界面配置项布局与样式

将原有的线性表单布局调整为分组的 fieldset 布局,提升视觉层次和可维护性。
新增文本设置、布局设置、主题设置、图案设置和其他设置等分类区域。
优化部分输入控件的结构和样式类名,增强用户体验和代码可读性。

* feat(data): 调整默认图案列表顺序

2025改为2026

feat(ui): 添加配置项注释说明

为 FaceConfig.vue 中的各个设置字段添加了 HTML 注释,便于识别和维护不同功能区域。包括:
- 文本设置(主标题、语言、文字大小)
- 布局设置(列数、卡片宽度、卡片高度)
- 主题设置(主题、背景图片)
- 图案设置
- 其他设置(是否常显奖项列表、是否显示头像)

* feat(PageHeader): 新增 PageHeader 组件并应用于多个配置页面

新增了 PageHeader 通用组件,用于统一页面标题和操作按钮区域的布局。
该组件包含 title 属性以及 buttons 和 alerts 两个具名插槽,便于复用和维护。
已在以下页面中集成使用:
- 图片管理页(ImageConfig.vue)
- 音乐管理页(MusicConfig.vue)
- 人员管理页(PersonAll/index.vue)
- 中奖者管理页(PersonAlready.vue)
- 奖品管理页(PrizeConfig.vue)

* fix(style): 调整 markdown 样式以支持主题变量

移除 media 查询,统一使用 CSS 变量定义颜色;修复组件中 i18n 导入位置问题。

* ```
fix(Config): 注释掉设置卡片颜色的逻辑

将设置卡片颜色的相关代码注释掉,完全使用用户使用的颜色,避免在主题切换时出现异常。
```

* ```
refactor(FaceConfig): 移除未使用的颜色验证工具函数

从 FaceConfig 组件中移除了不再需要的 isHex 和 isRgbOrRgba 工具函数导入,
因为这些函数当前并未在组件中使用。这有助于减少不必要的代码依赖并提高
组件的简洁性。
```

* fix(MusicConfig): 优化本地音乐列表项的显示样式

移除列表项中的 divide-y 类,为音乐名称添加 title 属性以显示完整名称,
并将音乐名称渲染为可点击的链接样式。

* build(workflow): 更新Node.js版本到22.x

将GitHub Actions工作流中的Node.js版本从20.x升级到22.x,
以使用最新的稳定版Node.js环境进行构建和测试。

* feat(router、config): 更新配置页面组件路径

将 FaceConfig、ImageConfig 和 MusicConfig 组件的导入路径从直接引用 `.vue` 文件改为引用 `index.vue`。

* build(eslint): 更新 ESLint 配置以忽略更多文件并添加警告规则

在原有忽略文件列表中新增了 '*.config.js' 文件类型,同时添加了
'no-console' 和 'no-debugger' 规则,并将其设置为警告级别,以提高
代码质量和一致性。

* feat(components): 添加图片上传组件及对话框功能

新增 `ImageUpload` 组件用于文件选择与预览,并集成到 `UploadDialog` 中实现图片上传逻辑。
更新了 `Dialog` 组件以支持可选属性和 model 绑定,增强其灵活性和可用性。
引入 `lucide-vue-next` 图标库支持图标渲染。
重构图片配置页面,移除旧上传逻辑,使用新的弹窗方式进行图片上传操作。

* feat(imgConfig): 优化图片存储结构

新增uuid依赖用于生成唯一ID,改进图片上传功能中的本地存储结构,
使用uuid替代时间戳作为键名以避免冲突,并调整从indexedDB读取数据的方式。

* feat: 音乐管理功能优化

* fix(MusicConfig): 移除上传对话框中的图片上传提示功能

移除了 UploadDialog 组件中不再使用的图片上传状态提示逻辑,
包括相关的响应式变量 imgUploadToast 及其对应的模板代码。
此变更简化了组件结构并移除了未使用的错误提示功能。

* fix(Config): 添加图片上传提示信息

在全局人脸配置页面添加了图片上传提示文本,提醒用户需要先上传图片到图片管理模块。

* feat(layout): 重构页面布局与音乐播放组件

将 PlayMusic 组件迁移至 layout/RightButton 并重构成通用右下角按钮组件,
提取音乐播放逻辑到独立 hook `usePlayMusic`,优化模态框提示逻辑并统一滚动行为。

* feat(prize-config): 引入拖拽功能并优化奖品配置界面

- 添加 `vue-draggable-plus` 和 `lodash-es` 依赖以支持列表拖拽排序
- 使用 `cloneDeep` 深拷贝奖品列表,避免直接修改原始数据
- 移除原有的手动排序逻辑(上下移动按钮),改用可视化拖拽方式
- 调整 UI 布局和样式,增强用户体验与可操作性
- 在 Demo 页面中添加 draggable 示例用于验证功能实现

* feat(Config): 动态显示当前年份并更新版权信息

将页脚中的版权年份从硬编码的 2024 改为动态获取的当前年份,并将作者名称添加跳转到 GitHub 主页的链接。

* fix(ui): 优化图片列表下拉选择器及表单项的样式与布局

* feat(person): 导入人员时添加uuid唯一标识字段 #91

* feat(utils): 添加安全洗牌与随机抽样函数 Feature #91

* test(random): 添加随机元素抽取函数的全面测试用例 Test #91

- 新增 Random.test.ts 文件,对 getRandomElements 函数进行详尽测试
- 包括基础功能、边界情况(count 为 0、负数、超出数组长度)
- 支持空数组、单元素数组、字符串数组、对象数组等多种数据类型
- 增加概率性测试,验证多次调用结果不完全一致
- 20万次循环验证各元素被抽中概率接近理论值,确保算法公平性

* feat(home): 重构抽奖逻辑并优化初始化流程 Feature #91

* feat(Home): 添加初始化完成状态控制头部标题显示 Fix #91

* feat(HeaderTitle): 添加加载状态显示 #91

* feat(store): 调整默认人员列表类型定义

* feat(deps): 添加tauri打包(遗留pnpm build:file打包好的文件跨域无法访问) #94

* feat(icons): 添加tauri多种平台的应用图标资源

* fix(tauri): 更新 Cargo.toml 配置格式 #94

* build(workflow): 更新 GitHub Actions 工作流配置 #94

* build(workflow): 更新发布流程并移除旧的构建文件

* chore(release): 调整 GitHub Actions 工作流中的缩进格式

* build(deps): 固定 three.js 相关依赖版本

* build(workflows): 添加 fast-glob 依赖到 GitHub 工作流

* build(release): 添加发布名称和文件名以包含版本信息

* build(github): 升级下载构件操作到v4版本

* ci(release): 上传web构建产物作为GitHub Actions工件

* feat(Config): 支持多语言 README 文档展示

* feat(Global/FaceConfig): 添加跳转到图片管理页面的链接 #96

* chore: 格式化

* feat(i18n): 更新抽奖配置相关国际化文案 #96

* build: git钩子

* build: pre-psuh

* feat(components): 添加 shadcn-vue 组件配置文件及配套组件库 #96

* feat(font): 添加本地字体选择功能(not done) #96

* feat: 更换字体v,以及页面加载之前就设置字体 #96

* chore: 删除v console.log #96

* build: pre-push

* feat(globalConfig): 设置默认字体为微软雅黑 #96

* feat: 设置标题字体与全局字体同步 #96

* build(.gitignore): 添加 husky 目录到忽略文件

* docs(readme): 格式

* docs(readme): 移除关于build:file命令的过时文档

* build: 🏗️ 使用低版本node会报错,强制限制使用node 22.x版本

可在.npmrc里面修改为不强制使用

* feat:  上传文件时校验是否模板文件,否则提示 #96

* feat(layout): 添加全屏切换功能 #96

* refactor: ♻️ 界面设置页面重构,抽离逻辑 #96

* fix(background): 修复从本地存储获取图片数据的问题 #96

* refactor: ♻️ 重构奖项配置模块 #96

* feat(personalready):  已中奖的员页面修改switch组件 #96

* refactor(personalready): ♻️ 已中奖的界面代码重构 #96

* style: 💄 删除shadcn-vue的样式预设,只使用daisyui的样式 #96

* feat:  标题样式跟随主题,设置文本颜色会覆盖主题的标题颜色 #96

* fix(Global/FaceConfig): 调整主题设置中文本颜色按钮的高度样式 #96

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96 (#119)

卸载路由时清除requestAnimationFrame

* 96 UI optimization (#122)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* Confilct dev date 12 22 (#131)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* fix(globalConfig): 修复当前音乐项类型缺失问题

* feat:  single person not done

* feat:  可添加单人 #96

* build(.gitignore): 添加 auto-imports.d.ts 到忽略文件

* fix: 🐛 上传、下载excel文件时修复路径错误

打包成应用和网页端的baseUrl不一样,使用环境变量来表示

* fix: 🐛 导入人员列表时处理有值为空的情况

* style: 💄 改变toaster的组件

* fix: 🐛 上传文件、解析数据与存储/读取数据的处理

、

* 96 UI optimization (#132)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* fix(globalConfig): 修复当前音乐项类型缺失问题

* feat:  single person not done

* feat:  可添加单人 #96

* build(.gitignore): 添加 auto-imports.d.ts 到忽略文件

* fix: 🐛 上传、下载excel文件时修复路径错误

打包成应用和网页端的baseUrl不一样,使用环境变量来表示

* fix: 🐛 导入人员列表时处理有值为空的情况

* style: 💄 改变toaster的组件

* fix: 🐛 上传文件、解析数据与存储/读取数据的处理

、

* fix(Config): 更新备案信息链接样式

将备案信息的段落标签替换为可点击的链接标签,使用户能够直接跳转到工信部备案查询页面。同时添加了悬停效果样式,提升用户体验。

* 96 UI optimization (#136)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* fix(globalConfig): 修复当前音乐项类型缺失问题

* feat:  single person not done

* feat:  可添加单人 #96

* build(.gitignore): 添加 auto-imports.d.ts 到忽略文件

* fix: 🐛 上传、下载excel文件时修复路径错误

打包成应用和网页端的baseUrl不一样,使用环境变量来表示

* fix: 🐛 导入人员列表时处理有值为空的情况

* style: 💄 改变toaster的组件

* fix: 🐛 上传文件、解析数据与存储/读取数据的处理

、

* fix(Config): 更新备案信息链接样式

将备案信息的段落标签替换为可点击的链接标签,使用户能够直接跳转到工信部备案查询页面。同时添加了悬停效果样式,提升用户体验。

* feat:  首页奖项列表样式修改 not done #96

* chore(deps): ✏️ 更新依赖版本

* chore: ✏️ gsap list demo

* build: 🏗️ docker构建优化

* chore: ✏️ gsap scroll demo

* style: 💄 gsap demno

* feat:  demo smooth scroll gsap scrolltrigger

* feat(Demo): 添加更多颜色选项并注释GSAP动画

* refactor(PrizeList): 重构奖品列表组件结构

* feat(PrizeList): 重构奖品列表组件并添加滚动动画

* feat:  增加定时抽取功能 #96

* feat:  添加定时抽取功能的说明

* feat:  优化gsap #96

项数不多时不触发gsap

* style: 💄 文本修改

* feat:  优化

* feat:  优化奖项列表

* fix(Home): 修复奖品列表滚动检测逻辑

* fix(home): 修复抽奖停止逻辑避免重复执行;调整卡片垂直居中位置计算

* feat:  播放中奖音频 #96

* 96 UI optimization (#137)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* fix(globalConfig): 修复当前音乐项类型缺失问题

* feat:  single person not done

* feat:  可添加单人 #96

* build(.gitignore): 添加 auto-imports.d.ts 到忽略文件

* fix: 🐛 上传、下载excel文件时修复路径错误

打包成应用和网页端的baseUrl不一样,使用环境变量来表示

* fix: 🐛 导入人员列表时处理有值为空的情况

* style: 💄 改变toaster的组件

* fix: 🐛 上传文件、解析数据与存储/读取数据的处理

、

* fix(Config): 更新备案信息链接样式

将备案信息的段落标签替换为可点击的链接标签,使用户能够直接跳转到工信部备案查询页面。同时添加了悬停效果样式,提升用户体验。

* feat:  首页奖项列表样式修改 not done #96

* chore(deps): ✏️ 更新依赖版本

* chore: ✏️ gsap list demo

* build: 🏗️ docker构建优化

* chore: ✏️ gsap scroll demo

* style: 💄 gsap demno

* feat:  demo smooth scroll gsap scrolltrigger

* feat(Demo): 添加更多颜色选项并注释GSAP动画

* refactor(PrizeList): 重构奖品列表组件结构

* feat(PrizeList): 重构奖品列表组件并添加滚动动画

* feat:  增加定时抽取功能 #96

* feat:  添加定时抽取功能的说明

* feat:  优化gsap #96

项数不多时不触发gsap

* style: 💄 文本修改

* feat:  优化

* feat:  优化奖项列表

* fix(Home): 修复奖品列表滚动检测逻辑

* fix(home): 修复抽奖停止逻辑避免重复执行;调整卡片垂直居中位置计算

* feat:  播放中奖音频 #96

* style: 💄 下载模板成功后进行提示

* 96 UI optimization (#141)

* fix(home): 🐛 解决多次切换路由后页面卡顿的问题 #96

卸载路由时清除requestAnimationFrame

* feat:  文件存储使用Blob格式

* style: 💄 修改部分类型any为具体类型

* feat:  界面设置中模块使用瀑布流布局 #96

* fix: 🐛 md文档更换文件夹解决控制台警告

* style: 💄 switch按钮改回使用daisyui组件

* refactor: ♻️ 所有人员列表提取tableColumn

* style: 💄 奖项列表中的图片类型修复

* fix(globalConfig): 修复当前音乐项类型缺失问题

* feat:  single person not done

* feat:  可添加单人 #96

* build(.gitignore): 添加 auto-imports.d.ts 到忽略文件

* fix: 🐛 上传、下载excel文件时修复路径错误

打包成应用和网页端的baseUrl不一样,使用环境变量来表示

* fix: 🐛 导入人员列表时处理有值为空的情况

* style: 💄 改变toaster的组件

* fix: 🐛 上传文件、解析数据与存储/读取数据的处理

、

* fix(Config): 更新备案信息链接样式

将备案信息的段落标签替换为可点击的链接标签,使用户能够直接跳转到工信部备案查询页面。同时添加了悬停效果样式,提升用户体验。

* feat:  首页奖项列表样式修改 not done #96

* chore(deps): ✏️ 更新依赖版本

* chore: ✏️ gsap list demo

* build: 🏗️ docker构建优化

* chore: ✏️ gsap scroll demo

* style: 💄 gsap demno

* feat:  demo smooth scroll gsap scrolltrigger

* feat(Demo): 添加更多颜色选项并注释GSAP动画

* refactor(PrizeList): 重构奖品列表组件结构

* feat(PrizeList): 重构奖品列表组件并添加滚动动画

* feat:  增加定时抽取功能 #96

* feat:  添加定时抽取功能的说明

* feat:  优化gsap #96

项数不多时不触发gsap

* style: 💄 文本修改

* feat:  优化

* feat:  优化奖项列表

* fix(Home): 修复奖品列表滚动检测逻辑

* fix(home): 修复抽奖停止逻辑避免重复执行;调整卡片垂直居中位置计算

* feat:  播放中奖音频 #96

* style: 💄 下载模板成功后进行提示

* docs: 📝 readme更新

* ci: 👷 git action触发改为推送release版本时执行

* Feature action (#143)

* ci: 👷 整合github action配置文件

* docs: 📝 贡献文档修改

* Feature action (#144)

* ci: 👷 整合github action配置文件

* docs: 📝 贡献文档修改

* style: 💄 更新版本

* style: 💄 cargo.lock版本更新

* Feature action (#149)

* ci: 👷 整合github action配置文件

* docs: 📝 贡献文档修改

* style: 💄 更新版本

* style: 💄 cargo.lock版本更新

* feat(husky): 增强Git标签版本校验脚本

添加了对Git标签指向提交与release分支一致性的校验功能。
脚本现在会检查tag指向的提交是否与当前或任何release分支的最新提交一致,
确保发布流程的准确性。如果当前在release分支上,直接比较分支HEAD与tag指向的提交;
如果不在release分支上,则遍历所有release分支查找匹配的提交。

* feat:  国际化

* Feature action (#150)

* ci: 👷 整合github action配置文件

* docs: 📝 贡献文档修改

* style: 💄 更新版本

* style: 💄 cargo.lock版本更新

* feat(husky): 增强Git标签版本校验脚本

添加了对Git标签指向提交与release分支一致性的校验功能。
脚本现在会检查tag指向的提交是否与当前或任何release分支的最新提交一致,
确保发布流程的准确性。如果当前在release分支上,直接比较分支HEAD与tag指向的提交;
如果不在release分支上,则遍历所有release分支查找匹配的提交。

* feat:  国际化

* fix: 🐛 修复国际化问题;修复字体大小未生效问题

* fix: 🐛 修复部分问题

* docs: 📝 update readme
This commit is contained in:
LOG1997
2025-12-30 17:31:19 +08:00
committed by GitHub
parent f952607ca5
commit f15494d6f1
265 changed files with 19456 additions and 9475 deletions

View File

@@ -4,11 +4,12 @@
## PR检查表 ## PR检查表
- 该项目主要的工作分支有`main``dev` - 该项目主要的工作分支有`main``release``dev`三个分支。
- `main`分支包含的是已经发布的功能,请勿提交PR到`main`分支。 - `main`分支用于生产环境,如果是修改文档、注释、代码格式化等不影响主要功能的修改,可以直接发起PR到`main`分支。
- 如要贡献代码,请从`dev`分支拉取代码,该分支一般包含最新待上线的功能 - `release`分支用于发布新版本,为当前网站上运行的正式版本的代码
- 你的代码提交添加新功能、修复bug、优化性能等需要发起PR到`dev`分支 - 如要贡献代码,请从`release`或者`main`分支拉取代码。
- 请尽可能地在您的PR请求中描述清楚添加功能或者修复的问题 - 你的代码提交(添加功能、修复bug、优化性能等尽量发起PR到`dev`分支。
- 请尽可能地在您的PR请求中描述清楚添加的功能或者修复的问题。
- 在一个PR中有多个小提交是没问题的但请确保每个提交都包含一个清晰的提交信息。 - 在一个PR中有多个小提交是没问题的但请确保每个提交都包含一个清晰的提交信息。
- 请确保您的提交信息遵循[Conventional Commits](https://www.conventionalcommits.org/)规范。 - 请确保您的提交信息遵循[Conventional Commits](https://www.conventionalcommits.org/)规范。
@@ -32,6 +33,12 @@ pnpm install # 安装依赖
pnpm dev pnpm dev
``` ```
启动tauri的开发服务
```bash
pnpm tauri dev
```
### `pnpm build` ### `pnpm build`
构建项目 构建项目
@@ -40,12 +47,10 @@ pnpm dev
pnpm build pnpm build
``` ```
### `pnpm build:file` 打包tauri安装包
打包后的项目可以直接通过html文件的形式直接在浏览器打开
```bash ```bash
pnpm build:file pnpm tauri build
``` ```
### `pnpm lint:fix` ### `pnpm lint:fix`

View File

@@ -1,44 +0,0 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- name: Copy and Rename index.html to dist
run: cp dist/index.html dist/404.html
- name: Deploy to gh-pages
uses: crazy-max/ghaction-github-pages@v2
with:
target_branch: gh-pages
build_dir: dist
env:
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}

View File

@@ -6,7 +6,7 @@ on:
- 'v*' - 'v*'
jobs: jobs:
build-and-release: build-web:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -32,31 +32,139 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
- name: Install dependency fast-glob
run: pnpm add fast-glob -D
- name: Build for production - name: Build for production
run: pnpm build run: pnpm build
- name: Build for file protocol
run: pnpm build:file
- name: Prepare directories - name: Prepare directories
run: | run: |
mv dist dist-prod mv dist dist-prod
mv dist-prod/index.html dist-prod/404.html
- name: Create production build archive - name: Create production build archive
run: | run: |
tar -czf dist.tar.gz dist-prod zip -r dist.zip dist-prod
- name: Create file protocol build archive - name: Upload web build artifact
uses: actions/upload-artifact@v4
with:
name: web-build
path: dist.zip
- name: Prepare GitHub Pages artifact
run: | run: |
tar -czf dist-file.tar.gz dist-file cp -r dist-prod dist-gh-pages
- name: Release - name: Upload GitHub Pages artifact
uses: actions/upload-artifact@v4
with:
name: github-pages
path: dist-gh-pages
- name: Prepare GitHub Pages artifact
run: |
cp -r dist-prod dist-gh-pages
- name: Upload GitHub Pages artifact
uses: actions/upload-artifact@v4
with:
name: github-pages
path: dist-gh-pages
publish-tauri:
permissions:
contents: write
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: ./src-tauri -> target
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 22.x
cache: pnpm
- name: Install frontend dependencies
run: pnpm install
- name: Install dependency fast-glob
run: pnpm add fast-glob -D
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tagName: ${{ github.ref_name }}
releaseName: 'Release ${{ github.ref_name }}'
releaseBody: See the assets to download this version and install.
releaseDraft: true
prerelease: false
run: pnpm tauri build
deploy-gh-pages:
needs: build-web
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Download GitHub Pages artifact
uses: actions/download-artifact@v4
with:
name: github-pages
path: dist-gh-pages
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Upload to GitHub Pages
uses: actions/deploy-pages@v2
id: deployment
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist-gh-pages
release:
needs: [build-web, publish-tauri, deploy-gh-pages]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download web build artifact
uses: actions/download-artifact@v4
with:
name: web-build
- name: Create Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
files: | files: |
dist.tar.gz dist.zip
dist-file.tar.gz name: 'Release ${{ github.ref_name }}'
draft: true draft: true
prerelease: false prerelease: false
generate_release_notes: true generate_release_notes: true

8
.gitignore vendored
View File

@@ -35,13 +35,15 @@ bower_components
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
pnpm-lock.yaml
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/ jspm_packages/
.husky/_
# Snowpack dependency directory (https://snowpack.dev/) # Snowpack dependency directory (https://snowpack.dev/)
web_modules/ web_modules/
@@ -79,6 +81,9 @@ web_modules/
.env.production.local .env.production.local
.env.local .env.local
**/components.d.ts
**/auto-imports.d.ts
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache
.parcel-cache .parcel-cache
@@ -90,6 +95,7 @@ out
# Nuxt.js build / generate output # Nuxt.js build / generate output
.nuxt .nuxt
dist dist
dist-file
# Gatsby files # Gatsby files
.cache/ .cache/

2
.husky/_/applypatch-msg Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/commit-msg Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

22
.husky/_/h Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env sh
[ "$HUSKY" = "2" ] && set -x
n=$(basename "$0")
s=$(dirname "$(dirname "$0")")/$n
[ ! -f "$s" ] && exit 0
if [ -f "$HOME/.huskyrc" ]; then
echo "husky - '~/.huskyrc' is DEPRECATED, please move your code to ~/.config/husky/init.sh"
fi
i="${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh"
[ -f "$i" ] && . "$i"
[ "${HUSKY-}" = "0" ] && exit 0
export PATH="node_modules/.bin:$PATH"
sh -e "$s" "$@"
c=$?
[ $c != 0 ] && echo "husky - $n script failed (code $c)"
[ $c = 127 ] && echo "husky - command not found in PATH=$PATH"
exit $c

9
.husky/_/husky.sh Normal file
View File

@@ -0,0 +1,9 @@
echo "husky - DEPRECATED
Please remove the following two lines from $0:
#!/usr/bin/env sh
. \"\$(dirname -- \"\$0\")/_/husky.sh\"
They WILL FAIL in v10.0.0
"

2
.husky/_/post-applypatch Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/post-checkout Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/post-commit Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/post-merge Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/post-rewrite Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/pre-applypatch Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/pre-auto-gc Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/pre-commit Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/pre-push Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

2
.husky/_/pre-rebase Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env sh
. "$(dirname "$0")/h"

38
.husky/pre-push Normal file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 读取 stdin 中的所有内容
while read local_ref local_sha remote_ref remote_sha; do
# 检查是否是 tag 推送
case "$remote_ref" in
refs/tags/*)
# 提取 tag 名称
TAG="${remote_ref#refs/tags/}"
# 只校验以 "v" 开头的 tag
if [[ $TAG == v* ]]; then
echo "🏷️ 检查推送的 tag: $TAG"
node .husky/scripts/verifyTagVersion.js "$TAG"
else
echo " 非版本 tag ($TAG),跳过校验"
fi
;;
*)
esac
done
# 如果 stdin 为空(没有推送任何引用),也检查最新 tag
if [ -t 0 ]; then
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
if [ $? -eq 0 ]; then
# 只校验以 "v" 开头的 tag
if [[ $LATEST_TAG == v* ]]; then
echo "🏷️ 检查最新的 tag: $LATEST_TAG"
node .husky/scripts/verifyTagVersion.js "$LATEST_TAG"
else
echo " 最新 tag ($LATEST_TAG) 不是版本 tag跳过校验"
fi
else
echo "⚠️ 没有找到任何 tag跳过版本校验"
fi
fi

View File

@@ -3,18 +3,18 @@ import fs from "fs";
const msg = fs.readFileSync(".git/COMMIT_EDITMSG", "utf-8").trim(); const msg = fs.readFileSync(".git/COMMIT_EDITMSG", "utf-8").trim();
const commitRE = const commitRE =
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/; /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/;
const mergeRe = /^(Merge pull request|Merge branch)/; const mergeRe = /^(Merge pull request|Merge branch)/;
if (!commitRE.test(msg)) { if (!commitRE.test(msg)) {
if (!mergeRe.test(msg)) { if (!mergeRe.test(msg)) {
console.log("git commit信息校验不通过"); console.log("git commit信息校验不通过");
console.error(`git commit的信息格式不对, 需要使用 title(scope): desc的格式 console.error(`git commit的信息格式不对, 需要使用 title(scope): desc的格式
比如 fix: xxbug 比如 fix: xxbug
feat(test): add new feat(test): add new
`); `);
process.exit(1); process.exit(1);
} }
} else { } else {
console.log("git commit信息校验通过"); console.log("git commit信息校验通过");
} }

View File

@@ -0,0 +1,106 @@
import fs from 'fs';
import { execSync } from 'child_process';
try {
// 读取 package.json 中的版本号
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const packageVersion = packageJson.version;
// 获取命令行参数中的 tag 名称
const tag = process.argv[2];
if (!tag) {
console.log('⚠️ 未提供 tag 参数,跳过版本校验');
process.exit(0);
}
// 检查 tag 是否与 package.json 版本匹配
const validTags = [`v${packageVersion}`, packageVersion];
if (!validTags.includes(tag)) {
console.log('🏷️ Git tag 版本校验不通过');
console.error(`当前 package.json 版本为: ${packageVersion}`);
console.error(`提供的 tag 为: ${tag}`);
process.exit(1);
} else {
console.log('✅ Git tag 版本校验通过');
}
// 获取 tag 指向的提交哈希
const tagCommit = execSync(`git rev-list -1 ${tag}`, { encoding: 'utf-8' }).trim();
// 获取当前分支名称
let currentBranch;
try {
currentBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
} catch (e) {
console.error('无法获取当前分支名称');
process.exit(1);
}
// 检查当前分支是否为 release 分支
if (currentBranch.startsWith('release/')) {
// 如果当前在 release 分支上,检查当前分支的 HEAD 提交是否与 tag 指向的提交一致
const currentBranchCommit = execSync(`git rev-parse ${currentBranch}`, { encoding: 'utf-8' }).trim();
if (tagCommit !== currentBranchCommit) {
console.log('🏷️ Git tag 指向的提交与当前 release 分支的最新提交不一致');
console.error(`当前 release 分支 "${currentBranch}" 的最新提交为: ${currentBranchCommit}`);
console.error(`Tag "${tag}" 指向的提交为: ${tagCommit}`);
process.exit(1);
} else {
console.log('✅ Git tag 指向的提交与当前 release 分支的最新提交一致');
}
} else {
// 如果当前不在 release 分支上,查找所有 release 分支并检查是否有分支的 HEAD 与 tag 指向的提交一致
console.log(`🔍 当前在 "${currentBranch}" 分支,检查 tag 指向的提交是否与任何 release 分支的最新提交一致`);
// 获取所有本地分支
const localBranchesOutput = execSync('git branch --format="%(refname:short)"', { encoding: 'utf-8' });
const localBranches = localBranchesOutput.split('\n').map(b => b.trim()).filter(b => b);
// 过滤出 release 分支
const releaseBranches = localBranches.filter(branch => branch.startsWith('release/'));
if (releaseBranches.length === 0) {
console.log('⚠️ 未找到 release 分支');
process.exit(1);
}
let foundMatchingBranch = false;
let matchingBranchName = '';
for (const branch of releaseBranches) {
try {
// 获取 release 分支的最新提交
const releaseBranchCommit = execSync(`git rev-parse ${branch}`, { encoding: 'utf-8' }).trim();
// 检查是否与 tag 指向的提交一致
if (tagCommit === releaseBranchCommit) {
foundMatchingBranch = true;
matchingBranchName = branch;
break;
}
} catch (e) {
// 如果无法获取分支信息,继续尝试下一个分支
continue;
}
}
if (!foundMatchingBranch) {
console.log('🏷️ Git tag 指向的提交与任何 release 分支的最新提交都不一致');
console.error(`提供的 tag 为: ${tag}`);
console.error(`tag 指向的提交为: ${tagCommit}`);
console.log(`检查了以下 release 分支:`, releaseBranches);
console.log('可能的原因:');
console.log('1. release 分支不是最新的');
console.log('2. tag 指向的提交不在 release 分支上');
process.exit(1);
} else {
console.log(`✅ Git tag 指向的提交与 release 分支 "${matchingBranchName}" 的最新提交一致`);
}
}
} catch (error) {
console.error('❌ 校验过程中发生错误:', error.message);
process.exit(1);
}

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
engine-strict=true

View File

@@ -1,5 +1,5 @@
# 使用官方的 Node 镜像作为基础镜像 # 使用更小的 Node 镜像作为构建基础镜像
FROM node:22 FROM node:22-alpine as builder
# 设置工作目录 # 设置工作目录
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@@ -17,20 +17,21 @@ log-lottery是一个可配置可定制化的抽奖应用炫酷3D球体
> 如果进入网站遇到图片无法显示或有报错的情况,请先到【全局配置】-【界面配置】菜单中点击【重置所有数据】按钮清除数据后进行更新。 > 如果进入网站遇到图片无法显示或有报错的情况,请先到【全局配置】-【界面配置】菜单中点击【重置所有数据】按钮清除数据后进行更新。
> 该项目将在近期进行**内部代码重构**及**开发新功能**,预计元旦节前三天上线新版本。
## 要求 ## 要求
使用PC端最新版Chrome或Edge浏览器。 使用PC端最新版Chrome或Edge浏览器。
访问地址: 访问地址:
<https://to2026.xyz/log-lottery> <https://lottery.to2026.xyz/log-lottery>
or or
<https://log1997.github.io/log-lottery/> <https://log1997.github.io/log-lottery/>
开发仓促若以上网站内容存在bug还请宽容。
如果想要访问2025年12月31日前的版本请前往<https://lottery.to2026.xyz/log-lottery>
## TODO ## TODO
- [x] 🕍 炫酷3D球体年会抽奖必备开箱即用 - [x] 🕍 炫酷3D球体年会抽奖必备开箱即用
@@ -70,33 +71,27 @@ or
## 预览 ## 预览
首页 首页
<div align="center">
![image_home](./static/images/home.png) <img src="./static/images/home.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
<img src="./static//images/home_prizelist.png" alt="img2-2" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
![image_home_prize_list](./static//images/home_prizelist.png) </div>
抽奖 抽奖
<div align="center">
![image_lottery](./static/images/lottery-enter.png) <img src="./static/images/lottery-enter.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
<img src="./static/images/lottery-done.png" alt="img2-2" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
![image_lottery_done](./static/images/lottery-done.png) </div>
配置 配置
<div align="center">
![image_config_person_all](./static/images/config_personall.png) <img src="./static/images/config_personall.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
<img src="./static/images/config_prize.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
![image_config_prize_list](./static/images/config_prize.png) <img src="./static/images/config-view.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
<img src="./static/images/config_pattern.png" alt="img2-1" width="400" style="border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin: 8px;">
![image_config_view](./static/images/config-view.png) </div>
![image_config_pattern](./static/images/config_pattern.png)
图片音乐配置 图片音乐配置
![image_config_img](./static/images/image_config.png)
![image_music](./static/images/music_music.png)
## 技术 ## 技术
- vue3 - vue3
@@ -131,31 +126,54 @@ or
npm run build npm run build
``` ```
若想直接以打开html文件的方式运行请执行以下命令进行打包。打包完成后在dist目录中直接打开index.html即可。
```bash
pnpm build:file
or
npm run build:file
```
> 项目思路来源于 <https://github.com/moshang-xc/lottery> > 项目思路来源于 <https://github.com/moshang-xc/lottery>
## Docker支持 ## Docker支持
构建镜像 以下任意方式选一种即可
```bash 1. 拉取镜像从Docker Hub拉取镜像[log-lottery](https://hub.docker.com/r/log1997/log-lottery)
docker build -t log-lottery .
```
运行容器 ```bash
docker pull log1997/log-lottery:latest
```
```bash 运行容器
docker run -d -p 9279:80 log-lottery
```
容器运行成功后即可在本地通过<http://localhost:9279/log-lottery/>访问 ```bash
docker run -d --name log-lottery -p 9279:80 log1997/log-lottery:latest
```
2. 手动构建镜像
```bash
docker build -t log-lottery .
```
运行容器
```bash
docker run -d -p 9279:80 log-lottery
```
容器运行成功后即可在本地通过<http://localhost:9279/log-lottery/>访问
3. 软件安装包
可前往[Releases](https://github.com/LOG1997/log-lottery/releases)下载。
目前只支持windows平台使用跨平台安装包暂不支持如有需要请自行编译参照[贡献文档](https://github.com/LOG1997/log-lottery/blob/dev/.github/CONTRIBUTING.md)
## 支持项目
<h3>💝 赞助支持</h3>
<p><em>如果您觉得 log-lottery 对您有帮助,欢迎赞助支持,您的支持是我们不断前进的动力!</em></p>
<div>
<img src="./static/images/ZanShang.png" height="240" alt="WeChat Code">
</div>
<br>
## Star History ## Star History

View File

@@ -1,41 +0,0 @@
import Button from '@/components/Button/index.vue'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'
// 测试分组
describe('Button', () => {
// mount
test('Buttons slot text', () => {
// @vue/test-utils
const wrapper = shallowMount(Button, {
slots: {
default: 'Button',
},
})
// 断言
expect(wrapper.text()).toBe('Button')
})
test('Button click', () => {
const wrapper = shallowMount(Button)
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
test('Button disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: true,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
test('Button not disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: false,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})

View File

@@ -1,41 +0,0 @@
import Button from '@/components/Button/index.vue'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'
// 测试分组
describe('Button', () => {
// mount
test('Buttons slot text', () => {
// @vue/test-utils
const wrapper = shallowMount(Button, {
slots: {
default: 'Button',
},
})
// 断言
expect(wrapper.text()).toBe('Button')
})
test('Button click', () => {
const wrapper = shallowMount(Button)
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
test('Button disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: true,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
test('Button not disabled', () => {
const wrapper = shallowMount(Button, {
props: {
disabled: false,
},
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})

124
__test__/Random.test.ts Normal file
View File

@@ -0,0 +1,124 @@
import { describe, expect, it } from 'vitest'
import { getRandomElements } from '@/views/Home/utils/random'
describe('getRandomElements', () => {
// 测试基本功能:从数组中获取指定数量的元素
it('should return specified number of elements', () => {
const sourceArray = [1, 2, 3, 4, 5]
const result = getRandomElements(sourceArray, 3)
expect(result).toHaveLength(3)
result.forEach((element) => {
expect(sourceArray).toContain(element)
})
})
// 测试边界情况count为0
it('should return empty array when count is 0', () => {
const sourceArray = [1, 2, 3]
const result = getRandomElements(sourceArray, 0)
expect(result).toEqual([])
})
// 测试边界情况count为负数
it('should return empty array when count is negative', () => {
const sourceArray = [1, 2, 3]
const result = getRandomElements(sourceArray, -1)
expect(result).toEqual([])
})
// 测试边界情况count大于等于数组长度
it('should return shuffled array when count equals or exceeds array length', () => {
const sourceArray = [1, 2, 3]
const result1 = getRandomElements(sourceArray, 3)
const result2 = getRandomElements(sourceArray, 5)
expect(result1).toHaveLength(3)
expect(result2).toHaveLength(3)
// 验证返回的元素与原数组相同
expect(result1.sort()).toEqual(sourceArray.sort())
expect(result2.sort()).toEqual(sourceArray.sort())
})
// 测试空数组情况
it('should return empty array when source array is empty', () => {
const sourceArray: number[] = []
const result = getRandomElements(sourceArray, 3)
expect(result).toEqual([])
})
// 测试单元素数组
it('should handle single element array', () => {
const sourceArray = [42]
const result = getRandomElements(sourceArray, 1)
expect(result).toEqual([42])
})
// 测试字符串数组
it('should work with string arrays', () => {
const sourceArray = ['a', 'b', 'c', 'd', 'e']
const result = getRandomElements(sourceArray, 2)
expect(result).toHaveLength(2)
result.forEach((element) => {
expect(sourceArray).toContain(element)
})
})
// 测试对象数组
it('should work with object arrays', () => {
const sourceArray = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]
const result = getRandomElements(sourceArray, 2)
expect(result).toHaveLength(2)
result.forEach((element) => {
expect(sourceArray).toContain(element)
})
})
// 测试多次调用应产生不同结果(概率性测试)
it('should produce different results on multiple calls', () => {
const sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
const results = new Set()
// 多次调用并收集结果
for (let i = 0; i < 10; i++) {
const result = getRandomElements(sourceArray, 5).sort().join(',')
results.add(result)
}
// 虽然有极小概率会相同,但大多数情况下应该有不同的结果
expect(results.size).toBeGreaterThan(1)
})
// 多次调用,每个元素抽中的概率基本上相等
it('should have approximately equal probabilities for each element', () => {
const sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
const times = 200000 // 次数
const count = 5 // 抽奖个数
const expectedProbability = count / sourceArray.length
const elementCounts = new Map()
// 多次调用并统计元素出现的次数
for (let i = 0; i < times; i++) {
const result = getRandomElements(sourceArray, count)
result.forEach((element) => {
const count = elementCounts.get(element) || 0
elementCounts.set(element, count + 1)
})
}
elementCounts.forEach((value) => {
// 验证每个元素出现的概率接近相等
const probability = value / times
expect(probability).toBeCloseTo(expectedProbability, 2)
})
})
})

View File

@@ -1,31 +0,0 @@
// test axios request
import Request from '@/api/request';
import { describe, it, expect, vi } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import axios from 'axios';
const fn = vi.fn();
const mockRes = {
data: {
code: 200,
success: true,
message: 'success',
data: {
name: 'test',
age: 18,
},
},
};
fn(mockRes);
fn.mock.calls[0] === [mockRes];
describe('Request', () => {
it('should return data when request success', async () => {
const request = new Request();
const res = await request({
url: '/test',
method: 'GET',
});
expect(res).toEqual(mockRes.data);
});
});

22
build/syncVersion.js Normal file
View File

@@ -0,0 +1,22 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 读取 package.json 版本号
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
const version = packageJson.version;
// 读取并更新 tauri.conf.json
const tauriConfPath = path.join(__dirname, '..', 'src-tauri', 'tauri.conf.json');
const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8'));
// 更新版本号
tauriConf.version = version;
// 写回 tauri.conf.json
fs.writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2));
console.log(`Tauri 配置版本号已同步至: ${version}`);

21
components.json Normal file
View File

@@ -0,0 +1,21 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": true,
"tailwind": {
"config": "",
"css": "src/style.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"composables": "@/composables"
},
"registries": {}
}

View File

@@ -1,7 +1,13 @@
import antfu from '@antfu/eslint-config' import antfu from '@antfu/eslint-config'
export default antfu( export default antfu(
{ {
ignores: ['**/node_modules', '**/public', '**/dist', '**/package.json', '**/*.yaml', '**/.gitignore', '**/.env*', '**/tsconfig*'], ignores: ['**/node_modules', '**/build', '**/.husky', '**/public', '**/dist', '**/package.json', '**/*.yaml', '**/.gitignore', '**/.env*', '**/tsconfig*', '**/*.config.js'],
}, },
{
rules: {
'no-console': 'warn',
'no-debugger': 'warn',
}
},
) )

View File

@@ -1,7 +1,7 @@
{ {
"name": "log-lottery", "name": "log-lottery",
"private": true, "private": true,
"version": "0.0.4", "version": "0.5.0",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@@ -9,78 +9,105 @@
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"build:pre": "vue-tsc --noEmit && vite build --mode prebuild", "build:pre": "vue-tsc --noEmit && vite build --mode prebuild",
"build:file": "vue-tsc --noEmit && vite build --mode file", "build:file": "vue-tsc --noEmit && vite build --mode file",
"tauri": "node ./build/syncVersion.js && tauri",
"test": "vitest", "test": "vitest",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint ./src", "lint": "eslint ./src",
"lint:fix": "eslint ./src --fix" "lint:fix": "eslint ./src --fix",
"prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@tweenjs/tween.js": "^23.1.2", "@tweenjs/tween.js": "23.1.2",
"@vueuse/core": "^11.3.0", "@vueuse/core": "^14.1.0",
"axios": "^1.7.8", "axios": "^1.13.2",
"canvas-confetti": "^1.9.3", "canvas-confetti": "^1.9.4",
"dayjs": "^1.11.13", "class-variance-authority": "^0.7.1",
"github-markdown-css": "^5.8.0", "clsx": "^2.1.1",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"github-markdown-css": "^5.8.1",
"gsap": "^3.14.2",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash-es": "^4.17.22",
"lucide-vue-next": "^0.562.0",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"pinia": "^2.2.6", "masonry-layout": "^4.2.2",
"pinia": "^3.0.4",
"pinia-plugin-persist": "^1.0.0", "pinia-plugin-persist": "^1.0.0",
"reka-ui": "^2.7.0",
"sparticles": "^1.3.1", "sparticles": "^1.3.1",
"three": "^0.166.0", "tailwind-merge": "^3.4.0",
"three-css3d": "^1.0.6", "three": "0.166.0",
"vue": "^3.5.13", "three-css3d": "1.0.6",
"vue-dompurify-html": "^5.2.0", "uuid": "^13.0.0",
"vue-i18n": "^10.0.4", "vue": "^3.5.26",
"vue-router": "^4.5.0", "vue-dompurify-html": "^5.3.0",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.2.7",
"vue-router": "^4.6.4",
"vue-sonner": "^2.0.9",
"vue-toast-notification": "^3", "vue-toast-notification": "^3",
"vue3-colorpicker": "^2.3.0", "vue3-colorpicker": "^2.3.0",
"xlsx": "^0.18.5", "xlsx": "^0.18.5",
"zod": "^3.23.8" "zod": "^4.2.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^3.9.2", "@antfu/eslint-config": "^6.7.3",
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.15.0", "@eslint/js": "^9.39.2",
"@iconify-json/ep": "^1.2.1", "@iconify-json/ep": "^1.2.3",
"@iconify-json/fluent": "^1.2.8", "@iconify-json/fluent": "^1.2.36",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.18",
"@tauri-apps/cli": "^2.9.6",
"@testing-library/vue": "^8.1.0", "@testing-library/vue": "^8.1.0",
"@types/canvas-confetti": "^1.6.4", "@types/canvas-confetti": "^1.9.0",
"@types/lodash-es": "^4.17.12",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/node": "^22.9.4", "@types/masonry-layout": "^4.2.8",
"@types/three": "^0.166.0", "@types/node": "^25.0.3",
"@typescript-eslint/eslint-plugin": "^8.16.0", "@types/three": "0.166.0",
"@typescript-eslint/parser": "^8.16.0", "@typescript-eslint/eslint-plugin": "^8.50.1",
"@vitejs/plugin-legacy": "^6.0.0", "@typescript-eslint/parser": "^8.50.1",
"@vitejs/plugin-vue": "^5.2.0", "@vitejs/plugin-legacy": "^7.2.1",
"@vitest/ui": "^2.1.5", "@vitejs/plugin-vue": "^6.0.3",
"@vitest/ui": "^4.0.16",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.20", "@vue/tsconfig": "^0.8.1",
"daisyui": "^4.12.14", "autoprefixer": "^10.4.23",
"eslint": "^9.15.0", "baseline-browser-mapping": "^2.9.11",
"eslint-plugin-vue": "^9.31.0", "child_process": "^1.0.2",
"globals": "^15.12.0", "daisyui": "^5.5.14",
"happy-dom": "^15.11.6", "eslint": "^9.39.2",
"eslint-plugin-vue": "^10.6.2",
"fast-glob": "^3.3.3",
"globals": "^16.5.0",
"happy-dom": "^20.0.11",
"husky": "^9.1.7", "husky": "^9.1.7",
"jsdom": "^25.0.1", "jsdom": "^27.3.0",
"path": "^0.12.7", "path": "^0.12.7",
"postcss": "^8.4.49", "postcss": "^8.5.6",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^6.0.5",
"sass": "^1.81.0", "sass": "^1.97.1",
"sass-loader": "^16.0.3", "sass-loader": "^16.0.6",
"tailwindcss": "^3.4.15", "tailwindcss": "^4.1.18",
"terser": "^5.36.0", "terser": "^5.44.1",
"typescript": "5.5.3", "tw-animate-css": "^1.4.0",
"unplugin-auto-import": "^0.18.5", "typescript": "~5.9.3",
"unplugin-icons": "^0.20.1", "unplugin-auto-import": "^20.3.0",
"unplugin-vue-components": "^0.27.4", "unplugin-icons": "^22.5.0",
"vite": "^5.4.11", "unplugin-vue-components": "^30.0.0",
"vite": "^7.3.0",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-inspect": "^0.8.8", "vite-plugin-inspect": "^11.3.3",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-devtools": "^7.6.4", "vite-plugin-vue-devtools": "^8.0.5",
"vitest": "^2.1.5", "vitest": "^4.0.16",
"vue-tsc": "^2.1.10" "vue-tsc": "^3.2.1"
} },
"engines": {
"node": ">=22.x"
},
"packageManager": "pnpm@10.26.1"
} }

9641
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

4
src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/gen/schemas

5005
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

25
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "app"
version = "0.5.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.3", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.9.4", features = [] }
tauri-plugin-log = "2"

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src-tauri/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

16
src-tauri/src/lib.rs Normal file
View File

@@ -0,0 +1,16 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

6
src-tauri/src/main.rs Normal file
View File

@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
app_lib::run();
}

37
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,37 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "log-lottery",
"version": "0.5.0",
"identifier": "to2026.xyz",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:6719/log-lottery",
"beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm build"
},
"app": {
"windows": [
{
"title": "log-lottery",
"width": 800,
"height": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@@ -1,86 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import PlayMusic from '@/components/PlayMusic/index.vue' import { provide } from 'vue'
import useStore from '@/store' import { loadingKey, loadingState } from '@/components/Loading'
import { themeChange } from '@/utils' // import PlayMusic from '@/components/PlayMusic/index.vue'
import { storeToRefs } from 'pinia'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n() provide(loadingKey, loadingState)
const globalConfig = useStore().globalConfig
const prizeConfig = useStore().prizeConfig
const system = useStore().system
const { getTheme: localTheme } = storeToRefs(globalConfig)
const { getPrizeConfig: prizeList } = storeToRefs(prizeConfig)
const tipDialog = ref()
// 设置当前奖列表
function setCurrentPrize() {
if (prizeList.value.length <= 0) {
return
}
for (let i = 0; i < prizeList.value.length; i++) {
if (!prizeList.value[i].isUsed) {
prizeConfig.setCurrentPrize(prizeList.value[i])
break
}
}
}
// 判断是否手机端访问
function judgeMobile() {
const ua = navigator.userAgent
const isAndroid = ua.includes('Android') || ua.includes('Adr')
const isIOS = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
system.setIsMobile(isAndroid || isIOS)
return isAndroid || isIOS
}
// 判断是否chrome或者edge访问
function judgeChromeOrEdge() {
const ua = navigator.userAgent
const isChrome = ua.includes('Chrome')
const isEdge = ua.includes('Edg')
system.setIsChrome(isChrome)
return isChrome || isEdge
}
onMounted(() => {
themeChange(localTheme.value.name)
setCurrentPrize()
if (judgeMobile() || !judgeChromeOrEdge()) {
tipDialog.value.showModal()
}
})
</script> </script>
<template> <template>
<dialog id="my_modal_1" ref="tipDialog" class="border-none modal">
<div class="modal-box">
<h3 class="text-lg font-bold">
{{ t('dialog.titleTip') }}
</h3>
<p v-if="judgeMobile()" class="py-4">
{{ t('dialog.dialogPCWeb') }}
</p>
<p v-if=" !judgeChromeOrEdge()" class="py-4">
{{ t('dialog.dialogLatestBrowser') }}
</p>
<div class="modal-action">
<form method="dialog" class="flex justify-start w-full gap-3">
<!-- if there is a button in form, it will close the modal -->
<button class="btn">
{{ t('button.confirm') }}
</button>
</form>
</div>
</div>
</dialog>
<router-view /> <router-view />
<PlayMusic class="absolute right-0 bottom-1/2" />
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

BIN
src/assets/audio/enter.wav Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More