├── .circleci └── config.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── commands ├── clear-commit-msg.sh ├── deploy.sh └── preview.js ├── jest.config.js ├── package.json ├── public ├── favicon.ico ├── img │ └── 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 ├── index.html ├── manifest.json ├── robots.txt └── service-worker.js ├── src ├── App.vue ├── assets │ ├── icons │ │ ├── author.svg │ │ ├── document.svg │ │ ├── explore.svg │ │ ├── github.svg │ │ └── index.js │ ├── images │ │ ├── logo.png │ │ ├── vue-cli-manage-cli-plugin.png │ │ ├── vue-cli-manage-project-dependencies.png │ │ └── vue-cli-npm.png │ └── styles │ │ ├── common.scss │ │ ├── element.scss │ │ ├── mixins.scss │ │ ├── style.scss │ │ └── variables.scss ├── components │ ├── Advertisement.vue │ ├── EditDialog.vue │ ├── Icon.vue │ ├── Instruction.vue │ ├── icons │ │ └── Arrow.vue │ └── markdown │ │ ├── Index.vue │ │ ├── MarkdownPreview.vue │ │ └── markdown.css ├── filters.js ├── global.js ├── helper │ ├── ajax.js │ ├── apis │ │ └── index.js │ ├── auth.js │ ├── document.js │ ├── index.js │ ├── lodash.js │ └── utils.js ├── main.js ├── mixins │ └── metaMixin.js ├── pages │ ├── ExploreMore.vue │ ├── Homepage.vue │ └── partials │ │ └── NotFound.vue ├── registerServiceWorker.js ├── router │ ├── beforeEachHooks.js │ ├── commonRoutes.js │ ├── index.js │ └── routes │ │ ├── index.js │ │ └── main.js └── store │ ├── index.js │ └── modules │ ├── demo │ ├── actions.js │ ├── getters.js │ ├── index.js │ └── mutations.js │ └── index.js ├── tests └── unit │ ├── .eslintrc.js │ └── Arrow.spec.js ├── vue.config.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:8.9.1 11 | # circleci branch only master 12 | branches: 13 | only: 14 | - master 15 | - /rc-.*/ 16 | 17 | # Specify service dependencies here if necessary 18 | # CircleCI maintains a library of pre-built images 19 | # documented at https://circleci.com/docs/2.0/circleci-images/ 20 | # - image: circleci/mongo:3.4.4 21 | 22 | working_directory: ~/repo 23 | 24 | steps: 25 | - checkout 26 | 27 | # Download and cache dependencies 28 | - restore_cache: 29 | keys: 30 | - v1-dependencies-{{ checksum "package.json" }} 31 | # fallback to using the latest cache if no exact match is found 32 | - v1-dependencies- 33 | 34 | - run: yarn install 35 | 36 | - save_cache: 37 | paths: 38 | - node_modules 39 | key: v1-dependencies-{{ checksum "package.json" }} 40 | 41 | # run tests! 42 | # - run: yarn test 43 | 44 | # run build 45 | # - run: git config --global user.email "jeffygisgreat@gmail.com" 46 | # - run: git config --global user.name "nicejade" 47 | # - run: yarn run deploy -------------------------------------------------------------------------------- /.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 | # Test 24 | tests/coverage 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist 4 | 5 | .circleci -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 JadeYang(杨琼璞) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Awesome Vue-Cli3 Example

