├── .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 |
10 |
11 |
12 | 在线抽卡模拟器
13 | 在,来吃井
14 |
15 |
16 |
17 | [小抽一井!](https://u1805.github.io/ba-gacha)
18 |
19 | ## 预览
20 |
21 | 
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 |
19 |
20 |
21 |
22 |
23 |
28 |
32 |
33 |
34 |
41 |
42 |
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 |
2 |
3 |
4 |
5 |
190 |
191 |
200 |
--------------------------------------------------------------------------------
/src/components/CustomModal.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
--------------------------------------------------------------------------------
/src/components/MainContainer.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
![]()
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
{{ select_info.duration }}
37 |
{{ select_info.title }}
38 |
{{ select_info.subtitle }}
39 |
{{ select_info.notice }}
40 |
41 |
42 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
64 |
--------------------------------------------------------------------------------
/src/components/ResultContainer.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |

26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
200 |
--------------------------------------------------------------------------------
/src/components/SignBoard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
310 |
311 |
317 |
--------------------------------------------------------------------------------
/src/components/icons/CloseIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
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 |
82 |
83 |
84 |
92 |
93 |
{{ bodyGachaModal }}
94 |
青耀石消耗数量
95 |
96 |

97 |
98 | {{ gachaStore.lastGachaNum * 120 }}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |

109 |
110 |
111 |
{{ item['Name'] }} * {{ item['Cnt'] }}
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
132 |
133 |
134 |
152 |
153 |
154 |
155 |
156 | 概率情报
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
176 |
--------------------------------------------------------------------------------
/src/views/ResultView.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
29 |
30 |
再次招募?
31 |
青耀石消耗数量
32 |
33 |

34 |
35 | {{ gachaStore.lastGachaNum * 120 }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
58 |
59 |
60 |
61 |
62 |
70 |
--------------------------------------------------------------------------------
/src/views/VideoView.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
42 |
43 |
44 |
53 |
54 |
57 |
60 |
63 |
64 |
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 |
--------------------------------------------------------------------------------