├── .eslintrc.cjs ├── .github └── workflows │ └── deploy.yaml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── public ├── banners │ ├── EventBanner0.png │ ├── EventBanner1.png │ └── EventBanner2.png ├── favicon.png └── images │ ├── Background.png │ ├── ButtonBlueBg0.png │ ├── ButtonBlueBg1.png │ ├── ButtonGrayBg0.png │ ├── ButtonGrayBg1.png │ ├── ButtonYellowBg0.png │ ├── ButtonYellowBg1.png │ ├── Gacha0.png │ ├── Gacha1.png │ ├── Header.png │ ├── HeaderBg.png │ ├── IntroBgLeft.png │ ├── IntroBgRight.png │ ├── New.png │ ├── Point.png │ ├── Star.png │ └── Stone.png ├── scripts └── fetch_non_limited_data.py ├── src ├── App.vue ├── assets │ ├── data │ │ ├── limited_students_1.ts │ │ └── non_limited_students.ts │ ├── styles │ │ ├── base.scss │ │ ├── button-group.scss │ │ ├── main-view.scss │ │ ├── mixin.scss │ │ └── modal.scss │ └── utils │ │ ├── api.ts │ │ └── interface.ts ├── components │ ├── ClickEffect.vue │ ├── CustomModal.vue │ ├── MainContainer.vue │ ├── ResultContainer.vue │ ├── SignBoard.vue │ └── icons │ │ └── CloseIcon.vue ├── declare.d.ts ├── main.ts ├── router │ └── index.ts ├── stores │ ├── gacha.ts │ └── index.ts └── views │ ├── MainView.vue │ ├── ResultView.vue │ └── VideoView.vue ├── tsconfig.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | # 将静态内容部署到 GitHub Pages 的简易工作流程 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # 仅在推送到默认分支时运行。 6 | push: 7 | branches: ['main'] 8 | 9 | # 这个选项可以使你手动在 Action tab 页面触发工作流 10 | workflow_dispatch: 11 | 12 | # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages。 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # 允许一个并发的部署 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # 单次部署的工作描述 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Set up Node 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18 37 | cache: 'npm' 38 | - name: Install dependencies 39 | run: npm install 40 | - name: Build 41 | run: npm run build 42 | - name: Copy 404.html 43 | run: npm run deploy 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v3 46 | - name: Upload artifact 47 | uses: actions/upload-pages-artifact@v1 48 | with: 49 | # Upload dist repository 50 | path: './docs' 51 | - name: Deploy to GitHub Pages 52 | id: deployment 53 | uses: actions/deploy-pages@v1 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | .vscode 31 | docs 32 | 33 | # all mp4 files 34 | *.mp4 -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 4, 5 | "singleQuote": true, 6 | "printWidth": 110, 7 | "trailingComma": "none" 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 U1805 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

碧蓝档案吃井模拟器