4 | 5 |
6 | 7 | 🦅 (Vue Webpack Vuex Vue-router Element-ui/...) out of the box 8 | 9 |
10 | 11 |
12 | 🦅 Awesome example for rapid Vue.js development using vue-cli3. 13 |
14 | 15 |
16 | 17 |
18 | 19 | Build Status 20 | 21 | 22 | node version 23 | 24 | 25 | LICENSE 26 | 27 | 28 | Prettier 29 | 30 | 31 | Chat On My Blog 32 | 33 | 34 | Author nicejade 35 | 36 |
37 | 38 | #### English | [中文](https://vue-cli3.lovejade.cn/??utm_source=awesome-vue-cli3-example) 39 | 40 | ## Goal and Philosophy 41 | 42 | To facilitate developers to use `Vue-cli3` more conveniently, and to build Web applications more efficiently and reasonably. 43 | 44 | ## Prerequisites 45 | 46 | [Node.js](https://nodejs.org/en/) (>= 8.*), Npm version 4+(Yarn preferred), and [Git](https://git-scm.com/). 47 | 48 | ## Online Demo 49 | 50 | Online Demo Page: https://vue-cli3.lovejade.cn 51 | 52 | ## Usage 53 | 54 | ```bash 55 | # 🎉 clone the project 56 | git clone https://github.com/nicejade/awesome-vue-cli3-example.git your-project-name 57 | cd your-project-name 58 | 59 | # ➕ install dependencies & start dev 60 | yarn && yarn start 61 | ``` 62 | 63 | ## Advantage 64 | 65 | This boilerplate built on [Vue-Cli3](https://github.com/vuejs/vue-cli/) inheriting the previous [vue-boilerplate-template](https://github.com/nicejade/vue-boilerplate-template) project to explore the more efficient construction for high-quality web applications (recommended to read [开箱即用的 Vue Webpack 脚手架模版](https://www.jeffjade.com/2018/05/20/140-vue-webpack-boilerplate-template/)), Some optimization items are designed, the specific list as followed: 66 | 67 | - [x] Import & configure [Vue-router](https://router.vuejs.org/zh/) to make the building of a single-page application (SPA) breeze; 68 | - [x] Import & configure [Vuex](https://vuex.vuejs.org/zh/) to handle the management of status for application development; 69 | - [x] Import [Element-ui](http://element.eleme.io/#/zh-CN) to build a website quickly without paying too much attention to the UI; 70 | - [x] Import & encapsulating [Axios](https://github.com/axios/axios) to response the Http requests more elegant; 71 | - [x] Import [Dayjs](https://github.com/iamkun/dayjs) to handle date-time correlation in a slight cost; 72 | - [x] Import & encapsulate the [Marked]() plugin so that it can be used as a rich text editor,and it also can achieve `Markdown` to draw the page due to its parsing function. 73 | - [x] Import [vue-meta](https://github.com/declandewet/vue-meta) plugin so that allows you to manage your app's meta information, much like [react-helmet](https://github.com/nfl/react-helmet) does for React. It'm awesome for `SEO`. 74 | - [x] Making the optimization based on the new features of `Webepack 4.*`. Getting the details on `vue.config.js`; 75 | - [x] Opening & using [Jest](https://jestjs.io/) to test the component with the Demo; 76 | - [x] Integrate & configure the [Prettier](https://prettier.io/) plugin to enable the team to code with better and consistent style. Getting the details on the [使用 ESLint & Prettier美化Vue代码](https://www.jeffjade.com/2018/06/18/142-beautify-vue-by-eslint-and-prettier/); 77 | - [x] Import [cli-plugin-pwa](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa) plugin,and configure in \`vue.config.js\` ,you can get started easily with [PWA](https://github.com/nicejade/nice-front-end-tutorial/blob/master/tutorial/pwa-tutorial.md) using Vue; 78 | - [x] Import the [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin) plugin to pre-render static HTML, optimizing SEO and first-screen rendering in a single-page application . 79 | - [x] Import the [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) plugin so that to get the contents of the building packages with optimization while running `Yarn analyz`. Getting the details: [Webpack 打包优化之体积篇](https://jeffjade.com/2017/08/06/124-webpack-packge-optimization-for-volume/#%E5%AE%9A%E4%BD%8D-webpack-%E5%A4%A7%E7%9A%84%E5%8E%9F%E5%9B%A0). 80 | - [x] Import the [size-plugin](https://github.com/GoogleChromeLabs/size-plugin) plugin to print the Gzip size of the Webpack asset and the changes since the last building while building the app. 81 | - [x] Import the [hard-source-webpack-plugin](https://github.com/mzgoddard/hard-source-webpack-plugin) plugin for webpack to provide an intermediate caching step for modules. It make the second build will be signficantly faster. 82 | - [x] The combination of this scaffolding with Node.js (Koa2) Nginx MondoDb Redis is integrated into Docker to make Front-End Developer build easily the entire web application.Its currently open sourced in [Docker Vue Node Nginx Mongodb Redis](https://github.com/nicejade/docker-vue-node-nginx-mongodb-redis). 83 | - [ ] Optimizing the built-in `Icon` (Svg) component so that you can receive input in different ways and extract Svg into a separate file to avoid repeated loading of resources; 84 | 85 | >**TIP**: [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin): Prerenders static HTML in a single-page application. But, it is not required. If you don't need it, you can remove it. Because it requires a lot of dependencies([puppeteer](https://github.com/GoogleChrome/puppeteer),Chromium: ~170MB Mac, ~282MB Linux, ~280MB Win) to download, this is time consuming. 86 | 87 | ## Recommended links 88 | 89 | - [**NICE LINKS**](https://nicelinks.site/?utm_source=awesome-vue-cli3-example) 90 | - [About Me](https://about.me/nicejade/?utm_source=awesome-vue-cli3-example) 91 | - [Latest Blog](https://quickapp.lovejade.cn/?utm_source=awesome-vue-cli3-example) 92 | - [First Blog](https://jeffjade.com/?utm_source=awesome-vue-cli3-example) 93 | - [Second Blog](https://nice.lovejade.cn/?utm_source=awesome-vue-cli3-example) 94 | - [Auxiliary blog](https://blog.lovejade.cn/?utm_source=awesome-vue-cli3-example) 95 | - [Weibo](http://weibo.com/jeffjade/) 96 | - [ZhiHu](https://www.zhihu.com/people/yang-qiong-pu/) 97 | - [SegmentFault](https://segmentfault.com/u/jeffjade) 98 | - [JianShu](http://www.jianshu.com/u/9aae3d8f4c3d) 99 | - [Twitter](https://twitter.com/nicejadeyang) 100 | - [Facebook](https://www.facebook.com/nice.jade.yang) 101 | 102 | ## License 103 | 104 | [MIT](http://opensource.org/licenses/MIT) 105 | 106 | Copyright (c) 2018-present, [nicejade](https://aboutme.lovejade.cn/?utm_source=awesome-vue-cli3-example). -------------------------------------------------------------------------------- /commands/clear-commit-msg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | git checkout --orphan latest_branch 6 | 7 | git add -A 8 | 9 | git commit -am "✨ initial create & commit" 10 | 11 | git branch -D master 12 | 13 | git branch -m master 14 | 15 | git push -f origin master 16 | -------------------------------------------------------------------------------- /commands/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | yarn run build 8 | 9 | # 进入生成的文件夹 10 | cd ./dist 11 | 12 | # 如果是发布到自定义域名 13 | echo 'vue-cli3.lovejade.cn' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m '🎉 update release' 18 | 19 | # 如果发布到 https://.github.io 20 | # git push -f git@github.com:/.github.io.git master 21 | 22 | # 如果发布到 https://.github.io/ 23 | git push -f https://github.com/nicejade/awesome-vue-cli3-example.git master:gh-pages 24 | cd - -------------------------------------------------------------------------------- /commands/preview.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const chalk = require('chalk') 3 | const childProcess = require('child_process') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const opn = require('opn') 7 | 8 | const app = express() 9 | 10 | const resolveRealPath = dir => { 11 | return path.join(__dirname, dir) 12 | } 13 | 14 | const entryFilePath = resolveRealPath('../dist/index.html') 15 | 16 | const openStaticServer = () => { 17 | app.use('/js', express.static(resolveRealPath('../dist/js/'))) 18 | app.use('/css', express.static(resolveRealPath('../dist/css/'))) 19 | app.use('/img', express.static(resolveRealPath('../dist/img/'))) 20 | app.use('/fonts', express.static(resolveRealPath('../dist/fonts/'))) 21 | 22 | app.get('*', function(req, res) { 23 | const content = fs.readFileSync(entryFilePath, 'utf8') 24 | res.send(content) 25 | }) 26 | 27 | app.listen(3000, function() { 28 | console.log(chalk.cyan('Example app listening on port 3000!\n')) 29 | console.log(chalk.yellow('You Can Visit: ') + chalk.green('http://localhost:3000/')) 30 | opn('http://localhost:3000') 31 | }) 32 | } 33 | 34 | const main = () => { 35 | const isExist = fs.existsSync(entryFilePath) 36 | if (isExist) { 37 | openStaticServer() 38 | } else { 39 | const commandStr = `vue-cli-service build` 40 | const output = childProcess.execSync(commandStr, { 41 | cwd: process.cwd(), 42 | timeout: 60000, 43 | encoding: 'utf8' 44 | }) 45 | openStaticServer() 46 | console.log(output) 47 | } 48 | } 49 | 50 | main() 51 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | const path = require('path') 3 | 4 | module.exports = { 5 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 6 | transform: { 7 | '^.+\\.vue$': 'vue-jest', 8 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 9 | '^.+\\.jsx?$': 'babel-jest' 10 | }, 11 | moduleNameMapper: { 12 | '^@/(.*)$': '/src/$1' 13 | }, 14 | snapshotSerializers: ['jest-serializer-vue'], 15 | testMatch: ['**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'], 16 | collectCoverage: true, 17 | collectCoverageFrom: ['src/**/*.{js,vue}', '!src/assets/**'], 18 | coverageReporters: ['html', 'text-summary'], 19 | coverageDirectory: path.resolve(__dirname, './tests/coverage') 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cli-overall-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "yarn serve", 7 | "serve": "vue-cli-service serve --open", 8 | "build": "vue-cli-service build", 9 | "watcher": "onchange '**/*.md' \"**/**/*.{js,css,scss,vue}\" -- prettier --write {{changed}}", 10 | "prettier": "prettier --write \"**/**/*.{js,css,scss,vue}\"", 11 | "lint": "vue-cli-service lint", 12 | "test:unit": "vue-cli-service test:unit", 13 | "deploy": "bash ./commands/deploy.sh", 14 | "analyz": "NODE_ENV=production npm_config_report=true npm run build", 15 | "clear-commit-msg": "bash ./commands/clear-commit-msg.sh", 16 | "eslint-fix": "eslint src/**/**/*.vue --fix", 17 | "format-code": "prettier-eslint --write \"src/**/*.js\" \"src/**/*.vue\"", 18 | "precommit-msg": "echo '🐉 Start pre-commit checks...' && exit 0", 19 | "preview": "node ./commands/preview.js" 20 | }, 21 | "dependencies": { 22 | "axios": "0.21.1", 23 | "dayjs": "^1.8.17", 24 | "element-ui": "2.4.6", 25 | "js-cookie": "^2.2.1", 26 | "lodash": "^4.17.15", 27 | "marked": "0.7.0", 28 | "node-sass": "4.9.0", 29 | "q": "^1.5.1", 30 | "register-service-worker": "^1.0.0", 31 | "vue": "^2.5.17", 32 | "vue-meta": "^2.3.1", 33 | "vue-router": "^3.1.3", 34 | "vuex": "^3.1.2" 35 | }, 36 | "devDependencies": { 37 | "@vue/cli-plugin-babel": "^4.0.5", 38 | "@vue/cli-plugin-eslint": "^4.0.5", 39 | "@vue/cli-plugin-pwa": "^4.0.5", 40 | "@vue/cli-plugin-unit-jest": "^4.0.5", 41 | "@vue/cli-service": "^4.0.5", 42 | "@vue/test-utils": "^1.0.0-beta.29", 43 | "babel-core": "7.0.0-bridge.0", 44 | "babel-eslint": "^10.0.3", 45 | "babel-jest": "^24.9.0", 46 | "eslint-config-prettier": "^6.5.0", 47 | "eslint-plugin-prettier": "^3.1.1", 48 | "eslint-plugin-vue": "^6.0.1", 49 | "husky": "^3.0.9", 50 | "lint-staged": "^9.4.3", 51 | "onchange": "^6.1.0", 52 | "prettier-eslint-cli": "^5.0.0", 53 | "sass-loader": "7.0.1", 54 | "size-plugin": "^2.0.1", 55 | "svg-sprite-loader": "^4.1.6", 56 | "svgo-loader": "2.2.1", 57 | "vue-svg-loader": "^0.15.0", 58 | "vue-template-compiler": "^2.5.17", 59 | "webpack-bundle-analyzer": "3.6.0" 60 | }, 61 | "babel": { 62 | "presets": [ 63 | "@vue/app" 64 | ] 65 | }, 66 | "eslintConfig": { 67 | "globals": { 68 | "L": true 69 | }, 70 | "root": true, 71 | "env": { 72 | "node": true, 73 | "es6": true 74 | }, 75 | "rules": { 76 | "no-console": 0, 77 | "no-useless-escape": 0, 78 | "no-multiple-empty-lines": [ 79 | 2, 80 | { 81 | "max": 3 82 | } 83 | ], 84 | "prettier/prettier": [ 85 | "error", 86 | { 87 | "singleQuote": true, 88 | "semi": false, 89 | "trailingComma": "none", 90 | "bracketSpacing": true, 91 | "jsxBracketSameLine": true, 92 | "insertPragma": true, 93 | "requirePragma": false 94 | } 95 | ] 96 | }, 97 | "plugins": [], 98 | "extends": [ 99 | "plugin:vue/essential", 100 | "plugin:prettier/recommended", 101 | "eslint:recommended" 102 | ], 103 | "parserOptions": { 104 | "parser": "babel-eslint" 105 | } 106 | }, 107 | "prettier": { 108 | "singleQuote": true, 109 | "semi": false, 110 | "printWidth": 100, 111 | "proseWrap": "never" 112 | }, 113 | "postcss": { 114 | "plugins": { 115 | "autoprefixer": {} 116 | } 117 | }, 118 | "browserslist": [ 119 | "> 1%", 120 | "last 2 versions", 121 | "not ie <= 8" 122 | ], 123 | "eslintIgnore": [ 124 | "package.json" 125 | ], 126 | "husky": { 127 | "hooks": { 128 | "pre-commit": "yarn run precommit-msg && lint-staged" 129 | } 130 | }, 131 | "lint-staged": { 132 | "**/**.{js,json,pcss,md,vue,css,scss}": [ 133 | "prettier --write" 134 | ] 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Awesome Vue-Cli3 Example 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue 脚手架", 3 | "short_name": "Vue 脚手架", 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": "https://vue-cli3.lovejade.cn/?from=device-screen", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/service-worker.js: -------------------------------------------------------------------------------- 1 | const HTMLToCache = '/' 2 | const version = 'v1.0.0' 3 | 4 | self.addEventListener('install', event => { 5 | event.waitUntil( 6 | caches.open(version).then(cache => { 7 | cache.add(HTMLToCache).then(self.skipWaiting()) 8 | }) 9 | ) 10 | }) 11 | 12 | self.addEventListener('activate', event => { 13 | event.waitUntil( 14 | caches 15 | .keys() 16 | .then(cacheNames => 17 | Promise.all( 18 | cacheNames.map(cacheName => { 19 | if (version !== cacheName) return caches.delete(cacheName) 20 | }) 21 | ) 22 | ) 23 | .then(self.clients.claim()) 24 | ) 25 | }) 26 | 27 | self.addEventListener('fetch', event => { 28 | const requestToFetch = event.request.clone() 29 | event.respondWith( 30 | caches.match(event.request.clone()).then(cached => { 31 | // We don't return cached HTML (except if fetch failed) 32 | if (cached) { 33 | const resourceType = cached.headers.get('content-type') 34 | // We only return non css/js/html cached response e.g images 35 | if (!hasHash(event.request.url) && !/text\/html/.test(resourceType)) { 36 | return cached 37 | } 38 | 39 | // If the CSS/JS didn't change since it's been cached, return the cached version 40 | if (hasHash(event.request.url) && hasSameHash(event.request.url, cached.url)) { 41 | return cached 42 | } 43 | } 44 | return fetch(requestToFetch) 45 | .then(response => { 46 | const clonedResponse = response.clone() 47 | const contentType = clonedResponse.headers.get('content-type') 48 | 49 | if ( 50 | !clonedResponse || 51 | clonedResponse.status !== 200 || 52 | clonedResponse.type !== 'basic' || 53 | /\/sockjs\//.test(event.request.url) 54 | ) { 55 | return response 56 | } 57 | 58 | if (/html/.test(contentType)) { 59 | caches.open(version).then(cache => cache.put(HTMLToCache, clonedResponse)) 60 | } else { 61 | // Delete old version of a file 62 | if (hasHash(event.request.url)) { 63 | caches.open(version).then(cache => 64 | cache.keys().then(keys => 65 | keys.forEach(asset => { 66 | if (new RegExp(removeHash(event.request.url)).test(removeHash(asset.url))) { 67 | cache.delete(asset) 68 | } 69 | }) 70 | ) 71 | ) 72 | } 73 | 74 | caches.open(version).then(cache => cache.put(event.request, clonedResponse)) 75 | } 76 | return response 77 | }) 78 | .catch(() => { 79 | if (hasHash(event.request.url)) return caches.match(event.request.url) 80 | // If the request URL hasn't been served from cache and isn't sockjs we suppose it's HTML 81 | else if (!/\/sockjs\//.test(event.request.url)) return caches.match(HTMLToCache) 82 | // Only for sockjs 83 | return new Response('No connection to the server', { 84 | status: 503, 85 | statusText: 'No connection to the server', 86 | headers: new Headers({ 'Content-Type': 'text/plain' }) 87 | }) 88 | }) 89 | }) 90 | ) 91 | }) 92 | 93 | function removeHash(element) { 94 | if (typeof element === 'string') return element.split('?hash=')[0] 95 | } 96 | 97 | function hasHash(element) { 98 | if (typeof element === 'string') return /\?hash=.*/.test(element) 99 | } 100 | 101 | function hasSameHash(firstUrl, secondUrl) { 102 | if (typeof firstUrl === 'string' && typeof secondUrl === 'string') { 103 | return /\?hash=(.*)/.exec(firstUrl)[1] === /\?hash=(.*)/.exec(secondUrl)[1] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /src/assets/icons/author.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/assets/icons/document.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 13 | 15 | 18 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/assets/icons/explore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 35 | 48 | 51 | 54 | 55 | 57 | 62 | 66 | 67 | 70 | 73 | 74 | 75 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /\.svg$/) 4 | const modules = {} 5 | 6 | files.keys().forEach(key => { 7 | modules[key.replace(/(\.\/|\.svg)/g, '')] = files(key) 8 | }) 9 | 10 | export default modules 11 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/vue-cli-manage-cli-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/src/assets/images/vue-cli-manage-cli-plugin.png -------------------------------------------------------------------------------- /src/assets/images/vue-cli-manage-project-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/src/assets/images/vue-cli-manage-project-dependencies.png -------------------------------------------------------------------------------- /src/assets/images/vue-cli-npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicejade/awesome-vue-cli3-example/8217ef4b240ee6923dac6d7f94c7312a4b54b931/src/assets/images/vue-cli-npm.png -------------------------------------------------------------------------------- /src/assets/styles/common.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: Open Sans; 6 | } 7 | 8 | *:focus { 9 | outline: none !important; 10 | } 11 | 12 | a { 13 | color: $brand; 14 | text-decoration: none; 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/styles/element.scss: -------------------------------------------------------------------------------- 1 | /* 改变主题色变量 */ 2 | $--color-primary: teal; 3 | 4 | /* 改变 icon 字体路径变量,必需 */ 5 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 6 | 7 | @import '~element-ui/packages/theme-chalk/src/index'; 8 | 9 | .el-collapse-item__header { 10 | font-size: $font-medium; 11 | } 12 | .el-collapse-item__content { 13 | font-size: $font-small; 14 | } 15 | .el-collapse { 16 | max-width: 38rem; 17 | } 18 | 19 | @media (max-width: 768px) { 20 | .el-collapse { 21 | max-width: 320px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | /* Clearfix 2 | For modern browsers 3 | 1. The space content is one way to avoid an Opera bug when the 4 | contenteditable attribute is included anywhere else in the document. 5 | Otherwise it causes space to appear at the top and bottom of elements 6 | that are clearfixed. 7 | 2. The use of `table` rather than `block` is only necessary if using 8 | `:before` to contain the top-margins of child elements. 9 | Source: http://nicolasgallagher.com/micro-clearfix-hack */ 10 | @mixin clearfix() { 11 | &:before, 12 | &:after { 13 | content: ' '; 14 | display: table; 15 | } 16 | &:after { 17 | clear: both; 18 | } 19 | } 20 | 21 | // (Responsive image)Keep images from scaling beyond the width of their parents. 22 | @mixin img-responsive($display: block) { 23 | display: $display; 24 | max-width: 100%; 25 | height: auto; 26 | } 27 | 28 | // Resize anything 29 | @mixin resizable($direction) { 30 | resize: $direction; 31 | overflow: auto; 32 | } 33 | 34 | // Text overflow 35 | // Requires inline-block or block for proper styling 36 | @mixin text-overflow() { 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | white-space: nowrap; 40 | } 41 | 42 | // Center-align a block level element 43 | @mixin center-block() { 44 | display: block; 45 | margin-left: auto; 46 | margin-right: auto; 47 | } 48 | 49 | @mixin absolute-center() { 50 | position: absolute; 51 | top: 50%; 52 | left: 50%; 53 | transform: translate(-50%, -50%); 54 | } 55 | 56 | @mixin fixed-center() { 57 | position: fixed; 58 | top: 50%; 59 | left: 50%; 60 | transform: translate(-50%, -50%); 61 | } 62 | 63 | @mixin flex-box-center($direction: row, $justify: center, $align-items: center) { 64 | flex-direction: $direction; 65 | display: -webkit-box; 66 | display: -moz-box; 67 | display: -webkit-flex; 68 | display: -ms-flex; 69 | display: flex; 70 | -webkit-box-align: center; 71 | -moz-box-align: center; 72 | -ms-box-align: center; 73 | -webkit-align-items: $align-items; 74 | -ms-align-items: $align-items; 75 | align-items: $align-items; 76 | -webkit-box-pack: center; 77 | -webkit-justify-content: center; 78 | justify-content: $justify; 79 | } 80 | -------------------------------------------------------------------------------- /src/assets/styles/style.scss: -------------------------------------------------------------------------------- 1 | @import 'element.scss'; 2 | @import 'variables.scss'; 3 | @import 'mixins.scss'; 4 | @import 'common.scss'; 5 | -------------------------------------------------------------------------------- /src/assets/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $size-factor: 0.5rem; 2 | 3 | $brand: #03a9f4; 4 | 5 | $bg-grey: #fafcff; 6 | $input-grey: #f2f4f7; 7 | $text-grey: #6a8bad; 8 | $icon-grey: #707780; 9 | 10 | $border-grey: #d7dce5; 11 | $border-black: #7a8ba9; 12 | 13 | $white: #ffffff; 14 | $black: #333333; 15 | $red: #fa0101; 16 | $grey: #e8ebf1; 17 | $orange: #6b5900; 18 | $yellow: #ffe564; 19 | 20 | $font-large: 2.1rem; 21 | $font-medium: 1.2rem; 22 | $font-small: 1rem; 23 | -------------------------------------------------------------------------------- /src/components/Advertisement.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 57 | 58 | 84 | -------------------------------------------------------------------------------- /src/components/EditDialog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 37 | 38 | 95 | 96 | 136 | -------------------------------------------------------------------------------- /src/components/Icon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 35 | 36 | 42 | -------------------------------------------------------------------------------- /src/components/Instruction.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 77 | 78 | 179 | 180 | 201 | -------------------------------------------------------------------------------- /src/components/icons/Arrow.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 38 | 39 | 63 | -------------------------------------------------------------------------------- /src/components/markdown/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 86 | 87 | 113 | -------------------------------------------------------------------------------- /src/components/markdown/MarkdownPreview.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 42 | -------------------------------------------------------------------------------- /src/components/markdown/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown-body ol ol, 2 | .markdown-body ul ol, 3 | .markdown-body ol ul, 4 | .markdown-body ul ul, 5 | .markdown-body ol ul ol, 6 | .markdown-body ul ul ol, 7 | .markdown-body ol ul ul, 8 | .markdown-body ul ul ul { 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | } 12 | .markdown-body { 13 | line-height: 1.6; 14 | word-wrap: break-word; 15 | padding: 0; 16 | -webkit-border-radius: 0 0 3px 3px; 17 | border-radius: 0 0 3px 3px; 18 | } 19 | .markdown-body > *:first-child { 20 | margin-top: 0 !important; 21 | } 22 | .markdown-body > *:last-child { 23 | margin-bottom: 0 !important; 24 | } 25 | .markdown-body * { 26 | -webkit-box-sizing: border-box; 27 | -moz-box-sizing: border-box; 28 | box-sizing: border-box; 29 | } 30 | .markdown-body h1, 31 | .markdown-body h2, 32 | .markdown-body h3, 33 | .markdown-body h4, 34 | .markdown-body h5, 35 | .markdown-body h6 { 36 | margin-top: 1em; 37 | margin-bottom: 16px; 38 | font-weight: bold; 39 | line-height: 1.4; 40 | } 41 | .markdown-body p, 42 | .markdown-body blockquote, 43 | .markdown-body ul, 44 | .markdown-body ol, 45 | .markdown-body dl, 46 | .markdown-body table, 47 | .markdown-body pre { 48 | margin-top: 0; 49 | margin-bottom: 16px; 50 | } 51 | .markdown-body h1 { 52 | margin: 0.67em 0; 53 | padding-bottom: 0.3em; 54 | font-size: 2.25em; 55 | line-height: 1.2; 56 | border-bottom: 1px solid #eee; 57 | } 58 | .markdown-body h2 { 59 | padding-bottom: 0.3em; 60 | font-size: 1.75em; 61 | line-height: 1.225; 62 | border-bottom: 1px solid #eee; 63 | } 64 | .markdown-body h3 { 65 | font-size: 1.5em; 66 | line-height: 1.43; 67 | } 68 | .markdown-body h4 { 69 | font-size: 1.25em; 70 | } 71 | .markdown-body h5 { 72 | font-size: 1em; 73 | } 74 | .markdown-body h6 { 75 | font-size: 1em; 76 | color: #777; 77 | } 78 | .markdown-body ol, 79 | .markdown-body ul { 80 | padding-left: 2em; 81 | } 82 | .markdown-body ol ol, 83 | .markdown-body ul ol { 84 | list-style-type: lower-roman; 85 | } 86 | .markdown-body ol ul, 87 | .markdown-body ul ul { 88 | list-style-type: circle; 89 | } 90 | .markdown-body ol ul ul, 91 | .markdown-body ul ul ul { 92 | list-style-type: square; 93 | } 94 | .markdown-body ol { 95 | list-style-type: decimal; 96 | } 97 | .markdown-body ul { 98 | list-style-type: disc; 99 | } 100 | .markdown-body blockquote { 101 | margin-left: 0; 102 | margin-right: 0; 103 | padding: 0 15px; 104 | color: #777; 105 | border-left: 4px solid #ddd; 106 | } 107 | .markdown-body table { 108 | display: block; 109 | width: 100%; 110 | overflow: auto; 111 | word-break: normal; 112 | word-break: keep-all; 113 | border-collapse: collapse; 114 | border-spacing: 0; 115 | } 116 | .markdown-body table tr { 117 | background-color: #fff; 118 | border-top: 1px solid #ccc; 119 | } 120 | .markdown-body table tr:nth-child(2n) { 121 | background-color: #f8f8f8; 122 | } 123 | .markdown-body table th, 124 | .markdown-body table td { 125 | padding: 6px 13px; 126 | border: 1px solid #ddd; 127 | } 128 | .markdown-body pre { 129 | word-wrap: normal; 130 | padding: 16px; 131 | overflow: auto; 132 | font-size: 85%; 133 | line-height: 1.45; 134 | background-color: #f7f7f7; 135 | -webkit-border-radius: 3px; 136 | border-radius: 3px; 137 | } 138 | .markdown-body pre code { 139 | display: inline; 140 | max-width: initial; 141 | padding: 0; 142 | margin: 0; 143 | overflow: initial; 144 | font-size: 100%; 145 | line-height: inherit; 146 | word-wrap: normal; 147 | white-space: pre; 148 | border: 0; 149 | -webkit-border-radius: 3px; 150 | border-radius: 3px; 151 | background-color: transparent; 152 | } 153 | .markdown-body pre code:before, 154 | .markdown-body pre code:after { 155 | content: normal; 156 | } 157 | .markdown-body code { 158 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 159 | padding: 0; 160 | padding-top: 0.2em; 161 | padding-bottom: 0.2em; 162 | margin: 0; 163 | font-size: 85%; 164 | background-color: rgba(0, 0, 0, 0.04); 165 | -webkit-border-radius: 3px; 166 | border-radius: 3px; 167 | } 168 | .markdown-body code:before, 169 | .markdown-body code:after { 170 | letter-spacing: -0.2em; 171 | content: '\00a0'; 172 | } 173 | .markdown-body a { 174 | text-decoration: none; 175 | background: transparent; 176 | } 177 | .markdown-body img { 178 | max-width: 100%; 179 | max-height: 100%; 180 | -webkit-border-radius: 4px; 181 | border-radius: 4px; 182 | -webkit-box-shadow: 0 0 10px #555; 183 | box-shadow: 0 0 10px #555; 184 | } 185 | .markdown-body strong { 186 | font-weight: bold; 187 | } 188 | .markdown-body em { 189 | font-style: italic; 190 | } 191 | .markdown-body del { 192 | text-decoration: line-through; 193 | } 194 | .task-list-item { 195 | list-style-type: none; 196 | } 197 | .task-list-item input { 198 | font: 13px/1.4 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, 199 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 200 | margin: 0 0.35em 0.25em -1.6em; 201 | vertical-align: middle; 202 | } 203 | .task-list-item input[disabled] { 204 | cursor: default; 205 | } 206 | .task-list-item input[type='checkbox'] { 207 | -webkit-box-sizing: border-box; 208 | -moz-box-sizing: border-box; 209 | box-sizing: border-box; 210 | padding: 0; 211 | } 212 | .task-list-item input[type='radio'] { 213 | -webkit-box-sizing: border-box; 214 | -moz-box-sizing: border-box; 215 | box-sizing: border-box; 216 | padding: 0; 217 | } 218 | -------------------------------------------------------------------------------- /src/filters.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import dayjs from 'dayjs' 4 | 5 | export default { 6 | dateTimeConvert(time) { 7 | return time && dayjs(time).format('YYYY-MM-DD HH:mm:ss') 8 | }, 9 | dateConvert(time) { 10 | return time && dayjs(time).format('YYYY-MM-DD') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Vue from 'vue' 4 | import Cookies from 'js-cookie' 5 | import Filters from './filters' 6 | 7 | /* ------------------------Vue Global Config------------------------------ */ 8 | Vue.config.productionTip = false 9 | 10 | const lang = Cookies.get('lang') || 'en' 11 | Vue.config.lang = lang 12 | 13 | /* ------------------------Vue Global Variable------------------------------ */ 14 | import { $apis, $utils, $document, $auth, $lodash } from '@helper' 15 | Vue.prototype.$_ = $lodash 16 | Vue.prototype.$apis = $apis 17 | Vue.prototype.$utils = $utils 18 | Vue.prototype.$auth = $auth 19 | Vue.prototype.$document = $document 20 | 21 | for (let key in Filters) { 22 | Vue.filter(key, Filters[key]) 23 | } 24 | 25 | /* ------------------------Vue Global Components------------------------------ */ 26 | import ElementUI from 'element-ui' 27 | Vue.use(ElementUI) 28 | 29 | import Markdown from '@components/markdown/Index' 30 | Vue.component('Markdown', Markdown) 31 | 32 | import MarkdownPreview from '@components/markdown/MarkdownPreview' 33 | Vue.component('MarkdownPreview', MarkdownPreview) 34 | 35 | import VueMeta from 'vue-meta' 36 | Vue.use(VueMeta) 37 | 38 | import Icon from '@components/Icon' 39 | Vue.component('icon', Icon) 40 | 41 | import Arrow from '@components/icons/Arrow' 42 | Vue.component('arrow', Arrow) 43 | -------------------------------------------------------------------------------- /src/helper/ajax.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import axios from 'axios' 4 | import $q from 'q' 5 | import { $utils } from '@helper' 6 | 7 | function requestHandle(params) { 8 | const defer = $q.defer() 9 | axios(params) 10 | .then(res => { 11 | if (res && (res.unauthorized || res.statusCode === 401)) { 12 | window.location.href = '/login' 13 | } 14 | if (res.type === 'application/x-msdownload') { 15 | redirectToIframe(res.request.responseURL) 16 | } else if (res.data) { 17 | if (res.data.success) { 18 | defer.resolve(res.data.value) 19 | } else { 20 | defer.reject(res.data.message) 21 | } 22 | } else { 23 | defer.reject() 24 | } 25 | }) 26 | .catch(err => { 27 | defer.reject(err) 28 | }) 29 | 30 | return defer.promise 31 | } 32 | 33 | function redirectToIframe(url) { 34 | const iframe = document.createElement('iframe') 35 | iframe.style.display = 'none' 36 | iframe.src = url 37 | iframe.onload = function() { 38 | document.body.removeChild(iframe) 39 | } 40 | document.body.appendChild(iframe) 41 | } 42 | 43 | export default { 44 | post: function(url, params) { 45 | return requestHandle({ 46 | method: 'post', 47 | url: url, 48 | data: params 49 | }) 50 | }, 51 | get: function(url, params) { 52 | return requestHandle({ 53 | method: 'get', 54 | url: $utils.queryString(url, params) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/helper/apis/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /\.js/) 4 | const modules = {} 5 | 6 | files.keys().forEach(key => { 7 | if (key === './index.js') { 8 | return 9 | } 10 | modules[key.replace(/(^\.\/|\.js$)/g, '')] = files(key).default 11 | }) 12 | 13 | export default modules 14 | -------------------------------------------------------------------------------- /src/helper/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* 3 | * 权限验证模块 4 | * 5 | * @format 6 | */ 7 | 8 | import Cookies from 'js-cookie' 9 | 10 | export default { 11 | checkSession() { 12 | return Cookies.get('isLogin') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/helper/document.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | toggleClass(el, className) { 5 | if (el.classList) { 6 | el.classList.toggle(className) 7 | } else { 8 | var classes = el.className.split(' ') 9 | var existingIndex = classes.indexOf(className) 10 | 11 | if (existingIndex >= 0) { 12 | classes.splice(existingIndex, 1) 13 | } else { 14 | classes.push(className) 15 | } 16 | el.className = classes.join(' ') 17 | } 18 | }, 19 | 20 | addClass(el, className) { 21 | if (el.classList) { 22 | el.classList.add(className) 23 | } else { 24 | el.className += ` ${className}` 25 | } 26 | }, 27 | 28 | removeClass(el, className) { 29 | if (el.classList) { 30 | el.classList.remove(className) 31 | } else { 32 | el.className = el.className.replace( 33 | new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), 34 | ' ' 35 | ) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/helper/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export const $ajax = require('./ajax').default 4 | export const $apis = require('./apis').default 5 | export const $auth = require('./auth').default 6 | export const $utils = require('./utils').default 7 | export const $lodash = require('./lodash').default 8 | export const $document = require('./document').default 9 | -------------------------------------------------------------------------------- /src/helper/lodash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* 3 | * https://www.npmjs.com/package/lodash-webpack-plugin 4 | * https://www.npmjs.com/package/babel-plugin-lodash 5 | * 6 | * @format 7 | * @date: 2017-08-01 8 | * @lastModify: 2018-05-16 9 | * @desc: Modular Lodash builds without the hassle. You can freely add the method you need 10 | * @detail: 11 | */ 12 | 13 | import _ from 'lodash' 14 | 15 | export default { 16 | clone: _.clone, 17 | cloneDeep: _.cloneDeep, 18 | endsWith: _.endsWith, 19 | debounce: _.debounce, 20 | throttle: _.throttle, 21 | find: _.find, 22 | isEmpty: _.isEmpty, 23 | flatten: _.flatten, 24 | flattenDepth: _.flattenDepth 25 | } 26 | -------------------------------------------------------------------------------- /src/helper/utils.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Vue from 'vue' 4 | 5 | if (typeof String.prototype.startsWith !== 'function') { 6 | Window.String.prototype.startsWith = function(prefix) { 7 | return this.slice(0, prefix.length) === prefix 8 | } 9 | } 10 | 11 | export default { 12 | resMsg(res) { 13 | let key = { 14 | zh: 'Chinese', 15 | en: 'English' 16 | }[Vue.config.lang] 17 | try { 18 | let obj = JSON.parse(res.Message) 19 | return obj[key] || obj.Chinese 20 | } catch (e) { 21 | return res && res.Message 22 | } 23 | }, 24 | 25 | serverUrl(apiName) { 26 | return `app/${apiName}` 27 | }, 28 | 29 | titleLang(zhStr, enStr) { 30 | return { 31 | zh: zhStr, 32 | en: enStr 33 | } 34 | }, 35 | 36 | query(search) { 37 | let str = search || window.location.search 38 | let objURL = {} 39 | 40 | str.replace(new RegExp('([^?=&]+)(=([^&]*))?', 'g'), ($0, $1, $2, $3) => { 41 | objURL[$1] = $3 42 | }) 43 | return objURL 44 | }, 45 | 46 | queryString(url, query) { 47 | let str = [] 48 | for (let key in query) { 49 | str.push(key + '=' + query[key]) 50 | } 51 | let paramStr = str.join('&') 52 | return paramStr ? `${url}?${paramStr}` : url 53 | }, 54 | 55 | /* -----------------------------localStorage------------------------------------ */ 56 | /* 57 | * set localStorage 58 | */ 59 | setStorage(name, content) { 60 | if (!name) return 61 | if (typeof content !== 'string') { 62 | content = JSON.stringify(content) 63 | } 64 | window.localStorage.setItem(name, content) 65 | }, 66 | 67 | /** 68 | * get localStorage 69 | */ 70 | getStorage(name) { 71 | if (!name) return 72 | let content = window.localStorage.getItem(name) 73 | return JSON.parse(content) 74 | }, 75 | 76 | /** 77 | * delete localStorage 78 | */ 79 | removeStorage(name) { 80 | if (!name) return 81 | window.localStorage.removeItem(name) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Vue from 'vue' 4 | import App from './App.vue' 5 | import router from './router' 6 | import store from './store' 7 | import './global.js' 8 | import './registerServiceWorker' 9 | 10 | new Vue({ 11 | router, 12 | store, 13 | render: h => h(App) 14 | }).$mount('#app') 15 | -------------------------------------------------------------------------------- /src/mixins/metaMixin.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | data() { 5 | const vm = this 6 | return { 7 | title: '首页', 8 | siteTitle: 'Vue-Cli3 实践参考', 9 | titleTemplate: '%s - ' + vm.siteTitle, 10 | keywords: 11 | 'vue,vue-cli3,webpack,vuex,vue-router,element-ui,TypeScript,ESLint,Prettier,Dayjs,Markdown,Jest,PWA,开箱即用,脚手架,模板', 12 | description: 13 | '此为基于 Vue-Cli3 搭建的开箱即用 Vue 脚手架模版,致力于探究更高效地构建优质 Web 应用。' 14 | } 15 | }, 16 | 17 | created() {}, 18 | 19 | metaInfo() { 20 | const titleContent = this.title ? `${this.title} - ${this.siteTitle}` : `${this.siteTitle}` 21 | return { 22 | title: this.title, 23 | titleTemplate: titleChunk => { 24 | return titleChunk ? `${titleChunk} - ${this.siteTitle}` : `${this.siteTitle}` 25 | }, 26 | meta: [ 27 | { vmid: 'title', name: 'title', content: titleContent }, 28 | { vmid: 'keywords', name: 'keywords', content: this.keywords }, 29 | { vmid: 'description', name: 'description', content: this.description }, 30 | { vmid: 'og:type', property: 'og:type', content: 'website' }, 31 | { vmid: 'og:title', property: 'og:title', content: titleContent }, 32 | { 33 | vmid: 'og:image', 34 | property: 'og:image', 35 | content: 'https://nice.lovejade.cn/logo.png' 36 | }, 37 | { 38 | vmid: 'og:keywords', 39 | property: 'og:keywords', 40 | content: this.keywords 41 | }, 42 | { 43 | vmid: 'og:description', 44 | property: 'og:description', 45 | content: this.description 46 | } 47 | ] 48 | } 49 | }, 50 | 51 | mounted() {}, 52 | 53 | methods: {} 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/ExploreMore.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 74 | 75 | 175 | 176 | 248 | -------------------------------------------------------------------------------- /src/pages/Homepage.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | 33 | 54 | 55 | 114 | -------------------------------------------------------------------------------- /src/pages/partials/NotFound.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 34 | 35 | 156 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* eslint-disable no-console 3 | * 4 | * @format 5 | */ 6 | 7 | import { register } from 'register-service-worker' 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | register(`${process.env.BASE_URL}service-worker.js`, { 11 | ready() { 12 | console.log( 13 | 'App is being served from cache by a service worker.\n' + 14 | 'For more details, visit https://goo.gl/AFskqB' 15 | ) 16 | }, 17 | cached() { 18 | console.log('Content has been cached for offline use.') 19 | }, 20 | updated() { 21 | console.log('New content is available; please refresh.') 22 | }, 23 | offline() { 24 | console.log('No internet connection found. App is running in offline mode.') 25 | }, 26 | error(error) { 27 | console.error('Error during service worker registration:', error) 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/router/beforeEachHooks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* 3 | * 路由跳转权限控制 4 | * 5 | * @format 6 | */ 7 | 8 | import Vue from 'vue' 9 | import { $auth } from '@helper' 10 | 11 | export default { 12 | // Check the login status 13 | checkLoginAuth(to, from, next) { 14 | if (to.meta.title && to.meta.title[Vue.config.lang]) { 15 | document.title = to.meta.title[Vue.config.lang] 16 | } 17 | 18 | if (to.meta && to.meta.ignoreAuth) { 19 | next() 20 | } else { 21 | if ($auth.checkSession()) { 22 | next() 23 | } else { 24 | next({ path: '/login' }) 25 | } 26 | } 27 | }, 28 | 29 | // Check page permissions 30 | checkPageAuth(to, from, next) { 31 | if (to.meta && to.meta.ignoreAuth) { 32 | next() 33 | } else { 34 | // check user auth here.... 35 | next() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/router/commonRoutes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /* 3 | * 通用路由配置,需要放在配置项数组的末端 4 | * 5 | * @format 6 | */ 7 | 8 | import NotFound from '@pages/partials/NotFound' 9 | 10 | export default [ 11 | { 12 | path: '/', 13 | meta: { 14 | title: 'Vue-cli Overall Example', 15 | ignoreAuth: true 16 | }, 17 | component: resolve => require(['@pages/Homepage'], resolve) 18 | }, 19 | { 20 | path: '/', 21 | redirect: '/homepage' 22 | }, 23 | { 24 | path: '*', 25 | meta: { 26 | title: 'Page Not Found', 27 | ignoreAuth: true 28 | }, 29 | component: NotFound 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Vue from 'vue' 4 | import Router from 'vue-router' 5 | import beforeEachHooks from './beforeEachHooks' 6 | import RoutesMapConfig from './routes' 7 | import commonRoutesMap from './commonRoutes' 8 | 9 | Vue.use(Router) 10 | 11 | const routerInstance = new Router({ 12 | mode: 'history', 13 | /* ~~~~~~~~~~~~~~~~~~~~~~~~@CHANGE@~~~~~~~~~~~~~~~~~~~~~~~~ */ 14 | /* 15 | @desc: base,应用的基路径;如整个单页应用服务在 /app/ 下,base 就应该设为 "/app/"; 16 | @reference: https://router.vuejs.org/zh-cn/api/options.html#base 17 | */ 18 | base: '/', 19 | linkActiveClass: 'active', 20 | scrollBehavior: () => ({ y: 0 }), 21 | routes: RoutesMapConfig.concat(commonRoutesMap) 22 | }) 23 | 24 | Object.values(beforeEachHooks).forEach(hook => { 25 | routerInstance.beforeEach(hook) 26 | }) 27 | 28 | export default routerInstance 29 | -------------------------------------------------------------------------------- /src/router/routes/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /\.js$/) 4 | 5 | var configArray = [] 6 | 7 | files.keys().forEach(key => { 8 | if (key === './index.js') return 9 | configArray = configArray.concat(files(key).default) 10 | }) 11 | export default configArray 12 | -------------------------------------------------------------------------------- /src/router/routes/main.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default [ 4 | { 5 | path: '/explore', 6 | meta: { 7 | ignoreAuth: true, 8 | title: '发现更多' 9 | }, 10 | component: resolve => require(['@pages/ExploreMore'], resolve) 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import Vue from 'vue' 4 | import Vuex from 'vuex' 5 | import createLogger from 'vuex/dist/logger' 6 | import modules from './modules' 7 | 8 | Vue.use(Vuex) 9 | 10 | const debug = process.env.NODE_ENV !== 'production' 11 | 12 | export default new Vuex.Store({ 13 | modules, 14 | strict: debug, 15 | plugins: debug ? [createLogger()] : [] 16 | }) 17 | -------------------------------------------------------------------------------- /src/store/modules/demo/actions.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | updateDefaultField({ commit }, value) { 5 | commit('UPDATE_DEFAULT_FIELD', value) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/store/modules/demo/getters.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | defaultField: state => state.defaultField 5 | } 6 | -------------------------------------------------------------------------------- /src/store/modules/demo/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import getters from './getters' 4 | import mutations from './mutations' 5 | import actions from './actions' 6 | 7 | const state = { 8 | defaultField: 1314 9 | } 10 | 11 | export default { 12 | namespaced: true, 13 | state, 14 | getters, 15 | mutations, 16 | actions 17 | } 18 | -------------------------------------------------------------------------------- /src/store/modules/demo/mutations.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | export default { 4 | ['UPDATE_DEFAULT_FIELD'](state, value) { 5 | state.defaultField = value 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const files = require.context('.', true, /index.js$/) 4 | const modules = {} 5 | 6 | files.keys().forEach(key => { 7 | if (key === './index.js') { 8 | return 9 | } 10 | const moduleName = key.split('/')[1] 11 | modules[moduleName] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/Arrow.spec.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { shallowMount } from '@vue/test-utils' 4 | import Arrow from '@/components/icons/Arrow.vue' 5 | 6 | describe('Arrow.vue', () => { 7 | // ------------------------Test [props.direction]------------------------Start 8 | it('renders props.direction when passed', () => { 9 | const parpDirection = 'right' 10 | const wrapper = shallowMount(Arrow, { 11 | propsData: { direction: parpDirection } 12 | }) 13 | expect(wrapper.classes()).toContain(parpDirection) 14 | }) 15 | 16 | it('renders props.direction when NOT passed', () => { 17 | // direction default Value is [left] 18 | const parpDirection = '~~left' 19 | const defaultDirection = 'left' 20 | const wrapper = shallowMount(Arrow, { 21 | propsData: { direction: parpDirection } 22 | }) 23 | expect(wrapper.classes()).toContain(defaultDirection) 24 | /* const compClassListStr = wrapper 25 | .find('.arrow-component') 26 | .element.classList.toString() 27 | expect(compClassListStr).toBe(`arrow-component left`) */ 28 | }) 29 | 30 | // ------------------------Test [props.color]------------------------Start 31 | it('renders props.color when passed', () => { 32 | // color default Vaule is [#479537] 33 | const propColor = '#666666' 34 | const wrapper = shallowMount(Arrow, { 35 | propsData: { color: propColor } 36 | }) 37 | const compBorderColor = wrapper.find('.arrow-component').element.style.borderColor 38 | expect(compBorderColor).toBe(propColor) 39 | }) 40 | 41 | it('renders props.color when Not passed', () => { 42 | // color default Vaule is [#479537] 43 | const propColor = '#666 666#' 44 | const defaultColor = '#479537' 45 | const wrapper = shallowMount(Arrow, { 46 | propsData: { color: propColor } 47 | }) 48 | const compBorderColor = wrapper.find('.arrow-component').element.style.borderColor 49 | expect(compBorderColor).toBe(defaultColor) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const SizePlugin = require('size-plugin') 6 | 7 | const isProductionEnvFlag = process.env.NODE_ENV === 'production' 8 | 9 | /* 是否开启 SPA 预渲染,如果要开启,则须另外安装 prerender-spa-plugin 插件 */ 10 | const isOpenPrerenderSPA = false 11 | 12 | function resolveRealPath(dir) { 13 | return path.join(__dirname, dir) 14 | } 15 | 16 | function loadGlobalStyles() { 17 | const variables = fs.readFileSync('src/assets/styles/variables.scss', 'utf-8') 18 | const mixins = fs.readFileSync('src/assets/styles/mixins.scss', 'utf-8') 19 | return variables + mixins 20 | } 21 | 22 | // https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/config.md 23 | module.exports = { 24 | // Project deployment base 25 | // By default we assume your app will be deployed at the root of a domain, 26 | // e.g. https://www.my-app.com/ 27 | // If your app is deployed at a sub-path, you will need to specify that 28 | // sub-path here. For example, if your app is deployed at 29 | // https://www.foobar.com/my-app/ 30 | // then change this to '/my-app/' 31 | publicPath: '/', 32 | 33 | // where to output built files 34 | outputDir: 'dist', 35 | 36 | // whether to use eslint-loader for lint on save. 37 | // valid values: true | false | 'error' 38 | // when set to 'error', lint errors will cause compilation to fail. 39 | lintOnSave: true, 40 | 41 | // https://cli.vuejs.org/config/#runtimecompiler 42 | runtimeCompiler: false, 43 | 44 | // babel-loader skips `node_modules` deps by default. 45 | // explicitly transpile a dependency with this option. 46 | transpileDependencies: [ 47 | /* string or regex */ 48 | ], 49 | 50 | // generate sourceMap for production build? 51 | productionSourceMap: process.env.NODE_ENV !== 'production', 52 | 53 | // https://github.com/vuejs/vue-cli/blob/dev/docs/css.md (#Need to put the top) 54 | css: { 55 | loaderOptions: { 56 | sass: { 57 | data: loadGlobalStyles() 58 | } 59 | } 60 | }, 61 | 62 | // tweak internal webpack configuration. 63 | // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 64 | chainWebpack: config => { 65 | config.resolve.alias 66 | .set('vue$', 'vue/dist/vue.esm.js') 67 | .set('@helper', resolveRealPath('src/helper')) 68 | .set('@pages', resolveRealPath('src/pages')) 69 | .set('@assets', resolveRealPath('src/assets')) 70 | .set('@router', resolveRealPath('src/router')) 71 | .set('@mixins', resolveRealPath('src/mixins')) 72 | .set('@components', resolveRealPath('src/components')) 73 | 74 | // remove the old loader & add new one 75 | config.module.rules.delete('svg') 76 | config.module 77 | .rule('svg') 78 | .test(/\.svg$/) 79 | .use('svg-sprite-loader') 80 | .loader('svg-sprite-loader') 81 | .options({ 82 | name: '[name]-[hash:7]', 83 | prefixize: true 84 | }) 85 | 86 | const splitOptions = config.optimization.get('splitChunks') 87 | config.optimization.splitChunks( 88 | Object.assign({}, splitOptions, { 89 | // (缺省值5)按需加载时的最大并行请求数 90 | maxAsyncRequests: 16, 91 | // (默认值3)入口点上的最大并行请求数 92 | maxInitialRequests: 16, 93 | // (默认值:1)分割前共享模块的最小块数 94 | minChunks: 1, 95 | // (默认值:30000)块的最小大小 96 | minSize: 30000, 97 | // webpack 将使用块的起源和名称来生成名称: `vendors~main.js`,如项目与"~"冲突,则可通过此值修改,Eg: '-' 98 | automaticNameDelimiter: '~', 99 | // cacheGroups is an object where keys are the cache group names. 100 | name: true, 101 | cacheGroups: { 102 | default: false, 103 | common: { 104 | name: `chunk-common`, 105 | minChunks: 2, 106 | priority: -20, 107 | chunks: 'initial', 108 | reuseExistingChunk: true 109 | }, 110 | element: { 111 | name: 'element', 112 | test: /[\\/]node_modules[\\/]element-ui[\\/]/, 113 | chunks: 'initial', 114 | // 默认组的优先级为负数,以允许任何自定义缓存组具有更高的优先级(默认值为0) 115 | priority: -30 116 | } 117 | } 118 | }) 119 | ) 120 | 121 | // https://github.com/webpack-contrib/webpack-bundle-analyzer 122 | if (process.env.npm_config_report) { 123 | config 124 | .plugin('webpack-bundle-analyzer') 125 | .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin) 126 | } 127 | }, 128 | 129 | configureWebpack: { 130 | plugins: [ 131 | isProductionEnvFlag && isOpenPrerenderSPA 132 | ? new require('prerender-spa-plugin')({ 133 | // Required - The path to the webpack-outputted app to prerender. 134 | staticDir: path.join(__dirname, 'dist'), 135 | // Required - Routes to render. 136 | routes: ['/', '/explore'] 137 | }) 138 | : () => {}, 139 | isProductionEnvFlag ? new SizePlugin() : () => {} 140 | ] 141 | }, 142 | 143 | // use thread-loader for babel & TS in production build 144 | // enabled by default if the machine has more than 1 cores 145 | parallel: require('os').cpus().length > 1, 146 | 147 | // options for the PWA plugin. 148 | // see => https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa 149 | // https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin 150 | pwa: { 151 | name: 'Vue-Cli3 实践', 152 | themeColor: '#4DBA87', 153 | msTileColor: '#000000', 154 | appleMobileWebAppCapable: 'yes', 155 | appleMobileWebAppStatusBarStyle: 'black', 156 | iconPaths: { 157 | favicon32: 'img/icons/fuji-mountain-32x32.png', 158 | favicon16: 'img/icons/fuji-mountain-16x16.png', 159 | appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', 160 | maskIcon: 'img/icons/safari-pinned-tab.svg', 161 | msTileImage: 'img/icons/msapplication-icon-144x144.png' 162 | }, 163 | // configure the workbox plugin (GenerateSW or InjectManifest) 164 | workboxPluginMode: 'InjectManifest', 165 | workboxOptions: { 166 | // swSrc is required in InjectManifest mode. 167 | swSrc: 'public/service-worker.js' 168 | // ...other Workbox options... 169 | } 170 | }, 171 | 172 | // configure webpack-dev-server behavior 173 | devServer: { 174 | open: process.platform === 'darwin', 175 | host: '0.0.0.0', 176 | port: 8080, 177 | https: false, 178 | hotOnly: false, 179 | // See https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#configuring-proxy 180 | proxy: null, // string | Object 181 | before: app => {} 182 | }, 183 | 184 | // options for 3rd party plugins 185 | pluginOptions: {} 186 | } 187 | --------------------------------------------------------------------------------