├── example └── img │ ├── home.png │ ├── akasha.png │ ├── empty.png │ ├── statistics1.png │ ├── statistics2.png │ ├── statistics3.png │ └── proto_bahamut.png ├── public ├── favicon.ico ├── statics │ ├── ffj.jpg │ ├── akasha.png │ ├── empty.jpg │ ├── icon.ico │ ├── red_ring.jpg │ ├── black_paper.jpg │ ├── black_ring.jpg │ ├── grand_order.jpg │ ├── hollow_key.jpg │ ├── red_paper.jpg │ ├── white_paper.jpg │ ├── white_ring.jpg │ ├── icon-512x512.png │ ├── proto_bahamut.png │ ├── mirage_munition.png │ ├── silver_centrum.jpg │ └── verdant_azurite.jpg └── icons │ ├── icon-16x16.png │ ├── icon-32x32.png │ ├── icon-96x96.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── icon-128x128.png │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ └── favicon-128x128.png ├── src ├── assets │ ├── olb.png │ ├── oqs.jpg │ ├── oh-ffj.jpg │ ├── home-background.jpg │ └── quasar-logo-full.svg ├── i18n │ ├── index.ts │ └── en-US │ │ └── index.ts ├── css │ ├── app.scss │ └── quasar.variables.scss ├── env.d.ts ├── App.vue ├── store │ ├── module-example │ │ ├── state.ts │ │ ├── mutations.ts │ │ ├── actions.ts │ │ ├── getters.ts │ │ └── index.ts │ ├── store-flag.d.ts │ └── index.ts ├── shims-vue.d.ts ├── boot │ ├── i18n.ts │ └── axios.ts ├── pages │ ├── Error404.vue │ ├── Index.vue │ ├── counter │ │ └── index.vue │ └── analysis │ │ └── index.vue ├── router │ ├── routes.ts │ └── index.ts ├── index.template.html ├── layouts │ ├── BlankLayout.vue │ └── MainLayout.vue ├── components │ ├── DropItemCard.vue │ └── RaidCard.vue ├── constants │ └── drop.ts └── utils │ └── gbfUtil.ts ├── src-electron ├── icons │ ├── icon.ico │ ├── icon.icns │ └── linux-512x512.png ├── utils │ ├── gbfUtil.js │ └── dbUtil.js ├── data │ └── constants.js ├── electron-preload.js └── electron-main.js ├── tsconfig.json ├── .eslintignore ├── .postcssrc.js ├── .prettierrc.json ├── babel.config.js ├── .editorconfig ├── README.md ├── .gitignore ├── package.json ├── .eslintrc.js └── quasar.conf.js /example/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/home.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/olb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src/assets/olb.png -------------------------------------------------------------------------------- /src/assets/oqs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src/assets/oqs.jpg -------------------------------------------------------------------------------- /example/img/akasha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/akasha.png -------------------------------------------------------------------------------- /example/img/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/empty.png -------------------------------------------------------------------------------- /public/statics/ffj.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/ffj.jpg -------------------------------------------------------------------------------- /src/assets/oh-ffj.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src/assets/oh-ffj.jpg -------------------------------------------------------------------------------- /public/statics/akasha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/akasha.png -------------------------------------------------------------------------------- /public/statics/empty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/empty.jpg -------------------------------------------------------------------------------- /public/statics/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/icon.ico -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import enUS from './en-US'; 2 | 3 | export default { 4 | 'en-US': enUS, 5 | }; 6 | -------------------------------------------------------------------------------- /example/img/statistics1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/statistics1.png -------------------------------------------------------------------------------- /example/img/statistics2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/statistics2.png -------------------------------------------------------------------------------- /example/img/statistics3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/statistics3.png -------------------------------------------------------------------------------- /public/icons/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-16x16.png -------------------------------------------------------------------------------- /public/icons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-32x32.png -------------------------------------------------------------------------------- /public/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-96x96.png -------------------------------------------------------------------------------- /public/statics/red_ring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/red_ring.jpg -------------------------------------------------------------------------------- /src-electron/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src-electron/icons/icon.ico -------------------------------------------------------------------------------- /example/img/proto_bahamut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/example/img/proto_bahamut.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-256x256.png -------------------------------------------------------------------------------- /public/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/statics/black_paper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/black_paper.jpg -------------------------------------------------------------------------------- /public/statics/black_ring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/black_ring.jpg -------------------------------------------------------------------------------- /public/statics/grand_order.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/grand_order.jpg -------------------------------------------------------------------------------- /public/statics/hollow_key.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/hollow_key.jpg -------------------------------------------------------------------------------- /public/statics/red_paper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/red_paper.jpg -------------------------------------------------------------------------------- /public/statics/white_paper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/white_paper.jpg -------------------------------------------------------------------------------- /public/statics/white_ring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/white_ring.jpg -------------------------------------------------------------------------------- /src-electron/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src-electron/icons/icon.icns -------------------------------------------------------------------------------- /src/assets/home-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src/assets/home-background.jpg -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /public/statics/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/icon-512x512.png -------------------------------------------------------------------------------- /public/statics/proto_bahamut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/proto_bahamut.png -------------------------------------------------------------------------------- /public/statics/mirage_munition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/mirage_munition.png -------------------------------------------------------------------------------- /public/statics/silver_centrum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/silver_centrum.jpg -------------------------------------------------------------------------------- /public/statics/verdant_azurite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/public/statics/verdant_azurite.jpg -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | body { 3 | background: #ffffff; 4 | overflow: hidden; 5 | } 6 | -------------------------------------------------------------------------------- /src-electron/icons/linux-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awayz/gbf-counter/HEAD/src-electron/icons/linux-512x512.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@quasar/app/tsconfig-preset", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "jsx": "preserve" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | .eslintrc.js 8 | babel.config.js 9 | /src-ssr 10 | /src-electron -------------------------------------------------------------------------------- /src/i18n/en-US/index.ts: -------------------------------------------------------------------------------- 1 | // This is just an example, 2 | // so you can safely delete all default props below 3 | 4 | export default { 5 | failed: 'Action failed', 6 | success: 'Action was successful', 7 | }; 8 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | NODE_ENV: string; 4 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; 5 | VUE_ROUTER_BASE: string | undefined; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /src/store/module-example/state.ts: -------------------------------------------------------------------------------- 1 | export interface ExampleStateInterface { 2 | prop: boolean; 3 | } 4 | 5 | function state(): ExampleStateInterface { 6 | return { 7 | prop: false, 8 | }; 9 | } 10 | 11 | export default state; 12 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // Mocks all files ending in `.vue` showing them as plain Vue instances 2 | declare module '*.vue' { 3 | import { ComponentOptions } from 'vue'; 4 | 5 | const component: ComponentOptions; 6 | export default component; 7 | } 8 | 9 | declare module 'vue-digit-animation'; 10 | -------------------------------------------------------------------------------- /src/store/module-example/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex'; 2 | import { ExampleStateInterface } from './state'; 3 | 4 | const mutation: MutationTree = { 5 | someMutation(/* state: ExampleStateInterface */) { 6 | // your code 7 | }, 8 | }; 9 | 10 | export default mutation; 11 | -------------------------------------------------------------------------------- /src/store/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true, 7 | "printWidth": 120, 8 | "bracketSpacing": true, 9 | "quoteProps": "as-needed", 10 | "arrowParens": "always", 11 | "proseWrap": "preserve", 12 | "htmlWhitespaceSensitivity": "css" 13 | } 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = api => { 4 | return { 5 | presets: [ 6 | [ 7 | '@quasar/babel-preset-app', 8 | api.caller(caller => caller && caller.target === 'node') 9 | ? { targets: { node: 'current' } } 10 | : {} 11 | ] 12 | ] 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/store/module-example/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree } from 'vuex'; 2 | import { StateInterface } from '../index'; 3 | import { ExampleStateInterface } from './state'; 4 | 5 | const actions: ActionTree = { 6 | someAction(/* context */) { 7 | // your code 8 | }, 9 | }; 10 | 11 | export default actions; 12 | -------------------------------------------------------------------------------- /src/store/module-example/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex'; 2 | import { StateInterface } from '../index'; 3 | import { ExampleStateInterface } from './state'; 4 | 5 | const getters: GetterTree = { 6 | someAction(/* context */) { 7 | // your code 8 | }, 9 | }; 10 | 11 | export default getters; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true # 表明是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件。 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/boot/i18n.ts: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers'; 2 | import { createI18n } from 'vue-i18n'; 3 | 4 | import messages from 'src/i18n'; 5 | 6 | const i18n = createI18n({ 7 | locale: 'en-US', 8 | messages, 9 | }); 10 | 11 | export default boot(({ app }) => { 12 | // Set i18n instance on app 13 | app.use(i18n); 14 | }); 15 | 16 | export { i18n }; 17 | -------------------------------------------------------------------------------- /src/store/module-example/index.ts: -------------------------------------------------------------------------------- 1 | import { Module } from 'vuex'; 2 | import { StateInterface } from '../index'; 3 | import state, { ExampleStateInterface } from './state'; 4 | import actions from './actions'; 5 | import getters from './getters'; 6 | import mutations from './mutations'; 7 | 8 | const exampleModule: Module = { 9 | namespaced: true, 10 | actions, 11 | getters, 12 | mutations, 13 | state, 14 | }; 15 | 16 | export default exampleModule; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GBF 大巴、阿卡夏计数器 2 | # GBF proto_bahamut\akasha counter 3 | 4 | ## 1. 使用方式 5 | 下载 `release` 中的压缩包,解压后双击 exe 即可食用 6 | 7 | ## 2. 功能 8 | 主界面 9 | 10 | ![主界面](https://github.com/awayz/gbf-counter/blob/main/example/img/home.png) 11 | 12 | ### 1. 大巴蓝箱计数 13 | 14 | ![大巴界面](https://github.com/awayz/gbf-counter/blob/main/example/img/proto_bahamut.png) 15 | 16 | ### 2. 阿卡夏蓝箱计数 17 | 18 | ![阿卡夏界面](https://github.com/awayz/gbf-counter/blob/main/example/img/akasha.png) 19 | 20 | ## 3. 相关链接 21 | 22 | [quasar]: https://quasar.dev/start/quasar-cli 23 | 24 | -------------------------------------------------------------------------------- /src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | pnpm-debug.log* 28 | debug.log* 29 | 30 | # Editor directories and files 31 | .idea 32 | .vscode 33 | *.suo 34 | *.ntvs* 35 | *.njsproj 36 | *.sln 37 | *.sw? 38 | -------------------------------------------------------------------------------- /src-electron/utils/gbfUtil.js: -------------------------------------------------------------------------------- 1 | // 找到 itemId 对应的列表中距离现在最近的那一个,并删除,返回删除后的 record 2 | export function removeClosest(record, raidId, itemId) { 3 | let result = []; 4 | if (record) { 5 | // 跳过找到的第一个,其他的复制进新的数组 6 | let find = false; 7 | for (let i = record.length - 1; i >= 0; i-=1 ) { 8 | let t = record[i]; 9 | if (!find && t && t.raidId === raidId && t.itemId === itemId) { 10 | find = true; 11 | continue; 12 | } 13 | result.unshift({...t}); 14 | } 15 | } 16 | return result; 17 | } 18 | 19 | export function findByRaidId(record, raidId) { 20 | let result = []; 21 | if (record) { 22 | for (let i = 0; i < record.length; i += 1 ) { 23 | let t = record[i]; 24 | if (t && t.raidId === raidId) { 25 | result.push({...t}); 26 | } 27 | } 28 | } 29 | return result; 30 | } -------------------------------------------------------------------------------- /src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary: #1976d2; 16 | $secondary: #26a69a; 17 | $accent: #9c27b0; 18 | 19 | $dark: #1d1d1d; 20 | 21 | $positive: #21ba45; 22 | $negative: #c10015; 23 | $info: #31ccec; 24 | $warning: #f2c037; 25 | 26 | $pink: #ff0080; 27 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | component: () => import('layouts/MainLayout.vue'), 7 | children: [{ path: '', component: () => import('pages/Index.vue') }], 8 | }, 9 | { 10 | path: '/counter', 11 | component: () => import('layouts/BlankLayout.vue'), 12 | children: [ 13 | { 14 | path: '/counter/:id(\\w+)?', 15 | component: () => import('pages/counter/index.vue'), 16 | }, 17 | ], 18 | }, 19 | { 20 | path: '/analysis', 21 | component: () => import('layouts/MainLayout.vue'), 22 | children: [{ path: '', component: () => import('pages/analysis/index.vue') }], 23 | }, 24 | // Always leave this as last one, 25 | // but you can also remove it 26 | { 27 | path: '/:catchAll(.*)*', 28 | component: () => import('pages/Error404.vue'), 29 | }, 30 | ]; 31 | 32 | export default routes; 33 | -------------------------------------------------------------------------------- /src-electron/utils/dbUtil.js: -------------------------------------------------------------------------------- 1 | export function existKey(storage, key) { 2 | return new Promise((resolve, reject) => { 3 | storage.has(key, function(error, hasKey) { 4 | if (error) { 5 | reject(error); 6 | } 7 | resolve(hasKey); 8 | }); 9 | }); 10 | } 11 | 12 | export function getByKey(storage, key) { 13 | return new Promise((resolve, reject) => { 14 | storage.get(key, function(error, data) { 15 | if (error) { 16 | reject(error); 17 | } 18 | resolve(data); 19 | }); 20 | }); 21 | } 22 | 23 | // 如果不存在,会返回默认值,并且 storage 会写入默认值 24 | export async function getByKeyOrDefault(storage, key, defaultValue) { 25 | try { 26 | let exist = await existKey(storage, key); 27 | let data; 28 | if (exist) { 29 | data = await getByKey(storage, key); 30 | } else { 31 | data = defaultValue; 32 | } 33 | return data; 34 | } catch(e) { 35 | console.log(e); 36 | throw Error('getByKeyOrDefault error'); 37 | } 38 | } -------------------------------------------------------------------------------- /src-electron/data/constants.js: -------------------------------------------------------------------------------- 1 | export const defaultGbfData = { 2 | "proto_bahamut": { 3 | "ffj": 0, 4 | "red_ring": 0, 5 | "black_ring": 0, 6 | "white_ring": 0, 7 | "empty": 0, 8 | }, 9 | "akasha": { 10 | "ffj": 0, 11 | "red_ring": 0, 12 | "black_ring": 0, 13 | "white_ring": 0, 14 | "silver_centrum":0, 15 | "red_paper": 0, 16 | "black_paper": 0, 17 | "white_paper": 0, 18 | "hollow_key": 0, 19 | "mirage_munition": 0 20 | }, 21 | "grand_order": { 22 | "ffj": 0, 23 | "red_ring": 0, 24 | "black_ring": 0, 25 | "white_ring": 0, 26 | "silver_centrum":0, 27 | "red_paper": 0, 28 | "black_paper": 0, 29 | "white_paper": 0, 30 | "verdant_azurite": 0, 31 | } 32 | }; 33 | 34 | export const defaultDetailData = { 35 | record: [ 36 | 37 | ] 38 | } 39 | 40 | // 存掉落数量的文件名,默认为 defaultGbfData 41 | export const GBF_JSON_KEY = 'gbf'; 42 | 43 | // 存详细掉落的文件名, 默认为 defaultDetailData 44 | export const DETAIL_JSON_KEY = 'gbf-detail'; -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers'; 2 | import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router'; 3 | import { StateInterface } from '../store'; 4 | import routes from './routes'; 5 | 6 | /* 7 | * If not building with SSR mode, you can 8 | * directly export the Router instantiation; 9 | * 10 | * The function below can be async too; either use 11 | * async/await or return a Promise which resolves 12 | * with the Router instance. 13 | */ 14 | 15 | export default route((/* { store, ssrContext } */) => { 16 | const createHistory = process.env.SERVER 17 | ? createMemoryHistory 18 | : process.env.VUE_ROUTER_MODE === 'history' 19 | ? createWebHistory 20 | : createWebHashHistory; 21 | 22 | const Router = createRouter({ 23 | scrollBehavior: () => ({ left: 0, top: 0 }), 24 | routes, 25 | 26 | // Leave this as is and make changes in quasar.conf.js instead! 27 | // quasar.conf.js -> build -> vueRouterMode 28 | // quasar.conf.js -> build -> publicPath 29 | history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE), 30 | }); 31 | 32 | return Router; 33 | }); 34 | -------------------------------------------------------------------------------- /src/boot/axios.ts: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers'; 2 | import axios, { AxiosInstance } from 'axios'; 3 | 4 | declare module '@vue/runtime-core' { 5 | interface ComponentCustomProperties { 6 | $axios: AxiosInstance; 7 | } 8 | } 9 | 10 | // Be careful when using SSR for cross-request state pollution 11 | // due to creating a Singleton instance here; 12 | // If any client changes this (global) instance, it might be a 13 | // good idea to move this instance creation inside of the 14 | // "export default () => {}" function below (which runs individually 15 | // for each client) 16 | const api = axios.create({ baseURL: 'https://api.example.com' }); 17 | 18 | export default boot(({ app }) => { 19 | // for use inside Vue files (Options API) through this.$axios and this.$api 20 | 21 | app.config.globalProperties.$axios = axios; 22 | // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) 23 | // so you won't necessarily have to import axios in each vue file 24 | 25 | app.config.globalProperties.$api = api; 26 | // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) 27 | // so you can easily perform requests against your app's API 28 | }); 29 | 30 | export { api }; 31 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | 59 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { store } from 'quasar/wrappers'; 2 | import { InjectionKey } from 'vue'; 3 | import { createStore, Store as VuexStore, useStore as vuexUseStore } from 'vuex'; 4 | 5 | // import example from './module-example' 6 | // import { ExampleStateInterface } from './module-example/state'; 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Store instantiation; 11 | * 12 | * The function below can be async too; either use 13 | * async/await or return a Promise which resolves 14 | * with the Store instance. 15 | */ 16 | 17 | export interface StateInterface { 18 | // Define your own store structure, using submodules if needed 19 | // example: ExampleStateInterface; 20 | // Declared as unknown to avoid linting issue. Best to strongly type as per the line above. 21 | example: unknown; 22 | } 23 | 24 | // provide typings for `this.$store` 25 | declare module '@vue/runtime-core' { 26 | interface ComponentCustomProperties { 27 | $store: VuexStore; 28 | } 29 | } 30 | 31 | // provide typings for `useStore` helper 32 | export const storeKey: InjectionKey> = Symbol('vuex-key'); 33 | 34 | export default store((/* { ssrContext } */) => { 35 | const Store = createStore({ 36 | modules: { 37 | // example 38 | }, 39 | 40 | // enable strict mode (adds overhead!) 41 | // for dev mode and --debug builds only 42 | strict: !!process.env.DEBUGGING, 43 | }); 44 | 45 | return Store; 46 | }); 47 | 48 | export function useStore() { 49 | return vuexUseStore(storeKey); 50 | } 51 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 50 | 51 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gbf-counter", 3 | "version": "0.4.1", 4 | "description": "GBF counter", 5 | "productName": "gbf-counter", 6 | "author": "awayz", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.ts,.vue ./", 10 | "test": "echo \"No test specified\" && exit 0" 11 | }, 12 | "dependencies": { 13 | "@quasar/extras": "^1.14.3", 14 | "axios": "^0.21.4", 15 | "dayjs": "^1.11.3", 16 | "echarts": "^5.3.3", 17 | "electron-json-storage": "^4.5.0", 18 | "quasar": "^2.7.5", 19 | "vue": "^3.2.37", 20 | "vue-digit-animation": "^0.2.1", 21 | "vue-i18n": "^9.1.10", 22 | "vue-router": "^4.1.2", 23 | "vuex": "^4.0.2" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.18.6", 27 | "@babel/eslint-parser": "^7.18.2", 28 | "@quasar/app": "^3.3.3", 29 | "@types/node": "^18.0.5", 30 | "@typescript-eslint/eslint-plugin": "^5.30.6", 31 | "@typescript-eslint/parser": "^5.30.6", 32 | "core-js": "^3.23.4", 33 | "electron": "^16.2.8", 34 | "electron-builder": "^22.14.13", 35 | "eslint": "^7.32.0", 36 | "eslint-config-airbnb-base": "^14.2.1", 37 | "eslint-config-prettier": "^8.5.0", 38 | "eslint-plugin-import": "^2.26.0", 39 | "eslint-plugin-prettier": "^4.2.1", 40 | "eslint-plugin-vue": "^9.2.0", 41 | "prettier": "^2.7.1", 42 | "typescript": "^4.7.4" 43 | }, 44 | "browserslist": [ 45 | "last 10 Chrome versions", 46 | "last 10 Firefox versions", 47 | "last 4 Edge versions", 48 | "last 7 Safari versions", 49 | "last 8 Android versions", 50 | "last 8 ChromeAndroid versions", 51 | "last 8 FirefoxAndroid versions", 52 | "last 10 iOS versions", 53 | "last 5 Opera versions" 54 | ], 55 | "engines": { 56 | "node": ">= 12.22.1", 57 | "npm": ">= 6.13.4", 58 | "yarn": ">= 1.21.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src-electron/electron-preload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically for security reasons. 3 | * Here you can access Nodejs stuff and inject functionality into 4 | * the renderer thread (accessible there through the "window" object) 5 | * 6 | * WARNING! 7 | * If you import anything from node_modules, then make sure that the package is specified 8 | * in package.json > dependencies and NOT in devDependencies 9 | * 10 | */ 11 | 12 | const { contextBridge, ipcRenderer } = require('electron'); 13 | 14 | contextBridge.exposeInMainWorld('api', { 15 | version: async () => { 16 | return await ipcRenderer.invoke('version'); 17 | }, 18 | startCount: (height) => { 19 | ipcRenderer.send('start-count', height); 20 | }, 21 | endCount: () => { 22 | ipcRenderer.send('end-count'); 23 | }, 24 | minimize: () => { 25 | ipcRenderer.send('minimize'); 26 | }, 27 | close: () => { 28 | ipcRenderer.send('close'); 29 | }, 30 | changeOnTop: () => { 31 | ipcRenderer.send('changeOnTop'); 32 | }, 33 | count: async (raidId) => { 34 | return await ipcRenderer.invoke('count', raidId); 35 | }, 36 | countAll: async () => { 37 | return await ipcRenderer.invoke('countAll'); 38 | }, 39 | save: async ({ raidId, itemId, num }) => { 40 | return await ipcRenderer.invoke('save', { raidId, itemId, num }); 41 | }, 42 | saveRaid: async ({ raidId, raidData }) => { 43 | return await ipcRenderer.invoke('saveRaid', { raidId, raidData }); 44 | }, 45 | increment: async ({ raidId, itemId, itemName }) => { 46 | return await ipcRenderer.invoke('increment', { raidId, itemId, itemName }); 47 | }, 48 | decrement: async ({ raidId, itemId }) => { 49 | return await ipcRenderer.invoke('decrement', { raidId, itemId }); 50 | }, 51 | listByRaidId: async (raidId) => { 52 | return await ipcRenderer.invoke('listByRaidId', raidId); 53 | }, 54 | list: async () => { 55 | return await ipcRenderer.invoke('list'); 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | 41 | 86 | -------------------------------------------------------------------------------- /src/components/DropItemCard.vue: -------------------------------------------------------------------------------- 1 | 24 | 89 | 90 | 107 | -------------------------------------------------------------------------------- /src/constants/drop.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | interface DropMapI { 3 | [index: string]: string[]; 4 | } 5 | 6 | export interface DropItemI { 7 | id: string; 8 | name: string; 9 | imgPath: string; 10 | } 11 | 12 | interface DropItemsI { 13 | [index: string]: DropItemI; 14 | } 15 | 16 | export const DropItems: DropItemsI = { 17 | ffj: { 18 | id: 'ffj', 19 | name: '绯绯金', 20 | imgPath: 'statics/ffj.jpg', 21 | }, 22 | red_ring: { 23 | id: 'red_ring', 24 | name: '红戒指', 25 | imgPath: 'statics/red_ring.jpg', 26 | }, 27 | black_ring: { 28 | id: 'black_ring', 29 | name: '黑戒指', 30 | imgPath: 'statics/black_ring.jpg', 31 | }, 32 | white_ring: { 33 | id: 'white_ring', 34 | name: '白戒指', 35 | imgPath: 'statics/white_ring.jpg', 36 | }, 37 | silver_centrum: { 38 | id: 'silver_centrum', 39 | name: 'KMRの宝物', 40 | imgPath: 'statics/silver_centrum.jpg', 41 | }, 42 | red_paper: { 43 | id: 'red_paper', 44 | name: '红纸', 45 | imgPath: 'statics/red_paper.jpg', 46 | }, 47 | black_paper: { 48 | id: 'black_paper', 49 | name: '黑纸', 50 | imgPath: 'statics/black_paper.jpg', 51 | }, 52 | white_paper: { 53 | id: 'white_paper', 54 | name: '白纸', 55 | imgPath: 'statics/white_paper.jpg', 56 | }, 57 | hollow_key: { 58 | id: 'hollow_key', 59 | name: '阿卡夏钥匙', 60 | imgPath: 'statics/hollow_key.jpg', 61 | }, 62 | mirage_munition: { 63 | id: 'mirage_munition', 64 | name: '加蛋', 65 | imgPath: 'statics/mirage_munition.png', 66 | }, 67 | verdant_azurite: { 68 | id: 'verdant_azurite', 69 | name: '海胆', 70 | imgPath: 'statics/verdant_azurite.jpg', 71 | }, 72 | empty: { 73 | id: 'empty', 74 | name: '我蓝箱呢', 75 | imgPath: 'statics/empty.jpg', 76 | }, 77 | }; 78 | 79 | export const DropItemMap: DropMapI = { 80 | proto_bahamut: ['ffj', 'red_ring', 'black_ring', 'white_ring', 'empty'], 81 | akasha: [ 82 | 'ffj', 83 | 'red_ring', 84 | 'black_ring', 85 | 'white_ring', 86 | 'silver_centrum', 87 | 'red_paper', 88 | 'black_paper', 89 | 'white_paper', 90 | 'hollow_key', 91 | 'mirage_munition', 92 | ], 93 | grand_order: [ 94 | 'ffj', 95 | 'red_ring', 96 | 'black_ring', 97 | 'white_ring', 98 | 'silver_centrum', 99 | 'red_paper', 100 | 'black_paper', 101 | 'white_paper', 102 | 'verdant_azurite', 103 | ], 104 | }; 105 | 106 | export const RaidList = [ 107 | { 108 | id: 'proto_bahamut', 109 | name: '大巴', 110 | img_path: 'statics/proto_bahamut.png', 111 | route: '/counter/proto_bahamut', 112 | }, 113 | { 114 | id: 'akasha', 115 | name: '阿卡夏', 116 | img_path: 'statics/akasha.png', 117 | route: '/counter/akasha', 118 | }, 119 | { 120 | id: 'grand_order', 121 | name: '大公', 122 | img_path: 'statics/grand_order.jpg', 123 | route: '/counter/grand_order', 124 | }, 125 | ]; 126 | -------------------------------------------------------------------------------- /src/pages/counter/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 109 | 137 | -------------------------------------------------------------------------------- /src/utils/gbfUtil.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | /* 物品掉落情况 4 | * { itemName1: count1, itemName2: count2 } 5 | */ 6 | export interface ItemCount { 7 | [index: string]: number; 8 | } 9 | 10 | /* 所有 raid 的掉落情况 11 | * { raidName1 : { itemName1: count1, itemName2: count2 }, raidName2: { xxx }} 12 | */ 13 | export interface AllRaidsItemCount { 14 | [index: string]: ItemCount; 15 | } 16 | 17 | export interface RaidDetail { 18 | raidId: string; 19 | itemId: string; 20 | itemName: string; 21 | num: number; 22 | damage: number; 23 | duration: number; 24 | time: string; 25 | } 26 | 27 | export interface DropInfoDTO { 28 | totalItemCount: ItemCount; 29 | blueTreasureCount: number; 30 | akashaTreasureCount: number; 31 | protoBahamutTreasureCount: number; 32 | grandOrderTreasureCount: number; 33 | ffjCount: number; 34 | } 35 | 36 | export interface YearRaidCount { 37 | [index: string]: number; 38 | } 39 | 40 | // 所有 raid 的掉落情况(按 raidId 分开记录) 41 | export async function countAll(): Promise { 42 | const data = (await (window as any).api.countAll()) as AllRaidsItemCount; 43 | return data; 44 | } 45 | 46 | // 单个 raid 的掉落情况 47 | export async function count(raidId: string): Promise { 48 | return (await (window as any).api.count(raidId)) as ItemCount; 49 | } 50 | 51 | // 所有 raid 的掉落情况(按物品 sum) 52 | export function getDropInfo(allRaidsItemCount: AllRaidsItemCount): DropInfoDTO { 53 | if (!allRaidsItemCount) { 54 | return { 55 | totalItemCount: {}, 56 | blueTreasureCount: 0, 57 | akashaTreasureCount: 0, 58 | grandOrderTreasureCount: 0, 59 | protoBahamutTreasureCount: 0, 60 | ffjCount: 0, 61 | }; 62 | } 63 | 64 | const totalItemCount: ItemCount = {}; 65 | let blueTreasure = 0; 66 | let akasha = 0; 67 | let protoBahamut = 0; 68 | let grandOrder = 0; 69 | let ffjCount = 0; 70 | const AKASHA = 'akasha'; 71 | const PROTOBAHAMUT = 'proto_bahamut'; 72 | const GRANDORDER = 'grand_order'; 73 | const FFJ = 'ffj'; 74 | const EMPTY = 'empty'; 75 | 76 | for (const [raidId, raidItemCount] of Object.entries(allRaidsItemCount)) { 77 | for (const [item, c] of Object.entries(raidItemCount)) { 78 | if (item === EMPTY) { 79 | continue; 80 | } 81 | 82 | if (Object.prototype.hasOwnProperty.call(totalItemCount, item)) { 83 | totalItemCount[item] += c; 84 | } else { 85 | totalItemCount[item] = c; 86 | } 87 | 88 | blueTreasure += c; 89 | if (raidId === AKASHA) { 90 | akasha += c; 91 | } else if (raidId === PROTOBAHAMUT) { 92 | protoBahamut += c; 93 | } else if (raidId === GRANDORDER) { 94 | grandOrder += c; 95 | } 96 | if (item === FFJ) { 97 | ffjCount += c; 98 | } 99 | } 100 | } 101 | const result = { 102 | totalItemCount, 103 | blueTreasureCount: blueTreasure, 104 | akashaTreasureCount: akasha, 105 | protoBahamutTreasureCount: protoBahamut, 106 | grandOrderTreasureCount: grandOrder, 107 | ffjCount, 108 | }; 109 | return result; 110 | } 111 | 112 | // 所有 raid 的掉落详情 113 | export async function listDetails(): Promise { 114 | const data = (await (window as any).api.list()) as RaidDetail[]; 115 | // console.log('detail data: ', data); 116 | return data; 117 | } 118 | 119 | // 计算 detail 记录里有多少不同的日期 120 | export function countRaidDays(details: RaidDetail[]): number { 121 | const daySet = new Set(); 122 | 123 | for (let i = 0; i < details.length; i += 1) { 124 | const d = details[i]; 125 | if (d && d.time) { 126 | const t = d.time; 127 | const date = dayjs(t).format('DD/MM/YYYY'); 128 | // console.log('data in count raid', date); 129 | daySet.add(date); 130 | } 131 | } 132 | return daySet.size; 133 | } 134 | 135 | // 每年每天打了几次 136 | export function yearRaidCountGroupByDay(year: string, details: RaidDetail[]): YearRaidCount { 137 | const yearRaidCount: YearRaidCount = {}; 138 | for (let i = 0; i < details.length; i += 1) { 139 | const d = details[i]; 140 | if (d && d.time) { 141 | const t = d.time; 142 | const date = dayjs(t); 143 | const y = date.get('year'); 144 | if (y !== +year) { 145 | continue; 146 | } 147 | const dateStr = date.format('YYYY-MM-DD'); 148 | if (Object.prototype.hasOwnProperty.call(yearRaidCount, dateStr)) { 149 | yearRaidCount[dateStr] += 1; 150 | } else { 151 | yearRaidCount[dateStr] = 1; 152 | } 153 | } 154 | } 155 | return yearRaidCount; 156 | } 157 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | module.exports = { 3 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 4 | // This option interrupts the configuration hierarchy at this file 5 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 6 | root: true, 7 | 8 | // https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser 9 | // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working 10 | // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted 11 | parserOptions: { 12 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration 13 | // https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint 14 | // Needed to make the parser take into account 'vue' files 15 | extraFileExtensions: ['.vue'], 16 | parser: '@typescript-eslint/parser', 17 | project: resolve(__dirname, './tsconfig.json'), 18 | tsconfigRootDir: __dirname, 19 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 20 | sourceType: 'module', // Allows for the use of imports 21 | }, 22 | 23 | env: { 24 | browser: true, 25 | }, 26 | 27 | // Rules order is important, please avoid shuffling them 28 | extends: [ 29 | // Base ESLint recommended rules 30 | // 'eslint:recommended', 31 | 32 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage 33 | // ESLint typescript rules 34 | 'plugin:@typescript-eslint/recommended', 35 | // consider disabling this class of rules if linting takes too long 36 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 37 | 38 | // Uncomment any of the lines below to choose desired strictness, 39 | // but leave only one uncommented! 40 | // See https://eslint.vuejs.org/rules/#available-rules 41 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 42 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 43 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 44 | 45 | 'airbnb-base', 46 | 'plugin:prettier/recommended', 47 | ], 48 | 49 | plugins: [ 50 | // required to apply rules which need type information 51 | '@typescript-eslint', 52 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file 53 | // required to lint *.vue files 54 | 'vue', 55 | ], 56 | 57 | globals: { 58 | ga: 'readonly', // Google Analytics 59 | cordova: 'readonly', 60 | __statics: 'readonly', 61 | __QUASAR_SSR__: 'readonly', 62 | __QUASAR_SSR_SERVER__: 'readonly', 63 | __QUASAR_SSR_CLIENT__: 'readonly', 64 | __QUASAR_SSR_PWA__: 'readonly', 65 | process: 'readonly', 66 | Capacitor: 'readonly', 67 | chrome: 'readonly', 68 | }, 69 | 70 | // add your custom rules here 71 | rules: { 72 | 'no-param-reassign': 'off', 73 | 'no-void': 'off', 74 | 75 | 'import/first': 'off', 76 | 'import/named': 'error', 77 | 'import/namespace': 'error', 78 | 'import/default': 'error', 79 | 'import/export': 'error', 80 | 'import/extensions': 'off', 81 | 'import/no-unresolved': 'off', 82 | 'import/no-extraneous-dependencies': 'off', 83 | 'import/prefer-default-export': 'off', 84 | 'prefer-promise-reject-errors': 'off', 85 | 86 | 'no-nested-ternary': 'off', 87 | 'no-underscore-dangle': 'off', 88 | 'no-empty': 'off', 89 | 'no-console': 'off', 90 | 'no-restricted-syntax': 'off', 91 | 'no-unused-vars': 'off', 92 | 'no-continue': 0, 93 | 94 | // TypeScript 95 | quotes: ['warn', 'single', { avoidEscape: true }], 96 | '@typescript-eslint/explicit-function-return-type': 'off', 97 | '@typescript-eslint/explicit-module-boundary-types': 'off', 98 | '@typescript-eslint/no-empty-function': 'off', 99 | '@typescript-eslint/no-unsafe-call': 'off', 100 | '@typescript-eslint/no-unsafe-member-access': 'off', 101 | '@typescript-eslint/no-explicit-any': 'off', 102 | '@typescript-eslint/no-unused-vars': 'off', 103 | '@typescript-eslint/no-unsafe-return': 'off', 104 | '@typescript-eslint/restrict-template-expressions': 'off', 105 | '@typescript-eslint/no-unsafe-assignment': 'off', 106 | 107 | // allow debugger during development only 108 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 109 | 110 | 'vue/script-setup-uses-vars': 'off', 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /src/components/RaidCard.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 171 | 172 | 184 | -------------------------------------------------------------------------------- /src-electron/electron-main.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, nativeTheme, ipcMain } from 'electron'; 2 | import { defaultGbfData, defaultDetailData, GBF_JSON_KEY, DETAIL_JSON_KEY } from './data/constants.js'; 3 | import { getByKeyOrDefault } from './utils/dbUtil.js'; 4 | import { removeClosest, findByRaidId } from './utils/gbfUtil.js'; 5 | import packageInfo from '../package.json'; 6 | 7 | const dayjs = require('dayjs'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | try { 12 | if (process.platform === 'win32' && nativeTheme.shouldUseDarkColors === true) { 13 | fs.unlinkSync(path.join(app.getPath('userData'), 'DevTools Extensions')); 14 | } 15 | } catch (_) {} 16 | 17 | const storage = require('electron-json-storage'); 18 | const STORE_PATH = app.getPath('userData'); 19 | storage.setDataPath(STORE_PATH); 20 | 21 | let mainWindow; 22 | const mainWidth = 900; 23 | const mainHeight = 550; 24 | 25 | function createWindow() { 26 | mainWindow = new BrowserWindow({ 27 | width: mainWidth, 28 | height: mainHeight, 29 | titleBarStyle: 'hidden', 30 | useContentSize: true, 31 | alwaysOnTop: true, 32 | resizable: false, 33 | icon: path.resolve(__dirname, 'statics/icon.ico'), 34 | frame: false, 35 | webPreferences: { 36 | contextIsolation: true, 37 | preload: path.resolve(__dirname, 'electron-preload.js'), 38 | }, 39 | }); 40 | 41 | mainWindow.loadURL(process.env.APP_URL); 42 | 43 | if (process.env.DEBUGGING) { 44 | // if on DEV or Production with debug enabled 45 | mainWindow.webContents.openDevTools(); 46 | } else { 47 | // we're on production; no access to devtools pls 48 | mainWindow.webContents.on('devtools-opened', () => { 49 | mainWindow.webContents.closeDevTools(); 50 | }); 51 | } 52 | 53 | mainWindow.on('closed', () => { 54 | mainWindow = null; 55 | }); 56 | } 57 | 58 | app.on('ready', createWindow); 59 | 60 | app.on('window-all-closed', () => { 61 | if (process.platform !== 'darwin') { 62 | app.quit(); 63 | } 64 | }); 65 | 66 | app.on('activate', () => { 67 | if (mainWindow === null) { 68 | createWindow(); 69 | } 70 | }); 71 | 72 | ipcMain.handle('version', async () => { 73 | return packageInfo.version; 74 | }); 75 | 76 | ipcMain.on('minimize', () => { 77 | mainWindow.minimize(); 78 | }); 79 | 80 | ipcMain.on('close', () => { 81 | mainWindow.close(); 82 | }); 83 | 84 | ipcMain.on('changeOnTop', () => { 85 | mainWindow.setAlwaysOnTop(!mainWindow.isAlwaysOnTop()); 86 | }); 87 | 88 | ipcMain.on('start-count', (_, arg) => { 89 | const height = arg; 90 | mainWindow.setResizable(true); 91 | if (mainWindow.isMaximized()) { 92 | mainWindow.restore(); 93 | } 94 | mainWindow.setSize(170, height); 95 | mainWindow.setResizable(false); 96 | }); 97 | 98 | ipcMain.on('end-count', () => { 99 | mainWindow.setResizable(true); 100 | if (mainWindow.isMaximized()) { 101 | mainWindow.restore(); 102 | } 103 | mainWindow.setSize(mainWidth, mainHeight); 104 | mainWindow.setResizable(false); 105 | }); 106 | 107 | ipcMain.handle('count', async (_, raidId) => { 108 | try { 109 | let data = await getByKeyOrDefault(storage, GBF_JSON_KEY, defaultGbfData); 110 | return data[raidId] || defaultGbfData[raidId]; 111 | } catch (e) { 112 | console.log(e); 113 | } 114 | }); 115 | 116 | ipcMain.handle('countAll', async (_, __) => { 117 | try { 118 | let data = await getByKeyOrDefault(storage, GBF_JSON_KEY, defaultGbfData); 119 | return data; 120 | } catch (e) { 121 | console.log(e); 122 | } 123 | }); 124 | 125 | ipcMain.handle('save', async (_, { raidId, itemId, num }) => { 126 | try { 127 | let data = await getByKeyOrDefault(storage, GBF_JSON_KEY, defaultGbfData); 128 | if (!data[raidId]) { 129 | data[raidId] = {}; 130 | } 131 | data[raidId][itemId] = num; 132 | storage.set(GBF_JSON_KEY, data); 133 | return true; 134 | } catch (e) { 135 | console.log(e); 136 | } 137 | return false; 138 | }); 139 | 140 | ipcMain.handle('saveRaid', async (_, { raidId, raidData }) => { 141 | try { 142 | let data = await getByKeyOrDefault(storage, GBF_JSON_KEY, defaultGbfData); 143 | data[raidId] = raidData; 144 | storage.set(GBF_JSON_KEY, data); 145 | return true; 146 | } catch (e) { 147 | console.log(e); 148 | } 149 | return false; 150 | }); 151 | 152 | ipcMain.handle('increment', async (_, { raidId, itemId, itemName }) => { 153 | try { 154 | let data = await getByKeyOrDefault(storage, DETAIL_JSON_KEY, defaultDetailData); 155 | let now = dayjs().toISOString(); 156 | data.record.push({ 157 | raidId: raidId, 158 | itemId: itemId, 159 | itemName, 160 | num: 1, 161 | damage: -1, 162 | duration: -1, 163 | time: now, 164 | }); 165 | storage.set(DETAIL_JSON_KEY, data); 166 | return true; 167 | } catch (e) { 168 | console.log(e); 169 | } 170 | }); 171 | 172 | ipcMain.handle('decrement', async (_, { raidId, itemId }) => { 173 | try { 174 | let data = await getByKeyOrDefault(storage, DETAIL_JSON_KEY, defaultDetailData); 175 | let newRecord = removeClosest(data.record, raidId, itemId); 176 | data.record = newRecord; 177 | storage.set(DETAIL_JSON_KEY, data); 178 | } catch (e) { 179 | console.log(e); 180 | } 181 | }); 182 | 183 | ipcMain.handle('listByRaidId', async (_, raidId) => { 184 | try { 185 | let data = await getByKeyOrDefault(storage, DETAIL_JSON_KEY, defaultDetailData); 186 | let dataByRaid = findByRaidId(data.record, raidId); 187 | return dataByRaid; 188 | } catch (e) { 189 | console.log(e); 190 | } 191 | }); 192 | 193 | ipcMain.handle('list', async (_, __) => { 194 | try { 195 | let data = await getByKeyOrDefault(storage, DETAIL_JSON_KEY, defaultDetailData); 196 | return data.record; 197 | } catch (e) { 198 | console.log(e); 199 | } 200 | }); 201 | -------------------------------------------------------------------------------- /quasar.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 3 | * the ES6 features that are supported by your Node version. https://node.green/ 4 | */ 5 | 6 | // Configuration for your app 7 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js 8 | 9 | /* eslint-env node */ 10 | /* eslint-disable @typescript-eslint/no-var-requires */ 11 | /* eslint func-names: 0 */ 12 | /* eslint global-require: 0 */ 13 | const { configure } = require('quasar/wrappers'); 14 | 15 | module.exports = configure((ctx) => ({ 16 | // https://v2.quasar.dev/quasar-cli/supporting-ts 17 | supportTS: { 18 | tsCheckerConfig: { 19 | eslint: { 20 | enabled: true, 21 | files: './src/**/*.{ts,tsx,js,jsx,vue}', 22 | }, 23 | }, 24 | }, 25 | 26 | // https://v2.quasar.dev/quasar-cli/prefetch-feature 27 | // preFetch: true, 28 | 29 | // app boot file (/src/boot) 30 | // --> boot files are part of "main.js" 31 | // https://v2.quasar.dev/quasar-cli/boot-files 32 | boot: ['i18n', 'axios'], 33 | 34 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css 35 | css: ['app.scss'], 36 | 37 | // https://github.com/quasarframework/quasar/tree/dev/extras 38 | extras: [ 39 | 'roboto-font', // optional, you are not bound to it 40 | 'material-icons', // optional, you are not bound to it 41 | ], 42 | 43 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build 44 | build: { 45 | vueRouterMode: 'hash', // available values: 'hash', 'history' 46 | 47 | // transpile: false, 48 | 49 | // Add dependencies for transpiling with Babel (Array of string/regex) 50 | // (from node_modules, which are by default not transpiled). 51 | // Applies only if "transpile" is set to true. 52 | // transpileDependencies: [], 53 | 54 | // rtl: true, // https://v2.quasar.dev/options/rtl-support 55 | // preloadChunks: true, 56 | showProgress: false, 57 | gzip: true, 58 | // analyze: true, 59 | 60 | // Options below are automatically set depending on the env, set them if you want to override 61 | // extractCSS: false, 62 | 63 | // https://v2.quasar.dev/quasar-cli/handling-webpack 64 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 65 | chainWebpack(/* chain */) { 66 | // 67 | }, 68 | }, 69 | 70 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer 71 | devServer: { 72 | https: false, 73 | port: 8080, 74 | open: true, // opens browser window automatically 75 | }, 76 | 77 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework 78 | framework: { 79 | config: { 80 | notify: { 81 | /* look at QuasarConfOptions from the API card */ 82 | }, 83 | }, 84 | 85 | // iconSet: 'material-icons', // Quasar icon set 86 | // lang: 'en-US', // Quasar language pack 87 | 88 | // For special cases outside of where the auto-import stategy can have an impact 89 | // (like functional components as one of the examples), 90 | // you can manually specify Quasar components/directives to be available everywhere: 91 | // 92 | // components: [], 93 | // directives: [], 94 | 95 | // Quasar plugins 96 | plugins: ['Notify'], 97 | }, 98 | 99 | // animations: 'all', // --- includes all animations 100 | // https://v2.quasar.dev/options/animations 101 | animations: [], 102 | 103 | // https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr 104 | ssr: { 105 | pwa: false, 106 | 107 | // manualStoreHydration: true, 108 | // manualPostHydrationTrigger: true, 109 | 110 | prodPort: 3000, // The default port that the production server should use 111 | // (gets superseded if process.env.PORT is specified at runtime) 112 | 113 | maxAge: 1000 * 60 * 60 * 24 * 30, 114 | // Tell browser when a file from the server should expire from cache (in ms) 115 | 116 | chainWebpackWebserver(/* chain */) { 117 | // 118 | }, 119 | 120 | middlewares: [ 121 | ctx.prod ? 'compression' : '', 122 | 'render', // keep this as last one 123 | ], 124 | }, 125 | 126 | // https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa 127 | pwa: { 128 | workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' 129 | workboxOptions: {}, // only for GenerateSW 130 | 131 | // for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts]) 132 | // if using workbox in InjectManifest mode 133 | chainWebpackCustomSW(/* chain */) { 134 | // 135 | }, 136 | 137 | manifest: { 138 | name: 'gbf-counter', 139 | short_name: 'gbf-counter', 140 | description: 'GBF counter', 141 | display: 'standalone', 142 | orientation: 'portrait', 143 | background_color: '#ffffff', 144 | theme_color: '#027be3', 145 | icons: [ 146 | { 147 | src: 'icons/icon-128x128.png', 148 | sizes: '128x128', 149 | type: 'image/png', 150 | }, 151 | { 152 | src: 'icons/icon-192x192.png', 153 | sizes: '192x192', 154 | type: 'image/png', 155 | }, 156 | { 157 | src: 'icons/icon-256x256.png', 158 | sizes: '256x256', 159 | type: 'image/png', 160 | }, 161 | { 162 | src: 'icons/icon-384x384.png', 163 | sizes: '384x384', 164 | type: 'image/png', 165 | }, 166 | { 167 | src: 'icons/icon-512x512.png', 168 | sizes: '512x512', 169 | type: 'image/png', 170 | }, 171 | ], 172 | }, 173 | }, 174 | 175 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova 176 | cordova: { 177 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 178 | }, 179 | 180 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor 181 | capacitor: { 182 | hideSplashscreen: true, 183 | }, 184 | 185 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron 186 | electron: { 187 | bundler: 'builder', // 'packager' or 'builder' 188 | 189 | packager: { 190 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 191 | // OS X / Mac App Store 192 | // appBundleId: '', 193 | // appCategoryType: '', 194 | // osxSign: '', 195 | // protocol: 'myapp://path', 196 | // Windows only 197 | // win32metadata: { ... } 198 | }, 199 | 200 | builder: { 201 | // https://www.electron.build/configuration/configuration 202 | appId: 'gbf-counter', 203 | win: { 204 | target: ['zip'], 205 | }, 206 | electronDownload: { 207 | mirror: 'https://npm.taobao.org/mirrors/electron/', 208 | }, 209 | }, 210 | 211 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 212 | chainWebpack(/* chain */) { 213 | // do something with the Electron main process Webpack cfg 214 | // extendWebpackMain also available besides this chainWebpackMain 215 | }, 216 | 217 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 218 | chainWebpackPreload(/* chain */) { 219 | // do something with the Electron main process Webpack cfg 220 | // extendWebpackPreload also available besides this chainWebpackPreload 221 | }, 222 | }, 223 | })); 224 | -------------------------------------------------------------------------------- /src/pages/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 114 | 396 | 397 | 462 | -------------------------------------------------------------------------------- /src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1554 | 1555 | --------------------------------------------------------------------------------