├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── babel.config.js ├── biliob_frontend.enc ├── deploy.sh ├── package.json ├── postcss.config.js ├── public ├── docs │ ├── Gmail - 【哔哩哔哩侵权告知函】关于立即停止搜集哔哩哔哩数据的函-BiliOB观测者-20201110.pdf │ ├── faq.json │ └── log.json ├── favicon.ico ├── img │ ├── 06-01.gif │ ├── 06-01.png │ ├── 06-app-bar.gif │ ├── aside-bright.png │ ├── aside-dark.png │ ├── biliob_qr.png │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── msapplication-icon-144x144.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg │ └── pendent │ │ ├── 与天同行的观测者.png │ │ ├── 传说级涨粉.png │ │ ├── 史诗级涨粉.png │ │ ├── 大量掉粉.png │ │ ├── 大量涨粉.png │ │ ├── 急转直下.png │ │ ├── 新星爆发.png │ │ ├── 末日级掉粉.png │ │ ├── 本心不渝的追寻者.png │ │ ├── 洞悉法度的观想者.png │ │ ├── 观测站的管理者.png │ │ └── 雪崩级掉粉.png ├── index.html ├── manifest.json ├── robots.txt └── test.html ├── remote.sh ├── src ├── App.vue ├── background.js ├── charts │ ├── author-channel.js │ ├── author-data-diff.js │ ├── author-fans-efficiency.js │ ├── author-fans-rate.js │ ├── author-fans-versus.js │ ├── author-fans.js │ ├── author-tag-cloud.js │ ├── biliob-candlestick.js │ ├── biliob-compare.js │ ├── biliob-line-chart.js │ ├── biliob-multi-line-chart.js │ ├── cloud-charts.js │ ├── danmaku-cloud.js │ ├── danmaku-density.js │ ├── data-set-chart.js │ ├── data-with-ma.js │ ├── get-card-chart.js │ ├── graph-charts.js │ ├── online.js │ ├── site_play.js │ ├── theme │ │ ├── card.json │ │ ├── colorful.json │ │ └── mydark.json │ ├── util │ │ ├── convertDateToUTC.js │ │ ├── format.js │ │ └── interpolation.js │ ├── video-main.js │ └── video-pie.js ├── components │ ├── ChiefRecommend.vue │ ├── DetailMain.vue │ ├── Home │ │ └── PopularTag.vue │ ├── Recommend.vue │ ├── TheHomeCarousel.vue │ ├── Tracer │ │ ├── Dashboard.vue │ │ ├── NoRole.vue │ │ ├── Upload.vue │ │ ├── User.vue │ │ ├── layout │ │ │ ├── Footer.vue │ │ │ ├── Nav.vue │ │ │ ├── Toolbar.vue │ │ │ └── View.vue │ │ ├── nav │ │ │ └── item.vue │ │ └── video.vue │ ├── admin │ │ ├── CrawlCard.vue │ │ ├── GraphCard.vue │ │ ├── ProgressCard.vue │ │ └── StatsCard.vue │ ├── aside │ │ └── AuthorInfo.vue │ ├── author │ │ └── BottomSheet.vue │ ├── biliob │ │ ├── ActiveCodeTextField.vue │ │ ├── Card.vue │ │ ├── Comment.vue │ │ ├── DarkInfo.vue │ │ ├── EventSlideCard.vue │ │ ├── FAB.vue │ │ ├── FirstLoadDialog.vue │ │ ├── InfoDialog.vue │ │ ├── Kanban.vue │ │ ├── Main.vue │ │ ├── Notice.vue │ │ ├── Notification.vue │ │ ├── OperationBtn.vue │ │ ├── Sheet.vue │ │ ├── SiteChart.vue │ │ ├── Slide.vue │ │ ├── SlideCard.vue │ │ ├── Sponsor.vue │ │ ├── Textarea.vue │ │ ├── Title.vue │ │ ├── VersusCard.vue │ │ ├── agenda │ │ │ ├── FormCard.vue │ │ │ ├── StateChip.vue │ │ │ └── TypeChip.vue │ │ ├── author │ │ │ ├── AchievementSlideCard.vue │ │ │ ├── Achievements.vue │ │ │ ├── Brief.vue │ │ │ ├── GroupInfoCard.vue │ │ │ ├── GroupInfoForm.vue │ │ │ ├── InfoCard.vue │ │ │ ├── ListItem.vue │ │ │ ├── Operation.vue │ │ │ └── Rank.vue │ │ ├── bangumi │ │ │ ├── Copyright.vue │ │ │ ├── Info.vue │ │ │ ├── Status.vue │ │ │ └── type.vue │ │ ├── comment │ │ │ ├── Input.vue │ │ │ └── Item.vue │ │ ├── compare │ │ │ └── AuthorInfo.vue │ │ ├── end.vue │ │ ├── guessing │ │ │ └── Item.vue │ │ ├── user │ │ │ ├── Info.vue │ │ │ └── rank │ │ │ │ └── Title.vue │ │ └── video │ │ │ ├── Operation.vue │ │ │ └── Rank.vue │ ├── common │ │ ├── BiliobFooter.vue │ │ ├── BottomNav.vue │ │ ├── BottomSheetTile.vue │ │ ├── ChevronBudget.vue │ │ ├── CircleIconBtn.vue │ │ ├── CreditBadget.vue │ │ ├── ExpBadget.vue │ │ ├── FavoriteBtn.vue │ │ ├── FocusBtn.vue │ │ ├── LevelIcon.vue │ │ ├── MyBadget.vue │ │ ├── NextBtn.vue │ │ ├── ObserveStatus.vue │ │ ├── RoleBadget.vue │ │ ├── SexIcon.vue │ │ ├── UserListItem.vue │ │ ├── VOdometer.vue │ │ ├── VSearchForm.vue │ │ └── VideoBottomSheet.vue │ ├── helper │ │ └── Offset.vue │ ├── index.js │ ├── layout │ │ ├── Master.vue │ │ └── master │ │ │ └── Footer.vue │ ├── main │ │ ├── AuthorDetailChannel.vue │ │ ├── AuthorList.vue │ │ ├── AuthorMain.vue │ │ ├── AuthorVersusCard.vue │ │ ├── DetailCharts.vue │ │ ├── FavoriteAuthorList.vue │ │ ├── FavoriteVideoList.vue │ │ ├── SiteChart.vue │ │ ├── UserRecordCard.vue │ │ ├── VideoDetailMainChart.vue │ │ ├── VideoDetailPieChart.vue │ │ ├── VideoDetailTitle.vue │ │ ├── VideoList.vue │ │ └── VideoMain.vue │ ├── material │ │ ├── Card.vue │ │ ├── ChartCard.vue │ │ ├── Notification.vue │ │ ├── SpiderCard.vue │ │ └── StatsCard.vue │ └── video │ │ └── RecommendList.vue ├── data │ └── index.js ├── echarts.js ├── main.js ├── plugins │ ├── theme.js │ └── vuetify.js ├── registerServiceWorker.js ├── router.js ├── sass │ └── variables.scss ├── service-worker.js ├── store.js ├── store │ ├── admin.js │ ├── author.js │ ├── event.js │ ├── rank.js │ └── site.js ├── styles │ ├── biliob │ │ └── dark-info.scss │ ├── index.scss │ └── material-dashboard │ │ ├── _alerts.scss │ │ ├── _buttons.scss │ │ ├── _cards.scss │ │ ├── _checkboxes.scss │ │ ├── _colors.scss │ │ ├── _dropdown.scss │ │ ├── _fixed-plugin.scss │ │ ├── _footer.scss │ │ ├── _inputs.scss │ │ ├── _misc.scss │ │ ├── _mixins.scss │ │ ├── _sidebar.scss │ │ ├── _tables.scss │ │ ├── _tabs.scss │ │ ├── _toolbar.scss │ │ ├── _tooltips.scss │ │ ├── _typography.scss │ │ └── _variables.scss ├── util │ ├── add-event.js │ ├── bvidDecode.js │ ├── bvidEncode.js │ ├── format-number.js │ ├── is-mobile.js │ └── scroll-animation.js └── views │ ├── Android.vue │ ├── HomeSite.vue │ ├── NotFound.vue │ ├── Test.vue │ ├── VersusPage.vue │ ├── Video.vue │ ├── admin │ ├── Index.vue │ ├── Layout.vue │ ├── Schedule.vue │ └── Spider.vue │ └── master │ ├── About.vue │ ├── Author.vue │ ├── AuthorList.vue │ ├── AuthorVersus.vue │ ├── Event.vue │ ├── FAQ.vue │ ├── Index.vue │ ├── Log.vue │ ├── Login.vue │ ├── Privacy.vue │ ├── Rank.vue │ ├── Signin.vue │ ├── User.vue │ ├── UserRank.vue │ ├── Video.vue │ ├── VideoList.vue │ ├── agenda │ └── Index.vue │ ├── author │ └── group │ │ ├── Detail.vue │ │ ├── Index.vue │ │ └── Manage.vue │ ├── bangumi │ ├── BangumiDetail.vue │ └── BangumiList.vue │ ├── download │ └── App.vue │ ├── index │ ├── Index.vue │ └── Keyword.vue │ ├── rank │ └── Up.vue │ ├── tools │ ├── DecodeBV.vue │ ├── EncodeAV.vue │ ├── Index.vue │ └── VideoDownload.vue │ ├── user │ ├── AuthorGroup.vue │ ├── AuthorGroupList.vue │ ├── FavoriteAuthorList.vue │ ├── FavoriteVideoList.vue │ ├── Info.vue │ ├── Password.vue │ └── Record.vue │ └── video │ └── Index.vue ├── static ├── .gitkeep └── app │ └── android.json ├── tests ├── test.js └── unit │ ├── .eslintrc.js │ └── example.spec.js ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | env: { 5 | node: true 6 | }, 7 | 8 | rules: { 9 | "no-console": "off", 10 | "no-debugger": "off", 11 | "vue/component-name-in-template-casing": [ 12 | "warn", 13 | "PascalCase", 14 | { 15 | registeredComponentsOnly: false, 16 | ignores: [] 17 | } 18 | ] 19 | }, 20 | 21 | parserOptions: { 22 | parser: "babel-eslint" 23 | }, 24 | 25 | extends: ["plugin:vue/recommended"] 26 | }; 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | 23 | #Electron-builder output 24 | /dist_electron -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: 5 | directories: 6 | - node_modules 7 | install: 8 | - yarn install 9 | script: 10 | - yarn build 11 | branches: 12 | only: 13 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jannchie 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 | [![Build Status](https://www.travis-ci.org/Jannchie/biliob-frontend.svg?branch=master)](https://www.travis-ci.org/Jannchie/biliob-frontend) 2 | # biliob-frontend 3 | 4 | ## Project setup 5 | ``` 6 | npm install 7 | ``` 8 | 9 | ### Compiles and hot-reloads for development 10 | ``` 11 | npm run serve 12 | ``` 13 | 14 | ### Compiles and minifies for production 15 | ``` 16 | npm run build 17 | ``` 18 | 19 | ### Run your tests 20 | ``` 21 | npm run test 22 | ``` 23 | 24 | ### Lints and fixes files 25 | ``` 26 | npm run lint 27 | ``` 28 | 29 | ### Run your unit tests 30 | ``` 31 | npm run test:unit 32 | ``` 33 | 34 | ### Customize configuration 35 | See [Configuration Reference](https://cli.vuejs.org/config/). 36 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /biliob_frontend.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/biliob_frontend.enc -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | source /etc/profile 2 | ssh -i ~/.ssh/login_rsa $M0_SERVER < remote.sh -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biliob", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "deploy": "bash deploy.sh", 10 | "electron:build": "vue-cli-service electron:build", 11 | "electron:serve": "vue-cli-service electron:serve", 12 | "postinstall": "electron-builder install-app-deps", 13 | "postuninstall": "electron-builder install-app-deps", 14 | "test:unit": "vue-cli-service test:unit" 15 | }, 16 | "main": "background.js", 17 | "dependencies": { 18 | "axios": "^0.19.0", 19 | "chai-as-promised": "^7.1.1", 20 | "date-fns": "^1.30.1", 21 | "echarts": "^4.9.0", 22 | "odometer": "^0.4.8", 23 | "register-service-worker": "^1.7.1", 24 | "vue": "^2.6.12", 25 | "vue-axios": "^2.1.4", 26 | "vue-cookies": "^1.5.12", 27 | "vue-echarts": "^4.0.3", 28 | "vue-emoji-picker": "^1.0.1", 29 | "vue-meta": "^2.3.1", 30 | "vue-router": "^3.0.1", 31 | "vuetify": "^2.3.13", 32 | "vuex": "^3.0.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/preset-env": "^7.5.5", 36 | "@fortawesome/fontawesome-free": "^5.8.1", 37 | "@mdi/font": "^3.3.92", 38 | "@vue/cli": "^4.5.7", 39 | "@vue/cli-plugin-babel": "~4.5.0", 40 | "@vue/cli-plugin-eslint": "~4.5.0", 41 | "@vue/cli-plugin-pwa": "~4.5.0", 42 | "@vue/cli-plugin-unit-mocha": "^3.3.0", 43 | "@vue/cli-service": "~4.5.0", 44 | "@vue/eslint-config-prettier": "^5.0.0", 45 | "@vue/test-utils": "^1.0.0-beta.28", 46 | "babel-eslint": "^10.0.1", 47 | "babel-polyfill": "^6.26.0", 48 | "chai": "^4.1.2", 49 | "compression-webpack-plugin": "^3.0.0", 50 | "deepmerge": "^4.2.2", 51 | "echarts-wordcloud": "^1.1.3", 52 | "electron": "^10.1.1", 53 | "electron-devtools-installer": "^3.1.0", 54 | "eslint": "^6.1.0", 55 | "eslint-plugin-prettier": "^3.1.0", 56 | "eslint-plugin-vue": "^5.1.0", 57 | "fibers": "^5.0.0", 58 | "lint-staged": "^9.2.0", 59 | "material-design-icons-iconfont": "^6.0.1", 60 | "node-sass": "^4.11.0", 61 | "sass": "^1.27.0", 62 | "sass-loader": "^10.0.2", 63 | "stylus": "^0.54.5", 64 | "stylus-loader": "^3.0.2", 65 | "vue-cli-plugin-electron-builder": "^2.0.0-rc.4", 66 | "vue-cli-plugin-vuetify": "~2.0.7", 67 | "vue-template-compiler": "^2.6.12", 68 | "vuetify-loader": "^1.6.0", 69 | "webpack-dev-server": ">=3.1.11" 70 | }, 71 | "gitHooks": { 72 | "pre-commit": "lint-staged" 73 | }, 74 | "lint-staged": { 75 | "*.js": [ 76 | "vue-cli-service lint", 77 | "git add" 78 | ], 79 | "*.vue": [ 80 | "vue-cli-service lint", 81 | "git add" 82 | ] 83 | } 84 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/docs/Gmail - 【哔哩哔哩侵权告知函】关于立即停止搜集哔哩哔哩数据的函-BiliOB观测者-20201110.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/docs/Gmail - 【哔哩哔哩侵权告知函】关于立即停止搜集哔哩哔哩数据的函-BiliOB观测者-20201110.pdf -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/favicon.ico -------------------------------------------------------------------------------- /public/img/06-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/06-01.gif -------------------------------------------------------------------------------- /public/img/06-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/06-01.png -------------------------------------------------------------------------------- /public/img/06-app-bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/06-app-bar.gif -------------------------------------------------------------------------------- /public/img/aside-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/aside-bright.png -------------------------------------------------------------------------------- /public/img/aside-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/aside-dark.png -------------------------------------------------------------------------------- /public/img/biliob_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/biliob_qr.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/pendent/与天同行的观测者.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/与天同行的观测者.png -------------------------------------------------------------------------------- /public/img/pendent/传说级涨粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/传说级涨粉.png -------------------------------------------------------------------------------- /public/img/pendent/史诗级涨粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/史诗级涨粉.png -------------------------------------------------------------------------------- /public/img/pendent/大量掉粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/大量掉粉.png -------------------------------------------------------------------------------- /public/img/pendent/大量涨粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/大量涨粉.png -------------------------------------------------------------------------------- /public/img/pendent/急转直下.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/急转直下.png -------------------------------------------------------------------------------- /public/img/pendent/新星爆发.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/新星爆发.png -------------------------------------------------------------------------------- /public/img/pendent/末日级掉粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/末日级掉粉.png -------------------------------------------------------------------------------- /public/img/pendent/本心不渝的追寻者.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/本心不渝的追寻者.png -------------------------------------------------------------------------------- /public/img/pendent/洞悉法度的观想者.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/洞悉法度的观想者.png -------------------------------------------------------------------------------- /public/img/pendent/观测站的管理者.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/观测站的管理者.png -------------------------------------------------------------------------------- /public/img/pendent/雪崩级掉粉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/public/img/pendent/雪崩级掉粉.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BiliOB观测者", 3 | "short_name": "BiliOB", 4 | "icons": [ 5 | { 6 | "src": "./img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "./", 17 | "display": "standalone", 18 | "background_color": "#333", 19 | "theme_color": "#333" 20 | } 21 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /remote.sh: -------------------------------------------------------------------------------- 1 | cd ~/biliob-frontend; 2 | git checkout biliob 3 | git pull; 4 | yarn install; 5 | yarn build; 6 | source /etc/profile 7 | scp -i ~/.ssh/login_rsa -r dist/* $BILIOB_NGINX_PATH; -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 38 | 47 | -------------------------------------------------------------------------------- /src/charts/author-channel.js: -------------------------------------------------------------------------------- 1 | function drawChart(data) { 2 | var max = 0; 3 | var indicator = []; 4 | var value = []; 5 | data.forEach(e => { 6 | value.push(e.count); 7 | if (e.count > max) { 8 | max = e.count; 9 | } 10 | }); 11 | data.forEach(e => { 12 | indicator.push({ 13 | name: e.name, 14 | max: max 15 | }); 16 | }); 17 | 18 | let option = { 19 | title: { 20 | text: "投稿分区分布" 21 | }, 22 | tooltip: { 23 | confine: true 24 | }, 25 | legend: { 26 | data: ["投稿分布"] 27 | }, 28 | radar: { 29 | // shape: 'circle', 30 | name: { 31 | textStyle: { 32 | borderRadius: 3, 33 | padding: [3, 5] 34 | } 35 | }, 36 | indicator: indicator 37 | }, 38 | series: [ 39 | { 40 | name: "", 41 | type: "radar", 42 | // areaStyle: {normal: {}}, 43 | data: [ 44 | { 45 | value: value, 46 | name: "投稿分配" 47 | } 48 | ] 49 | } 50 | ] 51 | }; 52 | return option; 53 | } 54 | export default drawChart; 55 | -------------------------------------------------------------------------------- /src/charts/author-data-diff.js: -------------------------------------------------------------------------------- 1 | import interpolation from "./util/interpolation"; 2 | import formatNumber from "@/util/format-number"; 3 | function drawChart(data) { 4 | data.forEach((e) => { 5 | e[0] = interpolation(e[0]); 6 | }); 7 | let series = data.map((e, i) => { 8 | return { 9 | name: e[1] + "变化", 10 | color: e[2], 11 | type: "heatmap", 12 | coordinateSystem: "calendar", 13 | calendarIndex: i, 14 | data: e[0], 15 | tooltip: { 16 | formatter: (params) => { 17 | return `${params.seriesName}
18 | 日期:${params.value[0]}
19 | 数值:${formatNumber(params.value[1])}`; 20 | }, 21 | }, 22 | }; 23 | }); 24 | 25 | let week = new Date().getDay(); 26 | let now = new Date(); 27 | let front = new Date(now - 86400 * 366 * 1000 - (6 - week) * 86400 * 1000); 28 | if (document.body.offsetWidth < 768) { 29 | front = new Date(now - 86400 * 58 * 1000 - (6 - week) * 86400 * 1000); 30 | } 31 | let cals = data.map(() => { 32 | now = new Date(); 33 | return { 34 | type: "continuous", 35 | right: "35", 36 | splitLine: { 37 | show: false, 38 | }, 39 | itemStyle: { 40 | color: "#fff", 41 | borderWidth: 4, 42 | borderColor: "#fff", 43 | }, 44 | cellSize: [18, 18], 45 | range: [front, now], 46 | yearLabel: { show: false }, 47 | dayLabel: { nameMap: "cn", position: "end", color: "#888", fontSize: 10 }, 48 | monthLabel: { nameMap: "cn", color: "#888", fontSize: 10 }, 49 | }; 50 | }); 51 | let vms = data.map((e, i) => { 52 | let max = Math.max.apply( 53 | Math, 54 | e[0].map((item) => { 55 | if (new Date(item[0]) < front) { 56 | return 0; 57 | } 58 | return item[1]; 59 | }) 60 | ); 61 | let min = Math.min.apply( 62 | Math, 63 | e[0].map((item) => { 64 | if (new Date(item[0]) < front) { 65 | return 0; 66 | } 67 | return item[1]; 68 | }) 69 | ); 70 | return { 71 | min: min, 72 | max: max, 73 | range: [0, max], 74 | inRange: { 75 | color: [e[2]], 76 | opacity: [0.2, 1], 77 | }, 78 | outOfRange: { 79 | color: ["#333"], 80 | opacity: [0.8, 0.2], 81 | }, 82 | orient: "horizontal", 83 | seriesIndex: i, 84 | left: "center", 85 | show: false, 86 | textStyle: { 87 | color: "#000", 88 | }, 89 | }; 90 | }); 91 | let option = { 92 | legend: { 93 | selectedMode: "single", 94 | }, 95 | tooltip: {}, 96 | visualMap: vms, 97 | calendar: cals, 98 | series: series, 99 | }; 100 | 101 | return option; 102 | } 103 | export default drawChart; 104 | -------------------------------------------------------------------------------- /src/charts/author-tag-cloud.js: -------------------------------------------------------------------------------- 1 | function drawChart(data, type = 0) { 2 | var d = []; 3 | if (type == 0) { 4 | for (let e in data) { 5 | d.push({ name: data[e]._id, value: data[e].count }); 6 | } 7 | } else if (type == 1) { 8 | for (let e in data) { 9 | d.push({ name: data[e]._id, value: data[e].totalView }); 10 | } 11 | } 12 | 13 | var maxSize = 72; 14 | 15 | let options = { 16 | tooltip: { 17 | trigger: "item", 18 | confine: true, 19 | formatter: "{a}
{b}" 20 | }, 21 | series: [ 22 | { 23 | name: "关键词", 24 | type: "wordCloud", 25 | 26 | // 使用矩形 27 | shape: function shapeSquare(theta) { 28 | return Math.min( 29 | 1 / Math.abs(Math.cos(theta)), 30 | 1 / Math.abs(Math.sin(theta)) 31 | ); 32 | }, 33 | 34 | left: "center", 35 | top: "center", 36 | width: "90%", 37 | height: "90%", 38 | right: null, 39 | bottom: null, 40 | sizeRange: [24, maxSize], 41 | rotationRange: [0, 0], 42 | rotationStep: 45, 43 | gridSize: 8, 44 | drawOutOfBound: false, 45 | 46 | textStyle: { 47 | normal: { 48 | fontWeight: "bold" 49 | } 50 | }, 51 | 52 | data: d 53 | } 54 | ] 55 | }; 56 | 57 | return options; 58 | } 59 | export default drawChart; 60 | -------------------------------------------------------------------------------- /src/charts/biliob-candlestick.js: -------------------------------------------------------------------------------- 1 | import formatNumber from "../util/format-number"; 2 | function calculateMA(dayCount, data) { 3 | var result = []; 4 | for (var i = 0, len = data.length; i < len; i++) { 5 | if (i < dayCount) { 6 | result.push("-"); 7 | continue; 8 | } 9 | var sum = 0; 10 | for (var j = 0; j < dayCount; j++) { 11 | sum += data[i - j][1]; 12 | } 13 | result.push(+(sum / dayCount).toFixed(3)); 14 | } 15 | return result; 16 | } 17 | function getOption(data) { 18 | let date = []; 19 | let value = []; 20 | data.sort((a, b) => { 21 | return new Date(a.date) - new Date(b.date); 22 | }); 23 | data.forEach(e => { 24 | if (e.min == 2147483647) { 25 | return; 26 | } 27 | date.push(e.date); 28 | value.push([e.first, e.last, e.min, e.max]); 29 | }); 30 | 31 | let option = { 32 | xAxis: { 33 | data: date 34 | }, 35 | grid: { 36 | left: "10px", 37 | right: "50px", 38 | top: "30px", 39 | bottom: "30px", 40 | containLabel: true 41 | }, 42 | yAxis: { 43 | min: "dataMin", 44 | max: "dataMax", 45 | axisLabel: { 46 | formatter: formatNumber 47 | } 48 | }, 49 | tooltip: {}, 50 | series: [ 51 | { 52 | type: "candlestick", 53 | data: value, 54 | itemStyle: { 55 | color: "#c12e34", 56 | color0: "#2b821d", 57 | borderColor: "#c12e34", 58 | borderColor0: "#2b821d" 59 | }, 60 | tooltip: { 61 | formatter: params => { 62 | return `${params.name}
首天最值: 63 | ${formatNumber(params.value[1])} 64 |
末天最值: 65 | ${formatNumber(params.value[2])} 66 |
周最小值: 67 | ${formatNumber(params.value[3])} 68 |
周最大值: 69 | ${formatNumber(params.value[4])}
`; 70 | } 71 | } 72 | }, 73 | { 74 | name: "4周均线", 75 | type: "line", 76 | data: calculateMA(4, value), 77 | smooth: true, 78 | lineStyle: { 79 | opacity: 0.5 80 | }, 81 | showSymbol: false 82 | }, 83 | { 84 | name: "12周均线", 85 | type: "line", 86 | data: calculateMA(12, value), 87 | smooth: true, 88 | lineStyle: { 89 | opacity: 0.5 90 | }, 91 | showSymbol: false 92 | } 93 | ] 94 | }; 95 | 96 | return option; 97 | } 98 | export default getOption; 99 | -------------------------------------------------------------------------------- /src/charts/biliob-compare.js: -------------------------------------------------------------------------------- 1 | import formatNumber from "../util/format-number"; 2 | import fmt from "date-fns/format"; 3 | function drawChart( 4 | authors, 5 | type = "line", 6 | format = "YYYY-MM-DD HH:mm", 7 | key = "fans" 8 | ) { 9 | if (authors.length == 0) return undefined; 10 | let series = authors.map(author => { 11 | let localOffset = new Date().getTimezoneOffset() / 60; 12 | let offset = -8 - localOffset; 13 | let date; 14 | let data = author.data.map(d => { 15 | if (typeof d.datetime == "string") { 16 | date = new Date(d.datetime.replace(new RegExp(/-/gm), "/")); 17 | date.setHours(date.getHours() + offset); 18 | } else { 19 | date = d.datetime; 20 | } 21 | return [date, d[key]]; 22 | }); 23 | 24 | let result = { 25 | name: author.name, 26 | data: data, 27 | smooth: false, 28 | type: "line", 29 | showSymbol: false 30 | }; 31 | return result; 32 | }); 33 | 34 | let Chart = { 35 | grid: { 36 | left: "10px", 37 | right: "50px", 38 | top: "30px", 39 | containLabel: true 40 | }, 41 | dataZoom: [ 42 | { 43 | type: "inside", 44 | filterMode: "filter" 45 | }, 46 | { 47 | handleSize: "100%", 48 | handleStyle: {}, 49 | bottom: "20px" 50 | } 51 | ], 52 | tooltip: { 53 | trigger: "axis", 54 | confine: true, 55 | axisPointer: { 56 | label: { 57 | formatter: function (params) { 58 | return fmt(params.value, format); 59 | } 60 | } 61 | } 62 | }, 63 | xAxis: { 64 | type: "time", 65 | data: authors[0].data.map(e => e.datetime), 66 | axisPointer: { 67 | label: { 68 | formatter: function (params) { 69 | return "日期:" + fmt(params.value, format); 70 | } 71 | } 72 | } 73 | }, 74 | yAxis: [ 75 | { 76 | type: "value", 77 | min: function (value) { 78 | return value.min > 0 && type == "area" ? 0 : value.min; 79 | }, 80 | axisLabel: { 81 | formatter: formatNumber 82 | } 83 | } 84 | ], 85 | series: series.filter(s => { 86 | return s.data.length == 0 ? false : true; 87 | }) 88 | }; 89 | 90 | return Chart; 91 | } 92 | export default drawChart; 93 | -------------------------------------------------------------------------------- /src/charts/biliob-line-chart.js: -------------------------------------------------------------------------------- 1 | var format = require("date-fns/format"); 2 | function drawChart(data, name, color = "#1e88e5") { 3 | let Chart = { 4 | grid: { 5 | left: "10px", 6 | right: "50px", 7 | top: "10px", 8 | containLabel: true 9 | }, 10 | dataZoom: [ 11 | { 12 | type: "inside", 13 | filterMode: "weakFilter" 14 | }, 15 | { 16 | handleSize: "100%", 17 | handleStyle: {}, 18 | bottom: "20px" 19 | } 20 | ], 21 | tooltip: { 22 | trigger: "axis", 23 | confine: true, 24 | axisPointer: { 25 | label: { 26 | formatter: function(params) { 27 | return Math.round(params.value); 28 | } 29 | } 30 | } 31 | }, 32 | xAxis: { 33 | type: "time", 34 | axisPointer: { 35 | label: { 36 | formatter: function(params) { 37 | return "日期:" + format(params.value, "YYYY-MM-DD HH:mm"); 38 | } 39 | } 40 | } 41 | }, 42 | color: [color], 43 | yAxis: [ 44 | { 45 | type: "value", 46 | min: "dataMin", 47 | axisLabel: { 48 | formatter: function toThousands(num) { 49 | let postfix = ""; 50 | if (num > 100000000) { 51 | postfix = "亿"; 52 | num /= 100000000; 53 | } else if (num > 10000) { 54 | postfix = "万"; 55 | num /= 10000; 56 | } else { 57 | return num; 58 | } 59 | num = num.toFixed(2); 60 | let [int, dec] = num.split("."); 61 | let inter = ""; 62 | while (int != "") { 63 | inter = int.slice(-3) + "," + inter; 64 | int = int.slice(0, -3); 65 | } 66 | return `${inter.slice(0, -1)}.${dec}${postfix}`; 67 | } 68 | } 69 | } 70 | ], 71 | series: [ 72 | { 73 | name: name, 74 | data: data, 75 | smooth: true, 76 | showSymbol: false, 77 | type: "line" 78 | } 79 | ] 80 | }; 81 | return Chart; 82 | } 83 | export default drawChart; 84 | -------------------------------------------------------------------------------- /src/charts/biliob-multi-line-chart.js: -------------------------------------------------------------------------------- 1 | import formatNumber from "../util/format-number"; 2 | import fmt from "date-fns/format"; 3 | function drawChart(data, type = "line", format = "YYYY-MM-DD HH:mm") { 4 | if (data.length == 0) return undefined; 5 | let series = data.map(e => { 6 | let localOffset = new Date().getTimezoneOffset() / 60; 7 | let offset = -8 - localOffset; 8 | e[0] = e[0].map(item => { 9 | let date; 10 | if (typeof item[0] == "string") { 11 | date = new Date(item[0].replace(new RegExp(/-/gm), "/")); 12 | date.setHours(date.getHours() + offset); 13 | } else { 14 | date = item[0]; 15 | } 16 | return [date, item[1]]; 17 | }); 18 | let result = { 19 | name: e[1], 20 | data: e[0], 21 | smooth: false, 22 | showSymbol: false, 23 | color: e[2] 24 | }; 25 | if (type == "area") { 26 | result.type = "line"; 27 | result.name += "增长"; 28 | result.areaStyle = { 29 | color: e[2] + "33" 30 | }; 31 | } else { 32 | result.type = type; 33 | } 34 | return result; 35 | }); 36 | let Chart = { 37 | legend: { 38 | selectedMode: "single" 39 | }, 40 | grid: { 41 | left: "10px", 42 | right: "50px", 43 | top: "30px", 44 | containLabel: true 45 | }, 46 | dataZoom: [ 47 | { 48 | type: "inside", 49 | filterMode: "weakFilter" 50 | }, 51 | { 52 | handleSize: "100%", 53 | handleStyle: {}, 54 | bottom: "20px" 55 | } 56 | ], 57 | tooltip: { 58 | trigger: "axis", 59 | confine: true, 60 | axisPointer: { 61 | label: { 62 | formatter: function(params) { 63 | return fmt(params.value, format); 64 | } 65 | } 66 | } 67 | }, 68 | xAxis: { 69 | type: "time", 70 | axisPointer: { 71 | label: { 72 | formatter: function(params) { 73 | return "日期:" + fmt(params.value, format); 74 | } 75 | } 76 | } 77 | }, 78 | yAxis: [ 79 | { 80 | type: "value", 81 | min: function(value) { 82 | return value.min > 0 && type == "area" ? 0 : value.min; 83 | }, 84 | axisLabel: { 85 | formatter: formatNumber 86 | } 87 | } 88 | ], 89 | series: series.filter(s => { 90 | return s.data.length == 0 ? false : true; 91 | }) 92 | }; 93 | 94 | return Chart; 95 | } 96 | export default drawChart; 97 | -------------------------------------------------------------------------------- /src/charts/cloud-charts.js: -------------------------------------------------------------------------------- 1 | function drawChart(data) { 2 | let textSize = (window.innerWidth + 1000) / 50; 3 | data.forEach(e => { 4 | if (e.value < 0) { 5 | e.value = 0; 6 | } else { 7 | e.value = Math.pow(e.value, 0.3); 8 | } 9 | }); 10 | let max = Math.max.apply( 11 | null, 12 | data.map(e => e.value) 13 | ); 14 | let min = Math.min.apply( 15 | null, 16 | data.map(e => e.value) 17 | ); 18 | function scale(a, b) { 19 | return function(c, d) { 20 | let delta = b - a; 21 | return function(e) { 22 | let r = (e - a) / delta; 23 | let d2 = d - c; 24 | return r * d2 + c; 25 | }; 26 | }; 27 | } 28 | let f = scale(min, max)(0.2, 1); 29 | let options = { 30 | title: [{}], 31 | tooltip: { 32 | trigger: "item", 33 | confine: true, 34 | formatter: "{a}
{b}" 35 | }, 36 | series: [ 37 | { 38 | name: "关键词", 39 | type: "wordCloud", 40 | 41 | // 使用矩形 42 | shape: function shapeSquare(theta) { 43 | return Math.min( 44 | 1 / Math.abs(Math.cos(theta)), 45 | 1 / Math.abs(Math.sin(theta)) 46 | ); 47 | }, 48 | 49 | left: "center", 50 | top: "center", 51 | width: "90%", 52 | height: "90%", 53 | right: null, 54 | bottom: null, 55 | sizeRange: [textSize / 3, textSize], 56 | rotationRange: [0, 0], 57 | rotationStep: 45, 58 | gridSize: 4, 59 | drawOutOfBound: false, 60 | 61 | textStyle: { 62 | normal: { 63 | color: function(e) { 64 | // Random color 65 | return "rgba(" + [30, 136, 229, f(e.value)].join(",") + ")"; 66 | }, 67 | fontWeight: "bold" 68 | } 69 | }, 70 | 71 | data: data 72 | } 73 | ] 74 | }; 75 | return options; 76 | } 77 | export default drawChart; 78 | -------------------------------------------------------------------------------- /src/charts/danmaku-cloud.js: -------------------------------------------------------------------------------- 1 | function drawChart(data) { 2 | var d = []; 3 | var sum = 0; 4 | var count = 0; 5 | for (const key in data) { 6 | sum += data[key]; 7 | count += 1; 8 | d.push({ 9 | name: key, 10 | value: data[key] 11 | }); 12 | } 13 | let avg = sum / count; 14 | var maxSize = 64; 15 | if (avg <= 0.06) { 16 | maxSize = 92; 17 | } else if (avg <= 0.07) { 18 | maxSize = 88; 19 | } else if (avg <= 0.08) { 20 | maxSize = 84; 21 | } else if (avg <= 0.09) { 22 | maxSize = 80; 23 | } else if (avg <= 0.1) { 24 | maxSize = 76; 25 | } else if (avg <= 0.11) { 26 | maxSize = 72; 27 | } else if (avg <= 0.12) { 28 | maxSize = 68; 29 | } 30 | 31 | let options = { 32 | title: [{}], 33 | tooltip: { 34 | trigger: "item", 35 | confine: true, 36 | formatter: "{a}
{b}" 37 | }, 38 | series: [ 39 | { 40 | name: "关键词", 41 | type: "wordCloud", 42 | 43 | // 使用矩形 44 | shape: function shapeSquare(theta) { 45 | return Math.min( 46 | 1 / Math.abs(Math.cos(theta)), 47 | 1 / Math.abs(Math.sin(theta)) 48 | ); 49 | }, 50 | 51 | left: "center", 52 | top: "center", 53 | width: "90%", 54 | height: "90%", 55 | right: null, 56 | bottom: null, 57 | sizeRange: [16, maxSize], 58 | rotationRange: [0, 0], 59 | rotationStep: 45, 60 | gridSize: 8, 61 | drawOutOfBound: false, 62 | 63 | textStyle: { 64 | normal: { 65 | fontWeight: "bold" 66 | } 67 | }, 68 | 69 | data: d 70 | } 71 | ] 72 | }; 73 | return options; 74 | } 75 | export default drawChart; 76 | -------------------------------------------------------------------------------- /src/charts/danmaku-density.js: -------------------------------------------------------------------------------- 1 | function formatSeconds(value) { 2 | var secondTime = parseInt(value); // 秒 3 | var minuteTime = 0; // 分 4 | var hourTime = 0; // 小时 5 | if (secondTime > 60) { 6 | minuteTime = parseInt(secondTime / 60); 7 | secondTime = parseInt(secondTime % 60); 8 | if (minuteTime > 60) { 9 | hourTime = parseInt(minuteTime / 60); 10 | minuteTime = parseInt(minuteTime % 60); 11 | } 12 | } 13 | 14 | function addZero(val) { 15 | let r; 16 | val < 10 ? (r = "0" + String(val)) : (r = String(val)); 17 | return r; 18 | } 19 | 20 | var result = "" + addZero(secondTime) + ""; 21 | 22 | if (minuteTime >= 0) { 23 | result = "" + addZero(minuteTime) + ":" + result; 24 | } 25 | if (hourTime > 0) { 26 | result = "" + addZero(hourTime) + "" + result; 27 | } 28 | return result; 29 | } 30 | 31 | function drawChart(data, duration) { 32 | var tick = duration / 50; 33 | var x = []; 34 | for (let i = 0; i < 50; i++) { 35 | x.push(tick * i); 36 | } 37 | let Chart = { 38 | title: { 39 | left: "center", 40 | subtext: "弹幕密度变化趋势" 41 | // text: data.name 42 | }, 43 | legend: { 44 | data: ["粉丝增量"], 45 | bottom: "5px" 46 | }, 47 | tooltip: { 48 | trigger: "axis", 49 | confine: true 50 | }, 51 | grid: { 52 | left: "10px", 53 | right: "50px", 54 | bottom: "10px", 55 | top: "10px", 56 | containLabel: true 57 | }, 58 | xAxis: { 59 | type: "category", 60 | data: x, 61 | axisLabel: { 62 | formatter: v => formatSeconds(v) 63 | }, 64 | axisPointer: { 65 | label: { 66 | formatter: function(params) { 67 | return ( 68 | "时间:" + 69 | formatSeconds(params.value) + 70 | "~" + 71 | formatSeconds(Number(params.value) + tick) 72 | ); 73 | } 74 | } 75 | } 76 | }, 77 | yAxis: [ 78 | { 79 | type: "value", 80 | min: function(value) { 81 | if (value.min > 0) { 82 | return 0; 83 | } else { 84 | return value.min; 85 | } 86 | }, 87 | splitLine: { 88 | show: true 89 | } 90 | } 91 | ], 92 | series: [ 93 | { 94 | name: "弹幕数", 95 | data: data, 96 | smooth: true, 97 | showSymbol: false, 98 | type: "line", 99 | areaStyle: {} 100 | } 101 | ] 102 | }; 103 | return Chart; 104 | } 105 | export default drawChart; 106 | -------------------------------------------------------------------------------- /src/charts/data-set-chart.js: -------------------------------------------------------------------------------- 1 | import fmt from "date-fns/format"; 2 | 3 | export default function getOpions(data, dateKey, fieldDict) { 4 | return { 5 | legend: { 6 | selectedMode: "single", 7 | }, 8 | tooltip: { 9 | show: true, 10 | axisPointer: { type: "cross" }, 11 | trigger: "axis", 12 | }, 13 | grid: { 14 | left: "10px", 15 | right: "50px", 16 | top: "30px", 17 | containLabel: true, 18 | }, 19 | dataZoom: [ 20 | { 21 | type: "inside", 22 | filterMode: "weakFilter", 23 | }, 24 | { 25 | handleSize: "100%", 26 | handleStyle: {}, 27 | bottom: "20px", 28 | }, 29 | ], 30 | xAxis: { 31 | type: "time", 32 | axisPointer: { 33 | label: { 34 | formatter: (d) => fmt(d.value, "YYYY-MM-DD HH:mm"), 35 | }, 36 | }, 37 | }, 38 | yAxis: { 39 | min: "dataMin", 40 | axisPointer: { 41 | label: { 42 | formatter: (d) => d.value.toFixed(0), 43 | }, 44 | }, 45 | axisLabel: { 46 | formatter: (d) => 47 | new Intl.NumberFormat("zh-CN", { 48 | notation: "compact", 49 | maximumFractionDigits: 2, 50 | }).format(d.toFixed(0)), 51 | type: "value", 52 | }, 53 | }, 54 | dataset: { source: data }, 55 | series: Object.keys(fieldDict).map((k, i) => { 56 | return { 57 | type: "line", 58 | name: fieldDict[k], 59 | showSymbol: false, 60 | encode: { x: dateKey, y: k }, 61 | }; 62 | }), 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/charts/data-with-ma.js: -------------------------------------------------------------------------------- 1 | import fmt from "date-fns/format"; 2 | 3 | export default function getOpions(data, dateKey, fieldDict) { 4 | return { 5 | legend: { 6 | selectedMode: "single", 7 | }, 8 | tooltip: { 9 | show: true, 10 | axisPointer: { type: "cross" }, 11 | trigger: "axis", 12 | }, 13 | grid: { 14 | left: "10px", 15 | right: "50px", 16 | top: "30px", 17 | containLabel: true, 18 | }, 19 | dataZoom: [ 20 | { 21 | type: "inside", 22 | filterMode: "weakFilter", 23 | }, 24 | { 25 | handleSize: "100%", 26 | handleStyle: {}, 27 | bottom: "20px", 28 | }, 29 | ], 30 | xAxis: { 31 | type: "time", 32 | axisPointer: { 33 | label: { 34 | formatter: (d) => fmt(d.value, "YYYY-MM-DD HH:mm"), 35 | }, 36 | }, 37 | }, 38 | yAxis: { 39 | min: "dataMin", 40 | axisPointer: { 41 | label: { 42 | formatter: (d) => d.value.toFixed(0), 43 | }, 44 | }, 45 | axisLabel: { 46 | formatter: (d) => 47 | new Intl.NumberFormat("zh-CN", { 48 | notation: "compact", 49 | maximumFractionDigits: 2, 50 | }).format(d.toFixed(0)), 51 | type: "value", 52 | }, 53 | }, 54 | dataset: { source: data }, 55 | series: Object.keys(fieldDict).map((k, i) => { 56 | return { 57 | type: "line", 58 | name: fieldDict[k], 59 | showSymbol: false, 60 | encode: { x: dateKey, y: k }, 61 | }; 62 | }), 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/charts/get-card-chart.js: -------------------------------------------------------------------------------- 1 | function getBarChart(x, y, type) { 2 | return { 3 | grid: { 4 | left: "10px", 5 | right: "10px", 6 | bottom: "10px", 7 | top: "20px", 8 | containLabel: true 9 | }, 10 | xAxis: [ 11 | { 12 | type: "category", 13 | data: x, 14 | axisTick: { 15 | alignWithLabel: true 16 | } 17 | } 18 | ], 19 | yAxis: [ 20 | { 21 | type: "value", 22 | min(val) { 23 | return val.min; 24 | } 25 | } 26 | ], 27 | series: [ 28 | { 29 | type: type, 30 | lineStyle: { 31 | normal: { 32 | width: "5" 33 | } 34 | }, 35 | data: y 36 | } 37 | ] 38 | }; 39 | } 40 | export default getBarChart; 41 | -------------------------------------------------------------------------------- /src/charts/graph-charts.js: -------------------------------------------------------------------------------- 1 | //定义抠图方法 2 | function getImgData( 3 | imgSrc, 4 | center = { 5 | x: 30, 6 | y: 30 7 | } 8 | ) { 9 | return new Promise(resolve => { 10 | let radius = 30; 11 | const canvas = document.createElement("canvas"); 12 | const contex = canvas.getContext("2d"); 13 | const img = new Image(); 14 | img.crossOrigin = ""; 15 | const diameter = 2 * radius; 16 | img.onload = function() { 17 | canvas.width = diameter; 18 | canvas.height = diameter; 19 | contex.clearRect(0, 0, diameter, diameter); 20 | contex.save(); 21 | contex.beginPath(); 22 | contex.arc(radius, radius, radius, 0, 2 * Math.PI); 23 | contex.clip(); 24 | contex.drawImage( 25 | img, 26 | center.x - radius, 27 | center.y - radius, 28 | diameter, 29 | diameter, 30 | 0, 31 | 0, 32 | diameter, 33 | diameter 34 | ); 35 | contex.restore(); 36 | resolve(canvas.toDataURL("image/png", 1)); 37 | }; 38 | 39 | img.src = imgSrc; 40 | }); 41 | } 42 | 43 | function drawChart(nodes, categories, links, pic) { 44 | var listPromise = []; 45 | 46 | categories.forEach(e => { 47 | listPromise.push(getImgData(pic[e])); 48 | }); 49 | let option = { 50 | series: [ 51 | { 52 | name: "up主名称", 53 | type: "graph", 54 | layout: "circular", 55 | data: nodes, 56 | links: links, 57 | categories: categories, 58 | roam: true, 59 | focusNodeAdjacency: true, 60 | itemStyle: { 61 | normal: { 62 | borderColor: "#fff", 63 | borderWidth: 2, 64 | shadowBlur: 10, 65 | shadowColor: "rgba(0, 0, 0, 0.3)" 66 | } 67 | }, 68 | force: { 69 | initLayout: "circular", 70 | edgeLength: 70, 71 | repulsion: 200 72 | }, 73 | label: { 74 | position: "right", 75 | formatter: "{b}" 76 | }, 77 | lineStyle: { 78 | color: "source", 79 | curveness: 0.1 80 | }, 81 | emphasis: { 82 | lineStyle: { 83 | width: 5 84 | } 85 | } 86 | } 87 | ] 88 | }; 89 | //当处理的图片数据量比较大时,可由后端来处理这个过程 90 | Promise.all(listPromise).then(images => { 91 | nodes[0].symbol = "image://" + images[0]; 92 | }); 93 | return option; 94 | } 95 | export default drawChart; 96 | -------------------------------------------------------------------------------- /src/charts/online.js: -------------------------------------------------------------------------------- 1 | var format = require("date-fns/format"); 2 | 3 | function drawChart(data) { 4 | //TODO: get date min and max 5 | var dateSet = new Set(); 6 | data.forEach((e) => { 7 | e.data.forEach((ed) => { 8 | dateSet.add( 9 | format(ed.datetime.replace("+00:00", ""), "YYYY-MM-DD HH:mm") 10 | ); 11 | }); 12 | }); 13 | let dateArray = Array.from(dateSet).sort((a, b) => { 14 | return new Date(a) - new Date(b); 15 | }); 16 | let dateMin = dateArray[0]; 17 | let dateMax = dateArray[dateArray.length - 1]; 18 | 19 | let series = data.map((d) => { 20 | let dd = d.data.map((ed) => { 21 | return [ 22 | format(ed.datetime.replace("+00:00", ""), "YYYY-MM-DD HH:mm"), 23 | ed.number, 24 | ]; 25 | }); 26 | return { 27 | name: d.title, 28 | type: "line", 29 | symbol: "none", 30 | data: dd, 31 | }; 32 | }); 33 | 34 | // [{ 35 | // 视频1: 0, 36 | // 视频2: 0, 37 | // datetime: format(new Date(data.datetime), "YYYY-MM-DD HH:mm") 38 | // }] 39 | // [{[]},{[]}] 40 | 41 | let Chart = { 42 | title: { 43 | left: "center", 44 | top: "-5px", 45 | subtext: "各项指标总量", 46 | }, 47 | dataZoom: [ 48 | { 49 | type: "inside", 50 | filterMode: "weakFilter", 51 | }, 52 | { 53 | handleSize: "100%", 54 | handleStyle: {}, 55 | bottom: "10px", 56 | }, 57 | ], 58 | tooltip: { 59 | confine: true, 60 | trigger: "axis", 61 | formatter: function(params) { 62 | let o = `日期:${params[0].value[0]}`; 63 | let tempData = []; 64 | 65 | params.forEach((e) => { 66 | tempData.push([e.seriesName, e.color, e.value[1]]); 67 | }); 68 | tempData 69 | .sort((a, b) => b[2] - a[2]) 70 | .forEach((e) => { 71 | o += `
72 | 73 | ${e[0]}: ${e[2]} 74 |
75 | `; 76 | }); 77 | return o; 78 | }, 79 | }, 80 | xAxis: { 81 | type: "time", 82 | 83 | min: dateMin, 84 | max: dateMax, 85 | }, 86 | yAxis: { 87 | type: "value", 88 | name: "同时在线人数", 89 | axisLabel: { 90 | formatter: function(params) { 91 | if (params >= 10000) { 92 | return Math.round(params / 100) / 100.0 + "万"; 93 | } else { 94 | return params; 95 | } 96 | }, 97 | }, 98 | }, 99 | series: series, 100 | }; 101 | return Chart; 102 | } 103 | export default drawChart; 104 | -------------------------------------------------------------------------------- /src/charts/site_play.js: -------------------------------------------------------------------------------- 1 | var format = require("date-fns/format"); 2 | import formatNumber from "../util/format-number"; 3 | function drawChart(data) { 4 | var xData = []; 5 | var playOninleData = []; 6 | var webOnlineData = []; 7 | data.forEach(e => { 8 | xData.unshift(format(new Date(e.datetime.replace(/-/g, "/")), "HH:mm")); 9 | playOninleData.unshift((e.playOnline / 10000).toFixed(2)); 10 | webOnlineData.unshift((e.webOnline / 10000).toFixed(2)); 11 | }); 12 | var option = { 13 | title: { 14 | left: "center" 15 | // text: "过去24小时全站在线观看人数" 16 | }, 17 | tooltip: { 18 | trigger: "axis", 19 | axisPointer: { 20 | type: "cross" 21 | } 22 | }, 23 | grid: { 24 | left: "0px", 25 | right: "0px", 26 | bottom: "0px", 27 | top: "10px", 28 | containLabel: true 29 | }, 30 | xAxis: { 31 | type: "category", 32 | data: xData 33 | }, 34 | yAxis: { 35 | type: "value", 36 | axisLabel: { 37 | formatter: val => { 38 | return formatNumber(val * 10000); 39 | } 40 | } 41 | }, 42 | series: [ 43 | { 44 | name: "在线观看", 45 | itemStyle: { 46 | borderWidth: 2 47 | }, 48 | data: playOninleData, 49 | type: "line" 50 | }, 51 | { 52 | name: "在线人数", 53 | itemStyle: { 54 | borderWidth: 2 55 | }, 56 | data: webOnlineData, 57 | type: "line" 58 | } 59 | ] 60 | }; 61 | return option; 62 | } 63 | export default drawChart; 64 | -------------------------------------------------------------------------------- /src/charts/util/convertDateToUTC.js: -------------------------------------------------------------------------------- 1 | export function convertDateToUTC(date) { 2 | return new Date( 3 | date.getUTCFullYear(), 4 | date.getUTCMonth(), 5 | date.getUTCDate(), 6 | date.getUTCHours(), 7 | date.getUTCMinutes(), 8 | date.getUTCSeconds() 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/charts/util/format.js: -------------------------------------------------------------------------------- 1 | export default function(num) { 2 | let postfix = ""; 3 | if (num > 100000000) { 4 | postfix = "亿"; 5 | num /= 100000000; 6 | } else if (num > 10000) { 7 | postfix = "万"; 8 | num /= 10000; 9 | } else { 10 | return num; 11 | } 12 | num = num.toFixed(2); 13 | let [int, dec] = num.split("."); 14 | let inter = ""; 15 | while (int != "") { 16 | inter = int.slice(-3) + "," + inter; 17 | int = int.slice(0, -3); 18 | } 19 | return `${inter.slice(0, -1)}.${dec}${postfix}`; 20 | } 21 | -------------------------------------------------------------------------------- /src/charts/util/interpolation.js: -------------------------------------------------------------------------------- 1 | var parse = require("date-fns/parse"); 2 | var format = require("date-fns/format"); 3 | var addDays = require("date-fns/add_days"); 4 | 5 | function interpolation(data, day = 1) { 6 | let formated = []; 7 | data = data 8 | .map(e => { 9 | return [parse(e[0], new Date()), e[1]]; 10 | }) 11 | .filter((e, idx, arr) => { 12 | if (idx - 1 >= 0 && arr[idx - 1][1] == arr[idx][1]) { 13 | return false; 14 | } else { 15 | return true; 16 | } 17 | }); 18 | if (data.length <= 1) { 19 | return data; 20 | } 21 | 22 | let start_date = data[0][0]; 23 | let c_str_date = format(start_date, "YYYY-MM-DD"); 24 | let c_date = parse(c_str_date, "YYYY-MM-DD", new Date()); 25 | let end_date = data[data.length - 1][0]; 26 | let e_str_date = format(end_date, "YYYY-MM-DD"); 27 | let last_index = 0; 28 | let next_index = 1; 29 | while (e_str_date != c_str_date) { 30 | c_date = addDays(c_date, 1); 31 | c_str_date = format(c_date, "YYYY-MM-DD"); 32 | 33 | while (next_index + 1 < data.length && data[next_index][0] < c_date) { 34 | next_index++; 35 | } 36 | 37 | while ( 38 | last_index + 2 < data.length && 39 | data[last_index + 1][0].getTime() < c_date 40 | ) { 41 | last_index++; 42 | } 43 | 44 | let nv = data[next_index][1]; 45 | let nd = data[next_index][0]; 46 | let lv = data[last_index][1]; 47 | let ld = data[last_index][0]; 48 | let c_val = lv + ((c_date - ld) / (nd - ld)) * (nv - lv); 49 | 50 | if (!isNaN(c_val)) formated.push([c_str_date, c_val]); 51 | } 52 | let out = []; 53 | let i = day; 54 | while (i < formated.length) { 55 | let val = formated[i][1] - formated[i - day][1]; 56 | 57 | if (val > 1 || val < -1) { 58 | val = Math.round(val); 59 | } else { 60 | val = val.toFixed(2); 61 | } 62 | 63 | out.push([formated[i - 1][0], val]); 64 | i++; 65 | } 66 | 67 | return out; 68 | } 69 | export default interpolation; 70 | -------------------------------------------------------------------------------- /src/components/ChiefRecommend.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 62 | -------------------------------------------------------------------------------- /src/components/DetailMain.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/components/Home/PopularTag.vue: -------------------------------------------------------------------------------- 1 | 8 | 64 | -------------------------------------------------------------------------------- /src/components/Recommend.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/TheHomeCarousel.vue: -------------------------------------------------------------------------------- 1 | 23 | 47 | -------------------------------------------------------------------------------- /src/components/Tracer/NoRole.vue: -------------------------------------------------------------------------------- 1 | 20 | 23 | -------------------------------------------------------------------------------- /src/components/Tracer/layout/Footer.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 51 | 52 | 61 | -------------------------------------------------------------------------------- /src/components/Tracer/layout/Nav.vue: -------------------------------------------------------------------------------- 1 | 33 | 92 | -------------------------------------------------------------------------------- /src/components/Tracer/layout/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /src/components/Tracer/layout/View.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /src/components/Tracer/nav/item.vue: -------------------------------------------------------------------------------- 1 | 19 | 37 | -------------------------------------------------------------------------------- /src/components/admin/CrawlCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 35 | -------------------------------------------------------------------------------- /src/components/admin/GraphCard.vue: -------------------------------------------------------------------------------- 1 | 25 | 35 | 52 | -------------------------------------------------------------------------------- /src/components/admin/StatsCard.vue: -------------------------------------------------------------------------------- 1 | 35 | 49 | 68 | -------------------------------------------------------------------------------- /src/components/aside/AuthorInfo.vue: -------------------------------------------------------------------------------- 1 | 43 | 77 | 78 | 94 | -------------------------------------------------------------------------------- /src/components/biliob/ActiveCodeTextField.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 72 | 73 | 101 | -------------------------------------------------------------------------------- /src/components/biliob/DarkInfo.vue: -------------------------------------------------------------------------------- 1 | 21 | 51 | 86 | -------------------------------------------------------------------------------- /src/components/biliob/InfoDialog.vue: -------------------------------------------------------------------------------- 1 | 45 | 63 | 72 | -------------------------------------------------------------------------------- /src/components/biliob/Kanban.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 65 | 66 | -------------------------------------------------------------------------------- /src/components/biliob/Main.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/biliob/Notice.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 60 | 61 | -------------------------------------------------------------------------------- /src/components/biliob/Notification.vue: -------------------------------------------------------------------------------- 1 | 22 | 35 | 40 | -------------------------------------------------------------------------------- /src/components/biliob/Sheet.vue: -------------------------------------------------------------------------------- 1 | 21 | 33 | -------------------------------------------------------------------------------- /src/components/biliob/Slide.vue: -------------------------------------------------------------------------------- 1 | 26 | 51 | 66 | -------------------------------------------------------------------------------- /src/components/biliob/SlideCard.vue: -------------------------------------------------------------------------------- 1 | 8 | 30 | -------------------------------------------------------------------------------- /src/components/biliob/Title.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/biliob/agenda/StateChip.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/biliob/agenda/TypeChip.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/biliob/author/AchievementSlideCard.vue: -------------------------------------------------------------------------------- 1 | 56 | 88 | -------------------------------------------------------------------------------- /src/components/biliob/author/Achievements.vue: -------------------------------------------------------------------------------- 1 | 49 | 100 | -------------------------------------------------------------------------------- /src/components/biliob/author/ListItem.vue: -------------------------------------------------------------------------------- 1 | 74 | 87 | -------------------------------------------------------------------------------- /src/components/biliob/bangumi/Copyright.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/biliob/bangumi/Status.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/biliob/bangumi/type.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/biliob/comment/Input.vue: -------------------------------------------------------------------------------- 1 | 45 | 95 | -------------------------------------------------------------------------------- /src/components/biliob/compare/AuthorInfo.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/biliob/end.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 74 | 75 | -------------------------------------------------------------------------------- /src/components/biliob/user/Info.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/biliob/user/rank/Title.vue: -------------------------------------------------------------------------------- 1 | 20 | 28 | 39 | -------------------------------------------------------------------------------- /src/components/common/BiliobFooter.vue: -------------------------------------------------------------------------------- 1 | 11 | 14 | -------------------------------------------------------------------------------- /src/components/common/BottomNav.vue: -------------------------------------------------------------------------------- 1 | 41 | 76 | -------------------------------------------------------------------------------- /src/components/common/ChevronBudget.vue: -------------------------------------------------------------------------------- 1 | 9 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/common/CircleIconBtn.vue: -------------------------------------------------------------------------------- 1 | 15 | 35 | 43 | -------------------------------------------------------------------------------- /src/components/common/CreditBadget.vue: -------------------------------------------------------------------------------- 1 | 17 | 50 | -------------------------------------------------------------------------------- /src/components/common/ExpBadget.vue: -------------------------------------------------------------------------------- 1 | 17 | 64 | -------------------------------------------------------------------------------- /src/components/common/FavoriteBtn.vue: -------------------------------------------------------------------------------- 1 | 27 | 78 | -------------------------------------------------------------------------------- /src/components/common/FocusBtn.vue: -------------------------------------------------------------------------------- 1 | 25 | 74 | -------------------------------------------------------------------------------- /src/components/common/LevelIcon.vue: -------------------------------------------------------------------------------- 1 | 59 | 64 | -------------------------------------------------------------------------------- /src/components/common/MyBadget.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /src/components/common/NextBtn.vue: -------------------------------------------------------------------------------- 1 | 14 | 40 | -------------------------------------------------------------------------------- /src/components/common/ObserveStatus.vue: -------------------------------------------------------------------------------- 1 | 14 | 61 | -------------------------------------------------------------------------------- /src/components/common/RoleBadget.vue: -------------------------------------------------------------------------------- 1 | 6 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /src/components/common/SexIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | 29 | -------------------------------------------------------------------------------- /src/components/common/VOdometer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 62 | 72 | -------------------------------------------------------------------------------- /src/components/common/VSearchForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 65 | -------------------------------------------------------------------------------- /src/components/helper/Offset.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 43 | 44 | 54 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import upperFirst from "lodash/upperFirst"; 3 | import camelCase from "lodash/camelCase"; 4 | 5 | const requireComponent = require.context("@/components", true, /\.vue$/); 6 | 7 | requireComponent.keys().forEach(fileName => { 8 | const componentConfig = requireComponent(fileName); 9 | 10 | const componentName = upperFirst( 11 | camelCase(fileName.replace(/^\.\//, "").replace(/\.\w+$/, "")) 12 | ); 13 | 14 | Vue.prototype.zipPic = function(url) { 15 | if (url === undefined) { 16 | return url; 17 | } 18 | url = url.replace("http:", ""); 19 | var subUrl = url.split("bfs/"); 20 | if (subUrl.length < 2) { 21 | return url; 22 | } 23 | var c = subUrl[1].split("/")[0]; 24 | var t = url.split(".")[3]; 25 | var postfix = ""; 26 | if (c === "face" && t === "jpg") { 27 | postfix = `@80w_80h.jpg`; 28 | } else if (c === "archive") { 29 | postfix = "@160w_100h.jpg"; 30 | } 31 | return `${url}${postfix}`; 32 | }; 33 | Vue.component(componentName, componentConfig.default || componentConfig); 34 | }); 35 | -------------------------------------------------------------------------------- /src/components/main/AuthorDetailChannel.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 57 | 58 | 74 | -------------------------------------------------------------------------------- /src/components/main/AuthorMain.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/components/main/SiteChart.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 59 | 60 | 74 | -------------------------------------------------------------------------------- /src/components/main/UserRecordCard.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/src/components/main/UserRecordCard.vue -------------------------------------------------------------------------------- /src/components/main/VideoDetailMainChart.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 45 | 46 | 52 | -------------------------------------------------------------------------------- /src/components/main/VideoDetailPieChart.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /src/components/main/VideoDetailTitle.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 63 | 64 | 85 | -------------------------------------------------------------------------------- /src/components/main/VideoMain.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /src/components/material/ChartCard.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /src/components/material/Notification.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 29 | 30 | 36 | -------------------------------------------------------------------------------- /src/components/material/SpiderCard.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 92 | 93 | 128 | -------------------------------------------------------------------------------- /src/components/material/StatsCard.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 87 | 88 | 123 | -------------------------------------------------------------------------------- /src/components/video/RecommendList.vue: -------------------------------------------------------------------------------- 1 | 31 | 43 | 50 | -------------------------------------------------------------------------------- /src/data/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | agenda: {}, 3 | alert: { 4 | message: undefined, 5 | type: undefined, 6 | display: false 7 | }, 8 | index: { 9 | recentlyRank: undefined, 10 | keyword: {} 11 | }, 12 | bangumi: { 13 | detail: {} 14 | }, 15 | sponsor: { 16 | latest: [], 17 | most: [] 18 | }, 19 | user: {}, 20 | favoriteAuthor: {}, 21 | // maintainAuthorGroup: [], 22 | // starAuthorGroup: [], 23 | comments: {}, 24 | fansGuessing: [], 25 | author: { 26 | group: { 27 | list: {}, 28 | detail: {} 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/echarts.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import ECharts from "vue-echarts/components/ECharts"; 3 | import "echarts/lib/component/tooltip"; 4 | import "echarts/lib/component/title"; 5 | import "echarts/lib/component/dataZoom"; 6 | import "echarts/lib/component/markLine"; 7 | import "echarts/lib/component/markPoint"; 8 | import "echarts/lib/component/radar"; 9 | import "echarts/lib/component/calendar"; 10 | import "echarts/lib/component/legend"; 11 | import "echarts/lib/component/visualMap"; 12 | // import "echarts/lib/chart/candlestick"; 13 | // import "echarts/lib/chart/radar"; 14 | import "echarts/lib/chart/line"; 15 | import "echarts/lib/chart/pie"; 16 | import "echarts/lib/chart/bar"; 17 | import "echarts/lib/chart/graph"; 18 | import "echarts/lib/chart/heatmap"; 19 | import "echarts-wordcloud"; 20 | 21 | // // custom theme 22 | import mydark from "./charts/theme/mydark.json"; 23 | import colorful from "./charts/theme/colorful.json"; 24 | import card from "./charts/theme/card.json"; 25 | 26 | // // registering custom theme 27 | ECharts.registerTheme("mydark", mydark); 28 | ECharts.registerTheme("colorful", colorful); 29 | ECharts.registerTheme("card", card); 30 | 31 | Vue.component("Chart", ECharts); 32 | -------------------------------------------------------------------------------- /src/plugins/theme.js: -------------------------------------------------------------------------------- 1 | let date = new Date(); 2 | let primaryColor = "#1e88e5"; 3 | if (date.getDate() == 1 && date.getMonth() == 3) { 4 | primaryColor = "#62c076"; 5 | } else if (date.getDate() == 1 && date.getMonth() == 9) { 6 | primaryColor = "#f55a4e"; 7 | } else if ( 8 | date.getDate() == 4 && 9 | date.getMonth() == 3 && 10 | date.getFullYear() == 2020 11 | ) { 12 | primaryColor = "#888"; 13 | } 14 | export default { 15 | themes: { 16 | light: { 17 | primary: primaryColor, 18 | secondary: "#4caf50", 19 | tertiary: "#495057", 20 | accent: "#82B1FF", 21 | error: "#f55a4e", 22 | info: "#00d3ee", 23 | success: "#5cb860", 24 | warning: "#ffa21a" 25 | }, 26 | options: { 27 | customProperties: true 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib"; 3 | import theme from "./theme"; 4 | import zhHans from "vuetify/es5/locale/zh-Hans"; 5 | Vue.use(Vuetify); 6 | export default new Vuetify({ 7 | breakpoint: { 8 | thresholds: { 9 | xs: 340, 10 | sm: 540, 11 | md: 800, 12 | lg: 1200 13 | }, 14 | scrollBarWidth: 24 15 | }, 16 | icons: { 17 | iconfont: "mdi" 18 | }, 19 | theme: { 20 | ...theme, 21 | dark: false 22 | }, 23 | lang: { 24 | locales: { zhHans }, 25 | current: "zh-Hans" 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from "register-service-worker"; 4 | 5 | if (process.env.NODE_ENV === "production") { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | "App is being served from cache by a service worker.\n" + 10 | "For more details, visit https://goo.gl/AFskqB" 11 | ); 12 | }, 13 | registered() { 14 | console.log("Service worker has been registered."); 15 | }, 16 | cached() { 17 | console.log("Content has been cached for offline use."); 18 | }, 19 | updatefound() { 20 | console.log("New content is downloading."); 21 | }, 22 | updated() { 23 | console.log("New content is available; please refresh."); 24 | }, 25 | offline() { 26 | console.log( 27 | "No internet connection found. App is running in offline mode." 28 | ); 29 | }, 30 | error(error) { 31 | console.error("Error during service worker registration:", error); 32 | }, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/sass/variables.scss: -------------------------------------------------------------------------------- 1 | $shadow-key-umbra-opacity: rgba(#222, 0.15); 2 | $shadow-key-penumbra-opacity: rgba(#222, 0.12); 3 | $shadow-key-ambient-opacity: rgba(#222, 0.1); 4 | $card-border-radius: 0px; 5 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | // 设置缓存前缀和后缀,请根据实际项目名修改 2 | workbox.core.setCacheNameDetails({ 3 | prefix: "biliob", 4 | suffix: "ver.11.2.1", 5 | }); 6 | 7 | // have our sw update and control a web page as soon as possible. 8 | workbox.core.skipWaiting(); // 强制等待中的 Service Worker 被激活 9 | workbox.core.clientsClaim(); // Service Worker 被激活后使其立即获得页面控制权 10 | 11 | // vue-cli3.0 supports pwa with the help of workbox-webpack-plugin, we need to get the precacheing list through this sentence. 12 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []); 13 | 14 | workbox.routing.registerRoute( 15 | // Cache CSS files 16 | /.*\.html/, 17 | // 使用缓存,但尽快在后台更新 18 | new workbox.strategies.StaleWhileRevalidate({ 19 | // 使用自定义缓存名称 20 | cacheName: "html-cache", 21 | }) 22 | ); 23 | 24 | workbox.routing.registerRoute( 25 | // Cache CSS files 26 | /.*\.css/, 27 | // 使用缓存,但尽快在后台更新 28 | new workbox.strategies.StaleWhileRevalidate({ 29 | // 使用自定义缓存名称 30 | cacheName: "css-cache", 31 | }) 32 | ); 33 | workbox.routing.registerRoute( 34 | // 缓存JS文件 35 | /.*\.js/, 36 | // 使用缓存,但尽快在后台更新 37 | new workbox.strategies.StaleWhileRevalidate({ 38 | // 使用自定义缓存名称 39 | cacheName: "js-cache", 40 | }) 41 | ); 42 | 43 | workbox.routing.registerRoute( 44 | /\.(?:png|gif|jpg|jpeg|svg)$/, 45 | new workbox.strategies.StaleWhileRevalidate({ 46 | cacheName: "images", 47 | plugins: [ 48 | new workbox.expiration.Plugin({ 49 | maxEntries: 60, 50 | maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days 51 | }), 52 | ], 53 | }) 54 | ); 55 | 56 | // api缓存,优选从网络获取,网络异常时再使用缓存,请根据实际api url配置RegExp,只支持get请求 57 | workbox.routing.registerRoute( 58 | new RegExp("https://api.biliob.com"), 59 | new workbox.strategies.NetworkFirst({ 60 | plugins: [ 61 | new workbox.cacheableResponse.Plugin({ 62 | statuses: [0, 200], 63 | }), 64 | ], 65 | }) 66 | ); 67 | -------------------------------------------------------------------------------- /src/store/author.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | export default { 3 | state: { hotSearch: undefined }, 4 | mutations: { 5 | setData(state, payload) { 6 | state[payload.name] = payload.data; 7 | } 8 | }, 9 | getters: { 10 | getHotSearchAuthor(state) { 11 | return state.hotSearch; 12 | } 13 | }, 14 | actions: { 15 | getHotSearchAuthor(context) { 16 | axios.get("/author/hot").then(response => { 17 | context.commit("setData", { 18 | data: response.data, 19 | name: "hotSearch" 20 | }); 21 | }); 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/store/event.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | export default { 3 | state: { recentEvent: undefined }, 4 | mutations: { 5 | setData(state, payload) { 6 | state[payload.name] = payload.data; 7 | } 8 | }, 9 | getters: { 10 | recentEventList(state) { 11 | return state.recentEvent; 12 | } 13 | }, 14 | actions: { 15 | getRecentEvent(context) { 16 | axios.get("/event/fans-variation").then(response => { 17 | context.commit("setData", { 18 | data: response.data.content, 19 | name: "recentEvent" 20 | }); 21 | }); 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/store/rank.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | export default { 3 | state: { 4 | rankListFansIncrease: undefined, 5 | rankListFansDecrease: undefined, 6 | rankListBangumi: undefined, 7 | rankListDonghua: undefined 8 | }, 9 | mutations: { 10 | setData(state, payload) { 11 | state[payload.name] = payload.data; 12 | } 13 | }, 14 | actions: { 15 | getIncrease(context) { 16 | axios.get("/rank/fans-increase-rate").then(response => { 17 | context.commit("setData", { 18 | data: response.data.content, 19 | name: "rankListFansIncrease" 20 | }); 21 | }); 22 | }, 23 | getDecrease(context) { 24 | axios.get("/rank/fans-decrease-rate").then(response => { 25 | context.commit("setData", { 26 | data: response.data.content, 27 | name: "rankListFansDecrease" 28 | }); 29 | }); 30 | }, 31 | getBangumiTopList(context) { 32 | axios.get("/bangumi").then(response => { 33 | let data = response.data.content; 34 | data.forEach(element => { 35 | element.cover = element.cover.slice(5); 36 | }); 37 | context.commit("setData", { 38 | data: data, 39 | name: "rankListBangumi" 40 | }); 41 | }); 42 | }, 43 | getDonghuaTopList(context) { 44 | axios.get("/donghua").then(response => { 45 | let data = response.data.content; 46 | data.forEach(element => { 47 | element.cover = element.cover.slice(5); 48 | }); 49 | context.commit("setData", { 50 | data: data, 51 | name: "rankListDonghua" 52 | }); 53 | }); 54 | } 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/styles/biliob/dark-info.scss: -------------------------------------------------------------------------------- 1 | .biliob-shadow { 2 | box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.05); 3 | } 4 | .inline { 5 | display: inline-block; 6 | } 7 | .v-expansion-panels { 8 | border-radius: 0px !important; 9 | } 10 | .v-expansion-panels:not(.v-expansion-panels--accordion) 11 | > .v-expansion-panel--active { 12 | border-radius: 0px !important; 13 | } 14 | 15 | // ::-webkit-scrollbar-track { 16 | // -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 17 | // box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 18 | // -moz-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 19 | // border-radius: 4px; 20 | 21 | // background-color: #ccc; 22 | // } 23 | 24 | // ::-webkit-scrollbar { 25 | // width: 4px; 26 | // background-color: #ccc; 27 | // } 28 | 29 | // ::-webkit-scrollbar-thumb { 30 | // border-radius: 2px; 31 | // -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 32 | // box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 33 | // -moz-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 34 | // background-color: #fff; 35 | // } 36 | .element { 37 | overflow: -moz-scrollbars-none; 38 | } 39 | .element { 40 | -ms-overflow-style: none; 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "material-dashboard/mixins"; 2 | @import "material-dashboard/variables"; 3 | @import "biliob/dark-info"; 4 | // @import "material-dashboard/typography"; 5 | // @import "material-dashboard/sidebar"; 6 | // @import "material-dashboard/misc"; 7 | // @import "material-dashboard/inputs"; 8 | // @import "material-dashboard/footer"; 9 | // @import "material-dashboard/cards"; 10 | // @import "material-dashboard/tables"; 11 | // @import "material-dashboard/tabs"; 12 | // @import "material-dashboard/checkboxes"; 13 | // @import "material-dashboard/tooltips"; 14 | // @import "material-dashboard/buttons"; 15 | // @import "material-dashboard/alerts"; 16 | // @import "material-dashboard/fixed-plugin"; 17 | // @import "material-dashboard/dropdown"; 18 | .v-slide-group__prev { 19 | display: none !important; 20 | } 21 | 22 | .v-application p { 23 | margin-bottom: auto; 24 | } 25 | 26 | .bscroll-horizontal-scrollbar { 27 | z-index: 2 !important; 28 | } 29 | .bscroll-vertical-scrollbar { 30 | z-index: 2 !important; 31 | } 32 | 33 | .container { 34 | padding: 0px !important; 35 | } -------------------------------------------------------------------------------- /src/styles/material-dashboard/_alerts.scss: -------------------------------------------------------------------------------- 1 | .v-alert { 2 | .v-alert__dismissible .v-icon { 3 | color: rgba($white, .5); 4 | font-size: $font-size-default + 2; 5 | } 6 | 7 | .v-alert__icon.v-icon { 8 | font-size: $font-size-alert-icon; 9 | color: $white; 10 | } 11 | } 12 | 13 | .v-snack { 14 | .v-icon:not(:first-child) { 15 | color: rgba($white, .5); 16 | margin-left: $margin-general + 1; 17 | } 18 | 19 | .v-icon:first-child { 20 | font-size: $font-size-alert-icon; 21 | } 22 | 23 | .v-snack__content { 24 | padding: $padding-general $padding-general + 5; 25 | height: auto; 26 | } 27 | 28 | .v-snack__wrapper { 29 | border-radius: $border-radius-base + 1; 30 | } 31 | } 32 | 33 | .v-snack .v-snack__wrapper, 34 | .v-alert { 35 | @include alert-shadow('info', lighten($brand-info, 5%)); 36 | @include alert-shadow('success', $brand-success); 37 | @include alert-shadow('error', $brand-danger); 38 | @include alert-shadow('warning', $brand-warning); 39 | @include alert-shadow('purple', $brand-primary); 40 | } 41 | 42 | .v-snack { 43 | @include notifications-color('info', lighten($brand-info, 5%)); 44 | @include notifications-color('success', $brand-success); 45 | @include notifications-color('error', $brand-danger); 46 | @include notifications-color('warning', $brand-warning); 47 | @include notifications-color('purple', $brand-primary); 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_buttons.scss: -------------------------------------------------------------------------------- 1 | .v-btn { 2 | margin: $margin-small 1px; 3 | padding: $padding-small + 2 $padding-general + 15; 4 | font-size: $font-size-small; 5 | font-weight: $font-weight-base !important; 6 | height: auto; 7 | line-height: $line-height-base; 8 | color: $white !important; 9 | cursor: pointer; 10 | border-radius: $border-radius-base; 11 | 12 | &.v-btn--round, 13 | &.v-btn--round:after { 14 | border-radius: $btn-round-padding; 15 | } 16 | 17 | &.v-btn-facebook { 18 | @include social-buttons-color($social-facebook); 19 | } 20 | &.v-btn-twitter { 21 | @include social-buttons-color($social-twitter); 22 | } 23 | 24 | .v-icon--left { 25 | margin-right: $btn-margin-icon; 26 | } 27 | .v-icon--right { 28 | margin-left: $btn-margin-icon; 29 | } 30 | 31 | &.v-btn--large { 32 | font-size: $font-size-general; 33 | padding: $btn-y-large-padding $btn-x-large-padding !important; 34 | line-height: $btn-large-line-height; 35 | } 36 | 37 | &.v-btn--small { 38 | padding: $btn-y-small-padding $btn-x-small-padding !important; 39 | font-size: $font-size-small - 1; 40 | } 41 | 42 | &.v-btn--icon { 43 | width: $btn-icon-dim; 44 | height: $btn-icon-dim; 45 | line-height: $btn-icon-dim; 46 | padding: 0; 47 | 48 | &.v-btn--round { 49 | border-radius: 50%; 50 | } 51 | } 52 | 53 | &.success { 54 | @include button-color($brand-success); 55 | } 56 | 57 | &.default { 58 | @include button-color($gray-light); 59 | } 60 | 61 | &.primary { 62 | @include button-color($brand-primary); 63 | } 64 | 65 | &.warning { 66 | @include button-color($brand-warning); 67 | } 68 | 69 | &.info { 70 | @include button-color($brand-info); 71 | } 72 | 73 | &.danger { 74 | @include button-color($brand-danger); 75 | } 76 | 77 | .v-icon { 78 | font-size: $btn-font-size-icon; 79 | } 80 | } 81 | 82 | .v-btn--fixed { 83 | border-radius: $border-radius-base + 3; 84 | border-bottom-right-radius: 0; 85 | border-top-right-radius: 0; 86 | background-color: rgba($black, .3) !important; 87 | right: 0; 88 | padding: 0; 89 | width: $btn-fixed-width; 90 | 91 | .v-icon { 92 | font-size: $btn-fixed-icon-size; 93 | padding: $padding-small; 94 | } 95 | } 96 | 97 | .v-btn--active:before, 98 | .v-btn:focus:before, 99 | .v-btn:hover:before { 100 | background-color: transparent; 101 | } 102 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_checkboxes.scss: -------------------------------------------------------------------------------- 1 | .mdi-checkbox-blank-outline:before { 2 | -webkit-text-stroke: 1px $white; 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown-menu { 2 | border-radius: $border-radius-base; 3 | 4 | .v-list__tile { 5 | border-radius: $border-radius-base - 1; 6 | color: #333 !important; 7 | display: flex; 8 | font-size: $font-size-dropdown; 9 | font-weight: $font-weight-base; 10 | margin: 0 $margin-small - 5; 11 | padding: $padding-small 1.5rem; 12 | text-transform: none; 13 | @include transition-except-props(.15s linear, color, background-color, box-shadow 0ms); 14 | white-space: nowrap; 15 | 16 | .v-list__tile__title { 17 | transition: none; 18 | } 19 | 20 | &:hover, 21 | &:focus, 22 | &:active { 23 | @include dropdown-menu-color($brand-primary); 24 | 25 | &, 26 | .v-list__tile__title { 27 | color: $white !important; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_fixed-plugin.scss: -------------------------------------------------------------------------------- 1 | .v-menu__content { 2 | border-radius: $border-radius-dropdown; 3 | box-shadow: $box-shadow-general; 4 | 5 | .sidebar-filter { 6 | height: 30px; 7 | line-height: 25px; 8 | font-size: $font-size-small !important; 9 | font-weight: $font-weight-bolder + 100; 10 | color: $black-color; 11 | } 12 | 13 | .v-responsive { 14 | max-height: 100px; 15 | border-radius: $border-radius-dropdown; 16 | max-width: 50px; 17 | margin: 0 auto; 18 | } 19 | 20 | .container.grid-list-xl .layout .flex { 21 | padding: $padding-small - 5; 22 | } 23 | 24 | .v-avatar, 25 | .v-responsive { 26 | border: 3px solid $white; 27 | transition: all .34s; 28 | 29 | &:not(:last-child) { 30 | margin-right: 5px; 31 | } 32 | 33 | &.image-active, 34 | &.color-active { 35 | border-color: $brand-info; 36 | } 37 | } 38 | 39 | .v-avatar { 40 | @include fixed-plugin-colors('primary', $brand-primary); 41 | @include fixed-plugin-colors('info', $brand-info); 42 | @include fixed-plugin-colors('danger', $brand-danger); 43 | @include fixed-plugin-colors('warning', $brand-warning); 44 | @include fixed-plugin-colors('success', $brand-success); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_footer.scss: -------------------------------------------------------------------------------- 1 | .theme--light.v-footer { 2 | border-top: $footer-border-top; 3 | background: transparent; 4 | padding: $padding-general $padding-x-footer; 5 | } 6 | 7 | .v-footer { 8 | .footer-links { 9 | font-weight: $font-weight-bolder; 10 | text-decoration: none; 11 | text-transform: uppercase; 12 | font-size: $font-size-small; 13 | padding: $padding-general; 14 | line-height: $line-height-footer-items; 15 | 16 | &:hover, 17 | &:focus, 18 | &:active { 19 | color: $brand-primary !important; 20 | } 21 | } 22 | 23 | .copyright { 24 | color: $black-color; 25 | 26 | a { 27 | &, 28 | &:hover, 29 | &:focus, 30 | &:active { 31 | color: $brand-primary; 32 | } 33 | } 34 | } 35 | } 36 | 37 | @media all and (max-width: 991px) { 38 | .v-footer { 39 | height: auto !important; 40 | flex-direction: column; 41 | } 42 | 43 | .footer-items, 44 | .copyright { 45 | width: 100%; 46 | } 47 | 48 | .copyright { 49 | text-align: right; 50 | } 51 | } 52 | 53 | @media all and (max-width: 550px) { 54 | .footer-items, 55 | .copyright { 56 | width: unset; 57 | text-align: center; 58 | } 59 | 60 | .copyright { 61 | margin-top: $margin-small; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_inputs.scss: -------------------------------------------------------------------------------- 1 | .theme--light.v-text-field .v-input__slot:before { 2 | border-color: $input-color !important; 3 | } 4 | 5 | .input--is-focused .v-input__control .v-input__slot:after{ 6 | border-color: red !important; 7 | } 8 | 9 | .v-text-field__slot { 10 | input { 11 | @include material-placeholder{ 12 | color: $input-color; 13 | }; 14 | } 15 | 16 | .v-label { 17 | color: $label-color !important; 18 | font-size: $font-size-default; 19 | font-weight: $font-weight-base; 20 | } 21 | } 22 | 23 | .theme--light.v-input:not(.v-input--is-disabled) input, 24 | .theme--light.v-input:not(.v-input--is-disabled) textarea { 25 | color: $black-color; 26 | font-size: $font-size-default; 27 | } 28 | 29 | @include inputs-color($brand-primary, 'purple'); 30 | @include inputs-color($brand-warning, 'orange'); 31 | @include inputs-color($brand-info, 'info'); 32 | @include inputs-color($brand-danger, 'danger'); 33 | @include inputs-color($brand-success, 'success'); 34 | @include inputs-color($brand-success, 'primary'); 35 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_misc.scss: -------------------------------------------------------------------------------- 1 | .theme--light.application { 2 | background: $body-color; 3 | } 4 | 5 | a { 6 | text-transform: none; 7 | text-decoration: none; 8 | } 9 | 10 | html { 11 | font-size: $font-size-general + 2!important; 12 | } 13 | 14 | .category { 15 | font-size: $font-size-general; 16 | } 17 | 18 | .theme--light.v-icon { 19 | color: $gray-light; 20 | } 21 | 22 | blockquote { 23 | display: block; 24 | margin-block-start: 1em; 25 | margin-block-end: 1em; 26 | margin-inline-start: 40px; 27 | margin-inline-end: 40px; 28 | 29 | p { 30 | font-style: italic; 31 | color: $blockquote-color; 32 | font-weight: $font-weight-bolder; 33 | } 34 | 35 | small { 36 | font-size: 70%; 37 | } 38 | } 39 | 40 | b, 41 | strong { 42 | font-size: $font-size-small; 43 | font-weight: $font-weight-bolder; 44 | } 45 | 46 | .v-list--three-line .v-list__tile { 47 | height: auto; 48 | } 49 | 50 | @include drawer-active-colors('primary', $brand-primary); 51 | @include drawer-active-colors('success', $brand-success); 52 | @include drawer-active-colors('danger', $brand-danger); 53 | @include drawer-active-colors('info', $brand-info); 54 | @include drawer-active-colors('warning', $brand-warning); 55 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .v-navigation-drawer { 2 | @include shadow-big(); 3 | 4 | .v-list { 5 | background: $sidebar-bg; 6 | padding: 0; 7 | 8 | .v-list-item { 9 | &:not(:nth-child(3)){ 10 | margin: $margin-small $margin-general 0; 11 | } 12 | 13 | &:nth-child(3) { 14 | margin: 0 $margin-general; 15 | } 16 | 17 | & > .v-list__tile { 18 | padding: $padding-small $padding-general; 19 | 20 | &:hover, 21 | &:focus, 22 | &:active { 23 | background-color: $list-item-hover; 24 | } 25 | 26 | .v-list__tile__title { 27 | font-size: $font-size-general !important; 28 | font-weight: $font-weight-lighter; 29 | padding: 0; 30 | } 31 | } 32 | 33 | .v-list__tile__action { 34 | margin-right: $margin-general; 35 | min-width: unset; 36 | 37 | i { 38 | width: $icon-width; 39 | } 40 | } 41 | 42 | .v-list__tile:not(.v-list__tile--active) .v-list__tile__action i { 43 | opacity: $list-item-icon-opacity; 44 | } 45 | 46 | .v-list__tile.v-list__tile--active { 47 | @include shadow-big-color($brand-success); 48 | } 49 | } 50 | 51 | .v-list__tile--avatar { 52 | height: unset; 53 | padding: $padding-logo 0; 54 | } 55 | 56 | .v-list__tile__avatar { 57 | margin-left: $margin-list-left; 58 | margin-right: $margin-list-right; 59 | min-width: unset; 60 | } 61 | 62 | .v-list__tile__title { 63 | line-height: $line-height; 64 | padding: $padding-avatar-title 0; 65 | height: unset; 66 | font-size: $font-size-avatar-title !important; 67 | font-weight: $font-weight-base; 68 | letter-spacing: unset !important; 69 | } 70 | 71 | .v-divider { 72 | border-color: $black-opacity-3; 73 | margin: $divider-margins auto $divider-margins + 25; 74 | width: $divider-width; 75 | 76 | } 77 | 78 | .v-image__image--contain { 79 | top: $top-dim; 80 | } 81 | 82 | .v-avatar { 83 | @include shadow-big; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_tables.scss: -------------------------------------------------------------------------------- 1 | .theme--light.v-table tbody tr:not(:last-child), 2 | .theme--light.v-table thead tr:first-child { 3 | border-bottom: 1px solid $table-border-color!important; 4 | } 5 | 6 | table.v-table thead tr { 7 | height: $table-head-height; 8 | } 9 | 10 | .theme--light.v-table tbody tr:hover:not(.v-datatable__expand-row) { 11 | background: $table-hover-color !important; 12 | } 13 | 14 | table.v-table tbody td { 15 | font-size: $font-size-general; 16 | font-weight: $font-weight-lighter; 17 | } 18 | 19 | table.v-table tbody td:first-child, table.v-table tbody td:not(:first-child), 20 | table.v-table tbody th:first-child, table.v-table tbody th:not(:first-child), 21 | table.v-table thead td:first-child, table.v-table thead td:not(:first-child), 22 | table.v-table thead th:first-child, table.v-table thead th:not(:first-child) { 23 | padding: $padding-small + 2 $tabs-text-box-padding; 24 | } 25 | 26 | .v-datatable__progress { 27 | display: none; 28 | } 29 | 30 | table thead tr th span{ 31 | font-size: 1.0625rem !important; 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_tabs.scss: -------------------------------------------------------------------------------- 1 | .card-tabs { 2 | .v-divider { 3 | margin-left: 0 !important; 4 | margin-right: 0 !important; 5 | } 6 | 7 | .flex { 8 | padding: 0 !important; 9 | } 10 | 11 | .v-tabs__item { 12 | border-radius: $border-radius-base; 13 | } 14 | 15 | .v-tabs__slider-wrapper .white{ 16 | background-color: $tabs-active-line !important; 17 | border-color: $tabs-active-line !important 18 | } 19 | 20 | .v-tabs__slider { 21 | height: 1px; 22 | } 23 | 24 | .v-list__tile__title { 25 | font-size: $font-size-default; 26 | font-weight: $font-weight-lighter; 27 | padding: 0 $tabs-text-box-padding; 28 | overflow: visible; 29 | white-space: inherit; 30 | height: auto; 31 | } 32 | 33 | .v-list .v-input, .v-list .v-input__slot { 34 | justify-content: center; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_toolbar.scss: -------------------------------------------------------------------------------- 1 | .v-toolbar { 2 | min-height: $toolbar-min-height; 3 | border-radius: $border-radius-base; 4 | margin-bottom: $margin-general; 5 | 6 | &:not(.v-toolbar--fixed) .v-toolbar__content { 7 | margin-left: $toolbar-margin-left; 8 | } 9 | 10 | @include toolbar-colors('bg-danger', $brand-danger); 11 | @include toolbar-colors('bg-warning', $brand-warning); 12 | @include toolbar-colors('bg-primary', $brand-primary); 13 | @include toolbar-colors('bg-info', $brand-info); 14 | @include toolbar-colors('bg-success', $brand-success); 15 | @include toolbar-colors('bg-default', $gray-light); 16 | 17 | &[class*="bg-"] { 18 | .v-toolbar__title, 19 | .v-btn__content, 20 | .v-ripple__container { 21 | color: $white !important; 22 | } 23 | } 24 | 25 | .v-toolbar__content { 26 | min-height: $toolbar-min-height; 27 | padding-bottom: $padding-small; 28 | padding-top: $padding-small; 29 | 30 | .v-toolbar__title { 31 | font-size: $font-size-toolbar-title; 32 | letter-spacing: unset; 33 | color: $black-color; 34 | } 35 | 36 | .v-toolbar__items { 37 | & > div { 38 | padding: 0 !important; 39 | } 40 | 41 | .toolbar-items{ 42 | align-items: center; 43 | border-radius: $border-radius-base; 44 | display: flex; 45 | min-height: $list-item-height; 46 | padding: $padding-small $padding-general; 47 | 48 | .v-badge__badge { 49 | border: $badge-border !important; 50 | font-size: $font-size-mini; 51 | height: $badge-dimension; 52 | line-height: normal; 53 | right: -7px; 54 | top: -12px; 55 | width: $badge-dimension; 56 | font-weight: $font-weight-base; 57 | } 58 | 59 | .v-icon { 60 | font-size: $font-size-toolbar-items; 61 | } 62 | } 63 | 64 | .v-input { 65 | margin: 0 !important; 66 | padding: 0; 67 | } 68 | } 69 | } 70 | } 71 | 72 | @media all and (max-width: 990px) { 73 | .v-toolbar:not(.v-toolbar--fixed) .v-toolbar__content { 74 | margin-left: 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_tooltips.scss: -------------------------------------------------------------------------------- 1 | .v-tooltip__content { 2 | background: $white; 3 | padding: $padding-small $padding-general; 4 | min-width: $tooltip-min-width; 5 | max-width: $tooltip-max-width; 6 | color: $gray !important; 7 | font-size: $font-size-small; 8 | font-weight: $font-weight-base; 9 | @include shadow-8dp(); 10 | text-align: center; 11 | 12 | &:after{ 13 | position: absolute; 14 | bottom: -$tooltip-border; 15 | height: 0; 16 | left: 0; 17 | right: 0; 18 | margin-left: auto; 19 | margin-right: auto; 20 | width: $tooltip-border; 21 | vertical-align: $tooltip-arrow-align; 22 | content: ""; 23 | } 24 | 25 | &.top:after { 26 | border-top: $tooltip-border solid $white; 27 | border-right: $tooltip-border solid transparent; 28 | border-bottom: 0; 29 | border-left: $tooltip-border solid transparent; 30 | } 31 | 32 | &.bottom:after{ 33 | top: -$tooltip-border; 34 | border-top: 0; 35 | border-right: $tooltip-border solid transparent; 36 | border-bottom: $tooltip-border solid $white; 37 | border-left: $tooltip-border solid transparent; 38 | } 39 | &.left:after{ 40 | margin-right: 0; 41 | right: -$tooltip-border; 42 | top: $tooltip-top-border-pos; 43 | border-top: $tooltip-border solid transparent; 44 | border-bottom: $tooltip-border solid transparent; 45 | border-left: $tooltip-border solid $white; 46 | } 47 | &.right:after{ 48 | margin-left: 0; 49 | left: -$tooltip-border; 50 | top: $tooltip-top-border-pos; 51 | border-top: $tooltip-border solid transparent; 52 | border-bottom: $tooltip-border solid transparent; 53 | border-right: $tooltip-border solid $white; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/styles/material-dashboard/_typography.scss: -------------------------------------------------------------------------------- 1 | body, h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4 { 2 | font-family: $font-family-sans-serif !important; 3 | font-weight: $font-weight-lighter !important; 4 | line-height: $line-height-base !important; 5 | } 6 | 7 | h1,h2,h3,.h1,.h2,.h3{ 8 | margin-top: $margin-general + 5; 9 | margin-bottom: $margin-small; 10 | } 11 | 12 | h4,h5,h6,.h4,.h5,.h6{ 13 | margin-top: $margin-small; 14 | margin-bottom: $margin-small; 15 | } 16 | 17 | h1, .h1 { 18 | font-size: $font-size-h1 !important; 19 | line-height: 1.15em !important; 20 | } 21 | h2, .h2{ 22 | font-size: $font-size-h2 !important; 23 | line-height: $line-height-base !important; 24 | } 25 | h3, .h3{ 26 | font-size: $font-size-h3 !important; 27 | line-height: 1.4em !important; 28 | } 29 | h4, .h4{ 30 | font-size: $font-size-h4 !important; 31 | line-height: $line-height-base !important; 32 | } 33 | h5, .h5 { 34 | font-size: $font-size-h5 !important; 35 | line-height: 1.55em !important; 36 | margin-bottom: $margin-general; 37 | } 38 | h6, .h6{ 39 | font-size: $font-size-h6 !important; 40 | text-transform: uppercase; 41 | font-weight: $font-weight-bolder; 42 | } 43 | 44 | p{ 45 | font-size: $font-paragraph !important; 46 | margin: 0 0 $margin-small; 47 | } 48 | 49 | h2.title{ 50 | margin-bottom: $margin-general * 2; 51 | } 52 | 53 | .description, 54 | .card-description, 55 | .footer-big p{ 56 | color: $gray-light; 57 | } 58 | 59 | .text-warning { 60 | color: $brand-warning !important; 61 | } 62 | .text-primary { 63 | color: $brand-primary !important; 64 | } 65 | .text-danger { 66 | color: $brand-danger !important; 67 | } 68 | .text-success { 69 | color: $brand-success !important; 70 | } 71 | .text-info { 72 | color: $brand-info !important; 73 | } 74 | .text-gray{ 75 | color: $gray-light !important; 76 | } 77 | -------------------------------------------------------------------------------- /src/util/add-event.js: -------------------------------------------------------------------------------- 1 | function addEvent(obj, type, fn) { 2 | if (obj.attachEvent) { //ie 3 | obj.attachEvent('on' + type, function () { 4 | fn.call(obj); 5 | }) 6 | } else { 7 | obj.addEventListener(type, fn, false); 8 | } 9 | } 10 | export default addEvent; -------------------------------------------------------------------------------- /src/util/bvidDecode.js: -------------------------------------------------------------------------------- 1 | function bvidDecode($bvid) { 2 | $bvid = $bvid.replace(/^bv1/i, ""); 3 | var $map = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"; 4 | var $idx = [8, 7, 0, 5, 1, 3]; 5 | var $aid = 0; 6 | for (var $i = 0; $i < 6; $i++) { 7 | $aid += $map.indexOf($bvid[$idx[$i]]) * Math.pow(58, $i); 8 | } 9 | var $result = ($aid - 8728348608) ^ 177451812; 10 | return $result > 0 ? $result : ""; 11 | } 12 | export default bvidDecode; 13 | -------------------------------------------------------------------------------- /src/util/bvidEncode.js: -------------------------------------------------------------------------------- 1 | var table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF"; 2 | var tr = {}; 3 | for (let i = 0; i < 58; i++) { 4 | tr[table[i]] = i; 5 | } 6 | var s = [11, 10, 3, 8, 4, 6]; 7 | var xor = 177451812, 8 | add = 8728348608; 9 | 10 | function bvidEncode(x) { 11 | if (x == "") { 12 | return ""; 13 | } 14 | x = (x ^ xor) + add; 15 | var r = "BV1 4 1 7 ".split(""); 16 | for (let i = 0; i < 6; i++) { 17 | r[s[i]] = table[Math.floor(x / Math.pow(58, i)) % 58]; 18 | } 19 | return r.join("").replace("BV", ""); 20 | } 21 | export default bvidEncode; 22 | -------------------------------------------------------------------------------- /src/util/format-number.js: -------------------------------------------------------------------------------- 1 | function formatNumber(num) { 2 | let absNum = Math.abs(num); 3 | let postfix = ""; 4 | if (absNum >= 100000000) { 5 | postfix = "亿"; 6 | num /= 100000000; 7 | } else if (absNum >= 10000000) { 8 | postfix = "千万"; 9 | num /= 10000000; 10 | } else if (absNum >= 10000) { 11 | postfix = "万"; 12 | num /= 10000; 13 | } else { 14 | return num; 15 | } 16 | num = num.toFixed(1); 17 | let [int, dec] = num.split("."); 18 | let inter = ""; 19 | while (int != "") { 20 | inter = int.slice(-3) + "," + inter; 21 | int = int.slice(0, -3); 22 | } 23 | if (dec == "0") { 24 | return `${inter.slice(0, -1)}${postfix}`; 25 | } else { 26 | return `${inter.slice(0, -1)}.${dec}${postfix}`; 27 | } 28 | } 29 | export default formatNumber; 30 | -------------------------------------------------------------------------------- /src/util/is-mobile.js: -------------------------------------------------------------------------------- 1 | function judgeMobile() { 2 | var userAgentInfo = navigator.userAgent; 3 | var Agents = [ 4 | "Android", 5 | "iPhone", 6 | "SymbianOS", 7 | "Windows Phone", 8 | "iPad", 9 | "iPod" 10 | ]; 11 | var flag = false; 12 | for (var v = 0; v < Agents.length; v++) { 13 | if (userAgentInfo.indexOf(Agents[v]) > 0) { 14 | flag = true; 15 | break; 16 | } 17 | } 18 | return flag; 19 | } 20 | module.exports = { 21 | judgeMobile 22 | }; 23 | -------------------------------------------------------------------------------- /src/util/scroll-animation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 动画垂直滚动到页面指定位置 3 | * @param { Number } currentY 当前位置 4 | * @param { Number } targetY 目标位置 5 | */ 6 | function scrollAnimation(value, targetID) { 7 | var targetDoc = document.getElementById(targetID); 8 | var target = targetDoc.scrollLeft + value; 9 | var max = targetDoc.scrollWidth - targetDoc.offsetWidth; 10 | 11 | if (max <= target) { 12 | target = max; 13 | } 14 | if (target < 0) { 15 | target = 0; 16 | } 17 | let count = 0; 18 | let int = setInterval(() => { 19 | let delta = Math.abs(document.getElementById(targetID).scrollLeft - target); 20 | if (delta > Math.abs(value / 10) && count++ < 10) { 21 | document.getElementById(targetID).scrollLeft += value / 10; 22 | } else { 23 | document.getElementById(targetID).scrollLeft = target; 24 | clearInterval(int); 25 | } 26 | }, 1); 27 | } 28 | module.exports = { 29 | scrollAnimation 30 | }; 31 | -------------------------------------------------------------------------------- /src/views/Android.vue: -------------------------------------------------------------------------------- 1 | 35 | 46 | -------------------------------------------------------------------------------- /src/views/HomeSite.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/views/Test.vue: -------------------------------------------------------------------------------- 1 | 16 | 39 | -------------------------------------------------------------------------------- /src/views/VersusPage.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /src/views/admin/Layout.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/views/master/About.vue: -------------------------------------------------------------------------------- 1 | 73 | -------------------------------------------------------------------------------- /src/views/master/FAQ.vue: -------------------------------------------------------------------------------- 1 | 73 | 82 | 94 | -------------------------------------------------------------------------------- /src/views/master/User.vue: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /src/views/master/download/App.vue: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /src/views/master/rank/Up.vue: -------------------------------------------------------------------------------- 1 | 62 | 97 | -------------------------------------------------------------------------------- /src/views/master/tools/DecodeBV.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/views/master/tools/EncodeAV.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/views/master/tools/Index.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /src/views/master/tools/VideoDownload.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/master/user/AuthorGroup.vue: -------------------------------------------------------------------------------- 1 | 21 | 28 | -------------------------------------------------------------------------------- /src/views/master/user/AuthorGroupList.vue: -------------------------------------------------------------------------------- 1 | 26 | 68 | -------------------------------------------------------------------------------- /src/views/master/user/FavoriteAuthorList.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 85 | -------------------------------------------------------------------------------- /src/views/master/user/FavoriteVideoList.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /src/views/master/user/Record.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 79 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jannchie/biliob-frontend/f0af0b151d8585093089a3d556d260498550825c/static/.gitkeep -------------------------------------------------------------------------------- /static/app/android.json: -------------------------------------------------------------------------------- 1 | { 2 | "$jason": { 3 | "head": { 4 | "title": "BiliOB观测者" 5 | }, 6 | "body": { 7 | "background": { 8 | "type": "html", 9 | "url": "https://biliob.com", 10 | "action": { 11 | "trigger": "visit" 12 | } 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | let a = [1, 2, 3, 4, 5] 2 | a.push(...[1, 2, 3, 4, 5]) -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { shallowMount } from "@vue/test-utils"; 3 | import HelloWorld from "@/components/HelloWorld.vue"; 4 | 5 | describe("HelloWorld.vue", () => { 6 | it("renders props.msg when passed", () => { 7 | const msg = "new message"; 8 | const wrapper = shallowMount(HelloWorld, { 9 | propsData: { msg } 10 | }); 11 | expect(wrapper.text()).to.include(msg); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const CompressionWebpackPlugin = require("compression-webpack-plugin"); 2 | const productionGzipExtensions = ["js", "css"]; 3 | module.exports = { 4 | // chainWebpack: config => { 5 | // config.optimization.minimize(true);//进行代码压缩 6 | // }, 7 | 8 | // 是否为生产环境构建生成 source map? 9 | productionSourceMap: false, 10 | runtimeCompiler: true, 11 | lintOnSave: false, 12 | // 简单Gzip 13 | configureWebpack: { 14 | plugins: [ 15 | new CompressionWebpackPlugin({ 16 | algorithm: "gzip", 17 | test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"), 18 | threshold: 1024, 19 | minRatio: 0.8, 20 | }), 21 | ], 22 | entry: { 23 | app: ["babel-polyfill", "@/main.js"], 24 | }, 25 | }, 26 | pwa: { 27 | themeColor: "#333", 28 | name: "BiliOB观测者", 29 | msTileColor: "#333", 30 | workboxPluginMode: "InjectManifest", 31 | importWorkboxFrom: "disabled", 32 | importScripts: "https://cdn.myun.info/workbox-v4.3.1/workbox-sw.js", 33 | workboxOptions: { 34 | swSrc: "src/service-worker.js", 35 | exclude: ["index.html"], 36 | }, 37 | }, 38 | transpileDependencies: [ 39 | "vue-echarts", 40 | "bresize-detector", 41 | "vuetify", 42 | "resize-detector", 43 | ], 44 | }; 45 | --------------------------------------------------------------------------------