2 | 3 |
4 | 5 | 6 | 7 | stars 8 | 9 |
10 | 11 |
12 | 在线抽卡模拟器
13 | 在,来吃井 14 |
15 | 16 | 17 | [小抽一井!](https://u1805.github.io/ba-gacha) 18 | 19 | ## 预览 20 | 21 | ![gacha1](https://github.com/U1805/blue-archive-gacha-simulator/assets/45514638/4e758b1d-71b0-4364-ace4-953bf6eeb91f) 22 | 23 | ## 使用方法 24 | 25 | **随便点** 26 | 27 | ## 贡献 28 | 29 | Issue 和 PR 大欢迎!如果你遇到任何问题或者有好想法,请开一个 issue。另外也欢迎提交 pull request 来解决问题或者实现新功能。 30 | 31 | ## todo-list 32 | ``` 33 | - 多卡池切换 34 | - 限时招募、长期招募、限定招募 done 35 | - 更多可以点击的地方 36 | - 鼠标点击效果 done 37 | - 吃井提示 38 | - 抽卡签名板 done 39 | ``` 40 | 41 | ## 感谢 42 | 43 | 项目灵感来自: 44 | 45 | - [Genshin Impact Wish Simulator](https://github.com/uzair-ashraf/genshin-impact-wish-simulator/) 46 | 47 | 角色数据来自: 48 | 49 | - [lonqie/SchaleDB](https://github.com/lonqie/SchaleDB) 50 | 51 | ## License 52 | [MIT License](./LICENSE) 53 | 54 | ## Copyrights 55 | 56 | 本应用中的资源均来自第三方网站获取,部分视频是游戏内录屏。 57 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blue Archive 吃井模拟器 | Blue Archive Gacha Simulator 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blue-archive-gacha-simulator", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite --host", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit --composite false", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 12 | "format": "prettier --write src/", 13 | "deploy": "cd docs && cp index.html 404.html" 14 | }, 15 | "dependencies": { 16 | "@types/vue": "^2.0.0", 17 | "animejs": "^3.2.2", 18 | "axios": "^1.7.2", 19 | "pinia": "^2.1.7", 20 | "vite-plugin-top-level-await": "^1.4.4", 21 | "vue": "^3.3.4", 22 | "vue-i18n": "^9.3.0-beta.25", 23 | "vue-router": "^4.4.3" 24 | }, 25 | "devDependencies": { 26 | "@rushstack/eslint-patch": "^1.3.2", 27 | "@tsconfig/node18": "^18.2.0", 28 | "@types/animejs": "^3.1.12", 29 | "@types/node": "^18.17.0", 30 | "@vitejs/plugin-vue": "^4.2.3", 31 | "@vue/eslint-config-prettier": "^8.0.0", 32 | "@vue/eslint-config-typescript": "^11.0.3", 33 | "@vue/tsconfig": "^0.4.0", 34 | "eslint": "^8.45.0", 35 | "eslint-plugin-vue": "^9.15.1", 36 | "npm-run-all": "^4.1.5", 37 | "prettier": "^3.0.0", 38 | "sass": "^1.64.2", 39 | "sass-loader": "^13.3.2", 40 | "typescript": "~5.1.6", 41 | "vite": "^4.4.6", 42 | "vue-tsc": "^1.8.6" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/banners/EventBanner0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/banners/EventBanner0.png -------------------------------------------------------------------------------- /public/banners/EventBanner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/banners/EventBanner1.png -------------------------------------------------------------------------------- /public/banners/EventBanner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/banners/EventBanner2.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/favicon.png -------------------------------------------------------------------------------- /public/images/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Background.png -------------------------------------------------------------------------------- /public/images/ButtonBlueBg0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonBlueBg0.png -------------------------------------------------------------------------------- /public/images/ButtonBlueBg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonBlueBg1.png -------------------------------------------------------------------------------- /public/images/ButtonGrayBg0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonGrayBg0.png -------------------------------------------------------------------------------- /public/images/ButtonGrayBg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonGrayBg1.png -------------------------------------------------------------------------------- /public/images/ButtonYellowBg0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonYellowBg0.png -------------------------------------------------------------------------------- /public/images/ButtonYellowBg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/ButtonYellowBg1.png -------------------------------------------------------------------------------- /public/images/Gacha0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Gacha0.png -------------------------------------------------------------------------------- /public/images/Gacha1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Gacha1.png -------------------------------------------------------------------------------- /public/images/Header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Header.png -------------------------------------------------------------------------------- /public/images/HeaderBg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/HeaderBg.png -------------------------------------------------------------------------------- /public/images/IntroBgLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/IntroBgLeft.png -------------------------------------------------------------------------------- /public/images/IntroBgRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/IntroBgRight.png -------------------------------------------------------------------------------- /public/images/New.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/New.png -------------------------------------------------------------------------------- /public/images/Point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Point.png -------------------------------------------------------------------------------- /public/images/Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Star.png -------------------------------------------------------------------------------- /public/images/Stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/U1805/ba-gacha/52b954008139a5d45e64efa15eb18dc54311799a/public/images/Stone.png -------------------------------------------------------------------------------- /scripts/fetch_non_limited_data.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import logging 4 | import re 5 | 6 | # name fixing table 7 | FIX_KIVO_NAME = { 8 | "ミク": "初音ミク", 9 | "美琴": "御坂美琴", 10 | "操祈": "食蜂操祈", 11 | "涙子": "佐天涙子", 12 | } 13 | FIX_KIVO_SKIN = {"骑行服": "骑行", "私服": "便服", "体育服": "运动服"} 14 | 15 | errors = [] 16 | 17 | 18 | # ANSI color codes 19 | class Colors: 20 | RESET = "\033[0m" 21 | RED = "\033[91m" 22 | GREEN = "\033[92m" 23 | YELLOW = "\033[93m" 24 | BLUE = "\033[94m" 25 | MAGENTA = "\033[95m" 26 | CYAN = "\033[96m" 27 | 28 | 29 | # Custom logger class with colors and emojis 30 | class ColoredLogger(logging.Logger): 31 | def __init__(self, name): 32 | super().__init__(name) 33 | 34 | def info(self, msg, *args, **kwargs): 35 | super().info(f"{Colors.GREEN} {msg}{Colors.RESET}", *args, **kwargs) 36 | 37 | def warning(self, msg, *args, **kwargs): 38 | super().warning(f"{Colors.YELLOW} ⚠️ {msg}{Colors.RESET}", *args, **kwargs) 39 | 40 | def error(self, msg, *args, **kwargs): 41 | super().error(f"{Colors.RED}❌ {msg}{Colors.RESET}", *args, **kwargs) 42 | 43 | def success(self, msg, *args, **kwargs): 44 | self.info(f"{Colors.CYAN}✨ {msg}{Colors.RESET}", *args, **kwargs) 45 | 46 | 47 | # Configure logging 48 | logging.setLoggerClass(ColoredLogger) 49 | logging.basicConfig( 50 | level=logging.INFO, format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" 51 | ) 52 | logger = logging.getLogger(__name__) 53 | 54 | 55 | class BlueRequest: 56 | def request_schale_list(self, lng="jp"): 57 | url = f"https://schaledb.com/data/{lng}/students.min.json" 58 | response = requests.get(url) 59 | if response.status_code != 200: 60 | logger.error( 61 | f"Failed to get SchaleDB student list, status code: {response.status_code}" 62 | ) 63 | raise Exception("Failed to get SchaleDB student list") 64 | return response.json() 65 | 66 | def request_kivo(self, url): 67 | response = requests.get(url) 68 | if response.status_code != 200: 69 | raise Exception("Fail to request data") 70 | data = response.json() 71 | if data["code"] != 2000: 72 | raise Exception(f"Fail to response, codename {data['codename']}") 73 | return data["data"] 74 | 75 | def request_kivo_list(self): 76 | url = "https://api.kivo.fun/api/v1/data/students/?page=1&page_size=5000&name=" 77 | data = self.request_kivo(url) 78 | return data["students"] 79 | 80 | def request_kivo_data(self, id): 81 | url = f"https://api.kivo.fun/api/v1/data/students/{id}" 82 | return self.request_kivo(url) 83 | 84 | 85 | class Schale: 86 | def __init__(self) -> None: 87 | self.ba = BlueRequest() 88 | 89 | @property 90 | def student_list(self): 91 | return self.ba.request_schale_list("zh") 92 | 93 | def get_list(self): 94 | res_jp = self.ba.request_schale_list("jp") 95 | res_zh = self.ba.request_schale_list("zh") 96 | 97 | student_list = {} 98 | for key in res_jp: 99 | jp_name = res_jp[key]["Name"] 100 | zh_name = res_zh[key]["Name"] 101 | 102 | student_name = jp_name 103 | pattern = r"(.*?)((.*))" 104 | if re.search(pattern, zh_name): 105 | name = re.search(pattern, jp_name).group(1) 106 | skin = re.search(pattern, zh_name).group(2) 107 | student_name = f"{name}({skin})" 108 | 109 | student_list[key] = student_name 110 | 111 | return student_list 112 | 113 | 114 | class Kivo: 115 | def __init__(self) -> None: 116 | self.ba = BlueRequest() 117 | self.schale_fetcher = Schale() 118 | self.name = "kivo" 119 | 120 | assert self.name in ["gamekee", "kivo"], f"Invalid site name: {self.name}" 121 | 122 | logger.info(f"🚀 Starting to process {self.name} student data") 123 | self.schale_student_list = self.schale_fetcher.get_list() 124 | self.student_list = self.get_list() 125 | 126 | logger.info(f"🔗 Starting to build schale-{self.name} ID mapping table") 127 | self.maptable = self.build_table(self.student_list, self.schale_student_list) 128 | 129 | @property 130 | def table(self): 131 | return self.maptable 132 | 133 | @staticmethod 134 | def _fix_kivo_name(name): 135 | for key, value in FIX_KIVO_NAME.items(): 136 | if key == name: 137 | logger.info( 138 | f"📝 Correcting name alias for student name {key} -> {value}" 139 | ) 140 | return name.replace(key, value) 141 | return name 142 | 143 | @staticmethod 144 | def _fix_kivo_skin(skin): 145 | for key, value in FIX_KIVO_SKIN.items(): 146 | if key == skin: 147 | logger.info( 148 | f"📝 Correcting name alias for student skin {key} -> {value}" 149 | ) 150 | return skin.replace(key, value) 151 | return skin 152 | 153 | @staticmethod 154 | def _replace_domain(result): 155 | result = [ 156 | item.replace("https://static.kivo.fun/images", "/kivo") for item in result 157 | ] 158 | return result 159 | 160 | def get(self, url): 161 | return self.ba.request_kivo(url) 162 | 163 | def get_list(self): 164 | students = self.ba.request_kivo_list() 165 | student_list = {} 166 | for student in students: 167 | student_name = self._fix_kivo_name(student["given_name_jp"]) 168 | if student["skin"]: 169 | student_name += f"({self._fix_kivo_skin(student['skin'])})" 170 | student_list[student["id"]] = student_name 171 | return student_list 172 | 173 | def build_table(self, kivo_student_list, schale_student_list): 174 | global errors 175 | schale_kivo_table = {} 176 | not_found_count = 0 177 | for key, name in schale_student_list.items(): 178 | search_result = [k for k, v in kivo_student_list.items() if v == name] 179 | if len(search_result) == 1: 180 | logger.info(f"🔍 Match found: {key} <- {name} -> {search_result[0]}") 181 | # schale_kivo_table[key] = search_result[0] 182 | schale_kivo_table[str(search_result[0])] = key 183 | else: 184 | not_found_count += 1 185 | errors.append(f"{key} - {name}") 186 | logger.warning(f"No match found: {key}, {name}") 187 | 188 | logger.success( 189 | f"schale-kivo ID mapping table built, total unmatched students: {not_found_count}" 190 | ) 191 | 192 | return schale_kivo_table 193 | 194 | 195 | if __name__ == "__main__": 196 | kivo = Kivo() 197 | schale = Schale() 198 | 199 | schale_kivo_table = kivo.table 200 | schale_student_list = schale.student_list 201 | 202 | result = [] 203 | 204 | non_limited_list = kivo.get( 205 | "https://api.kivo.fun/api/v1/data/students/?page=1&page_size=2000&is_install=true&release_date_sort=asc&limited=false" 206 | ) 207 | for student in non_limited_list["students"]: 208 | schale_id = schale_kivo_table[str(student["id"])] 209 | schale_student = schale_student_list[schale_id] 210 | student = { 211 | "Id": schale_student["Id"], 212 | "Name": schale_student["Name"], 213 | "StarGrade": schale_student["StarGrade"], 214 | } 215 | result.append(student) 216 | 217 | with open("../src/assets/data/non_limited_students_all.ts", "w", encoding="utf-8") as f: 218 | f.write( 219 | "import type { myStudent } from \"../utils/interface\";\n\n" 220 | + "export const non_limited_students: myStudent[] = " 221 | + json.dumps(result, indent=4, ensure_ascii=False) 222 | ) -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | 44 | 74 | -------------------------------------------------------------------------------- /src/assets/data/limited_students_1.ts: -------------------------------------------------------------------------------- 1 | import type { myStudent } from "../utils/interface"; 2 | 3 | export const limited_students_1: myStudent[] = [ 4 | { 5 | "Id": 10022, 6 | "Name": "日奈(泳装)", 7 | "StarGrade": 3 8 | } 9 | ] -------------------------------------------------------------------------------- /src/assets/data/non_limited_students.ts: -------------------------------------------------------------------------------- 1 | import type { myStudent } from "../utils/interface"; 2 | 3 | export const non_limited_students: myStudent[] = [ 4 | { 5 | "Id": 23000, 6 | "Name": "爱莉", 7 | "StarGrade": 2 8 | }, 9 | { 10 | "Id": 13000, 11 | "Name": "茜", 12 | "StarGrade": 2 13 | }, 14 | { 15 | "Id": 13010, 16 | "Name": "优香", 17 | "StarGrade": 2 18 | }, 19 | { 20 | "Id": 20000, 21 | "Name": "响", 22 | "StarGrade": 3 23 | }, 24 | { 25 | "Id": 10004, 26 | "Name": "日奈", 27 | "StarGrade": 3 28 | }, 29 | { 30 | "Id": 10006, 31 | "Name": "伊织", 32 | "StarGrade": 3 33 | }, 34 | { 35 | "Id": 10009, 36 | "Name": "泉", 37 | "StarGrade": 3 38 | }, 39 | { 40 | "Id": 10003, 41 | "Name": "日富美", 42 | "StarGrade": 3 43 | }, 44 | { 45 | "Id": 10005, 46 | "Name": "星野", 47 | "StarGrade": 3 48 | }, 49 | { 50 | "Id": 13008, 51 | "Name": "芹香", 52 | "StarGrade": 2 53 | }, 54 | { 55 | "Id": 13001, 56 | "Name": "千世", 57 | "StarGrade": 2 58 | }, 59 | { 60 | "Id": 23005, 61 | "Name": "绫音", 62 | "StarGrade": 2 63 | }, 64 | { 65 | "Id": 10010, 66 | "Name": "白子", 67 | "StarGrade": 3 68 | }, 69 | { 70 | "Id": 20002, 71 | "Name": "纱绫", 72 | "StarGrade": 3 73 | }, 74 | { 75 | "Id": 10011, 76 | "Name": "瞬", 77 | "StarGrade": 3 78 | }, 79 | { 80 | "Id": 26003, 81 | "Name": "芹娜", 82 | "StarGrade": 1 83 | }, 84 | { 85 | "Id": 23007, 86 | "Name": "花子", 87 | "StarGrade": 2 88 | }, 89 | { 90 | "Id": 13009, 91 | "Name": "椿", 92 | "StarGrade": 2 93 | }, 94 | { 95 | "Id": 10013, 96 | "Name": "鹤城", 97 | "StarGrade": 3 98 | }, 99 | { 100 | "Id": 23001, 101 | "Name": "枫香", 102 | "StarGrade": 2 103 | }, 104 | { 105 | "Id": 23002, 106 | "Name": "花江", 107 | "StarGrade": 2 108 | }, 109 | { 110 | "Id": 13007, 111 | "Name": "纯子", 112 | "StarGrade": 2 113 | }, 114 | { 115 | "Id": 13006, 116 | "Name": "睦月", 117 | "StarGrade": 2 118 | }, 119 | { 120 | "Id": 13002, 121 | "Name": "明里", 122 | "StarGrade": 2 123 | }, 124 | { 125 | "Id": 13003, 126 | "Name": "莲见", 127 | "StarGrade": 2 128 | }, 129 | { 130 | "Id": 10000, 131 | "Name": "阿露", 132 | "StarGrade": 3 133 | }, 134 | { 135 | "Id": 16001, 136 | "Name": "明日奈", 137 | "StarGrade": 1 138 | }, 139 | { 140 | "Id": 26000, 141 | "Name": "千夏", 142 | "StarGrade": 1 143 | }, 144 | { 145 | "Id": 26005, 146 | "Name": "好美", 147 | "StarGrade": 1 148 | }, 149 | { 150 | "Id": 10001, 151 | "Name": "艾米", 152 | "StarGrade": 3 153 | }, 154 | { 155 | "Id": 23003, 156 | "Name": "晴", 157 | "StarGrade": 2 158 | }, 159 | { 160 | "Id": 20001, 161 | "Name": "花凛", 162 | "StarGrade": 3 163 | }, 164 | { 165 | "Id": 13005, 166 | "Name": "佳代子", 167 | "StarGrade": 2 168 | }, 169 | { 170 | "Id": 16003, 171 | "Name": "铃美", 172 | "StarGrade": 1 173 | }, 174 | { 175 | "Id": 10007, 176 | "Name": "真纪", 177 | "StarGrade": 3 178 | }, 179 | { 180 | "Id": 23004, 181 | "Name": "歌原", 182 | "StarGrade": 2 183 | }, 184 | { 185 | "Id": 26002, 186 | "Name": "茱莉", 187 | "StarGrade": 1 188 | }, 189 | { 190 | "Id": 10012, 191 | "Name": "堇", 192 | "StarGrade": 3 193 | }, 194 | { 195 | "Id": 16000, 196 | "Name": "遥香", 197 | "StarGrade": 1 198 | }, 199 | { 200 | "Id": 26004, 201 | "Name": "志美子", 202 | "StarGrade": 1 203 | }, 204 | { 205 | "Id": 10002, 206 | "Name": "晴奈", 207 | "StarGrade": 3 208 | }, 209 | { 210 | "Id": 16002, 211 | "Name": "琴里", 212 | "StarGrade": 1 213 | }, 214 | { 215 | "Id": 26001, 216 | "Name": "小玉", 217 | "StarGrade": 1 218 | }, 219 | { 220 | "Id": 10008, 221 | "Name": "尼露", 222 | "StarGrade": 3 223 | }, 224 | { 225 | "Id": 16004, 226 | "Name": "菲娜", 227 | "StarGrade": 1 228 | }, 229 | { 230 | "Id": 20003, 231 | "Name": "真白", 232 | "StarGrade": 3 233 | }, 234 | { 235 | "Id": 23006, 236 | "Name": "静子", 237 | "StarGrade": 2 238 | }, 239 | { 240 | "Id": 10014, 241 | "Name": "泉奈", 242 | "StarGrade": 3 243 | }, 244 | { 245 | "Id": 10015, 246 | "Name": "爱丽丝", 247 | "StarGrade": 3 248 | }, 249 | { 250 | "Id": 13011, 251 | "Name": "桃井", 252 | "StarGrade": 2 253 | }, 254 | { 255 | "Id": 10016, 256 | "Name": "绿", 257 | "StarGrade": 3 258 | }, 259 | { 260 | "Id": 10017, 261 | "Name": "切里诺", 262 | "StarGrade": 3 263 | }, 264 | { 265 | "Id": 10018, 266 | "Name": "柚子", 267 | "StarGrade": 3 268 | }, 269 | { 270 | "Id": 10019, 271 | "Name": "梓", 272 | "StarGrade": 3 273 | }, 274 | { 275 | "Id": 10020, 276 | "Name": "小春", 277 | "StarGrade": 3 278 | }, 279 | { 280 | "Id": 20005, 281 | "Name": "日富美(泳装)", 282 | "StarGrade": 3 283 | }, 284 | { 285 | "Id": 10024, 286 | "Name": "白子(骑行)", 287 | "StarGrade": 3 288 | }, 289 | { 290 | "Id": 13012, 291 | "Name": "桐乃", 292 | "StarGrade": 2 293 | }, 294 | { 295 | "Id": 10025, 296 | "Name": "瞬(幼女)", 297 | "StarGrade": 3 298 | }, 299 | { 300 | "Id": 20006, 301 | "Name": "纱绫(便服)", 302 | "StarGrade": 3 303 | }, 304 | { 305 | "Id": 10028, 306 | "Name": "明日奈(兔女郎)", 307 | "StarGrade": 3 308 | }, 309 | { 310 | "Id": 23008, 311 | "Name": "玛丽", 312 | "StarGrade": 2 313 | }, 314 | { 315 | "Id": 10029, 316 | "Name": "夏", 317 | "StarGrade": 3 318 | }, 319 | { 320 | "Id": 20008, 321 | "Name": "亚子", 322 | "StarGrade": 3 323 | }, 324 | { 325 | "Id": 10030, 326 | "Name": "千夏(温泉)", 327 | "StarGrade": 3 328 | }, 329 | { 330 | "Id": 20009, 331 | "Name": "切里诺(温泉)", 332 | "StarGrade": 3 333 | }, 334 | { 335 | "Id": 20010, 336 | "Name": "和香(温泉)", 337 | "StarGrade": 3 338 | }, 339 | { 340 | "Id": 20011, 341 | "Name": "芹香(正月)", 342 | "StarGrade": 3 343 | }, 344 | { 345 | "Id": 20012, 346 | "Name": "濑名", 347 | "StarGrade": 3 348 | }, 349 | { 350 | "Id": 20013, 351 | "Name": "千寻", 352 | "StarGrade": 3 353 | }, 354 | { 355 | "Id": 10034, 356 | "Name": "三森", 357 | "StarGrade": 3 358 | }, 359 | { 360 | "Id": 10035, 361 | "Name": "忧", 362 | "StarGrade": 3 363 | }, 364 | { 365 | "Id": 10036, 366 | "Name": "日向", 367 | "StarGrade": 3 368 | }, 369 | { 370 | "Id": 10037, 371 | "Name": "玛利娜", 372 | "StarGrade": 3 373 | }, 374 | { 375 | "Id": 20014, 376 | "Name": "咲", 377 | "StarGrade": 3 378 | }, 379 | { 380 | "Id": 10038, 381 | "Name": "宫子", 382 | "StarGrade": 3 383 | }, 384 | { 385 | "Id": 10039, 386 | "Name": "美游", 387 | "StarGrade": 3 388 | }, 389 | { 390 | "Id": 20015, 391 | "Name": "枫", 392 | "StarGrade": 3 393 | }, 394 | { 395 | "Id": 20016, 396 | "Name": "伊吕波", 397 | "StarGrade": 3 398 | }, 399 | { 400 | "Id": 10040, 401 | "Name": "月咏", 402 | "StarGrade": 3 403 | }, 404 | { 405 | "Id": 10041, 406 | "Name": "美咲", 407 | "StarGrade": 3 408 | }, 409 | { 410 | "Id": 20017, 411 | "Name": "日和", 412 | "StarGrade": 3 413 | }, 414 | { 415 | "Id": 10042, 416 | "Name": "亚津子", 417 | "StarGrade": 3 418 | }, 419 | { 420 | "Id": 10044, 421 | "Name": "野宫(泳装)", 422 | "StarGrade": 3 423 | }, 424 | { 425 | "Id": 10043, 426 | "Name": "若藻(泳装)", 427 | "StarGrade": 3 428 | }, 429 | { 430 | "Id": 10048, 431 | "Name": "纱织", 432 | "StarGrade": 3 433 | }, 434 | { 435 | "Id": 20018, 436 | "Name": "萌绘", 437 | "StarGrade": 3 438 | }, 439 | { 440 | "Id": 10049, 441 | "Name": "和纱", 442 | "StarGrade": 3 443 | }, 444 | { 445 | "Id": 10050, 446 | "Name": "心奈", 447 | "StarGrade": 3 448 | }, 449 | { 450 | "Id": 10052, 451 | "Name": "诺亚", 452 | "StarGrade": 3 453 | }, 454 | { 455 | "Id": 10051, 456 | "Name": "歌原(应援团)", 457 | "StarGrade": 3 458 | }, 459 | { 460 | "Id": 20019, 461 | "Name": "茜(兔女郎)", 462 | "StarGrade": 3 463 | }, 464 | { 465 | "Id": 20020, 466 | "Name": "日鞠", 467 | "StarGrade": 3 468 | }, 469 | { 470 | "Id": 10055, 471 | "Name": "时雨", 472 | "StarGrade": 3 473 | }, 474 | { 475 | "Id": 10056, 476 | "Name": "芹娜(圣诞节)", 477 | "StarGrade": 3 478 | }, 479 | { 480 | "Id": 20021, 481 | "Name": "花江(圣诞节)", 482 | "StarGrade": 3 483 | }, 484 | { 485 | "Id": 10058, 486 | "Name": "美祢", 487 | "StarGrade": 3 488 | }, 489 | { 490 | "Id": 20023, 491 | "Name": "叶渚", 492 | "StarGrade": 3 493 | }, 494 | { 495 | "Id": 10060, 496 | "Name": "惠", 497 | "StarGrade": 3 498 | }, 499 | { 500 | "Id": 10061, 501 | "Name": "樱子", 502 | "StarGrade": 3 503 | }, 504 | { 505 | "Id": 10063, 506 | "Name": "小雪", 507 | "StarGrade": 3 508 | }, 509 | { 510 | "Id": 10064, 511 | "Name": "佳代子(正月)", 512 | "StarGrade": 3 513 | }, 514 | { 515 | "Id": 20025, 516 | "Name": "遥香(正月)", 517 | "StarGrade": 3 518 | }, 519 | { 520 | "Id": 10065, 521 | "Name": "果穗", 522 | "StarGrade": 3 523 | }, 524 | { 525 | "Id": 10066, 526 | "Name": "爱丽丝(女仆)", 527 | "StarGrade": 3 528 | }, 529 | { 530 | "Id": 10067, 531 | "Name": "时(兔女郎)", 532 | "StarGrade": 3 533 | }, 534 | { 535 | "Id": 10068, 536 | "Name": "玲纱", 537 | "StarGrade": 3 538 | }, 539 | { 540 | "Id": 10069, 541 | "Name": "瑠美", 542 | "StarGrade": 3 543 | }, 544 | { 545 | "Id": 10070, 546 | "Name": "弥奈", 547 | "StarGrade": 3 548 | }, 549 | { 550 | "Id": 20026, 551 | "Name": "实里", 552 | "StarGrade": 3 553 | }, 554 | { 555 | "Id": 26010, 556 | "Name": "美游(泳装)", 557 | "StarGrade": 1 558 | }, 559 | { 560 | "Id": 10072, 561 | "Name": "咲(泳装)", 562 | "StarGrade": 3 563 | }, 564 | { 565 | "Id": 10071, 566 | "Name": "宫子(泳装)", 567 | "StarGrade": 3 568 | }, 569 | { 570 | "Id": 20027, 571 | "Name": "白子(泳装)", 572 | "StarGrade": 3 573 | }, 574 | { 575 | "Id": 20029, 576 | "Name": "三森(泳装)", 577 | "StarGrade": 3 578 | }, 579 | { 580 | "Id": 13013, 581 | "Name": "红叶", 582 | "StarGrade": 2 583 | }, 584 | { 585 | "Id": 10075, 586 | "Name": "芽瑠", 587 | "StarGrade": 3 588 | }, 589 | { 590 | "Id": 10076, 591 | "Name": "琴里(应援团)", 592 | "StarGrade": 3 593 | }, 594 | { 595 | "Id": 20030, 596 | "Name": "晴奈(运动服)", 597 | "StarGrade": 3 598 | }, 599 | { 600 | "Id": 10077, 601 | "Name": "一花", 602 | "StarGrade": 3 603 | }, 604 | { 605 | "Id": 10078, 606 | "Name": "霞", 607 | "StarGrade": 3 608 | }, 609 | { 610 | "Id": 20031, 611 | "Name": "时雨(温泉)", 612 | "StarGrade": 3 613 | }, 614 | { 615 | "Id": 10081, 616 | "Name": "紫草", 617 | "StarGrade": 3 618 | }, 619 | { 620 | "Id": 10082, 621 | "Name": "莲华", 622 | "StarGrade": 3 623 | }, 624 | { 625 | "Id": 10083, 626 | "Name": "桔梗", 627 | "StarGrade": 3 628 | }, 629 | { 630 | "Id": 20032, 631 | "Name": "艾米(泳装)", 632 | "StarGrade": 3 633 | }, 634 | { 635 | "Id": 10085, 636 | "Name": "晴(露营)", 637 | "StarGrade": 3 638 | }, 639 | { 640 | "Id": 10084, 641 | "Name": "小玉(露营)", 642 | "StarGrade": 3 643 | }, 644 | { 645 | "Id": 16014, 646 | "Name": "伊吹", 647 | "StarGrade": 1 648 | }, 649 | { 650 | "Id": 10089, 651 | "Name": "阿露(礼服)", 652 | "StarGrade": 3 653 | }, 654 | { 655 | "Id": 10088, 656 | "Name": "佳代子(礼服)", 657 | "StarGrade": 3 658 | }, 659 | { 660 | "Id": 20034, 661 | "Name": "明里(正月)", 662 | "StarGrade": 3 663 | }, 664 | { 665 | "Id": 10090, 666 | "Name": "海香", 667 | "StarGrade": 3 668 | }, 669 | { 670 | "Id": 20035, 671 | "Name": "椿(导游)", 672 | "StarGrade": 3 673 | }, 674 | { 675 | "Id": 16015, 676 | "Name": "爱莉(乐队)", 677 | "StarGrade": 1 678 | }, 679 | { 680 | "Id": 10093, 681 | "Name": "绮罗罗", 682 | "StarGrade": 3 683 | }, 684 | { 685 | "Id": 10094, 686 | "Name": "桃井(女仆)", 687 | "StarGrade": 3 688 | }, 689 | { 690 | "Id": 10095, 691 | "Name": "绿(女仆)", 692 | "StarGrade": 3 693 | }, 694 | { 695 | "Id": 20036, 696 | "Name": "芹香(泳装)", 697 | "StarGrade": 3 698 | }, 699 | { 700 | "Id": 10096, 701 | "Name": "叶渚(泳装)", 702 | "StarGrade": 3 703 | }, 704 | { 705 | "Id": 20037, 706 | "Name": "吹雪(泳装)", 707 | "StarGrade": 3 708 | }, 709 | { 710 | "Id": 10097, 711 | "Name": "萌绘(泳装)", 712 | "StarGrade": 3 713 | }, 714 | { 715 | "Id": 20038, 716 | "Name": "智惠(旗袍)", 717 | "StarGrade": 3 718 | }, 719 | { 720 | "Id": 10103, 721 | "Name": "玛利娜(旗袍)", 722 | "StarGrade": 3 723 | } 724 | ] -------------------------------------------------------------------------------- /src/assets/styles/base.scss: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: #f5f5f5; 3 | @include display-center; 4 | @include hw-filled-screen; 5 | margin: 0; 6 | } 7 | 8 | #app{ 9 | @include display-center; 10 | width: 100%; 11 | height: 100%; 12 | margin: 0; 13 | } -------------------------------------------------------------------------------- /src/assets/styles/button-group.scss: -------------------------------------------------------------------------------- 1 | .button-container.main { 2 | display: flex; 3 | justify-content: space-evenly; 4 | @include position(auto, auto, 5%, 0); 5 | @include hw(24%, 100%); 6 | 7 | .gacha-button { 8 | @include display-center; 9 | position: relative; 10 | overflow: hidden; 11 | 12 | padding-left: 3%; 13 | box-shadow: 0px 5px 5px -5px rgb(0 0 0/0.6); 14 | @include skew; 15 | 16 | .right { 17 | @include display-center; 18 | flex-wrap: wrap; 19 | 20 | .cost { 21 | @include display-center; 22 | @include font-regular; 23 | @include hw(1.8vw, 75%); 24 | margin-left: -10px; 25 | border-radius: 3px; 26 | background: $black-shadow; 27 | color: white; 28 | 29 | span { 30 | @include noskew; 31 | } 32 | } 33 | 34 | .text { 35 | @include display-center; 36 | @include hw-filled; 37 | @include font-big; 38 | 39 | margin-top: 0.3vw; 40 | color: $black-shadow; 41 | line-height: 1.8vw; 42 | text-align: right; 43 | @include noskew; 44 | } 45 | } 46 | } 47 | 48 | .button-blue { 49 | @include gacha-button($blue, '/images/ButtonBlueBg0.png', '/images/ButtonBlueBg1.png'); 50 | } 51 | 52 | .button-yellow { 53 | @include gacha-button($golden, '/images/ButtonYellowBg0.png', '/images/ButtonYellowBg1.png'); 54 | } 55 | 56 | .gacha_icon { 57 | width: 4vw; 58 | @include noskew; 59 | } 60 | 61 | .stone_icon { 62 | width: 3.5vw; 63 | margin: -10px -20px; 64 | @include noskew; 65 | } 66 | } 67 | 68 | .button-container.result { 69 | @include hw(13%, 40%); 70 | display: flex; 71 | justify-content: space-evenly; 72 | margin-bottom: 5%; 73 | 74 | .gacha-button { 75 | position: relative; 76 | overflow: hidden; 77 | box-shadow: 0px 5px 5px -5px rgb(0 0 0/0.6); 78 | @include skew; 79 | @include display-center; 80 | 81 | div { 82 | @include noskew; 83 | @include font(25px); 84 | } 85 | } 86 | 87 | .button-blue { 88 | @include gacha-button($blue, '/images/ButtonBlueBg0.png', '/images/ButtonBlueBg1.png'); 89 | margin-right: 5vw; 90 | } 91 | 92 | .button-yellow { 93 | @include gacha-button($golden, '/images/ButtonYellowBg0.png', '/images/ButtonYellowBg1.png'); 94 | } 95 | } 96 | 97 | .point-container { 98 | @include position(auto, 15vw, auto, auto); 99 | @include hw(15%, 10%); 100 | @include skew; 101 | border-radius: 10px; 102 | 103 | img { 104 | @include position(auto, auto, auto, -4vw); 105 | @include noskew; 106 | width: 7vw; 107 | z-index: 32; 108 | } 109 | 110 | .text, 111 | .num { 112 | height: 2.3vw; 113 | border: 2px solid $black-shadow; 114 | @include display-center; 115 | 116 | span { 117 | @include font-regular; 118 | @include noskew; 119 | } 120 | } 121 | 122 | .text { 123 | color: $black-shadow; 124 | background-color: white; 125 | border-radius: 5px 5px 0 0; 126 | } 127 | 128 | .num { 129 | color: white; 130 | background-color: $black-shadow; 131 | border-radius: 0 0 5px 5px; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/assets/styles/main-view.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | z-index: 10; 3 | 4 | &.link { 5 | @include position(7%, auto, auto, 3.5%, fixed); 6 | @include hw(60%, 5%); 7 | border-radius: 50%; 8 | } 9 | 10 | &.help { 11 | @include position(10%, auto, auto, 18%, fixed); 12 | @include hw(25%, 2%); 13 | border-radius: 10px; 14 | cursor: pointer; 15 | } 16 | 17 | &.history { 18 | @include position(5%, auto, auto, 93%, fixed); 19 | @include hw(38%, 3%); 20 | border-radius: 50%; 21 | cursor: pointer; 22 | } 23 | 24 | &.setting { 25 | @include position(5%, auto, auto, 87.6%, fixed); 26 | @include hw(38%, 3%); 27 | border-radius: 10px; 28 | cursor: pointer; 29 | } 30 | } 31 | 32 | // 顶部状态栏(体力,金钱,石头等) 33 | .header { 34 | grid-area: 1 / 1 / 2 / 3; 35 | background: url('/images/Header.png') top no-repeat; 36 | background-size: contain; 37 | filter: drop-shadow(0 0 5px rgba(0, 0, 0, 0.441)); 38 | background-size: contain; 39 | position: relative; 40 | z-index: 5; 41 | 42 | div { 43 | position: fixed; 44 | top: 0.4vw; 45 | color: rgb(76, 88, 102); 46 | @include font(1.3vw); 47 | } 48 | 49 | .title { 50 | @include font(1.5vw); 51 | left: 9%; 52 | font-weight: 700; 53 | } 54 | 55 | .ap { 56 | left: 44.5%; 57 | } 58 | 59 | .crash { 60 | left: 60%; 61 | } 62 | 63 | .stone { 64 | left: 75%; 65 | } 66 | } 67 | 68 | // 主界面整体,左侧卡池预览,中间渐变过渡,右侧卡池卡片,左下角其他信息 69 | .table-container { 70 | display: grid; 71 | grid-template-rows: 5vw 1fr; 72 | 73 | @include hw-filled; 74 | background: url('/images/Background.png') no-repeat; 75 | background-size: cover; 76 | 77 | .preview { 78 | grid-area: 1 / 1 / 3 / 1; 79 | video { 80 | overflow: hidden; 81 | min-height: 100%; 82 | object-fit: cover; 83 | transform: translate(0); 84 | transition: opacity 0.3s ease; 85 | user-select: none; 86 | } 87 | } 88 | 89 | .gradient { 90 | grid-area: 1 / 1 / 3 / 3; 91 | position: relative; 92 | left: 30vw; 93 | z-index: 3; 94 | 95 | @include hw(100%, 50vw); 96 | content: url('/images/Background.png'); 97 | mask-image: linear-gradient(to left, #fff 70%, transparent 100%); 98 | } 99 | 100 | .gacha-wrapper { 101 | @include display-center; 102 | grid-area: 1 / 1 / 3 / 3; 103 | position: relative; 104 | left: 35vw; 105 | flex-direction: column; 106 | z-index: 4; 107 | @include hw(100%, 45vw); 108 | } 109 | 110 | .left-bottom { 111 | @include display-center; 112 | grid-area: 1 / 1 / 1 / 1; 113 | position: relative; 114 | top: 41vw; 115 | left: 4vw; 116 | z-index: 4; 117 | @include hw(fit-content, fit-content); 118 | 119 | .text { 120 | color: $black-main; 121 | background: white; 122 | margin-left: -17px; 123 | border-radius: 3px; 124 | box-shadow: 0px 5px 5px -5px rgb(0 0 0/0.6); 125 | @include display-center; 126 | @include hw(1.8vw, 5.5vw); 127 | @include skew; 128 | 129 | span { 130 | @include noskew; 131 | @include font-regular; 132 | } 133 | } 134 | } 135 | } 136 | 137 | // 卡池卡片,上侧多个卡池选择,下侧抽卡信息和按钮 138 | .gacha-wrapper { 139 | .event-banner { 140 | display: flex; 141 | justify-content: center; 142 | overflow: hidden; 143 | height: 13.5%; 144 | } 145 | 146 | .event-scroll { 147 | @include display-center; 148 | @include hw(2%, fit-content); 149 | margin: 8px; 150 | border-radius: 20px; 151 | padding: 0 10px; 152 | background-color: #ffffffba; 153 | 154 | .dot { 155 | display: inline-block; 156 | @include hw(5px, 5px); 157 | margin: 0 2px; 158 | border-radius: 50%; 159 | background-color: $black-shadow; 160 | 161 | cursor: pointer; 162 | transition: background-color 0.6s ease; 163 | } 164 | 165 | .active, 166 | .dot:hover { 167 | background-color: $blue; 168 | } 169 | } 170 | 171 | .tab-container { 172 | display: flex; 173 | flex-direction: column; 174 | justify-content: space-between; 175 | position: relative; 176 | 177 | background: $white-main; 178 | border: 1px solid $white-border; 179 | border-radius: 3px; 180 | @include hw(60%, 80%); 181 | box-shadow: 0px 4px 6px -3px rgb(0 0 0/0.5); 182 | } 183 | } 184 | 185 | // 卡池信息,抽卡按钮,抽卡历史信息 186 | .tab-container { 187 | .tab-body { 188 | position: relative; 189 | padding: 1vw; 190 | text-indent: 2px; 191 | @include hw(80%, calc(100% - 2vw)); 192 | 193 | &::before { 194 | @include position(auto, auto, 0, 0); 195 | @include hw-filled; 196 | content: ''; 197 | background: url('/images/IntroBgLeft.png') no-repeat left bottom; 198 | background-size: 75%; 199 | } 200 | 201 | .duration { 202 | position: relative; 203 | z-index: 2; 204 | width: 100%; 205 | color: $black-shadow; 206 | background-color: $blue-light; 207 | line-height: 1vw; 208 | padding: 2% 0; 209 | margin: 0; 210 | @include font-light; 211 | } 212 | 213 | .title { 214 | position: relative; 215 | z-index: 2; 216 | width: 100%; 217 | line-height: 1.5vw; 218 | color: $black-main; 219 | padding: 1.3vw 0 1.3vw 2px; 220 | border-bottom: 3px solid $white-border; 221 | @include font-large; 222 | } 223 | 224 | .subtitle { 225 | position: relative; 226 | z-index: 2; 227 | width: 100%; 228 | line-height: 1vw; 229 | color: rgb(0, 126, 255); 230 | padding: 0.6vw 0 0.6vw 2px; 231 | border-bottom: 3px solid $white-border; 232 | @include font-regular; 233 | } 234 | 235 | .notice { 236 | position: relative; 237 | z-index: 2; 238 | width: 100%; 239 | line-height: 1.8vw; 240 | color: $black-shadow; 241 | white-space: pre-wrap; 242 | @include font-light; 243 | } 244 | } 245 | 246 | .tab-foot { 247 | height: 15%; 248 | background: $blue-main; 249 | @include display-center; 250 | 251 | .point_icon { 252 | z-index: 1; 253 | margin: -10px -20px; 254 | width: 17%; 255 | } 256 | 257 | .text { 258 | color: $black-main; 259 | background: white; 260 | border: 2px solid $black-main; 261 | margin-left: -17px; 262 | @include display-center; 263 | @include hw(50%, 35%); 264 | @include skew; 265 | 266 | span { 267 | @include noskew; 268 | @include font-regular; 269 | } 270 | } 271 | 272 | .point { 273 | color: white; 274 | background: $black-shadow; 275 | border-radius: 0 3px 3px 0; 276 | border: 2px solid $black-shadow; 277 | margin-right: 20px; 278 | @include display-center; 279 | @include hw(50%, 20%); 280 | @include skew; 281 | 282 | span { 283 | @include font-regular; 284 | @include noskew; 285 | } 286 | } 287 | 288 | .select { 289 | color: $black-main; 290 | background: $blue-light; 291 | border-radius: 7px; 292 | border: 1px solid $black-border; 293 | box-shadow: 0px 5px 5px -5px rgb(0 0 0/0.5); 294 | cursor: pointer; 295 | @include display-center; 296 | @include hw(60%, 25%); 297 | @include skew; 298 | 299 | span { 300 | @include font(1.3vw); 301 | @include noskew; 302 | } 303 | 304 | &:active { 305 | @include active; 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/assets/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin display-center { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | } 6 | 7 | @mixin hw($height, $width){ 8 | height: $height; 9 | width: $width; 10 | } 11 | 12 | @mixin hw-filled { 13 | @include hw(100%, 100%); 14 | } 15 | 16 | @mixin hw-filled-screen { 17 | @include hw(100vh, 100vw); 18 | } 19 | 20 | @mixin position($top:auto, $right:auto, $bottom:auto, $left:auto, $position:absolute) { 21 | position: $position; 22 | top: $top; 23 | right: $right; 24 | bottom: $bottom; 25 | left: $left; 26 | } 27 | 28 | @mixin skew { 29 | transform: skewX(-10deg); 30 | -webkit-transform: skewX(-10deg); 31 | } 32 | 33 | @mixin noskew { 34 | transform: skewX(10deg); 35 | -webkit-transform: skewX(10deg); 36 | } 37 | 38 | @mixin active { 39 | transform: scale(0.95) skewX(-10deg); 40 | } 41 | 42 | // font 43 | 44 | @mixin font($size) { 45 | font-size: $size; 46 | font-weight: 700; 47 | user-select:none; 48 | } 49 | 50 | @mixin font-big { 51 | font-size: 1.7vw; 52 | font-weight: 700; 53 | user-select:none; 54 | } 55 | 56 | @mixin font-large { 57 | font-size: 2.5vw; 58 | font-weight: 700; 59 | user-select:none; 60 | } 61 | 62 | @mixin font-heavy { 63 | font-size: 1.1vw; 64 | font-weight: 1400; 65 | user-select:none; 66 | } 67 | 68 | @mixin font-regular { 69 | font-size: 1.1vw; 70 | font-weight: 700; 71 | user-select:none; 72 | } 73 | 74 | @mixin font-light { 75 | font-size: 1.1vw; 76 | user-select:none; 77 | } 78 | 79 | // button 80 | 81 | @mixin gacha-button($color, $url0, $url1) { 82 | width: 40%; 83 | border-radius: 10px; 84 | background: $color; 85 | cursor: pointer; 86 | 87 | &::before, 88 | &::after { 89 | content: ''; 90 | position: absolute; 91 | background-size: contain; 92 | z-index: -1; 93 | @include hw-filled; 94 | } 95 | 96 | &::before { 97 | @include position(-10px, auto, auto, -20px); 98 | background: url($url0) left top no-repeat; 99 | -webkit-filter: opacity(0.5); 100 | filter: opacity(0.5); 101 | } 102 | 103 | &::after { 104 | @include position(auto, -20px, -10px, auto); 105 | background: url($url1) right bottom no-repeat; 106 | -webkit-filter: opacity(0.5); 107 | filter: opacity(0.5); 108 | } 109 | 110 | &:active { 111 | @include active; 112 | } 113 | } 114 | 115 | // scroll bar 116 | 117 | @mixin hide-scrollbar { 118 | overflow-y: scroll; 119 | -ms-overflow-style: none; /* IE 和 Edge */ 120 | scrollbar-width: none; /* Firefox */ 121 | &::-webkit-scrollbar { 122 | display: none; 123 | } 124 | } 125 | 126 | // color 127 | 128 | $blue: rgb(119, 221, 255); 129 | $golden: rgb(245, 233, 75); 130 | $gray: rgb(220, 240, 246); 131 | 132 | $gray-card: rgb(222, 235, 240); 133 | $golden-card: rgb(255, 252, 158); 134 | $pink-card: rgb(246, 186, 213); 135 | 136 | $gray-shadow: rgb(0 0 0 / 33%); 137 | $golden-shadow: rgb(255 240 156); 138 | $pink-shadow: rgb(255 156 221 / 36%); 139 | 140 | $blue-light: rgb(219, 246, 251); 141 | $blue-main: rgb(169, 224, 244); 142 | 143 | $white-main: rgb(241, 251, 253); 144 | $white-border: rgb(205, 214, 218); 145 | 146 | $black-main: rgb(75, 112, 155); 147 | $black-shadow: rgb(45, 70, 99); 148 | $black-border: rgb(122 171 200/0.3); 149 | $black-shadow-opacity: rgb(45 70 99 / 85%); -------------------------------------------------------------------------------- /src/assets/styles/modal.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | z-index: 10; 3 | 4 | &.close { 5 | position: absolute; 6 | right: -50px; 7 | height: 50px; 8 | cursor: pointer; 9 | &:active { 10 | transform: scale(0.9); 11 | } 12 | } 13 | } 14 | 15 | .modal-backdrop { 16 | position: fixed; 17 | @include hw-filled-screen; 18 | @include display-center; 19 | background-color: rgba(0, 0, 0, 0.5); 20 | z-index: 100; 21 | } 22 | 23 | .modal { 24 | @include hw(30vw, 40vw); 25 | display: flex; 26 | flex-direction: column; 27 | border-radius: 16px; 28 | box-shadow: 0 10px 6px -2px; 29 | background: 30 | url('/images/IntroBgRight.png') right bottom no-repeat, 31 | rgb(242, 242, 242); 32 | background-size: 80%; 33 | 34 | .modal-header { 35 | @include display-center; 36 | height: 70px; 37 | position: relative; 38 | color: $black-shadow; 39 | background: rgb(239, 242, 244); 40 | background-size: contain; 41 | border-radius: 10px 10px 0 0; 42 | border-bottom: 1px solid rgb(218, 221, 220); 43 | & > span:first-child { 44 | border-bottom: 8px solid rgb(255, 244, 126); 45 | @include font-big; 46 | padding: 5px; 47 | } 48 | &::after { 49 | content: ''; 50 | @include position(0, auto, auto, 0); 51 | @include hw-filled; 52 | background: url('/images/HeaderBg.png') no-repeat left top; 53 | background-size: contain; 54 | } 55 | button { 56 | margin-left: 8px; 57 | height: 35px; 58 | width: 35px; 59 | background-color: $black-shadow; 60 | border-radius: 5px; 61 | border: 0 solid; 62 | color: white; 63 | font-size: x-large; 64 | } 65 | } 66 | .modal-body { 67 | @include display-center; 68 | @include font(24px); 69 | flex-direction: column; 70 | height: 45%; 71 | padding: 3%; 72 | color: black; 73 | 74 | &.modal-body--filled { 75 | height: 100%; 76 | overflow: hidden; 77 | } 78 | } 79 | .modal-footer { 80 | @include hw(18%, 100%); 81 | display: flex; 82 | justify-content: space-evenly; 83 | margin: 2.5vw 0 2.5vw 0; 84 | 85 | .gacha-button { 86 | position: relative; 87 | overflow: hidden; 88 | box-shadow: 0px 5px 5px -5px rgb(0 0 0/0.6); 89 | @include skew; 90 | @include display-center; 91 | div { 92 | @include noskew; 93 | @include font-big; 94 | } 95 | &:active { 96 | @include active; 97 | } 98 | } 99 | 100 | .button-gray { 101 | @include gacha-button($gray, '/images/ButtonGrayBg0.png', '/images/ButtonGrayBg1.png'); 102 | } 103 | 104 | .button-blue { 105 | @include gacha-button($blue, '/images/ButtonBlueBg0.png', '/images/ButtonBlueBg1.png'); 106 | } 107 | 108 | .button-yellow { 109 | @include gacha-button($golden, '/images/ButtonYellowBg0.png', '/images/ButtonYellowBg1.png'); 110 | } 111 | } 112 | } 113 | 114 | .modal{ 115 | .gacha{ 116 | @include display-center; 117 | flex-direction: column; 118 | } 119 | 120 | .point { 121 | @include hw(15%, 25%); 122 | @include skew; 123 | @include display-center; 124 | border-radius: 10px; 125 | 126 | img { 127 | @include position(auto, auto, auto, -2.5vw); 128 | @include noskew; 129 | width: 5vw; 130 | z-index: 32; 131 | } 132 | 133 | .num { 134 | height: 2vw; 135 | width: 30vw; 136 | border: 2px solid $black-shadow; 137 | @include display-center; 138 | color: white; 139 | background-color: $black-shadow; 140 | border-radius: 5px; 141 | 142 | span { 143 | @include font-regular; 144 | @include noskew; 145 | } 146 | } 147 | } 148 | } 149 | 150 | .modal{ 151 | .history { 152 | display: flex; 153 | justify-content: flex-start; 154 | flex-direction: row; 155 | flex-wrap: wrap; 156 | height: 100%; 157 | @include hide-scrollbar; 158 | .card { 159 | @include display-center; 160 | flex-direction: column; 161 | margin: -20px 2px 0 2px; 162 | } 163 | .card > a { 164 | @include display-center; 165 | flex-direction: column; 166 | padding: 0 15px; 167 | } 168 | } 169 | $img-height: 10.3vw; 170 | .char img { 171 | height: $img-height; 172 | } 173 | .star { 174 | position: relative; 175 | bottom: -$img-height; 176 | width: 100%; 177 | height: 40px; 178 | background-color: $black-shadow-opacity; 179 | @include display-center; 180 | img { 181 | height: 25px; 182 | opacity: 1; 183 | } 184 | } 185 | .name { 186 | @include font(1vw); 187 | color: $black-shadow; 188 | text-decoration: underline $golden-card; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/assets/utils/api.ts: -------------------------------------------------------------------------------- 1 | const getAvatarBg = (id: string|number) => `https://schale.gg/images/student/collection/${id}.webp` 2 | 3 | const getAvatarNoBg = (id: string|number) => `https://beta.schaledb.com/images/student/icon/${id}.webp` 4 | 5 | const getVideoPaths = () => { 6 | const videos = { 7 | arona: [ 8 | 'https://thumbsnap.com/i/p1NQdUNp.mp4', // arona_normal.mp4 9 | 'https://thumbsnap.com/i/JMG87Vuj.mp4', // arona_special.mp4 10 | 'https://thumbsnap.com/i/p1NQdUNp.mp4', // arona_normal.mp4 11 | ], 12 | wait: [ 13 | 'https://thumbsnap.com/i/QWAoRYfz.mp4', // wait_normal.mp4 14 | 'https://thumbsnap.com/i/JiSP5QFF.mp4', // wait_special.mp4 15 | 'https://thumbsnap.com/i/QWAoRYfz.mp4', // wait_normal.mp4 16 | ], 17 | sign: [ 18 | 'https://thumbsnap.com/i/XEAELMLQ.mp4', // sign_normal.mp4 19 | 'https://thumbsnap.com/i/3LXYXnek.mp4', // sign_special.mp4 20 | 'https://thumbsnap.com/i/3LXYXnek.mp4', // sign_special.mp4 21 | ], 22 | preview: [ 23 | 'https://thumbsnap.com/i/Cte3fY43.mp4', // Gacha_Banner_Normal_gb 24 | 'https://thumbsnap.com/i/ak16qZdv.mp4', // Gacha_Banner_3star_gb 25 | 'https://thumbsnap.com/i/C6iE4g1n.mp4', // Gacha_Banner_220322_gb_1 26 | ] 27 | } 28 | return videos 29 | } 30 | 31 | export { getAvatarBg, getAvatarNoBg, getVideoPaths } 32 | -------------------------------------------------------------------------------- /src/assets/utils/interface.ts: -------------------------------------------------------------------------------- 1 | interface myStudent { 2 | Id: number 3 | Name: string 4 | StarGrade: number 5 | } 6 | 7 | interface resultItem extends myStudent { 8 | isNew: boolean 9 | } 10 | 11 | interface historyItem extends myStudent { 12 | Cnt: number 13 | } 14 | 15 | interface bannerItem extends myStudent { 16 | LastProb: number 17 | Prob: number 18 | } 19 | 20 | export type { myStudent, resultItem, historyItem, bannerItem } 21 | -------------------------------------------------------------------------------- /src/components/ClickEffect.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 190 | 191 | 200 | -------------------------------------------------------------------------------- /src/components/CustomModal.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 67 | 68 | 71 | -------------------------------------------------------------------------------- /src/components/MainContainer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 60 | 61 | 64 | -------------------------------------------------------------------------------- /src/components/ResultContainer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | 38 | 200 | -------------------------------------------------------------------------------- /src/components/SignBoard.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 310 | 311 | 317 | -------------------------------------------------------------------------------- /src/components/icons/CloseIcon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/declare.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@/stores/*' 2 | 3 | declare module '*.vue' { 4 | import { ComponentOptions } from 'vue' 5 | const componentOptions: ComponentOptions 6 | export default componentOptions 7 | } 8 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/styles/base.scss' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | import { createPinia } from 'pinia' 6 | import router from './router' 7 | 8 | const pinia = createPinia() 9 | const app = createApp(App) 10 | app.use(router) 11 | app.use(pinia) 12 | app.mount('#app') 13 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import MainView from '@/views/MainView.vue' 3 | import VideoView from '@/views/VideoView.vue' 4 | import ResultView from '@/views/ResultView.vue' 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(import.meta.env.BASE_URL), 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'main', 12 | component: MainView 13 | }, 14 | { 15 | path: '/gacha', 16 | name: 'gacha', 17 | component: VideoView 18 | }, 19 | { 20 | path: '/result', 21 | name: 'result', 22 | component: ResultView 23 | } 24 | ] 25 | }) 26 | 27 | export default router 28 | -------------------------------------------------------------------------------- /src/stores/gacha.ts: -------------------------------------------------------------------------------- 1 | import { useGachaStore } from '@/stores' 2 | import type { bannerItem, myStudent, resultItem } from '../assets/utils/interface' 3 | 4 | class Gacha { 5 | // 学生列表, 两星up学生id列表, 三星up学生id列表 6 | constructor(database: myStudent[], up_list_2: number[] = [], up_list_3: number[] = []) { 7 | const star_2_up = up_list_2.length 8 | const star_3_up = up_list_3.length 9 | const star_1 = database.filter((s) => s.StarGrade == 1).length 10 | const star_2 = database.filter((s) => s.StarGrade == 2).length - star_2_up 11 | const star_3 = database.filter((s) => s.StarGrade == 3).length - star_3_up 12 | this.database = database.map((item) => { 13 | let prob = 0 14 | let last_prob = 0 15 | if (item.StarGrade === 3) { 16 | if (up_list_3.includes(item.Id)) { 17 | prob = 0.007 / star_3_up 18 | last_prob = 0.007 / star_3_up 19 | } else { 20 | prob = 0.023 / star_3 21 | last_prob = 0.023 / star_3 22 | } 23 | } else if (item.StarGrade === 2) { 24 | if (up_list_2.includes(item.Id)) { 25 | prob = 0.03 / star_2_up 26 | last_prob = 0.03 / star_2_up 27 | } else { 28 | prob = 0.155 / star_2 29 | last_prob = 0.94 / star_2 30 | } 31 | } else if (item.StarGrade === 1) { 32 | prob = 0.785 / star_1 33 | last_prob = 0 34 | } 35 | return { 36 | ...item, 37 | Prob: prob, 38 | LastProb: last_prob 39 | } 40 | }) 41 | this.gachaStore = useGachaStore() 42 | } 43 | gachaStore: any 44 | database: bannerItem[] 45 | flag: boolean = false // 是否获得三星 46 | flag2: boolean = false // 特殊三星 47 | special: number = 0.05 // 蓝变紫概率 48 | result: resultItem[] = [] 49 | /* 50 | 抽卡概率 51 | 一般情况下1星学生概率78.5%,2星学生概率18.5%,3星学生概率3%。 52 | TODO: 限时招募和限定招募中 53 | - UP3星学生从3星总概率中分走0.7%,其他3星学生均分剩余2.3%; 54 | - UP2星学生从2星总概率中分走3%,其他2星学生均分剩余15.5% 55 | 蓝变紫概率不确定 56 | */ 57 | 58 | fisherYatesShuffle(arr: any[]) { 59 | for (let i = arr.length - 1; i > 0; i--) { 60 | const j = Math.floor(Math.random() * (i + 1)) // random index 61 | ;[arr[i], arr[j]] = [arr[j], arr[i]] // swap 62 | } 63 | } 64 | 65 | // FIXME: 随机获取一个学生 66 | getRandomStudent(last: boolean = false) { 67 | const data_temp = JSON.parse(JSON.stringify(this.database)) 68 | this.fisherYatesShuffle(data_temp) 69 | let result = null 70 | 71 | while (!result){ 72 | const random = Math.random() 73 | let sum = 0 74 | 75 | for (let i = 0; i < data_temp.length; i++) { 76 | const item = data_temp[i] 77 | sum += last ? item.LastProb : item.Prob 78 | if (sum >= random){ 79 | result = { 80 | Id: item.Id, 81 | Name: item.Name, 82 | StarGrade: item.StarGrade 83 | } as myStudent 84 | break 85 | } 86 | } 87 | } 88 | return result 89 | } 90 | 91 | // 确定三星,此时 database 中只有三星学生 92 | getSureStudent() { 93 | const data_temp = JSON.parse(JSON.stringify(this.database)) 94 | this.fisherYatesShuffle(data_temp) 95 | 96 | const index = Math.floor(Math.random() * data_temp.length) 97 | const item = data_temp[index] 98 | return { 99 | Id: item.Id, 100 | Name: item.Name, 101 | StarGrade: item.StarGrade 102 | } as myStudent 103 | } 104 | 105 | // 抽卡 106 | getStudents(num: number, sure: boolean = false) { 107 | this.flag = false // 是否获得三星 108 | this.flag2 = false // 特殊三星 109 | this.result = [] 110 | 111 | for (let i = 0; i < num; i++) { 112 | let student: myStudent 113 | if (sure) student = this.getSureStudent() 114 | else student = this.getRandomStudent() 115 | 116 | const studentR: resultItem = { 117 | ...student, 118 | isNew: this.gachaStore.isNew(student.Id) 119 | } 120 | this.gachaStore.pushHistory(student) 121 | this.result.push(studentR) 122 | if (student.StarGrade == 3) this.flag = true 123 | } 124 | if (this.flag && Math.random() < this.special) this.flag2 = true 125 | } 126 | } 127 | 128 | export { Gacha } 129 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref, type Ref } from 'vue' 2 | import { defineStore } from 'pinia' 3 | import type { historyItem, myStudent, resultItem } from '@/assets/utils/interface' 4 | import { Gacha } from './gacha' 5 | 6 | export const useGachaStore = defineStore('counter', () => { 7 | const history: Ref = ref([]) 8 | const gacha: Ref = ref(new Gacha([])) 9 | const gachaResult: Ref = ref([]) 10 | const lastGachaNum: Ref = ref(1) 11 | const lastGachaBanner: Ref = ref(0) 12 | 13 | const totalCnt = computed(() => { 14 | let cnt = 0 15 | history.value.forEach((item) => (cnt += item.Cnt)) 16 | return cnt 17 | }) 18 | 19 | // 本地持久化 20 | const setData = () => { 21 | localStorage.setItem('history', JSON.stringify(history.value)) 22 | } 23 | const getData = () => { 24 | const data = localStorage.getItem('history') 25 | history.value = (data != null ? JSON.parse(data) : []) as historyItem[] 26 | } 27 | const resetData = () => { 28 | history.value = [] as historyItem[] 29 | setData() 30 | } 31 | 32 | // 抽卡相关 33 | const setGacha = (data: myStudent[], up_list_2: number[] = [], up_list_3: number[] = []) => { 34 | gacha.value = new Gacha(data, up_list_2, up_list_3) 35 | } 36 | const isNew = (id: number) => { 37 | const index = history.value.findIndex((ele) => ele.Id === id) 38 | return index === -1 39 | } 40 | const pushHistory = (student: myStudent) => { 41 | if (isNew(student.Id)) { 42 | history.value.push({ 43 | ...student, 44 | Cnt: 1 45 | }) 46 | } else { 47 | const index = history.value.findIndex((ele) => ele.Id === student.Id) 48 | history.value[index].Cnt++ 49 | } 50 | } 51 | const gachaStudents = (num: number, sure: boolean = false) => { 52 | gacha.value.getStudents(num, sure) 53 | gachaResult.value = gacha.value.result 54 | setData() 55 | } 56 | const gachaResultStar = () => { 57 | if (gacha.value.flag2) return 2 // 特殊三星 58 | if (gacha.value.flag) return 1 // 三星 59 | return 0 // 无三星 60 | } 61 | 62 | return { 63 | lastGachaNum, 64 | lastGachaBanner, 65 | history, 66 | totalCnt, 67 | gachaResult, 68 | setData, 69 | getData, 70 | resetData, 71 | setGacha, 72 | isNew, 73 | pushHistory, 74 | gachaStudents, 75 | gachaResultStar // 抽卡结果显示 无三星/普通三星/特殊三星 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /src/views/MainView.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 163 | 164 | 176 | -------------------------------------------------------------------------------- /src/views/ResultView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 61 | 62 | 70 | -------------------------------------------------------------------------------- /src/views/VideoView.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 65 | 66 | 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@vue/tsconfig/tsconfig.dom.json", "@tsconfig/node18/tsconfig.json"], 3 | "exclude": ["src/**/__tests__/*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": ["./src/*"] 9 | }, 10 | "moduleResolution": "node", 11 | "module": "ESNext", 12 | "types": ["node"], 13 | "lib": ["DOM", "DOM.Iterable"], 14 | }, 15 | "include": [ 16 | "env.d.ts", 17 | "src/**/*.vue", 18 | "**/*.vue", 19 | "src/**/*.ts", 20 | "vite.config.*", 21 | "vitest.config.*", 22 | "cypress.config.*", 23 | "nightwatch.conf.*", 24 | "playwright.config.*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | resolve: { 10 | alias: { 11 | '@': fileURLToPath(new URL('./src', import.meta.url)) 12 | } 13 | }, 14 | css: { 15 | // css预处理器 16 | preprocessorOptions: { 17 | scss: { 18 | additionalData: '@import "@/assets/styles/mixin.scss";' 19 | } 20 | }, 21 | postcss: { 22 | plugins: [] 23 | } 24 | }, 25 | base: '/ba-gacha/', 26 | build: { 27 | outDir: 'docs' 28 | } 29 | }) 30 | --------------------------------------------------------------------------------