├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc.json ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── index.html └── vendor │ ├── vendor-manifest.json │ └── vendor.dll.6346.js ├── src ├── App.vue ├── api │ └── permission.js ├── assets │ └── image │ │ └── a.png ├── components │ ├── SvgIcon │ │ └── index.vue │ └── dynamic-menu.vue ├── config │ ├── baseUrl.js │ └── httpConfig.js ├── filters │ └── filters.js ├── icons │ ├── index.js │ └── svg │ │ ├── example.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── table.svg │ │ ├── tree.svg │ │ └── user.svg ├── main.js ├── pages │ ├── errorPage │ │ ├── 403.vue │ │ └── 404.vue │ ├── goods-manage │ │ ├── goods-classify │ │ │ └── index.vue │ │ ├── goods-list │ │ │ └── index.vue │ │ └── index.vue │ ├── home │ │ ├── component │ │ │ ├── charts.vue │ │ │ ├── todo-list.vue │ │ │ └── total-status.vue │ │ └── index.vue │ ├── layout │ │ ├── component │ │ │ ├── main-content │ │ │ │ ├── content.vue │ │ │ │ ├── index.vue │ │ │ │ └── top-aside.vue │ │ │ └── sidebar-nav.vue │ │ └── index.vue │ ├── login │ │ └── login.vue │ ├── order-manage │ │ ├── index.vue │ │ ├── order-list │ │ │ └── index.vue │ │ ├── product-manage │ │ │ ├── index.vue │ │ │ ├── production-list │ │ │ │ └── index.vue │ │ │ └── review-manage │ │ │ │ └── index.vue │ │ └── return-goods │ │ │ └── index.vue │ └── permission │ │ ├── index.vue │ │ ├── menu-manage │ │ └── index.vue │ │ ├── role-manage │ │ └── index.vue │ │ └── user-manage │ │ └── index.vue ├── router │ ├── dynamic-router.js │ └── index.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── modules.js │ ├── modules │ │ └── permission.js │ ├── mutations.js │ └── state.js ├── styles │ ├── _mixin.scss │ ├── _normalize.scss │ ├── _reset_element.scss │ ├── _sidebar.scss │ ├── index.scss │ └── loading.scss └── utils │ └── recursion-router.js ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.js ├── vue.config.js ├── webpack.dll.conf.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 4 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | "space-before-function-paren": 0, 14 | "indent": [2, 4] 15 | }, 16 | parserOptions: { 17 | parser: 'babel-eslint' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote":true, 4 | "semi":false 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vue-admin-permission 2 | 项目基于vue-cli3.0进行搭建,并使用了vue全家桶vue vuex axios vue-router elementui,增加了按钮级别的权限控制 3 | 4 | 5 | ### 线上预览地址 6 | ### React版本请移步 7 | ### VUE3版本请移步 8 | 9 | 10 | #### 脚手架版本: 11 | *vue-cli-3.0 12 | 13 | #### 用到vue相关的生态链模块: 14 | * `vue` 15 | * `vue-router` 16 | * `vuex` 17 | * `axios` 18 | 19 | #### 项目要点 20 | * `路由懒加载` 21 | * `根据权限生成动态路由` 22 | * `全局数据请求拦截处理及loading` 23 | * `多个代理配置` 24 | * `抽离第三方库文件dll` 25 | 26 | ### 项目启动步骤 27 | 1. 安装包(这里需要使用npm进行安装,如果用cnpm或者yarn会有热更新失效的问题) 28 | npm install 29 | 2. 开发运行 30 | npm run serve:randy 31 | 3. 生产打包 32 | npm run dll (仅需运行一次) 33 | npm run build 34 | 35 | #### 项目分析 36 | npm run analyze 37 | 38 | #### 温馨提示 39 | 在新增vue页面的时候,热更新可能会失效,重启一下项目 40 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'] 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | transformIgnorePatterns: ['/node_modules/'], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | snapshotSerializers: ['jest-serializer-vue'], 14 | testMatch: [ 15 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 16 | ], 17 | testURL: 'http://localhost/', 18 | watchPlugins: [ 19 | 'jest-watch-typeahead/filename', 20 | 'jest-watch-typeahead/testname' 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-permission", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Hui <543245444@qq.com>", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "serve:randy": "cross-env API_TYPE=randy vue-cli-service serve", 9 | "serve:peter": "cross-env API_TYPE=peter vue-cli-service serve", 10 | "build": "vue-cli-service build", 11 | "analyze": "vue-cli-service build", 12 | "lint": "vue-cli-service lint", 13 | "test:unit": "vue-cli-service test:unit", 14 | "dll": "webpack -p --progress --config ./webpack.dll.conf.js" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.19.0", 18 | "core-js": "^2.6.5", 19 | "element-ui": "^2.9.2", 20 | "moment": "^2.24.0", 21 | "vue": "^2.6.10", 22 | "vue-router": "^3.0.3", 23 | "vuex": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "^3.8.0", 27 | "@vue/cli-plugin-eslint": "^3.8.0", 28 | "@vue/cli-plugin-unit-jest": "^3.8.0", 29 | "@vue/cli-service": "^3.8.0", 30 | "@vue/eslint-config-standard": "^4.0.0", 31 | "@vue/test-utils": "1.0.0-beta.29", 32 | "add-asset-html-webpack-plugin": "^3.1.3", 33 | "babel-core": "7.0.0-bridge.0", 34 | "babel-eslint": "^10.0.1", 35 | "babel-jest": "^23.6.0", 36 | "clean-webpack-plugin": "^1.0.1", 37 | "compression-webpack-plugin": "^3.0.0", 38 | "cross-env": "^5.2.0", 39 | "eslint": "^5.16.0", 40 | "eslint-plugin-vue": "^5.0.0", 41 | "qiniu-webpack-plugin": "^0.4.2", 42 | "sass": "^1.18.0", 43 | "sass-loader": "^7.1.0", 44 | "svg-sprite-loader": "^3.9.2", 45 | "vue-template-compiler": "^2.6.10", 46 | "webpack-bundle-analyzer": "^2.13.1", 47 | "webpack-cli": "^3.2.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveRandy/vue-cli3.0-vueadmin/0744413afe7cfd4d57be7d203d5a71c3fef70776/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | loveRandy 9 | 10 | 11 | 14 |
15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/vendor/vendor-manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"vendor_7e20ce9158d88fef385c","content":{"./node_modules/async-validator/es/util.js":{"id":0,"buildMeta":{"exportsType":"namespace","providedExports":["warning","format","isEmptyValue","isEmptyObject","asyncMap","complementError","deepMerge"]}},"./node_modules/async-validator/es/rule/index.js":{"id":1,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/axios/lib/utils.js":{"id":2,"buildMeta":{"providedExports":true}},"./node_modules/vue/dist/vue.runtime.esm.js":{"id":3,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/babel-runtime/helpers/typeof.js":{"id":4,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/util.js":{"id":5,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/dom.js":{"id":6,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_global.js":{"id":7,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/mixins/emitter.js":{"id":8,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_descriptors.js":{"id":9,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_has.js":{"id":10,"buildMeta":{"providedExports":true}},"./node_modules/babel-runtime/helpers/extends.js":{"id":11,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_hide.js":{"id":12,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-dp.js":{"id":13,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-iobject.js":{"id":14,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_wks.js":{"id":15,"buildMeta":{"providedExports":true}},"./node_modules/async-validator/es/messages.js":{"id":16,"buildMeta":{"exportsType":"namespace","providedExports":["newMessages","messages"]}},"./node_modules/webpack/buildin/global.js":{"id":17,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_core.js":{"id":18,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_is-object.js":{"id":19,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_fails.js":{"id":20,"buildMeta":{"providedExports":true}},"./node_modules/async-validator/es/validator/index.js":{"id":21,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/element-ui/lib/locale/index.js":{"id":22,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/merge.js":{"id":23,"buildMeta":{"providedExports":true}},"./node_modules/throttle-debounce/debounce.js":{"id":24,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_an-object.js":{"id":25,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_property-desc.js":{"id":26,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-keys.js":{"id":27,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_library.js":{"id":28,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_uid.js":{"id":29,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-pie.js":{"id":30,"buildMeta":{"providedExports":true}},"./node_modules/async-validator/es/rule/required.js":{"id":31,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/type.js":{"id":32,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/element-ui/lib/mixins/locale.js":{"id":33,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/vue-popper.js":{"id":34,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/scrollbar-width.js":{"id":35,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/input.js":{"id":36,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/resize-event.js":{"id":37,"buildMeta":{"providedExports":true}},"./node_modules/throttle-debounce/throttle.js":{"id":38,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/scrollbar.js":{"id":39,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/mixins/focus.js":{"id":40,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/shared.js":{"id":41,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/scroll-into-view.js":{"id":42,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/aria-utils.js":{"id":43,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_export.js":{"id":44,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-primitive.js":{"id":45,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_defined.js":{"id":46,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-integer.js":{"id":47,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_shared-key.js":{"id":48,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_shared.js":{"id":49,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_enum-bug-keys.js":{"id":50,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-gops.js":{"id":51,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-object.js":{"id":52,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_iterators.js":{"id":53,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_set-to-string-tag.js":{"id":54,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_wks-ext.js":{"id":55,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_wks-define.js":{"id":56,"buildMeta":{"providedExports":true}},"./node_modules/process/browser.js":{"id":57,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/bind.js":{"id":58,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/buildURL.js":{"id":59,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/isCancel.js":{"id":60,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/defaults.js":{"id":61,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/adapters/xhr.js":{"id":62,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/createError.js":{"id":63,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/mergeConfig.js":{"id":64,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/Cancel.js":{"id":65,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/date.js":{"id":66,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/types.js":{"id":67,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/popup/index.js":{"id":68,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/mixins/migrating.js":{"id":69,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/clickoutside.js":{"id":70,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/checkbox.js":{"id":71,"buildMeta":{"providedExports":true}},"./node_modules/babel-helper-vue-jsx-merge-props/index.js":{"id":72,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/tag.js":{"id":73,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_ie8-dom-define.js":{"id":74,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_dom-create.js":{"id":75,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-keys-internal.js":{"id":76,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_iobject.js":{"id":77,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_cof.js":{"id":78,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_iter-define.js":{"id":79,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_redefine.js":{"id":80,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-create.js":{"id":81,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-gopn.js":{"id":82,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/radio.js":{"id":83,"buildMeta":{"providedExports":true}},"./node_modules/async-validator/es/validator/string.js":{"id":84,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/rule/whitespace.js":{"id":85,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/rule/type.js":{"id":86,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/rule/range.js":{"id":87,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/rule/enum.js":{"id":88,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/rule/pattern.js":{"id":89,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/method.js":{"id":90,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/number.js":{"id":91,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/boolean.js":{"id":92,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/regexp.js":{"id":93,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/integer.js":{"id":94,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/float.js":{"id":95,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/array.js":{"id":96,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/object.js":{"id":97,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/enum.js":{"id":98,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/pattern.js":{"id":99,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/date.js":{"id":100,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/async-validator/es/validator/required.js":{"id":101,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/timers-browserify/main.js":{"id":103,"buildMeta":{"providedExports":true}},"./node_modules/setimmediate/setImmediate.js":{"id":104,"buildMeta":{"providedExports":true}},"./node_modules/vue-router/dist/vue-router.esm.js":{"id":105,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/vuex/dist/vuex.esm.js":{"id":106,"buildMeta":{"exportsType":"namespace","providedExports":["default","Store","install","mapState","mapMutations","mapGetters","mapActions","createNamespacedHelpers"]}},"./node_modules/axios/index.js":{"id":107,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/axios.js":{"id":108,"buildMeta":{"providedExports":true}},"./node_modules/is-buffer/index.js":{"id":109,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/Axios.js":{"id":110,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/InterceptorManager.js":{"id":111,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/dispatchRequest.js":{"id":112,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/transformData.js":{"id":113,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/normalizeHeaderName.js":{"id":114,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/settle.js":{"id":115,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/enhanceError.js":{"id":116,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/parseHeaders.js":{"id":117,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isURLSameOrigin.js":{"id":118,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/cookies.js":{"id":119,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isAbsoluteURL.js":{"id":120,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/combineURLs.js":{"id":121,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/CancelToken.js":{"id":122,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/spread.js":{"id":123,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/element-ui.common.js":{"id":124,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/date-util.js":{"id":125,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/locale/lang/zh-CN.js":{"id":126,"buildMeta":{"providedExports":true}},"./node_modules/deepmerge/dist/cjs.js":{"id":127,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/locale/format.js":{"id":128,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/popup/popup-manager.js":{"id":129,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/popper.js":{"id":130,"buildMeta":{"providedExports":true}},"./node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js":{"id":131,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/element-ui/lib/button.js":{"id":132,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/transitions/collapse-transition.js":{"id":133,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/vdom.js":{"id":134,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/tooltip.js":{"id":135,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/checkbox-group.js":{"id":136,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/after-leave.js":{"id":137,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/progress.js":{"id":138,"buildMeta":{"providedExports":true}},"./node_modules/throttle-debounce/index.js":{"id":139,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/select.js":{"id":140,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/option.js":{"id":141,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/button-group.js":{"id":142,"buildMeta":{"providedExports":true}},"./node_modules/normalize-wheel/index.js":{"id":143,"buildMeta":{"providedExports":true}},"./node_modules/normalize-wheel/src/normalizeWheel.js":{"id":144,"buildMeta":{"providedExports":true}},"./node_modules/normalize-wheel/src/UserAgent_DEPRECATED.js":{"id":145,"buildMeta":{"providedExports":true}},"./node_modules/normalize-wheel/src/isEventSupported.js":{"id":146,"buildMeta":{"providedExports":true}},"./node_modules/normalize-wheel/src/ExecutionEnvironment.js":{"id":147,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/utils/aria-dialog.js":{"id":148,"buildMeta":{"providedExports":true}},"./node_modules/async-validator/es/index.js":{"id":149,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/babel-runtime/core-js/object/assign.js":{"id":150,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/fn/object/assign.js":{"id":151,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es6.object.assign.js":{"id":152,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_ctx.js":{"id":153,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_a-function.js":{"id":154,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-assign.js":{"id":155,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_array-includes.js":{"id":156,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-length.js":{"id":157,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_to-absolute-index.js":{"id":158,"buildMeta":{"providedExports":true}},"./node_modules/babel-runtime/core-js/symbol/iterator.js":{"id":159,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/fn/symbol/iterator.js":{"id":160,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es6.string.iterator.js":{"id":161,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_string-at.js":{"id":162,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_iter-create.js":{"id":163,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-dps.js":{"id":164,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_html.js":{"id":165,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-gpo.js":{"id":166,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/web.dom.iterable.js":{"id":167,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es6.array.iterator.js":{"id":168,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_add-to-unscopables.js":{"id":169,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_iter-step.js":{"id":170,"buildMeta":{"providedExports":true}},"./node_modules/babel-runtime/core-js/symbol.js":{"id":171,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/fn/symbol/index.js":{"id":172,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es6.symbol.js":{"id":173,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_meta.js":{"id":174,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_enum-keys.js":{"id":175,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_is-array.js":{"id":176,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-gopn-ext.js":{"id":177,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/_object-gopd.js":{"id":178,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es6.object.to-string.js":{"id":179,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es7.symbol.async-iterator.js":{"id":180,"buildMeta":{"providedExports":true}},"./node_modules/core-js/library/modules/es7.symbol.observable.js":{"id":181,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/input-number.js":{"id":182,"buildMeta":{"providedExports":true}},"./node_modules/element-ui/lib/cascader-panel.js":{"id":183,"buildMeta":{"providedExports":true}}}} -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/api/permission.js: -------------------------------------------------------------------------------- 1 | import axios from '@/config/httpConfig' 2 | 3 | export function fetchPermission() { 4 | return axios.get('/user/info') 5 | } 6 | 7 | // 获取用户列表 8 | export function getUserList() { 9 | return axios.get('/user/list') 10 | } 11 | // 获取角色列表 12 | export function getRoleList() { 13 | return axios.get('/role/list') 14 | } 15 | // 获取所有权限 16 | export function getAllPermissiion() { 17 | return axios.get('/permission/all') 18 | } 19 | // 获取一级权限列表 20 | export function getFirstLevel() { 21 | return axios.get('/permission/resource') 22 | } 23 | // 获取次级权限列表 24 | export function getNextLevel(id) { 25 | return axios.get(`/permission/level?id=${id}`) 26 | } 27 | export function login(data) { 28 | return axios.post('/user/login', data) 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/image/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveRandy/vue-cli3.0-vueadmin/0744413afe7cfd4d57be7d203d5a71c3fef70776/src/assets/image/a.png -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /src/components/dynamic-menu.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /src/config/baseUrl.js: -------------------------------------------------------------------------------- 1 | const baseUrl = process.env.NODE_ENV === 'production' ? '/' : '/api' 2 | export default baseUrl 3 | -------------------------------------------------------------------------------- /src/config/httpConfig.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store/index.js' 3 | import baseURL from './baseUrl' 4 | import { Message } from 'element-ui' 5 | const http = {} 6 | 7 | var instance = axios.create({ 8 | timeout: 5000, 9 | baseURL, 10 | validateStatus(status) { 11 | switch (status) { 12 | case 400: 13 | Message.error('请求出错') 14 | break 15 | case 401: 16 | Message.warning({ 17 | message: '授权失败,请重新登录' 18 | }) 19 | store.commit('LOGIN_OUT') 20 | setTimeout(() => { 21 | window.location.reload() 22 | }, 1000) 23 | return 24 | case 403: 25 | Message.warning({ 26 | message: '拒绝访问' 27 | }) 28 | break 29 | case 404: 30 | Message.warning({ 31 | message: '请求错误,未找到该资源' 32 | }) 33 | break 34 | case 500: 35 | Message.warning({ 36 | message: '服务端错误' 37 | }) 38 | break 39 | } 40 | return status >= 200 && status < 300 41 | } 42 | }) 43 | 44 | // 添加请求拦截器 45 | instance.interceptors.request.use( 46 | function(config) { 47 | // 请求头添加token 48 | if (store.state.UserToken) { 49 | config.headers.Authorization = `Bearer ${store.state.UserToken}` 50 | } 51 | return config 52 | }, 53 | function(error) { 54 | return Promise.reject(error) 55 | } 56 | ) 57 | 58 | // 响应拦截器即异常处理 59 | instance.interceptors.response.use( 60 | response => { 61 | return response.data 62 | }, 63 | err => { 64 | if (err && err.response) { 65 | } else { 66 | err.message = '连接服务器失败' 67 | } 68 | // Message.error({ 69 | // message: err.message 70 | // }) 71 | return Promise.reject(err.response) 72 | } 73 | ) 74 | 75 | http.get = function(url, options) { 76 | let loading 77 | if (!options || options.isShowLoading !== false) { 78 | loading = document.getElementById('ajaxLoading') 79 | loading.style.display = 'block' 80 | } 81 | return new Promise((resolve, reject) => { 82 | instance 83 | .get(url, options) 84 | .then(response => { 85 | if (!options || options.isShowLoading !== false) { 86 | loading = document.getElementById('ajaxLoading') 87 | loading.style.display = 'none' 88 | } 89 | if (response.code === 1) { 90 | resolve(response.data) 91 | } else { 92 | Message.error({ 93 | message: response.msg 94 | }) 95 | reject(response.msg) 96 | } 97 | }) 98 | .catch(e => { 99 | console.log(e) 100 | }) 101 | }) 102 | } 103 | 104 | http.post = function(url, data, options) { 105 | let loading 106 | if (!options || options.isShowLoading !== false) { 107 | loading = document.getElementById('ajaxLoading') 108 | loading.style.display = 'block' 109 | } 110 | return new Promise((resolve, reject) => { 111 | instance 112 | .post(url, data, options) 113 | .then(response => { 114 | if (!options || options.isShowLoading !== false) { 115 | loading = document.getElementById('ajaxLoading') 116 | loading.style.display = 'none' 117 | } 118 | if (response.code === 1) { 119 | resolve(response.data) 120 | } else { 121 | Message.error({ 122 | message: response.msg 123 | }) 124 | reject(response.message) 125 | } 126 | }) 127 | .catch(e => { 128 | console.log(e) 129 | }) 130 | }) 131 | } 132 | 133 | export default http 134 | -------------------------------------------------------------------------------- /src/filters/filters.js: -------------------------------------------------------------------------------- 1 | function formatNumber(n) { 2 | const str = n.toString() 3 | return str[1] ? str : `0${str}` 4 | } 5 | 6 | export function formatTime(date) { 7 | const year = date.getFullYear() 8 | const month = date.getMonth() + 1 9 | const day = date.getDate() 10 | 11 | const hour = date.getHours() 12 | const minute = date.getMinutes() 13 | const second = date.getSeconds() 14 | 15 | const t1 = [year, month, day].map(formatNumber).join('/') 16 | const t2 = [hour, minute, second].map(formatNumber).join(':') 17 | 18 | return `${t1} ${t2}` 19 | } 20 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | // console.log(req) 10 | requireAll(req) 11 | -------------------------------------------------------------------------------- /src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from '@/App' 3 | import store from '@/store/index' 4 | import router from '@/router/index' 5 | 6 | import ElementUI from 'element-ui' 7 | import 'element-ui/lib/theme-chalk/index.css' 8 | import './styles/index.scss' 9 | 10 | import axios from './config/httpConfig' 11 | import * as globalFilter from './filters/filters' 12 | import '@/icons' 13 | 14 | Vue.prototype.$http = axios 15 | 16 | for (var key in globalFilter) { 17 | Vue.filter(key, globalFilter[key]) 18 | } 19 | 20 | Vue.use(ElementUI) 21 | 22 | Vue.config.productionTip = false 23 | 24 | router.beforeEach((to, from, next) => { 25 | if (!store.state.UserToken) { 26 | if (to.matched.length > 0 && !to.matched.some(record => record.meta.requiresAuth)) { 27 | next() 28 | } else { 29 | next({ path: '/login' }) 30 | } 31 | } else { 32 | if (!store.state.permission.permissionList) { 33 | store.dispatch('permission/FETCH_PERMISSION').then(() => { 34 | next({ path: to.path }) 35 | }) 36 | } else { 37 | if (to.path !== '/login') { 38 | next() 39 | } else { 40 | next(from.fullPath) 41 | } 42 | } 43 | } 44 | }) 45 | 46 | router.afterEach((to, from, next) => { 47 | var routerList = to.matched 48 | store.commit('setCrumbList', routerList) 49 | store.commit('permission/SET_CURRENT_MENU', to.name) 50 | }) 51 | 52 | /* eslint-disable no-new */ 53 | new Vue({ 54 | el: '#app', 55 | router, 56 | store, 57 | render: h => h(App) 58 | }) 59 | -------------------------------------------------------------------------------- /src/pages/errorPage/403.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | 221 | -------------------------------------------------------------------------------- /src/pages/errorPage/404.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | 221 | -------------------------------------------------------------------------------- /src/pages/goods-manage/goods-classify/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 36 | -------------------------------------------------------------------------------- /src/pages/goods-manage/goods-list/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/pages/goods-manage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/pages/home/component/charts.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/pages/home/component/todo-list.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/pages/home/component/total-status.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/pages/home/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /src/pages/layout/component/main-content/content.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/pages/layout/component/main-content/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /src/pages/layout/component/main-content/top-aside.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 68 | 69 | 186 | -------------------------------------------------------------------------------- /src/pages/layout/component/sidebar-nav.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | -------------------------------------------------------------------------------- /src/pages/layout/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/pages/login/login.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 89 | 90 | 122 | 123 | 183 | -------------------------------------------------------------------------------- /src/pages/order-manage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/pages/order-manage/order-list/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /src/pages/order-manage/product-manage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/pages/order-manage/product-manage/production-list/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 51 | -------------------------------------------------------------------------------- /src/pages/order-manage/product-manage/review-manage/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | 30 | 38 | -------------------------------------------------------------------------------- /src/pages/order-manage/return-goods/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /src/pages/permission/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/pages/permission/menu-manage/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 96 | 97 | 319 | 440 | 445 | -------------------------------------------------------------------------------- /src/pages/permission/role-manage/index.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 438 | 439 | 453 | -------------------------------------------------------------------------------- /src/pages/permission/user-manage/index.vue: -------------------------------------------------------------------------------- 1 | 153 | 320 | 321 | 332 | 333 | 338 | -------------------------------------------------------------------------------- /src/router/dynamic-router.js: -------------------------------------------------------------------------------- 1 | /* 订单管理 */ 2 | const Order = () => import('@/pages/order-manage') 3 | const OrderList = () => import('@/pages/order-manage/order-list') 4 | const ProductManage = () => import('@/pages/order-manage/product-manage') 5 | const ProductionList = () => 6 | import('@/pages/order-manage/product-manage/production-list') 7 | const ReviewManage = () => 8 | import('@/pages/order-manage/product-manage/review-manage') 9 | const ReturnGoods = () => import('@/pages/order-manage/return-goods') 10 | 11 | /* 产品管理 */ 12 | const Goods = () => import('@/pages/goods-manage') 13 | const GoodsList = () => import('@/pages/goods-manage/goods-list') 14 | const GoodsClassify = () => import('@/pages/goods-manage/goods-classify') 15 | // 权限管理 16 | const Permission = () => import('@/pages/permission') 17 | const UserManage = () => import('@/pages/permission/user-manage') 18 | const RoleManage = () => import('@/pages/permission/role-manage') 19 | const MenuManage = () => import('@/pages/permission/menu-manage') 20 | /* 需要权限判断的路由 */ 21 | const dynamicRoutes = [ 22 | { 23 | path: '/order', 24 | component: Order, 25 | name: 'order-manage', 26 | meta: { 27 | name: '订单管理', 28 | icon: 'example' 29 | }, 30 | children: [ 31 | { 32 | path: 'list', 33 | name: 'order-list', 34 | component: OrderList, 35 | meta: { 36 | name: '订单列表', 37 | icon: 'table' 38 | } 39 | }, 40 | { 41 | path: 'product', 42 | name: 'product-manage', 43 | component: ProductManage, 44 | meta: { 45 | name: '生产管理', 46 | icon: 'user' 47 | }, 48 | children: [ 49 | { 50 | path: 'list', 51 | name: 'product-list', 52 | component: ProductionList, 53 | meta: { 54 | name: '生产列表', 55 | icon: 'table' 56 | } 57 | }, 58 | { 59 | path: 'review', 60 | name: 'review-manage', 61 | component: ReviewManage, 62 | meta: { 63 | name: '审核管理', 64 | icon: 'eye' 65 | } 66 | } 67 | ] 68 | }, 69 | { 70 | path: 'returnGoods', 71 | name: 'return-goods', 72 | component: ReturnGoods, 73 | meta: { 74 | name: '退货管理', 75 | icon: 'nested' 76 | } 77 | } 78 | ] 79 | }, 80 | { 81 | path: '/goods', 82 | component: Goods, 83 | name: 'goods', 84 | meta: { 85 | name: '产品管理', 86 | icon: 'user' 87 | }, 88 | children: [ 89 | { 90 | path: 'list', 91 | name: 'goods-list', 92 | component: GoodsList, 93 | meta: { 94 | name: '产品列表', 95 | icon: 'table' 96 | } 97 | }, 98 | { 99 | path: 'classify', 100 | name: 'goods-classify', 101 | component: GoodsClassify, 102 | meta: { 103 | name: '产品分类', 104 | icon: 'tree' 105 | } 106 | } 107 | ] 108 | }, 109 | { 110 | path: '/permission', 111 | component: Permission, 112 | name: 'permission', 113 | meta: { 114 | name: '权限管理', 115 | icon: 'table' 116 | }, 117 | children: [ 118 | { 119 | path: 'user', 120 | name: 'user-manage', 121 | component: UserManage, 122 | meta: { 123 | name: '用户管理', 124 | icon: 'table' 125 | } 126 | }, 127 | { 128 | path: 'role', 129 | name: 'role-manage', 130 | component: RoleManage, 131 | meta: { 132 | name: '角色管理', 133 | icon: 'eye' 134 | } 135 | }, 136 | { 137 | path: 'menu', 138 | name: 'menu-manage', 139 | component: MenuManage, 140 | meta: { 141 | name: '菜单管理', 142 | icon: 'tree' 143 | } 144 | } 145 | ] 146 | } 147 | ] 148 | 149 | export default dynamicRoutes 150 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import Login from '@/pages/login/login' 5 | import NotFound from '@/pages/errorPage/404' 6 | import Forbidden from '@/pages/errorPage/403' 7 | import Layout from '@/pages/layout/index' 8 | import Home from '@/pages/home/index' 9 | 10 | Vue.use(Router) 11 | 12 | /* 初始路由 */ 13 | export default new Router({ 14 | routes: [ 15 | { 16 | path: '/login', 17 | component: Login 18 | } 19 | ] 20 | }) 21 | 22 | /* 准备动态添加的路由 */ 23 | export const DynamicRoutes = [ 24 | { 25 | path: '', 26 | component: Layout, 27 | name: 'container', 28 | redirect: 'home', 29 | meta: { 30 | requiresAuth: true, 31 | name: '首页' 32 | }, 33 | children: [ 34 | { 35 | id: 1, 36 | path: 'home', 37 | component: Home, 38 | name: 'home', 39 | meta: { 40 | name: '首页', 41 | icon: 'tree' 42 | } 43 | } 44 | ] 45 | }, 46 | { 47 | path: '/403', 48 | component: Forbidden 49 | }, 50 | { 51 | path: '*', 52 | component: NotFound 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import state from './state' 5 | import getters from './getters' 6 | import modules from './modules' 7 | import actions from './actions' 8 | import mutations from './mutations' 9 | 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | state, 14 | getters, 15 | mutations, 16 | actions, 17 | modules 18 | }) 19 | -------------------------------------------------------------------------------- /src/store/modules.js: -------------------------------------------------------------------------------- 1 | import permission from './modules/permission' 2 | 3 | export default { 4 | permission 5 | } 6 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { fetchPermission } from '@/api/permission' 2 | import router, { DynamicRoutes } from '@/router/index' 3 | import { recursionRouter } from '@/utils/recursion-router' 4 | import dynamicRouter from '@/router/dynamic-router' 5 | 6 | export default { 7 | namespaced: true, 8 | state: { 9 | permissionList: null /** 所有路由 */, 10 | sidebarMenu: [] /** 导航菜单 */, 11 | currentMenu: '' /** 当前active导航菜单 */, 12 | control_list: [] /** 完整的权限列表 */, 13 | avatar: ''/** 头像 */, 14 | account: ''/** 用户角色 */ 15 | }, 16 | getters: {}, 17 | mutations: { 18 | SET_AVATAR(state, avatar) { 19 | state.avatar = avatar 20 | }, 21 | SET_ACCOUNT(state, account) { 22 | state.account = account 23 | }, 24 | SET_PERMISSION(state, routes) { 25 | state.permissionList = routes 26 | }, 27 | CLEAR_PERMISSION(state) { 28 | state.permissionList = null 29 | }, 30 | SET_MENU(state, menu) { 31 | state.sidebarMenu = menu 32 | }, 33 | CLEAR_MENU(state) { 34 | state.sidebarMenu = [] 35 | }, 36 | SET_CURRENT_MENU(state, currentMenu) { 37 | state.currentMenu = currentMenu 38 | }, 39 | SET_CONTROL_LIST(state, list) { 40 | state.control_list = list 41 | } 42 | }, 43 | actions: { 44 | async FETCH_PERMISSION({ commit, state }) { 45 | let permissionList = await fetchPermission() 46 | commit('SET_AVATAR', permissionList.avatar) 47 | commit('SET_ACCOUNT', permissionList.name) 48 | let routes = recursionRouter(permissionList.data, dynamicRouter) 49 | let MainContainer = DynamicRoutes.find(v => v.path === '') 50 | let children = MainContainer.children 51 | commit('SET_CONTROL_LIST', [...children, ...dynamicRouter]) 52 | children.push(...routes) 53 | commit('SET_MENU', children) 54 | let initialRoutes = router.options.routes 55 | router.addRoutes(DynamicRoutes) 56 | commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes]) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | LOGIN_IN(state, token) { 3 | state.UserToken = token 4 | }, 5 | LOGIN_OUT(state) { 6 | state.UserToken = '' 7 | }, 8 | toggleNavCollapse(state) { 9 | state.isSidebarNavCollapse = !state.isSidebarNavCollapse 10 | }, 11 | setCrumbList(state, list) { 12 | state.crumbList = list 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | get UserToken() { 3 | return localStorage.getItem('token') 4 | }, 5 | set UserToken(value) { 6 | localStorage.setItem('token', value) 7 | }, 8 | /* 导航菜单是否折叠 */ 9 | isSidebarNavCollapse: false, 10 | /* 面包屑导航列表 */ 11 | crumbList: [] 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/_mixin.scss: -------------------------------------------------------------------------------- 1 | $mainColor:#151519; 2 | 3 | @mixin table-center { 4 | display: table-cell; 5 | vertical-align: middle; 6 | text-align: center; 7 | } 8 | 9 | @mixin poa-center($w, $h) { 10 | position: absolute; 11 | width: $w; 12 | height: $h; 13 | left: 50%; 14 | top: 50%; 15 | transition: translate(-50%, -50%) 16 | } 17 | 18 | @mixin flex-center { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | @mixin t-overflow($line:1) { 25 | @if $line==1 { 26 | overflow: hidden; 27 | text-overflow: ellipsis; 28 | white-space: nowrap; 29 | } 30 | @else { 31 | display: -webkit-box; 32 | -webkit-line-clamp: $line; 33 | -webkit-box-orient: vertical; 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/styles/_normalize.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | html { 4 | 5 | color: #000; 6 | background: #fff; 7 | overflow-y: scroll; 8 | -webkit-text-size-adjust: 100%; 9 | -ms-text-size-adjust: 100%; 10 | } 11 | 12 | html * { 13 | outline: none; 14 | -webkit-text-size-adjust: none; 15 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 16 | box-sizing: border-box; 17 | } 18 | 19 | html, 20 | body { 21 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 22 | height: 100%; 23 | width: 100%; 24 | overflow: auto; 25 | } 26 | 27 | body, 28 | div, 29 | dl, 30 | dt, 31 | dd, 32 | ul, 33 | ol, 34 | li, 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6, 41 | pre, 42 | code, 43 | form, 44 | fieldset, 45 | legend, 46 | input, 47 | textarea, 48 | p, 49 | blockquote, 50 | th, 51 | td, 52 | hr, 53 | button, 54 | article, 55 | aside, 56 | details, 57 | figcaption, 58 | figure, 59 | footer, 60 | header, 61 | hgroup, 62 | menu, 63 | nav, 64 | section { 65 | margin: 0; 66 | padding: 0; 67 | } 68 | 69 | input, 70 | select, 71 | textarea { 72 | font-size: 100%; 73 | } 74 | 75 | table { 76 | border-collapse: collapse; 77 | border-spacing: 0; 78 | } 79 | 80 | fieldset, 81 | img { 82 | border: 0; 83 | } 84 | 85 | abbr, 86 | acronym { 87 | border: 0; 88 | font-variant: normal; 89 | } 90 | 91 | del { 92 | text-decoration: line-through; 93 | } 94 | 95 | address, 96 | caption, 97 | cite, 98 | code, 99 | dfn, 100 | em, 101 | th, 102 | i, 103 | var { 104 | font-style: normal; 105 | font-weight: 500; 106 | } 107 | 108 | ol, 109 | ul { 110 | list-style: none; 111 | } 112 | 113 | caption, 114 | th { 115 | text-align: left; 116 | } 117 | 118 | h1, 119 | h2, 120 | h3, 121 | h4, 122 | h5, 123 | h6 { 124 | font-size: 100%; 125 | font-weight: 500; 126 | } 127 | 128 | q:before, 129 | q:after { 130 | content: ''; 131 | } 132 | 133 | sub, 134 | sup { 135 | font-size: 75%; 136 | line-height: 0; 137 | position: relative; 138 | vertical-align: baseline; 139 | } 140 | 141 | sup { 142 | top: -0.5em; 143 | } 144 | 145 | sub { 146 | bottom: -0.25em; 147 | } 148 | 149 | a:hover { 150 | text-decoration: underline; 151 | } 152 | 153 | ins, 154 | a, 155 | a:active, 156 | a:visited, 157 | a:link { 158 | text-decoration: none; 159 | } 160 | 161 | .clearfix { 162 | &:after { 163 | display: table; 164 | clear: both; 165 | content: ""; 166 | visibility: hidden; 167 | ; 168 | height: 0; 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/styles/_reset_element.scss: -------------------------------------------------------------------------------- 1 | .el-menu { 2 | border: none; 3 | .iconfont { 4 | color: #fff; 5 | font-size: 16px; 6 | } 7 | .el-submenu__title { 8 | &:hover { 9 | background: none !important; 10 | } 11 | i.el-submenu__icon-arrow { 12 | color: #ddd; 13 | font-size: 15px; 14 | } 15 | } 16 | .el-menu-item-group__title { 17 | padding: 0; 18 | } 19 | } 20 | 21 | .el-breadcrumb { 22 | display: inline-block; 23 | vertical-align: middle; 24 | font-size: 14px; 25 | margin-left: 5px; 26 | .el-breadcrumb__inner { 27 | &.is-link { 28 | display: inline-block; 29 | font-weight: normal; 30 | color: #424040 !important; 31 | } 32 | } 33 | .is-last-link .is-link { 34 | font-weight: normal; 35 | color: #999 !important; 36 | } 37 | } 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | .sidebar-container { 49 | transition: width .28s; 50 | width: 180px ; 51 | height: 100%; 52 | position: fixed; 53 | top: 0; 54 | bottom: 0; 55 | left: 0; 56 | z-index: 1001; 57 | background: rgba(0,0,0,0.5) 58 | } 59 | 60 | 61 | 62 | 63 | .sidebar-container.navCollapsed { 64 | width: 63px ; 65 | } 66 | 67 | .navCollapsed{ 68 | .el-submenu { 69 | &>.el-submenu__title { 70 | &>span { 71 | display: none; 72 | } 73 | .el-submenu__icon-arrow { 74 | display: none; 75 | } 76 | } 77 | } 78 | 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/styles/_sidebar.scss: -------------------------------------------------------------------------------- 1 | /* 侧边栏 */ 2 | .sidebar { 3 | width: 200px !important; 4 | height: 100%; 5 | background: #304156; 6 | transition: all 0.25s; 7 | position: fixed; 8 | top: 0; 9 | bottom: 0; 10 | left: 0; 11 | z-index: 100; 12 | overflow-x: hidden; 13 | .iconfont { 14 | margin-right: 8px; 15 | color: #fff; 16 | font-size: 18px; 17 | } 18 | >div.menu-container>li.el-menu-item, 19 | >div.menu-container>li.el-submenu>.el-submenu__title { 20 | border-bottom: 1px solid rgba(238, 238, 238, 0.1); 21 | } 22 | .el-menu-item { 23 | background: #304156 !important; 24 | } 25 | /* 菜单hover时的背景 */ 26 | .el-submenu__title:hover, 27 | .el-menu-item:hover { 28 | background: #223041 !important; 29 | } 30 | /* 菜单active时的背景 */ 31 | /* .el-menu-item.is-active { 32 | background: #293748 !important; 33 | } */ 34 | } 35 | 36 | /* 主体内容 */ 37 | .main-container { 38 | min-height: 100%; 39 | margin-left: 200px; 40 | transition: margin-left 0.25s; 41 | position: relative; 42 | box-sizing: border-box; 43 | padding: 60px 20px 0 20px; 44 | } 45 | 46 | /* 折叠菜单下的样式 */ 47 | .navCollapsed { 48 | .sidebar { 49 | width: 64px !important; 50 | ul { 51 | display: none; 52 | } 53 | .iconfont+span { 54 | display: none; 55 | } 56 | .el-submenu__icon-arrow { 57 | display: none; 58 | } 59 | } 60 | .main-container { 61 | margin-left: 64px; 62 | } 63 | .aside__top{ 64 | left:64px!important; 65 | } 66 | } -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './normalize.scss'; 2 | @import './reset_element.scss'; 3 | @import './sidebar.scss'; 4 | @import './loading.scss'; 5 | @import url('//at.alicdn.com/t/font_641452_q3ah7ae4qvndn29.css'); -------------------------------------------------------------------------------- /src/styles/loading.scss: -------------------------------------------------------------------------------- 1 | .ajax-loading { 2 | display: none; 3 | .loading { 4 | position: fixed; 5 | top: 50%; 6 | left: 50%; 7 | transform: translate(-50%, -50%); 8 | padding: 0 40px; 9 | height: 80px; 10 | line-height: 80px; 11 | background: rgba(0, 0, 0, 0.75); 12 | border-radius: 6px; 13 | text-align: center; 14 | z-index: 9999; 15 | font-size: 16px; 16 | color: #fff; 17 | img { 18 | width: 32px; 19 | vertical-align: middle; 20 | } 21 | span { 22 | margin-left: 12px; 23 | } 24 | } 25 | .overlay { 26 | position: fixed; 27 | left: 0; 28 | right: 0; 29 | top: 0; 30 | bottom: 0; 31 | z-index: 9998; 32 | background: rgb(255, 255, 255); 33 | opacity: 0.1; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/recursion-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Array} userRouter 后台返回的用户权限json 4 | * @param {Array} allRouter 前端配置好的所有动态路由的集合 5 | * @return {Array} realRoutes 过滤后的路由 6 | */ 7 | 8 | export function recursionRouter(userRouter = [], allRouter = []) { 9 | var realRoutes = allRouter 10 | .filter(item => userRouter.includes(item.name)) 11 | .map(item => ({ 12 | ...item, 13 | children: item.children 14 | ? recursionRouter(userRouter, item.children) 15 | : null 16 | })) 17 | return realRoutes 18 | } 19 | 20 | /** 21 | * 22 | * @param {Array} routes 用户过滤后的路由 23 | * 24 | * 递归为所有有子路由的路由设置第一个children.path为默认路由 25 | */ 26 | export function setDefaultRoute(routes) { 27 | routes.forEach((v, i) => { 28 | if (v.children && v.children.length > 0) { 29 | v.redirect = { name: v.children[0].name } 30 | setDefaultRoute(v.children) 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 2 | .BundleAnalyzerPlugin 3 | const path = require('path') 4 | const webpack = require('webpack') 5 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin') 6 | // 导入compression-webpack-plugin 7 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 8 | // 定义压缩文件类型 9 | const productionGzipExtensions = ['js', 'css'] 10 | 11 | function resolve(dir) { 12 | return path.join(__dirname, dir) 13 | } 14 | const proxyTargetMap = { 15 | prod: 'https://xxx.xxx.com/', 16 | randy: 'http://47.105.71.81:3306', 17 | peter: 'http://192.168.11.178:3001' 18 | } 19 | let proxyTarget = proxyTargetMap[process.env.API_TYPE] || proxyTargetMap.prod 20 | let publicPath = process.env.NODE_ENV === 'production' ? '/' : '/' 21 | let dllPublishPath = '/vendor' 22 | module.exports = { 23 | publicPath: publicPath, 24 | outputDir: 'dist', 25 | 26 | // 放置静态资源的地方 (js/css/img/font/...) 27 | // assetsDir: '', 28 | 29 | // 是否在保存的时候使用 `eslint-loader` 进行检查。 30 | // 有效的值:`ture` | `false` | `"error"` 31 | // 当设置为 `"error"` 时,检查出的错误会触发编译失败。 32 | lintOnSave: true, 33 | 34 | // 使用带有浏览器内编译器的完整构建版本 35 | // 查阅 https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时 36 | // compiler: false, 37 | 38 | // babel-loader 默认会跳过 node_modules 依赖。 39 | // 通过这个选项可以显式转译一个依赖。 40 | transpileDependencies: [ 41 | /* string or regex */ 42 | ], 43 | 44 | // 是否为生产环境构建生成 source map? 45 | productionSourceMap: false, 46 | 47 | // 调整内部的 webpack 配置。 48 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/webpack.md 49 | chainWebpack: config => { 50 | // 移除 prefetch 插件,解决组件懒加载失效的问题 51 | config.plugins.delete('prefetch') 52 | // 添加新的svg-sprite-loader处理svgIcon 53 | config.module 54 | .rule('svgIcon') 55 | .test(/\.svg$/) 56 | .include.add(resolve('src/icons')) 57 | .end() 58 | .use('svg-sprite-loader') 59 | .loader('svg-sprite-loader') 60 | .tap(options => { 61 | options = { 62 | symbolId: 'icon-[name]' 63 | } 64 | return options 65 | }) 66 | 67 | // 原有的svg图像处理loader添加exclude 68 | config.module 69 | .rule('svg') 70 | .exclude.add(resolve('src/icons')) 71 | .end() 72 | }, 73 | // CSS 相关选项 74 | css: { 75 | // 将组件内的 CSS 提取到一个单独的 CSS 文件 (只用在生产环境中) 76 | // 也可以是一个传递给 `extract-text-webpack-plugin` 的选项对象 77 | extract: true, 78 | 79 | // 是否开启 CSS source map? 80 | sourceMap: true, 81 | 82 | // 为预处理器的 loader 传递自定义选项。比如传递给 83 | // sass-loader 时,使用 `{ sass: { ... } }`。 84 | loaderOptions: {}, 85 | 86 | // 为所有的 CSS 及其预处理文件开启 CSS Modules。 87 | // 这个选项不会影响 `*.vue` 文件。 88 | modules: false 89 | }, 90 | 91 | // 在生产环境下为 Babel 和 TypeScript 使用 `thread-loader` 92 | // 在多核机器下会默认开启。 93 | parallel: require('os').cpus().length > 1, 94 | 95 | // PWA 插件的选项。 96 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-pwa/README.md 97 | pwa: {}, 98 | 99 | // 配置 webpack-dev-server 行为。 100 | devServer: { 101 | disableHostCheck: true, 102 | open: process.platform === 'darwin', 103 | host: 'localhost', 104 | port: 8080, 105 | https: false, 106 | hotOnly: false, 107 | // eslint-disable-next-line no-dupe-keys 108 | open: true, 109 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/cli-service.md#配置代理 110 | proxy: { 111 | '/api': { 112 | target: proxyTarget, 113 | changeOrigin: true, 114 | pathRewrite: { 115 | '^/api': '' 116 | } 117 | } 118 | }, 119 | before: app => {} 120 | }, 121 | // eslint-disable-next-line no-dupe-keys 122 | configureWebpack: config => { 123 | if (process.env.NODE_ENV === 'production') { 124 | // 为生产环境修改配置... 125 | config.plugins.push( 126 | new webpack.DllReferencePlugin({ 127 | context: process.cwd(), 128 | manifest: require('./public/vendor/vendor-manifest.json') 129 | }), 130 | // 将 dll 注入到 生成的 html 模板中 131 | new AddAssetHtmlPlugin({ 132 | // dll文件位置 133 | filepath: path.resolve(__dirname, './public/vendor/*.js'), 134 | // dll 引用路径 135 | publicPath: dllPublishPath, 136 | // dll最终输出的目录 137 | outputPath: './vendor' 138 | }), 139 | // 开启压缩 140 | new CompressionWebpackPlugin({ 141 | filename: '[path].gz[query]', 142 | algorithm: 'gzip', 143 | test: new RegExp( 144 | '\\.(' + productionGzipExtensions.join('|') + ')$' 145 | ), 146 | threshold: 10240, 147 | minRatio: 0.8 148 | }) 149 | ) 150 | if (process.env.npm_lifecycle_event === 'analyze') { 151 | config.plugins.push(new BundleAnalyzerPlugin()) 152 | } 153 | } else { 154 | // 为开发环境修改配置... 155 | } 156 | }, 157 | 158 | // 第三方插件的选项 159 | pluginOptions: {} 160 | } 161 | -------------------------------------------------------------------------------- /webpack.dll.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CleanWebpackPlugin = require('clean-webpack-plugin') 4 | 5 | // dll文件存放的目录 6 | const dllPath = 'public/vendor' 7 | 8 | module.exports = { 9 | entry: { 10 | // 需要提取的库文件 11 | vendor: ['vue', 'vue-router', 'vuex', 'axios', 'element-ui'] 12 | }, 13 | output: { 14 | path: path.join(__dirname, dllPath), 15 | // 保证每次打包出来的文件名都是唯一的 16 | filename: `[name].dll.${Math.ceil(Math.random() * 10000)}.js`, 17 | // vendor.dll.js中暴露出的全局变量名 18 | // 保持与 webpack.DllPlugin 中名称一致 19 | library: '[name]_[hash]' 20 | }, 21 | plugins: [ 22 | // 清除之前的dll文件 23 | new CleanWebpackPlugin(['*.*'], { 24 | root: path.join(__dirname, dllPath) 25 | }), 26 | // 设置环境变量 27 | new webpack.DefinePlugin({ 28 | 'process.env': { 29 | NODE_ENV: 'production' 30 | } 31 | }), 32 | // manifest.json 描述动态链接库包含了哪些内容 33 | new webpack.DllPlugin({ 34 | path: path.join(__dirname, dllPath, '[name]-manifest.json'), 35 | // 保持与 output.library 中名称一致 36 | name: '[name]_[hash]', 37 | context: process.cwd() 38 | }) 39 | ] 40 | } 41 | --------------------------------------------------------------------------------