├── .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 | [](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 |
2 |
3 |
4 |
5 |
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 |
2 | 首屏
3 |
4 |
5 |
39 |
40 |
62 |
--------------------------------------------------------------------------------
/src/components/DetailMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/src/components/Home/PopularTag.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
64 |
--------------------------------------------------------------------------------
/src/components/Recommend.vue:
--------------------------------------------------------------------------------
1 |
2 | 推荐
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/TheHomeCarousel.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
14 |
18 | {{ item.title }}
19 |
20 |
21 |
22 |
23 |
47 |
--------------------------------------------------------------------------------
/src/components/Tracer/NoRole.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 | mdi-alert-decagram
11 |
12 |
13 | 无访问权限
14 |
15 |
16 | 该页面只允许管理员访问,请确保已经登录或者拥有管理员身份。
17 |
18 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/src/components/Tracer/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
22 |
23 |
24 | ©
25 | {{ new Date().getFullYear() }}
26 | Jannchie,
30 | made with
31 | mdi-heart
35 | for the Bilibili
36 |
37 |
38 |
39 |
40 |
51 |
52 |
61 |
--------------------------------------------------------------------------------
/src/components/Tracer/layout/Nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | BiliOB观测者
16 |
17 |
18 |
19 | 管理系统
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
92 |
--------------------------------------------------------------------------------
/src/components/Tracer/layout/Toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ title }}
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/src/components/Tracer/layout/View.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
27 |
--------------------------------------------------------------------------------
/src/components/Tracer/nav/item.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | {{ icon }}
11 |
12 |
13 |
14 | {{ name }}
15 |
16 |
17 |
18 |
19 |
37 |
--------------------------------------------------------------------------------
/src/components/admin/CrawlCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
35 |
--------------------------------------------------------------------------------
/src/components/admin/GraphCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
16 |
17 |
18 | {{ title }}
19 |
20 |
21 | {{ subTitle }}
22 |
23 |
24 |
25 |
35 |
52 |
--------------------------------------------------------------------------------
/src/components/admin/StatsCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 | {{ icon }}
13 |
14 |
15 |
16 | {{ title }}
17 |
18 | {{ value }}{{ smallValue }}
19 |
20 |
21 |
25 |
26 |
30 | {{ subIcon }}
31 | {{ subText }}
32 |
33 |
34 |
35 |
49 |
68 |
--------------------------------------------------------------------------------
/src/components/aside/AuthorInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
13 |
18 |
19 |
20 |
21 |
22 | {{ authorData.name }}
23 |
24 |
28 |
32 |
33 |
34 | 粉丝数:{{ authorData.cFans }}
35 |
36 |
37 | {{ authorData.official }}
38 |
39 |
40 |
41 |
42 |
43 |
77 |
78 |
94 |
--------------------------------------------------------------------------------
/src/components/biliob/ActiveCodeTextField.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
72 |
73 |
101 |
--------------------------------------------------------------------------------
/src/components/biliob/DarkInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
15 | {{ title }}
16 |
17 |
18 |
19 |
20 |
21 |
51 |
86 |
--------------------------------------------------------------------------------
/src/components/biliob/InfoDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
18 | mdi-power
19 | 服务器临时维护中
20 |
21 |
22 |
23 | 为了提供更好的服务,
24 |
25 | 服务器正在升级。
26 |
27 | 请稍后进行访问。
28 |
29 | 你也可以通过邮件向我报告问题。
30 |
31 |
32 | 邮箱地址:jannchie@gmail.com
33 |
34 |
35 |
36 |
37 | 很抱歉对您造成了不便。
38 |
39 |
42 |
43 |
44 |
45 |
63 |
72 |
--------------------------------------------------------------------------------
/src/components/biliob/Kanban.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
65 |
66 |
--------------------------------------------------------------------------------
/src/components/biliob/Main.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
29 |
30 |
31 |
32 |
47 |
48 |
49 |
50 |
51 |
52 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/biliob/Notice.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
12 |
13 |
14 |
15 | {{ date }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 | mdi-window-close
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
60 |
61 |
--------------------------------------------------------------------------------
/src/components/biliob/Notification.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ message }}
9 |
10 |
11 |
17 | Close
18 |
19 |
20 |
21 |
22 |
35 |
40 |
--------------------------------------------------------------------------------
/src/components/biliob/Sheet.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | {{ title }}
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
33 |
--------------------------------------------------------------------------------
/src/components/biliob/Slide.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
21 | mdi-chevron-right 更多..
22 |
23 |
24 |
25 |
26 |
51 |
66 |
--------------------------------------------------------------------------------
/src/components/biliob/SlideCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
30 |
--------------------------------------------------------------------------------
/src/components/biliob/Title.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/biliob/agenda/StateChip.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ text }}
10 |
11 |
12 |
13 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/biliob/agenda/TypeChip.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ text }}
10 |
11 |
12 |
13 |
46 |
47 |
--------------------------------------------------------------------------------
/src/components/biliob/author/AchievementSlideCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
13 |
14 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
31 | {{ achievement.author.name }} / {{ achievement.name }}
32 |
33 |
37 | {{ achievement.desc }}
38 |
39 |
43 | {{
44 | $timeFormat(
45 | achievement.date.replace("+00:00", "+0800"),
46 | "YYYY-MM-DD"
47 | )
48 | }}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
88 |
--------------------------------------------------------------------------------
/src/components/biliob/author/Achievements.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
20 | {{ achievement.name }}
21 |
22 |
26 | {{ achievement.desc }}
27 |
28 | ( 观测检定值
29 | {{ $numberFormat(achievement.value, false, 0) }})
30 |
31 |
35 | {{
36 | achievement.date == undefined
37 | ? "遥远的过去"
38 | : $timeFormat(
39 | achievement.date.replace("+00:00", "+0800"),
40 | "YYYY-MM-DD HH:mm:ss"
41 | )
42 | }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
100 |
--------------------------------------------------------------------------------
/src/components/biliob/author/ListItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
16 |
17 | {{ author.name }}
18 |
19 |
20 |
24 |
28 | mdi-flash-circle
29 |
33 | {{ author.official }}
34 |
35 |
39 |
43 | mdi-account-multiple
44 | {{ $numberFormat(author.cFans) }}({{
45 | author.cRate > 0
46 | ? "+" + $numberFormat(author.cRate)
47 | : $numberFormat(author.cRate)
48 | }})
52 | mdi-thumb-up
53 | {{ $numberFormat(author.cLike) }}
54 |
58 | mdi-play
59 | {{ $numberFormat(author.cArchiveView) }}
60 |
61 |
62 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
87 |
--------------------------------------------------------------------------------
/src/components/biliob/bangumi/Copyright.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 | {{ displayCopyright }} {{ charge }}
11 |
12 |
13 |
14 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/biliob/bangumi/Status.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 | {{ displayText }}
11 |
12 |
13 |
14 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/biliob/bangumi/type.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 | {{ displayType }}
11 |
12 |
13 |
14 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/components/biliob/comment/Input.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $db.user.title }} (#{{ $db.user.rank }}) / {{ $db.user.nickName }}:
7 |
8 |
9 |
10 |
11 |
16 |
21 | mdi-send
22 |
23 |
24 |
25 |
29 | 登陆后,且经验值大于100才能发表观测记录!
30 |
31 |
32 |
38 |
39 | mdi-login
40 | 前往登陆页面
41 |
42 |
43 |
44 |
45 |
95 |
--------------------------------------------------------------------------------
/src/components/biliob/compare/AuthorInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
19 | {{ author.name }}
20 |
21 | {{ author.official }}
22 |
23 |
24 |
30 |
31 | 粉丝数
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/components/biliob/end.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | END
9 |
10 |
11 | 非常感谢各位观测者使用这个网站。从2018年8月3日到2020年11月10日,我凭一己之力已经维护了两年多。
12 |
13 |
14 | 观测站的注册观测者达2万人,月活20万,最多同时在线人数超过1000人。服务器也曾宕机数次,有时也会宕机数天,但是最近增多了服务器开支,小站越发稳定了。
15 |
16 |
17 | 遗憾的是截至目前未曾盈利,收到捐款8949.74元,净亏损服务器维护费9087.28元。
18 |
19 |
20 |
21 | 观测站曾经提供了不少有用的服务。在这里观测者们能够观测大部分知名UP主的历史数据,能够聚合B站的各大关键词提供指数查询,能够判断视频是否被限流,能够欣赏涨掉粉榜,喝最棒的彩,吃最香的瓜。
22 |
23 |
24 | 很多我喜欢的UP主,比如毕导、Lks都使用过我的网站,感谢你们的宣传,令我不胜荣幸。
25 |
26 |
27 | 今天收到了哔哩哔哩侵权告知函。老实说已有心里准备——爬虫项目本就游走在法律的边缘,如果B站想要小站关闭,那我这里确实应该无条件关站。
32 |
33 |
34 | 我会暂时保留除了数据之外的接口,大家的账号并不会被注销。你仍然可以登录、签到、甚至发布观测纪录,尽管这里已经不再有什么好观测的了。
35 |
36 |
37 | 与此同时,大部分数据访问的接口都会被我关闭。在网络上,他们会同未曾存在过一般。源代码是公开的,在我的github中可以找到,有心人是可以做一个完美复刻版的,但那就与我无关了。
38 |
39 |
40 | 虽然观测站停止展示数据,但我的毕生所学并未就此消逝。欢迎咨询或者商务合作:jannchie@gmail.com
41 |
42 |
48 | 前往见齐的B站空间
49 |
50 |
56 | 前往见齐的GitHub主页
57 |
58 |
64 | 加入粉丝群
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
--------------------------------------------------------------------------------
/src/components/biliob/user/Info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ user.title }}
5 |
6 |
7 | {{ user.role }}
8 |
9 | (#{{ user.ban ? "-" : user.rank }}) / {{ user.nickName }}
10 |
11 |
12 |
13 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/components/biliob/user/rank/Title.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 | {{
12 | icon
13 | }}
14 | {{ title }}
15 |
16 |
17 |
18 |
19 |
20 |
28 |
39 |
--------------------------------------------------------------------------------
/src/components/common/BiliobFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | © {{ new Date().getFullYear() }} - Jannchie见齐
7 |
8 |
9 |
10 |
11 |
14 |
--------------------------------------------------------------------------------
/src/components/common/BottomNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
16 | 视频追踪
17 | mdi-video-outline
18 |
19 |
26 | UP主追踪
27 | mdi-account-search-outline
28 |
29 |
36 | 排行榜
37 | mdi-chart-histogram
38 |
39 |
40 |
41 |
76 |
--------------------------------------------------------------------------------
/src/components/common/ChevronBudget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ icon }}{{ displayValue }}
7 |
8 |
9 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/common/CircleIconBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
13 |
14 |
15 |
35 |
43 |
--------------------------------------------------------------------------------
/src/components/common/CreditBadget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 积分
8 |
9 |
13 | {{ credit }}
14 |
15 |
16 |
17 |
50 |
--------------------------------------------------------------------------------
/src/components/common/ExpBadget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | {{ name }}
8 |
9 |
13 | {{ exp }}
14 |
15 |
16 |
17 |
64 |
--------------------------------------------------------------------------------
/src/components/common/FavoriteBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 | mdi-star-outline
13 |
14 |
23 | mdi-star
24 |
25 |
26 |
27 |
78 |
--------------------------------------------------------------------------------
/src/components/common/FocusBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 | mdi-heart-outline
12 |
13 |
21 | mdi-heart
22 |
23 |
24 |
25 |
74 |
--------------------------------------------------------------------------------
/src/components/common/LevelIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | Lv.0
9 |
10 |
16 | Lv.1
17 |
18 |
24 | Lv.2
25 |
26 |
32 | Lv.3
33 |
34 |
40 | Lv.4
41 |
42 |
48 | Lv.5
49 |
50 |
56 | Lv.6
57 |
58 |
59 |
64 |
--------------------------------------------------------------------------------
/src/components/common/MyBadget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ leftText }}
7 | {{ rightText }}
11 |
12 |
13 |
14 |
24 |
--------------------------------------------------------------------------------
/src/components/common/NextBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 | {{ nextBtnText }}
12 |
13 |
14 |
40 |
--------------------------------------------------------------------------------
/src/components/common/ObserveStatus.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | {{ icon }}
9 |
10 | {{ text }}
11 |
12 |
13 |
14 |
61 |
--------------------------------------------------------------------------------
/src/components/common/RoleBadget.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mdi-account {{ role }}
4 |
5 |
6 |
24 |
25 |
32 |
--------------------------------------------------------------------------------
/src/components/common/SexIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 | mdi-gender-male
8 |
9 |
14 | mdi-gender-female
15 |
16 |
21 | mdi-gender-male-female
22 |
23 |
24 |
29 |
--------------------------------------------------------------------------------
/src/components/common/VOdometer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
62 |
72 |
--------------------------------------------------------------------------------
/src/components/common/VSearchForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
17 |
18 |
19 |
65 |
--------------------------------------------------------------------------------
/src/components/helper/Offset.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
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 |
2 |
3 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
57 |
58 |
74 |
--------------------------------------------------------------------------------
/src/components/main/AuthorMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/src/components/main/SiteChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
45 |
46 |
52 |
--------------------------------------------------------------------------------
/src/components/main/VideoDetailPieChart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
46 |
47 |
53 |
--------------------------------------------------------------------------------
/src/components/main/VideoDetailTitle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
17 |
18 |
22 |
23 |
24 |
25 |
29 | {{ title }}
30 |
31 |
AV{{
35 | $route.params.aid
36 | }}
37 |
38 |
BV{{ bv }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
63 |
64 |
85 |
--------------------------------------------------------------------------------
/src/components/main/VideoMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
21 |
--------------------------------------------------------------------------------
/src/components/material/ChartCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
31 |
--------------------------------------------------------------------------------
/src/components/material/Notification.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
29 |
30 |
36 |
--------------------------------------------------------------------------------
/src/components/material/SpiderCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
14 |
15 | {{ icon }}
16 |
17 |
18 |
19 |
23 |
24 | {{ value }} {{ smallValue }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 | {{ subIcon }}
39 |
40 |
45 |
46 |
47 |
48 |
49 |
92 |
93 |
128 |
--------------------------------------------------------------------------------
/src/components/material/StatsCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
14 |
15 | {{ icon }}
16 |
17 |
18 |
19 |
23 |
24 | {{ value }} {{ smallValue }}
25 |
26 |
27 |
28 |
29 |
34 | {{ subIcon }}
35 |
36 | {{ subText }}
40 |
41 |
42 |
43 |
44 |
87 |
88 |
123 |
--------------------------------------------------------------------------------
/src/components/video/RecommendList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
18 |
19 | {{ item.title }}
20 |
21 |
22 | mdi-account
23 |
24 | {{ item.author }}
25 |
26 |
27 |
28 |
29 |
30 |
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 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 | 打开手机,扫描上方二维码即可下载安卓端BILIOB观测者APP~
16 |
17 |
18 |
25 | 手机端网站点我跳转到下载链接
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
46 |
--------------------------------------------------------------------------------
/src/views/HomeSite.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | mdi-link-variant-off
8 |
9 |
10 | 404
11 |
12 |
13 | 不存在的
14 |
15 |
16 |
17 | 服务器没有找到该页面。
18 |
该页面有可能正在施工中,也有可能是您来到了没有知识的荒原。
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ userAgent }}
5 |
6 |
7 |
14 |
15 |
16 |
39 |
--------------------------------------------------------------------------------
/src/views/VersusPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
28 |
--------------------------------------------------------------------------------
/src/views/admin/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/master/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 本网站所有数据来自于Bilibili。
8 |
本网站的目的是搜集并观测B站的UP主、视频或番剧等数据。
9 |
一方面,这些数据能够给UP主作为参考,了解自己作品的受欢迎程度。从而帮助作者创作更加优秀的作品。
10 |
另一方面,通过观测这些数据,能够了解到B站的最新动态。这些数据本身就非常具有观赏性。
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 前端
20 | 使用Vue.js
21 |
22 | 应用了Vue全家桶,使用VueCli
23 | 3.0手脚架,集成Vuex前端状态管理、vue-router前端路由。
24 |
25 |
26 | 应用了PWA(渐进式Web应用程序),能够在PC端和Web端获得原生应用般的体验,前提是使用Chrome、Safari、edge等主流浏览器。
27 |
28 | 图表库使用了Echart.js。由百度开源。
29 |
30 | 前端UI组件库采用的是Vuetify.js,由歪果仁维护。
31 | 这个前端UI组件库的设计语言采用Material Design。
32 | 是一种谷歌公司推出的移动端优先的设计语言。
33 |
34 |
35 | 后端
36 | 后端采用Springboot构建微服务。
37 | 采用RESTful风格的API。
38 | 权限认证采用Shrio框架。
39 |
40 |
41 | 数据库
42 |
43 | 数据库采用MongoDB。据说存取速度快,但是看上去巨占内存,
44 | 存取爬虫爬取的结构奇葩的数据或许用这样的非关系数据库更好。
45 |
46 |
47 | 缓存
48 |
49 | 缓存数据库采用的是Redis。 Redis还用于分布式爬虫的消息队列。
50 |
51 |
52 | 持续集成
53 | 后端持续集成目前采用的是Jenkins。前端目前使用Travis-CI。
54 |
55 | 其他支持
56 | 服务器由阿里云、腾讯云提供支持,使用了CDN内容分发网络服务。
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 这个网站在可以预见的未来很长一段时间内,由我个人维护。
66 | 由于技术有限、资源有限等原因,该网站可能还有很多不尽如人意的地方。
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/views/master/FAQ.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
33 |
34 |
35 |
41 |
46 |
47 |
55 |
56 | 问题{{ questionIndex + 1 }}:{{ eachQuestion.question }}
57 |
58 |
59 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
82 |
94 |
--------------------------------------------------------------------------------
/src/views/master/User.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 | mdi-account-card-details
12 | 个人信息
13 |
14 |
15 |
16 | mdi-account-group
17 | UP主群组
18 |
19 |
20 |
21 | mdi-timetable
22 | 操作记录
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/master/download/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 安卓用户
7 |
8 | 方式1:下载APK
9 |
13 |
14 | mdi-android-head
15 |
16 | 点此下载BiliOB
17 |
18 |
19 |
20 | 方式2:使用PWA技术
21 | 首先,确认浏览器拥有添加应用到主屏幕的权限。
22 |
23 | 然后,在手机中使用Chrome \ Edge \ UC 等浏览器打开该网站,
24 | 并将网站添加到主屏幕,即可获得APP。
25 |
推荐使用这种方式,无需下载,且体验优于APK。
26 |
27 |
28 |
29 | 苹果用户
30 |
31 | 使用PWA技术
32 | 使用Safari(苹果自带浏览器)打开该网站;
33 | 点击地址栏右侧的"分享"按钮;
34 | 在弹出的菜单中点击添加到主屏幕按钮;
35 | 点击菜单右上角的添加按钮,即可获得APP。
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/views/master/rank/Up.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
{{ eachData.name }}
23 |
24 |
28 | mdi-flash-circle
29 |
33 | {{ eachData.official }}
34 |
35 |
36 |
37 |
38 |
42 |
48 |
53 | {{ getIcon() }}
54 |
55 | {{ $numberFormat(Math.abs(eachData.cRate), false) }}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
97 |
--------------------------------------------------------------------------------
/src/views/master/tools/DecodeBV.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mdi-code-equal
9 |
10 | BV号转AV号
11 |
12 |
13 | 将BV号解码为av号
14 |
15 | Powered By
16 |
20 | mcfx@zhihu
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/views/master/tools/EncodeAV.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mdi-code-equal
9 |
10 | av号转BV号
11 |
12 |
13 | 将av号编码为BV号
14 |
15 | Powered By
16 |
20 | mcfx@zhihu
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/views/master/tools/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
13 |
14 | mdi-code-equal
15 |
16 | BV号转av号
17 |
18 |
19 | 一个BV号的解码器
20 |
21 | Powered By
22 |
26 | mcfx@zhihu
27 |
28 |
29 |
30 |
31 |
35 |
39 |
40 |
41 | mdi-code-equal
42 |
43 | av号转BV号
44 |
45 |
46 | 一个av号的编码器
47 |
48 | Powered By
49 |
53 | mcfx@zhihu
54 |
55 |
56 |
57 |
58 |
71 |
72 |
73 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/src/views/master/tools/VideoDownload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mdi-download
9 |
10 | 视频缓存
11 |
12 |
13 | 获取视频的下载地址。
14 |
15 | Powered By you-get
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/views/master/user/AuthorGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | mdi-star
10 | 我收藏的
11 |
12 |
13 |
14 | mdi-pencil
15 | 我维护的
16 |
17 |
18 |
19 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/views/master/user/AuthorGroupList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
14 |
20 |
23 |
24 |
25 |
26 |
68 |
--------------------------------------------------------------------------------
/src/views/master/user/FavoriteAuthorList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
25 |
32 | 上一页
33 |
34 |
35 |
38 |
45 | 下一页
46 |
47 |
48 |
49 |
50 |
51 |
52 |
85 |
--------------------------------------------------------------------------------
/src/views/master/user/FavoriteVideoList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
--------------------------------------------------------------------------------
/src/views/master/user/Record.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
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 |
--------------------------------------------------------------------------------