├── .gitignore ├── README.md ├── vue-dashboard ├── .browserslistrc ├── .circleci │ └── config.yml ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .github │ ├── CODE_OF_CONDUCT.md │ ├── COMMIT_CONVENTION.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── babel.config.js ├── cypress.json ├── demo │ └── demo.gif ├── jest.config.js ├── package.json ├── postcss.config.js ├── 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 ├── src │ ├── App.vue │ ├── api │ │ ├── articles.ts │ │ ├── types.d.ts │ │ └── users.ts │ ├── assets │ │ └── 404-images │ │ │ ├── 404-cloud.png │ │ │ └── 404.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ └── Hamburger │ │ │ └── index.vue │ ├── icons │ │ ├── README.md │ │ ├── components │ │ │ ├── dashboard.ts │ │ │ ├── example.ts │ │ │ ├── eye-off.ts │ │ │ ├── eye-on.ts │ │ │ ├── form.ts │ │ │ ├── hamburger.ts │ │ │ ├── index.ts │ │ │ ├── link.ts │ │ │ ├── nested.ts │ │ │ ├── password.ts │ │ │ ├── table.ts │ │ │ ├── tree.ts │ │ │ └── user.ts │ │ └── svg │ │ │ ├── dashboard.svg │ │ │ ├── example.svg │ │ │ ├── eye-off.svg │ │ │ ├── eye-on.svg │ │ │ ├── form.svg │ │ │ ├── hamburger.svg │ │ │ ├── link.svg │ │ │ ├── nested.svg │ │ │ ├── password.svg │ │ │ ├── table.svg │ │ │ ├── tree.svg │ │ │ └── user.svg │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar │ │ │ │ └── index.vue │ │ │ ├── Sidebar │ │ │ │ ├── SidebarItem.vue │ │ │ │ ├── SidebarItemLink.vue │ │ │ │ └── index.vue │ │ │ └── index.ts │ │ ├── index.vue │ │ └── mixin │ │ │ └── resize.ts │ ├── main.ts │ ├── permission.ts │ ├── registerServiceWorker.ts │ ├── router.ts │ ├── shims-vue.d.ts │ ├── store │ │ ├── index.ts │ │ └── modules │ │ │ ├── app.ts │ │ │ └── user.ts │ ├── styles │ │ ├── _mixins.scss │ │ ├── _svgicon.scss │ │ ├── _transition.scss │ │ ├── _variables.scss │ │ ├── _variables.scss.d.ts │ │ ├── element-variables.scss │ │ └── index.scss │ ├── types │ │ └── vue.d.ts │ ├── utils │ │ ├── cookies.ts │ │ ├── request.ts │ │ └── validate.ts │ └── views │ │ ├── 404.vue │ │ ├── dashboard │ │ └── index.vue │ │ ├── detail │ │ └── index.vue │ │ └── login │ │ └── index.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── components │ │ └── Breadcrumb.spec.ts │ │ └── utils │ │ └── validate.spec.ts ├── tsconfig.json ├── vue.config.js └── yarn.lock ├── vue-example ├── .browserslistrc ├── .circleci │ └── config.yml ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .github │ ├── CODE_OF_CONDUCT.md │ ├── COMMIT_CONVENTION.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── babel.config.js ├── cypress.json ├── demo │ └── demo.gif ├── jest.config.js ├── package.json ├── postcss.config.js ├── 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 ├── src │ ├── App.vue │ ├── api │ │ ├── articles.ts │ │ ├── types.d.ts │ │ └── users.ts │ ├── assets │ │ └── 404-images │ │ │ ├── 404-cloud.png │ │ │ └── 404.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ └── Hamburger │ │ │ └── index.vue │ ├── icons │ │ ├── README.md │ │ ├── components │ │ │ ├── dashboard.ts │ │ │ ├── example.ts │ │ │ ├── eye-off.ts │ │ │ ├── eye-on.ts │ │ │ ├── form.ts │ │ │ ├── hamburger.ts │ │ │ ├── index.ts │ │ │ ├── link.ts │ │ │ ├── nested.ts │ │ │ ├── password.ts │ │ │ ├── table.ts │ │ │ ├── tree.ts │ │ │ └── user.ts │ │ └── svg │ │ │ ├── dashboard.svg │ │ │ ├── example.svg │ │ │ ├── eye-off.svg │ │ │ ├── eye-on.svg │ │ │ ├── form.svg │ │ │ ├── hamburger.svg │ │ │ ├── link.svg │ │ │ ├── nested.svg │ │ │ ├── password.svg │ │ │ ├── table.svg │ │ │ ├── tree.svg │ │ │ └── user.svg │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar │ │ │ │ └── index.vue │ │ │ ├── Sidebar │ │ │ │ ├── SidebarItem.vue │ │ │ │ ├── SidebarItemLink.vue │ │ │ │ └── index.vue │ │ │ └── index.ts │ │ ├── index.vue │ │ └── mixin │ │ │ └── resize.ts │ ├── main.ts │ ├── permission.ts │ ├── registerServiceWorker.ts │ ├── router.ts │ ├── shims-vue.d.ts │ ├── store │ │ ├── index.ts │ │ └── modules │ │ │ ├── app.ts │ │ │ └── user.ts │ ├── styles │ │ ├── _mixins.scss │ │ ├── _svgicon.scss │ │ ├── _transition.scss │ │ ├── _variables.scss │ │ ├── _variables.scss.d.ts │ │ ├── element-variables.scss │ │ └── index.scss │ ├── utils │ │ ├── cookies.ts │ │ ├── request.ts │ │ └── validate.ts │ └── views │ │ ├── 404.vue │ │ ├── dashboard │ │ └── index.vue │ │ ├── form │ │ └── index.vue │ │ ├── login │ │ └── index.vue │ │ ├── nested │ │ ├── menu1 │ │ │ ├── index.vue │ │ │ ├── menu1-1 │ │ │ │ └── index.vue │ │ │ ├── menu1-2 │ │ │ │ ├── index.vue │ │ │ │ ├── menu1-2-1 │ │ │ │ │ └── index.vue │ │ │ │ └── menu1-2-2 │ │ │ │ │ └── index.vue │ │ │ └── menu1-3 │ │ │ │ └── index.vue │ │ └── menu2 │ │ │ └── index.vue │ │ ├── table │ │ └── index.vue │ │ └── tree │ │ └── index.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── components │ │ └── Breadcrumb.spec.ts │ │ └── utils │ │ └── validate.spec.ts ├── tsconfig.json ├── vue.config.js └── yarn.lock └── vue-main ├── .browserslistrc ├── .circleci └── config.yml ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODE_OF_CONDUCT.md ├── COMMIT_CONVENTION.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── babel.config.js ├── cypress.json ├── demo └── demo.gif ├── jest.config.js ├── package.json ├── postcss.config.js ├── 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 ├── src ├── App.vue ├── api │ ├── articles.ts │ ├── types.d.ts │ └── users.ts ├── assets │ └── 404-images │ │ ├── 404-cloud.png │ │ └── 404.png ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ └── HelloWorld.vue ├── icons │ ├── README.md │ ├── components │ │ ├── dashboard.ts │ │ ├── example.ts │ │ ├── eye-off.ts │ │ ├── eye-on.ts │ │ ├── form.ts │ │ ├── hamburger.ts │ │ ├── index.ts │ │ ├── link.ts │ │ ├── nested.ts │ │ ├── password.ts │ │ ├── table.ts │ │ ├── tree.ts │ │ └── user.ts │ └── svg │ │ ├── dashboard.svg │ │ ├── example.svg │ │ ├── eye-off.svg │ │ ├── eye-on.svg │ │ ├── form.svg │ │ ├── hamburger.svg │ │ ├── link.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── table.svg │ │ ├── tree.svg │ │ └── user.svg ├── layout │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar │ │ │ └── index.vue │ │ ├── Sidebar │ │ │ ├── SidebarItem.vue │ │ │ ├── SidebarItemLink.vue │ │ │ └── index.vue │ │ ├── TagsView │ │ │ ├── ScrollPane.vue │ │ │ └── index.vue │ │ └── index.ts │ ├── index.vue │ └── mixin │ │ └── resize.ts ├── main.ts ├── permission.ts ├── registerServiceWorker.ts ├── router.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── store │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── tags-view.ts │ │ └── user.ts ├── styles │ ├── _mixins.scss │ ├── _svgicon.scss │ ├── _transition.scss │ ├── _variables.scss │ ├── _variables.scss.d.ts │ ├── element-variables.scss │ └── index.scss ├── utils │ ├── cookies.ts │ ├── request.ts │ └── validate.ts └── views │ ├── 404.vue │ ├── form │ └── index.vue │ ├── framework │ └── index.vue │ ├── login │ └── index.vue │ ├── nested │ ├── menu1 │ │ ├── index.vue │ │ ├── menu1-1 │ │ │ └── index.vue │ │ ├── menu1-2 │ │ │ ├── index.vue │ │ │ ├── menu1-2-1 │ │ │ │ └── index.vue │ │ │ └── menu1-2-2 │ │ │ │ └── index.vue │ │ └── menu1-3 │ │ │ └── index.vue │ └── menu2 │ │ └── index.vue │ ├── table │ └── index.vue │ └── tree │ └── index.vue ├── tests └── unit │ ├── .eslintrc.js │ ├── components │ └── Breadcrumb.spec.ts │ └── utils │ └── validate.spec.ts ├── tsconfig.json ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-ts-admin-microApp 2 | 3 | ## 说明 4 | 中后台微前端应用项目模板,基于vue-typescript-admin-template模板修改而来。微前端部分采用了qiankun微前端框架。 5 | 6 | ## 安装 7 | - 子项目vue-dashboard: ```yarn install or npm install``` 8 | - 子项目vue-example: ```yarn install or npm install``` 9 | - 主项目vue-main: ```yarn install or npm install``` 10 | 11 | 12 | ### 开发模式 13 | 在每个项目上运行 14 | ``` 15 | npm run dev 16 | ``` 17 | -------------------------------------------------------------------------------- /vue-dashboard/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /vue-dashboard/.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 | 7 | jobs: 8 | build: 9 | docker: 10 | - image: circleci/node:11 11 | 12 | working_directory: ~/vue-typescript-admin-template 13 | 14 | steps: 15 | - checkout 16 | 17 | # Download and cache dependencies 18 | - restore_cache: 19 | keys: 20 | - v1-dependencies-{{ checksum "package.json" }} 21 | # fallback to using the latest cache if no exact match is found 22 | - v1-dependencies- 23 | 24 | - run: yarn install 25 | 26 | - save_cache: 27 | paths: 28 | - node_modules 29 | key: v1-dependencies-{{ checksum "package.json" }} 30 | 31 | - run: yarn lint && yarn build 32 | -------------------------------------------------------------------------------- /vue-dashboard/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # Indentation override for js(x), ts(x) and vue files 14 | [*.{js,jsx,ts,tsx,vue}] 15 | indent_size = 2 16 | indent_style = space 17 | 18 | # Indentation override for css related files 19 | [*.{css,styl,scss,less,sass}] 20 | indent_size = 2 21 | indent_style = space 22 | 23 | # Indentation override for html files 24 | [*.html] 25 | indent_size = 2 26 | indent_style = space 27 | 28 | # Trailing space override for markdown file 29 | [*.md] 30 | trim_trailing_whitespace = false 31 | 32 | # Indentation override for config files 33 | [*.{json,yml}] 34 | indent_size = 2 35 | indent_style = space 36 | -------------------------------------------------------------------------------- /vue-dashboard/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_API = 'https://vue-typescript-admin-mock-server.armour.now.sh/mock-api/v1/' 2 | -------------------------------------------------------------------------------- /vue-dashboard/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | src/assets 3 | tests/unit/coverage 4 | -------------------------------------------------------------------------------- /vue-dashboard/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true 7 | }, 8 | parserOptions: { 9 | parser: '@typescript-eslint/parser', 10 | sourceType: 'module' 11 | }, 12 | plugins: [ 13 | 'vue' 14 | ], 15 | rules: { 16 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 18 | 'space-before-function-paren': [2, 'never'], 19 | 'vue/array-bracket-spacing': 'error', 20 | 'vue/arrow-spacing': 'error', 21 | 'vue/block-spacing': 'error', 22 | 'vue/brace-style': 'error', 23 | 'vue/camelcase': 'error', 24 | 'vue/comma-dangle': 'error', 25 | 'vue/component-name-in-template-casing': 'error', 26 | 'vue/eqeqeq': 'error', 27 | 'vue/key-spacing': 'error', 28 | 'vue/match-component-file-name': 'error', 29 | 'vue/object-curly-spacing': 'error' 30 | }, 31 | 'extends': [ 32 | 'eslint:recommended', 33 | 'plugin:vue/recommended', 34 | '@vue/standard', 35 | '@vue/typescript' 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /vue-dashboard/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | # I'm submitting a 10 | 11 | 20 | 21 | ## Current behavior 22 | 23 | 24 | 25 | ## Expected behavior 26 | 27 | 28 | 29 | ## Minimal reproduction of the problem with instructions 30 | 31 | 34 | 35 | ## What is the motivation / use case for changing the behavior 36 | 37 | 38 | 39 | ## Environment 40 | 41 | 42 | -------------------------------------------------------------------------------- /vue-dashboard/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | **Make sure the PR fulfills these requirements:** 9 | 10 | - When resolving a specific issue, make sure it's referenced in the PR's title (e.g. `Closes #xxx[,#xxx]`, where "xxx" is the issue number) 11 | 12 | - If adding a **new feature**, the PR's description includes: A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) 13 | 14 | - If this PR introduce a **breaking change**, please describe the impact and migration path for existing applications 15 | 16 | **What kind of change does this PR introduce?** 17 | 18 | 27 | 28 | **More information:** 29 | -------------------------------------------------------------------------------- /vue-dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | /tests/**/coverage/ 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | -------------------------------------------------------------------------------- /vue-dashboard/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chong Guo 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 | -------------------------------------------------------------------------------- /vue-dashboard/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue-dashboard/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vue-dashboard/demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/demo/demo.gif -------------------------------------------------------------------------------- /vue-dashboard/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue', 7 | 'ts', 8 | 'tsx' 9 | ], 10 | transform: { 11 | '^.+\\.vue$': 'vue-jest', 12 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 13 | '^.+\\.tsx?$': 'ts-jest' 14 | }, 15 | transformIgnorePatterns: [ 16 | '/node_modules/' 17 | ], 18 | moduleNameMapper: { 19 | '^@/(.*)$': '/src/$1' 20 | }, 21 | snapshotSerializers: [ 22 | 'jest-serializer-vue' 23 | ], 24 | testMatch: [ 25 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 26 | ], 27 | collectCoverage: true, 28 | collectCoverageFrom: [ 29 | 'src/utils/**/*.{ts,vue}', 30 | '!src/utils/auth.ts', 31 | '!src/utils/request.ts', 32 | 'src/components/**/*.{ts,vue}' 33 | ], 34 | coverageDirectory: '/tests/unit/coverage', 35 | coverageReporters: [ 36 | 'lcov', 37 | 'text-summary' 38 | ], 39 | testURL: 'http://localhost/', 40 | watchPlugins: [ 41 | 'jest-watch-typeahead/filename', 42 | 'jest-watch-typeahead/testname' 43 | ], 44 | globals: { 45 | 'ts-jest': { 46 | babelConfig: true 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vue-dashboard/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-dashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/favicon.ico -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /vue-dashboard/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /vue-dashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue-dashboard/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue Typescript Admin", 3 | "short_name": "Vue Ts Admin", 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": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#fff", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /vue-dashboard/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /vue-dashboard/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /vue-dashboard/src/api/articles.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getArticles = (params: any) => 4 | request({ 5 | url: '/articles', 6 | method: 'get', 7 | params 8 | }) 9 | -------------------------------------------------------------------------------- /vue-dashboard/src/api/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface IArticleData { 2 | id: number 3 | status: string 4 | title: string 5 | abstractContent: string 6 | fullContent: string 7 | sourceURL: string 8 | imageURL: string 9 | timestamp: string | number 10 | platforms: string[] 11 | disableComment: boolean 12 | importance: number 13 | author: string 14 | reviewer: string 15 | type: string 16 | pageviews: number 17 | } 18 | -------------------------------------------------------------------------------- /vue-dashboard/src/api/users.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getUserInfo = (data: any) => 4 | request({ 5 | url: '/users/info', 6 | method: 'post', 7 | data 8 | }) 9 | 10 | export const login = (data: any) => 11 | request({ 12 | url: '/users/login', 13 | method: 'post', 14 | data 15 | }) 16 | 17 | export const logout = () => 18 | request({ 19 | url: '/users/logout', 20 | method: 'post' 21 | }) 22 | -------------------------------------------------------------------------------- /vue-dashboard/src/assets/404-images/404-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/src/assets/404-images/404-cloud.png -------------------------------------------------------------------------------- /vue-dashboard/src/assets/404-images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-dashboard/src/assets/404-images/404.png -------------------------------------------------------------------------------- /vue-dashboard/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/README.md: -------------------------------------------------------------------------------- 1 | # vue-svgicon 2 | 3 | ## English 4 | 5 | * All svg components were generated by `vue-svgicon` using svg files 6 | * After you adding new svg files into `icons/svg` folder, run `yarn svg` to regerenrate all svg components (before this, you should have `vue-svgicon` installed globally or use `npx`) 7 | * See details at: [https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 8 | 9 | ## 中文 10 | 11 | * 所有的 svg 组件都是由 `vue-svgicon` 生成的 12 | * 每当在 `icons/svg` 文件夹内添加 icon 之后,可以通过执行 `yarn svg` 来重新生成所有组件 (在此之前需要全局安装 `vue-svgicon` 或使用 `npx`) 13 | * 详细文档请见:[https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 14 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/dashboard.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'dashboard': { 7 | width: 128, 8 | height: 100, 9 | viewBox: '0 0 128 100', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/example.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'example': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/eye-off.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-off': { 7 | width: 128, 8 | height: 64, 9 | viewBox: '0 0 128 64', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/eye-on.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-on': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/form.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'form': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/hamburger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'hamburger': { 7 | width: 64, 8 | height: 64, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import './dashboard' 3 | import './example' 4 | import './eye-off' 5 | import './eye-on' 6 | import './form' 7 | import './hamburger' 8 | import './link' 9 | import './nested' 10 | import './password' 11 | import './table' 12 | import './tree' 13 | import './user' 14 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/link.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'link': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/nested.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'nested': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/password.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'password': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/table.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'table': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/tree.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'tree': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/components/user.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'user': { 7 | width: 130, 8 | height: 130, 9 | viewBox: '0 0 130 130', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/eye-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/eye-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/hamburger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /vue-dashboard/src/layout/components/Sidebar/SidebarItemLink.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /vue-dashboard/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | 54 | 80 | 81 | 92 | -------------------------------------------------------------------------------- /vue-dashboard/src/layout/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain.vue' 2 | export { default as Navbar } from './Navbar/index.vue' 3 | export { default as Sidebar } from './Sidebar/index.vue' 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/layout/mixin/resize.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue, Watch } from 'vue-property-decorator' 2 | import { AppModule, DeviceType } from '@/store/modules/app' 3 | 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | @Component({ 7 | name: 'ResizeMixin' 8 | }) 9 | export default class extends Vue { 10 | get device() { 11 | return AppModule.device 12 | } 13 | 14 | get sidebar() { 15 | return AppModule.sidebar 16 | } 17 | 18 | @Watch('$route') 19 | private onRouteChange() { 20 | if (this.device === DeviceType.Mobile && this.sidebar.opened) { 21 | AppModule.CloseSideBar(false) 22 | } 23 | } 24 | 25 | beforeMount() { 26 | window.addEventListener('resize', this.resizeHandler) 27 | } 28 | 29 | mounted() { 30 | const isMobile = this.isMobile() 31 | if (isMobile) { 32 | AppModule.ToggleDevice(DeviceType.Mobile) 33 | AppModule.CloseSideBar(true) 34 | } 35 | } 36 | 37 | beforeDestroy() { 38 | window.removeEventListener('resize', this.resizeHandler) 39 | } 40 | 41 | private isMobile() { 42 | const rect = document.body.getBoundingClientRect() 43 | return rect.width - 1 < WIDTH 44 | } 45 | 46 | private resizeHandler() { 47 | if (!document.hidden) { 48 | const isMobile = this.isMobile() 49 | AppModule.ToggleDevice(isMobile ? DeviceType.Mobile : DeviceType.Desktop) 50 | if (isMobile) { 51 | AppModule.CloseSideBar(true) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vue-dashboard/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css' 4 | import ElementUI from 'element-ui' 5 | import SvgIcon from 'vue-svgicon' 6 | 7 | import '@/styles/element-variables.scss' 8 | import '@/styles/index.scss' 9 | 10 | import App from '@/App.vue' 11 | import store from '@/store' 12 | import router from '@/router' 13 | import '@/icons/components' 14 | import '@/permission' 15 | 16 | Vue.use(ElementUI) 17 | Vue.use(SvgIcon, { 18 | tagName: 'svg-icon', 19 | defaultWidth: '1em', 20 | defaultHeight: '1em' 21 | }) 22 | 23 | Vue.config.productionTip = false 24 | 25 | const name = 'dashboard' 26 | let app: Vue | undefined = undefined 27 | 28 | export async function bootstrap() { 29 | console.log(`${name}应用初始化`) 30 | } 31 | 32 | /** 33 | * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 34 | */ 35 | export async function mount(props: any) { 36 | Vue.prototype.$isFramework = props.isFramework 37 | Vue.prototype.$mainApp = props.mainInstance 38 | if (!app) { 39 | app = new Vue({ 40 | router, 41 | store, 42 | render: (h) => h(App) 43 | }) 44 | } 45 | app.$mount('#app') 46 | props.callback(app) 47 | } 48 | 49 | /** 50 | * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载子应用的应用实例 51 | */ 52 | export async function unmount() { 53 | console.log(`${name}应用卸载`) 54 | // 销毁 55 | if (app) { 56 | app.$destroy() 57 | app = undefined 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vue-dashboard/src/permission.ts: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | import { Message } from 'element-ui' 5 | import { Route } from 'vue-router' 6 | import { UserModule } from '@/store/modules/user' 7 | 8 | declare module 'vue/types/vue' { 9 | interface Vue { 10 | $isFramework: boolean, 11 | $mainApp: Vue 12 | } 13 | } 14 | 15 | NProgress.configure({ showSpinner: false }) 16 | 17 | const whiteList = ['/login'] 18 | 19 | router.beforeEach(async(to: Route, _: Route, next: any) => { 20 | // Start progress bar 21 | NProgress.start() 22 | 23 | // Determine whether the user has logged in 24 | if (UserModule.token) { 25 | if (to.path === '/login') { 26 | // If is logged in, redirect to the home page 27 | next({ path: '/' }) 28 | NProgress.done() 29 | } else { 30 | // Check whether the user has obtained his permission roles 31 | if (UserModule.roles.length === 0) { 32 | try { 33 | // Get user info, including roles 34 | await UserModule.GetUserInfo() 35 | // Set the replace: true, so the navigation will not leave a history record 36 | next({ ...to, replace: true }) 37 | } catch (err) { 38 | // Remove token and redirect to login page 39 | UserModule.ResetToken() 40 | Message.error(err || 'Has Error') 41 | next(`/login?redirect=${to.path}`) 42 | NProgress.done() 43 | } 44 | } else { 45 | next() 46 | } 47 | } 48 | } else { 49 | // Has no token 50 | if (whiteList.indexOf(to.path) !== -1) { 51 | // In the free login whitelist, go directly 52 | next() 53 | } else { 54 | // Other pages that do not have permission to access are redirected to the login page. 55 | next(`/login?redirect=${to.path}`) 56 | NProgress.done() 57 | } 58 | } 59 | }) 60 | 61 | router.afterEach((to: Route) => { 62 | // Finish progress bar 63 | NProgress.done() 64 | 65 | // set page title 66 | document.title = to.meta.title 67 | if (router.app && router.app.$mainApp) { 68 | router.app.$mainApp.$emit('openTag', to) 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /vue-dashboard/src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.') 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline() { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error(error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /vue-dashboard/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /vue-dashboard/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { IAppState } from './modules/app' 4 | import { IUserState } from './modules/user' 5 | 6 | Vue.use(Vuex) 7 | 8 | export interface IRootState { 9 | app: IAppState 10 | user: IUserState 11 | } 12 | 13 | // Declare empty store first, dynamically register all modules later. 14 | export default new Vuex.Store({}) 15 | -------------------------------------------------------------------------------- /vue-dashboard/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators' 2 | import { getSidebarStatus, setSidebarStatus } from '@/utils/cookies' 3 | import store from '@/store' 4 | 5 | export enum DeviceType { 6 | Mobile, 7 | Desktop, 8 | } 9 | 10 | export interface IAppState { 11 | device: DeviceType 12 | sidebar: { 13 | opened: boolean 14 | withoutAnimation: boolean 15 | } 16 | } 17 | 18 | @Module({ dynamic: true, store, name: 'app' }) 19 | class App extends VuexModule implements IAppState { 20 | public sidebar = { 21 | opened: getSidebarStatus() !== 'closed', 22 | withoutAnimation: false 23 | } 24 | public device = DeviceType.Desktop 25 | 26 | @Mutation 27 | private TOGGLE_SIDEBAR(withoutAnimation: boolean) { 28 | this.sidebar.opened = !this.sidebar.opened 29 | this.sidebar.withoutAnimation = withoutAnimation 30 | if (this.sidebar.opened) { 31 | setSidebarStatus('opened') 32 | } else { 33 | setSidebarStatus('closed') 34 | } 35 | } 36 | 37 | @Mutation 38 | private CLOSE_SIDEBAR(withoutAnimation: boolean) { 39 | this.sidebar.opened = false 40 | this.sidebar.withoutAnimation = withoutAnimation 41 | setSidebarStatus('closed') 42 | } 43 | 44 | @Mutation 45 | private TOGGLE_DEVICE(device: DeviceType) { 46 | this.device = device 47 | } 48 | 49 | @Action 50 | public ToggleSideBar(withoutAnimation: boolean) { 51 | this.TOGGLE_SIDEBAR(withoutAnimation) 52 | } 53 | 54 | @Action 55 | public CloseSideBar(withoutAnimation: boolean) { 56 | this.CLOSE_SIDEBAR(withoutAnimation) 57 | } 58 | 59 | @Action 60 | public ToggleDevice(device: DeviceType) { 61 | this.TOGGLE_DEVICE(device) 62 | } 63 | } 64 | 65 | export const AppModule = getModule(App) 66 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* Mixins */ 2 | @mixin clearfix { 3 | &:after { 4 | content: ""; 5 | display: table; 6 | clear: both; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/_svgicon.scss: -------------------------------------------------------------------------------- 1 | /* Recommended css code for vue-svgicon */ 2 | .svg-icon { 3 | display: inline-block; 4 | width: 16px; 5 | height: 16px; 6 | color: inherit; 7 | fill: none; 8 | stroke: currentColor; 9 | vertical-align: -0.15em; 10 | } 11 | 12 | .svg-fill { 13 | fill: currentColor; 14 | stroke: none; 15 | } 16 | 17 | .svg-up { 18 | transform: rotate(0deg); 19 | } 20 | 21 | .svg-right { 22 | transform: rotate(90deg); 23 | } 24 | 25 | .svg-down { 26 | transform: rotate(180deg); 27 | } 28 | 29 | .svg-left { 30 | transform: rotate(-90deg); 31 | } 32 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/_transition.scss: -------------------------------------------------------------------------------- 1 | /* Global transition */ 2 | // See https://vuejs.org/v2/guide/transitions.html for detail 3 | 4 | // fade 5 | .fade-enter-active, 6 | .fade-leave-active { 7 | transition: opacity 0.28s; 8 | } 9 | 10 | .fade-enter, 11 | .fade-leave-active { 12 | opacity: 0; 13 | } 14 | 15 | // fade-transform 16 | .fade-transform-leave-active, 17 | .fade-transform-enter-active { 18 | transition: all .5s; 19 | } 20 | 21 | .fade-transform-enter { 22 | opacity: 0; 23 | transform: translateX(-30px); 24 | } 25 | 26 | .fade-transform-leave-to { 27 | opacity: 0; 28 | transform: translateX(30px); 29 | } 30 | 31 | // breadcrumb 32 | .breadcrumb-enter-active, 33 | .breadcrumb-leave-active { 34 | transition: all .5s; 35 | } 36 | 37 | .breadcrumb-enter, 38 | .breadcrumb-leave-active { 39 | opacity: 0; 40 | transform: translateX(20px); 41 | } 42 | 43 | .breadcrumb-move { 44 | transition: all .5s; 45 | } 46 | 47 | .breadcrumb-leave-active { 48 | position: absolute; 49 | } 50 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | 3 | // Base color 4 | $blue:#324157; 5 | $light-blue:#3A71A8; 6 | $red:#C03639; 7 | $pink: #E65D6E; 8 | $green: #30B08F; 9 | $tiffany: #4AB7BD; 10 | $yellow:#FEC171; 11 | $panGreen: #30B08F; 12 | 13 | // Sidebar 14 | $sideBarWidth: 210px; 15 | // $sideBarWidth: 0; 16 | $subMenuBg:#1f2d3d; 17 | $subMenuHover:#001528; 18 | $subMenuActiveText:#f4f4f5; 19 | $menuBg:#304156; 20 | $menuText:#bfcbd9; 21 | $menuActiveText:#409EFF; // Also see settings.sidebarTextTheme 22 | 23 | // Login page 24 | $lightGray: #eee; 25 | $darkGray:#889aa4; 26 | $loginBg: #2d3a4b; 27 | $loginCursorColor: #fff; 28 | 29 | // The :export directive is the magic sauce for webpack 30 | // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript 31 | :export { 32 | menuBg: $menuBg; 33 | menuText: $menuText; 34 | menuActiveText: $menuActiveText; 35 | } 36 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/_variables.scss.d.ts: -------------------------------------------------------------------------------- 1 | export interface IScssVariables { 2 | menuBg: string 3 | menuText: string 4 | menuActiveText: string 5 | } 6 | 7 | export const variables: IScssVariables 8 | 9 | export default variables 10 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* Element Variables */ 2 | 3 | // Override Element UI variables 4 | $--color-primary: #1890ff; 5 | $--color-success: #13ce66; 6 | $--color-warning: #FFBA00; 7 | $--color-danger: #ff4949; 8 | $--color-info: #5d5d5d; 9 | $--button-font-weight: 400; 10 | $--color-text-regular: #1f2d3d; 11 | $--border-color-light: #dfe4ed; 12 | $--border-color-lighter: #e6ebf5; 13 | $--table-border:1px solid#dfe6ec; 14 | 15 | // Icon font path, required 16 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 17 | 18 | // Apply overrided variables in Element UI 19 | @import '~element-ui/packages/theme-chalk/src/index'; 20 | -------------------------------------------------------------------------------- /vue-dashboard/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // @import './variables.scss'; // Already imported in style-resources-loader 2 | // @import './mixins.scss'; // Already imported in style-resources-loader 3 | @import './transition.scss'; 4 | @import './svgicon.scss'; 5 | 6 | /* Global scss */ 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | html { 16 | height: 100%; 17 | } 18 | 19 | #app { 20 | height: 100%; 21 | } 22 | 23 | *, 24 | *:before, 25 | *:after { 26 | box-sizing: border-box; 27 | } 28 | 29 | a, 30 | a:focus, 31 | a:hover { 32 | color: inherit; 33 | outline: none; 34 | text-decoration: none; 35 | } 36 | 37 | div:focus { 38 | outline: none; 39 | } 40 | 41 | .clearfix { 42 | @include clearfix; 43 | } 44 | 45 | .app-container { 46 | padding: 20px; 47 | } 48 | -------------------------------------------------------------------------------- /vue-dashboard/src/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vue-dashboard/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | // App 4 | const sidebarStatusKey = 'sidebar_status' 5 | export const getSidebarStatus = () => Cookies.get(sidebarStatusKey) 6 | export const setSidebarStatus = (sidebarStatus: string) => Cookies.set(sidebarStatusKey, sidebarStatus) 7 | 8 | // User 9 | const tokenKey = 'vue_typescript_admin_access_token' 10 | export const getToken = () => Cookies.get(tokenKey) 11 | export const setToken = (token: string) => Cookies.set(tokenKey, token) 12 | export const removeToken = () => Cookies.remove(tokenKey) 13 | -------------------------------------------------------------------------------- /vue-dashboard/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import { UserModule } from '@/store/modules/user' 4 | 5 | const service = axios.create({ 6 | baseURL: process.env.VUE_APP_BASE_API, 7 | timeout: 5000 8 | }) 9 | 10 | // Request interceptors 11 | service.interceptors.request.use( 12 | (config) => { 13 | // Add X-Access-Token header to every request, you can add other custom headers here 14 | if (UserModule.token) { 15 | config.headers['X-Access-Token'] = UserModule.token 16 | } 17 | return config 18 | }, 19 | (error) => { 20 | Promise.reject(error) 21 | } 22 | ) 23 | 24 | // Response interceptors 25 | service.interceptors.response.use( 26 | (response) => { 27 | // Some example codes here: 28 | // code == 20000: success 29 | // code == 50001: invalid access token 30 | // code == 50002: already login in other place 31 | // code == 50003: access token expired 32 | // code == 50004: invalid user (user not exist) 33 | // code == 50005: username or password is incorrect 34 | // You can change this part for your own usage. 35 | const res = response.data 36 | if (res.code !== 20000) { 37 | Message({ 38 | message: res.message || 'Error', 39 | type: 'error', 40 | duration: 5 * 1000 41 | }) 42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 43 | MessageBox.confirm( 44 | 'You have been logged out, try to login again.', 45 | 'Log out', 46 | { 47 | confirmButtonText: 'Relogin', 48 | cancelButtonText: 'Cancel', 49 | type: 'warning' 50 | } 51 | ).then(() => { 52 | UserModule.ResetToken() 53 | location.reload() // To prevent bugs from vue-router 54 | }) 55 | } 56 | return Promise.reject(new Error(res.message || 'Error')) 57 | } else { 58 | return response.data 59 | } 60 | }, 61 | (error) => { 62 | Message({ 63 | message: error.message, 64 | type: 'error', 65 | duration: 5 * 1000 66 | }) 67 | return Promise.reject(error) 68 | } 69 | ) 70 | 71 | export default service 72 | -------------------------------------------------------------------------------- /vue-dashboard/src/utils/validate.ts: -------------------------------------------------------------------------------- 1 | export const isValidUsername = (str: string) => ['admin', 'editor'].indexOf(str.trim()) >= 0 2 | 3 | export const isExternal = (path: string) => /^(https?:|mailto:|tel:)/.test(path) 4 | -------------------------------------------------------------------------------- /vue-dashboard/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 58 | 59 | 75 | -------------------------------------------------------------------------------- /vue-dashboard/src/views/detail/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /vue-dashboard/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-dashboard/tests/unit/utils/validate.spec.ts: -------------------------------------------------------------------------------- 1 | import { isValidUsername, isExternal } from '@/utils/validate' 2 | 3 | describe('Utils:validate', () => { 4 | it('isValidUsername', () => { 5 | expect(isValidUsername('admin')).toBe(true) 6 | expect(isValidUsername('editor')).toBe(true) 7 | expect(isValidUsername('xxxx')).toBe(false) 8 | }) 9 | 10 | it('isExternal', () => { 11 | expect(isExternal('https://www.armour.com/')).toBe(true) 12 | expect(isExternal('mailto:someone@test.com')).toBe(true) 13 | expect(isExternal('123aBC')).toBe(false) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /vue-dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "node", 16 | "jest", 17 | "webpack-env" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue-dashboard/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { name } = require('./package') 3 | const cors = require('cors') 4 | const port = 5001 5 | 6 | module.exports = { 7 | devServer: { 8 | port, 9 | before(app, server) { 10 | app.use(cors()) 11 | } 12 | }, 13 | publicPath: process.env.NODE_ENV === 'production' ? '/vue-typescript-admin-template/' : `//localhost:${port}`, // TODO: Remember to change this to fit your need 14 | lintOnSave: process.env.NODE_ENV === 'development', 15 | pwa: { 16 | name: name 17 | }, 18 | pluginOptions: { 19 | 'style-resources-loader': { 20 | preProcessor: 'scss', 21 | patterns: [ 22 | path.resolve(__dirname, 'src/styles/_variables.scss'), 23 | path.resolve(__dirname, 'src/styles/_mixins.scss') 24 | ] 25 | } 26 | }, 27 | /* configureWebpack: { 28 | output: { 29 | // 把子应用打包成 umd 库格式 30 | library: `${name}-[name]`, 31 | libraryTarget: 'umd', 32 | jsonpFunction: `webpackJsonp_${name}`, 33 | } 34 | }, */ 35 | chainWebpack(config) { 36 | // Provide the app's title in webpack's name field, so that 37 | // it can be accessed in index.html to inject the correct title. 38 | config.set('name', name) 39 | config.plugin("html").tap(args => { 40 | args[0].minify = false; 41 | return args; 42 | }); 43 | // 把子应用打包成 umd 库格式 44 | config.output 45 | .jsonpFunction(`webpackJsonp_${name}`) 46 | .library(`${name}-[name]`) 47 | .libraryTarget('umd') 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vue-example/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /vue-example/.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 | 7 | jobs: 8 | build: 9 | docker: 10 | - image: circleci/node:11 11 | 12 | working_directory: ~/vue-typescript-admin-template 13 | 14 | steps: 15 | - checkout 16 | 17 | # Download and cache dependencies 18 | - restore_cache: 19 | keys: 20 | - v1-dependencies-{{ checksum "package.json" }} 21 | # fallback to using the latest cache if no exact match is found 22 | - v1-dependencies- 23 | 24 | - run: yarn install 25 | 26 | - save_cache: 27 | paths: 28 | - node_modules 29 | key: v1-dependencies-{{ checksum "package.json" }} 30 | 31 | - run: yarn lint && yarn build 32 | -------------------------------------------------------------------------------- /vue-example/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # Indentation override for js(x), ts(x) and vue files 14 | [*.{js,jsx,ts,tsx,vue}] 15 | indent_size = 2 16 | indent_style = space 17 | 18 | # Indentation override for css related files 19 | [*.{css,styl,scss,less,sass}] 20 | indent_size = 2 21 | indent_style = space 22 | 23 | # Indentation override for html files 24 | [*.html] 25 | indent_size = 2 26 | indent_style = space 27 | 28 | # Trailing space override for markdown file 29 | [*.md] 30 | trim_trailing_whitespace = false 31 | 32 | # Indentation override for config files 33 | [*.{json,yml}] 34 | indent_size = 2 35 | indent_style = space 36 | -------------------------------------------------------------------------------- /vue-example/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_API = 'https://vue-typescript-admin-mock-server.armour.now.sh/mock-api/v1/' 2 | -------------------------------------------------------------------------------- /vue-example/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | src/assets 3 | tests/unit/coverage 4 | -------------------------------------------------------------------------------- /vue-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true 7 | }, 8 | parserOptions: { 9 | parser: '@typescript-eslint/parser', 10 | sourceType: 'module' 11 | }, 12 | plugins: [ 13 | 'vue' 14 | ], 15 | rules: { 16 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 18 | 'space-before-function-paren': [2, 'never'], 19 | 'vue/array-bracket-spacing': 'error', 20 | 'vue/arrow-spacing': 'error', 21 | 'vue/block-spacing': 'error', 22 | 'vue/brace-style': 'error', 23 | 'vue/camelcase': 'error', 24 | 'vue/comma-dangle': 'error', 25 | 'vue/component-name-in-template-casing': 'error', 26 | 'vue/eqeqeq': 'error', 27 | 'vue/key-spacing': 'error', 28 | 'vue/match-component-file-name': 'error', 29 | 'vue/object-curly-spacing': 'error' 30 | }, 31 | 'extends': [ 32 | 'eslint:recommended', 33 | 'plugin:vue/recommended', 34 | '@vue/standard', 35 | '@vue/typescript' 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /vue-example/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | # I'm submitting a 10 | 11 | 20 | 21 | ## Current behavior 22 | 23 | 24 | 25 | ## Expected behavior 26 | 27 | 28 | 29 | ## Minimal reproduction of the problem with instructions 30 | 31 | 34 | 35 | ## What is the motivation / use case for changing the behavior 36 | 37 | 38 | 39 | ## Environment 40 | 41 | 42 | -------------------------------------------------------------------------------- /vue-example/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | **Make sure the PR fulfills these requirements:** 9 | 10 | - When resolving a specific issue, make sure it's referenced in the PR's title (e.g. `Closes #xxx[,#xxx]`, where "xxx" is the issue number) 11 | 12 | - If adding a **new feature**, the PR's description includes: A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) 13 | 14 | - If this PR introduce a **breaking change**, please describe the impact and migration path for existing applications 15 | 16 | **What kind of change does this PR introduce?** 17 | 18 | 27 | 28 | **More information:** 29 | -------------------------------------------------------------------------------- /vue-example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | /tests/**/coverage/ 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | -------------------------------------------------------------------------------- /vue-example/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chong Guo 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 | -------------------------------------------------------------------------------- /vue-example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue-example/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vue-example/demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/demo/demo.gif -------------------------------------------------------------------------------- /vue-example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue', 7 | 'ts', 8 | 'tsx' 9 | ], 10 | transform: { 11 | '^.+\\.vue$': 'vue-jest', 12 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 13 | '^.+\\.tsx?$': 'ts-jest' 14 | }, 15 | transformIgnorePatterns: [ 16 | '/node_modules/' 17 | ], 18 | moduleNameMapper: { 19 | '^@/(.*)$': '/src/$1' 20 | }, 21 | snapshotSerializers: [ 22 | 'jest-serializer-vue' 23 | ], 24 | testMatch: [ 25 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 26 | ], 27 | collectCoverage: true, 28 | collectCoverageFrom: [ 29 | 'src/utils/**/*.{ts,vue}', 30 | '!src/utils/auth.ts', 31 | '!src/utils/request.ts', 32 | 'src/components/**/*.{ts,vue}' 33 | ], 34 | coverageDirectory: '/tests/unit/coverage', 35 | coverageReporters: [ 36 | 'lcov', 37 | 'text-summary' 38 | ], 39 | testURL: 'http://localhost/', 40 | watchPlugins: [ 41 | 'jest-watch-typeahead/filename', 42 | 'jest-watch-typeahead/testname' 43 | ], 44 | globals: { 45 | 'ts-jest': { 46 | babelConfig: true 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vue-example/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/favicon.ico -------------------------------------------------------------------------------- /vue-example/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /vue-example/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /vue-example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue-example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue Typescript Admin", 3 | "short_name": "Vue Ts Admin", 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": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#fff", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /vue-example/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /vue-example/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /vue-example/src/api/articles.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getArticles = (params: any) => 4 | request({ 5 | url: '/articles', 6 | method: 'get', 7 | params 8 | }) 9 | -------------------------------------------------------------------------------- /vue-example/src/api/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface IArticleData { 2 | id: number 3 | status: string 4 | title: string 5 | abstractContent: string 6 | fullContent: string 7 | sourceURL: string 8 | imageURL: string 9 | timestamp: string | number 10 | platforms: string[] 11 | disableComment: boolean 12 | importance: number 13 | author: string 14 | reviewer: string 15 | type: string 16 | pageviews: number 17 | } 18 | -------------------------------------------------------------------------------- /vue-example/src/api/users.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getUserInfo = (data: any) => 4 | request({ 5 | url: '/users/info', 6 | method: 'post', 7 | data 8 | }) 9 | 10 | export const login = (data: any) => 11 | request({ 12 | url: '/users/login', 13 | method: 'post', 14 | data 15 | }) 16 | 17 | export const logout = () => 18 | request({ 19 | url: '/users/logout', 20 | method: 'post' 21 | }) 22 | -------------------------------------------------------------------------------- /vue-example/src/assets/404-images/404-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/src/assets/404-images/404-cloud.png -------------------------------------------------------------------------------- /vue-example/src/assets/404-images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-example/src/assets/404-images/404.png -------------------------------------------------------------------------------- /vue-example/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /vue-example/src/icons/README.md: -------------------------------------------------------------------------------- 1 | # vue-svgicon 2 | 3 | ## English 4 | 5 | * All svg components were generated by `vue-svgicon` using svg files 6 | * After you adding new svg files into `icons/svg` folder, run `yarn svg` to regerenrate all svg components (before this, you should have `vue-svgicon` installed globally or use `npx`) 7 | * See details at: [https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 8 | 9 | ## 中文 10 | 11 | * 所有的 svg 组件都是由 `vue-svgicon` 生成的 12 | * 每当在 `icons/svg` 文件夹内添加 icon 之后,可以通过执行 `yarn svg` 来重新生成所有组件 (在此之前需要全局安装 `vue-svgicon` 或使用 `npx`) 13 | * 详细文档请见:[https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 14 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/dashboard.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'dashboard': { 7 | width: 128, 8 | height: 100, 9 | viewBox: '0 0 128 100', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/example.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'example': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/eye-off.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-off': { 7 | width: 128, 8 | height: 64, 9 | viewBox: '0 0 128 64', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/eye-on.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-on': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/form.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'form': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/hamburger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'hamburger': { 7 | width: 64, 8 | height: 64, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import './dashboard' 3 | import './example' 4 | import './eye-off' 5 | import './eye-on' 6 | import './form' 7 | import './hamburger' 8 | import './link' 9 | import './nested' 10 | import './password' 11 | import './table' 12 | import './tree' 13 | import './user' 14 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/link.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'link': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/nested.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'nested': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/password.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'password': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/table.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'table': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/tree.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'tree': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/components/user.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'user': { 7 | width: 130, 8 | height: 130, 9 | viewBox: '0 0 130 130', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/eye-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/eye-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/hamburger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-example/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /vue-example/src/layout/components/Sidebar/SidebarItemLink.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /vue-example/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | 54 | 80 | 81 | 92 | -------------------------------------------------------------------------------- /vue-example/src/layout/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain.vue' 2 | export { default as Navbar } from './Navbar/index.vue' 3 | export { default as Sidebar } from './Sidebar/index.vue' 4 | -------------------------------------------------------------------------------- /vue-example/src/layout/mixin/resize.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue, Watch } from 'vue-property-decorator' 2 | import { AppModule, DeviceType } from '@/store/modules/app' 3 | 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | @Component({ 7 | name: 'ResizeMixin' 8 | }) 9 | export default class extends Vue { 10 | get device() { 11 | return AppModule.device 12 | } 13 | 14 | get sidebar() { 15 | return AppModule.sidebar 16 | } 17 | 18 | @Watch('$route') 19 | private onRouteChange() { 20 | if (this.device === DeviceType.Mobile && this.sidebar.opened) { 21 | AppModule.CloseSideBar(false) 22 | } 23 | } 24 | 25 | beforeMount() { 26 | window.addEventListener('resize', this.resizeHandler) 27 | } 28 | 29 | mounted() { 30 | const isMobile = this.isMobile() 31 | if (isMobile) { 32 | AppModule.ToggleDevice(DeviceType.Mobile) 33 | AppModule.CloseSideBar(true) 34 | } 35 | } 36 | 37 | beforeDestroy() { 38 | window.removeEventListener('resize', this.resizeHandler) 39 | } 40 | 41 | private isMobile() { 42 | const rect = document.body.getBoundingClientRect() 43 | return rect.width - 1 < WIDTH 44 | } 45 | 46 | private resizeHandler() { 47 | if (!document.hidden) { 48 | const isMobile = this.isMobile() 49 | AppModule.ToggleDevice(isMobile ? DeviceType.Mobile : DeviceType.Desktop) 50 | if (isMobile) { 51 | AppModule.CloseSideBar(true) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vue-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css' 4 | import ElementUI from 'element-ui' 5 | import SvgIcon from 'vue-svgicon' 6 | 7 | import '@/styles/element-variables.scss' 8 | import '@/styles/index.scss' 9 | 10 | import App from '@/App.vue' 11 | import store from '@/store' 12 | import router from '@/router' 13 | import '@/icons/components' 14 | import '@/permission' 15 | 16 | Vue.use(ElementUI) 17 | Vue.use(SvgIcon, { 18 | tagName: 'svg-icon', 19 | defaultWidth: '1em', 20 | defaultHeight: '1em' 21 | }) 22 | 23 | Vue.config.productionTip = false 24 | 25 | let app: Vue | undefined = undefined 26 | 27 | export async function bootstrap() { 28 | console.log('example应用初始化') 29 | } 30 | 31 | /** 32 | * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 33 | */ 34 | export async function mount(props: any) { 35 | Vue.prototype.$isFramework = props.isFramework 36 | Vue.prototype.$mainApp = props.mainInstance 37 | if (!app) { 38 | app = new Vue({ 39 | router, 40 | store, 41 | render: (h) => h(App) 42 | }) 43 | } 44 | app.$mount('#app') 45 | props.callback(app) 46 | } 47 | 48 | /** 49 | * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载子应用的应用实例 50 | */ 51 | export async function unmount() { 52 | console.log('example应用卸载') 53 | // 销毁 54 | if (app) { 55 | app.$destroy() 56 | app = undefined 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /vue-example/src/permission.ts: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | import { Message } from 'element-ui' 5 | import { Route } from 'vue-router' 6 | import { UserModule } from '@/store/modules/user' 7 | 8 | declare module 'vue/types/vue' { 9 | interface Vue { 10 | $isFramework: boolean, 11 | $mainApp: Vue 12 | } 13 | } 14 | 15 | NProgress.configure({ showSpinner: false }) 16 | 17 | const whiteList = ['/login'] 18 | 19 | router.beforeEach(async(to: Route, _: Route, next: any) => { 20 | // Start progress bar 21 | NProgress.start() 22 | 23 | // Determine whether the user has logged in 24 | if (UserModule.token) { 25 | if (to.path === '/login') { 26 | // If is logged in, redirect to the home page 27 | next({ path: '/' }) 28 | NProgress.done() 29 | } else { 30 | // Check whether the user has obtained his permission roles 31 | if (UserModule.roles.length === 0) { 32 | try { 33 | // Get user info, including roles 34 | await UserModule.GetUserInfo() 35 | // Set the replace: true, so the navigation will not leave a history record 36 | next({ ...to, replace: true }) 37 | } catch (err) { 38 | // Remove token and redirect to login page 39 | UserModule.ResetToken() 40 | Message.error(err || 'Has Error') 41 | next(`/login?redirect=${to.path}`) 42 | NProgress.done() 43 | } 44 | } else { 45 | next() 46 | } 47 | } 48 | } else { 49 | // Has no token 50 | if (whiteList.indexOf(to.path) !== -1) { 51 | // In the free login whitelist, go directly 52 | next() 53 | } else { 54 | // Other pages that do not have permission to access are redirected to the login page. 55 | next(`/login?redirect=${to.path}`) 56 | NProgress.done() 57 | } 58 | } 59 | }) 60 | 61 | router.afterEach((to: Route) => { 62 | // Finish progress bar 63 | NProgress.done() 64 | 65 | // set page title 66 | document.title = to.meta.title 67 | if (router.app && router.app.$mainApp) { 68 | router.app.$mainApp.$emit('openTag', to) 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /vue-example/src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.') 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline() { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error(error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /vue-example/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /vue-example/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { IAppState } from './modules/app' 4 | import { IUserState } from './modules/user' 5 | 6 | Vue.use(Vuex) 7 | 8 | export interface IRootState { 9 | app: IAppState 10 | user: IUserState 11 | } 12 | 13 | // Declare empty store first, dynamically register all modules later. 14 | export default new Vuex.Store({}) 15 | -------------------------------------------------------------------------------- /vue-example/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators' 2 | import { getSidebarStatus, setSidebarStatus } from '@/utils/cookies' 3 | import store from '@/store' 4 | 5 | export enum DeviceType { 6 | Mobile, 7 | Desktop, 8 | } 9 | 10 | export interface IAppState { 11 | device: DeviceType 12 | sidebar: { 13 | opened: boolean 14 | withoutAnimation: boolean 15 | } 16 | } 17 | 18 | @Module({ dynamic: true, store, name: 'app' }) 19 | class App extends VuexModule implements IAppState { 20 | public sidebar = { 21 | opened: getSidebarStatus() !== 'closed', 22 | withoutAnimation: false 23 | } 24 | public device = DeviceType.Desktop 25 | 26 | @Mutation 27 | private TOGGLE_SIDEBAR(withoutAnimation: boolean) { 28 | this.sidebar.opened = !this.sidebar.opened 29 | this.sidebar.withoutAnimation = withoutAnimation 30 | if (this.sidebar.opened) { 31 | setSidebarStatus('opened') 32 | } else { 33 | setSidebarStatus('closed') 34 | } 35 | } 36 | 37 | @Mutation 38 | private CLOSE_SIDEBAR(withoutAnimation: boolean) { 39 | this.sidebar.opened = false 40 | this.sidebar.withoutAnimation = withoutAnimation 41 | setSidebarStatus('closed') 42 | } 43 | 44 | @Mutation 45 | private TOGGLE_DEVICE(device: DeviceType) { 46 | this.device = device 47 | } 48 | 49 | @Action 50 | public ToggleSideBar(withoutAnimation: boolean) { 51 | this.TOGGLE_SIDEBAR(withoutAnimation) 52 | } 53 | 54 | @Action 55 | public CloseSideBar(withoutAnimation: boolean) { 56 | this.CLOSE_SIDEBAR(withoutAnimation) 57 | } 58 | 59 | @Action 60 | public ToggleDevice(device: DeviceType) { 61 | this.TOGGLE_DEVICE(device) 62 | } 63 | } 64 | 65 | export const AppModule = getModule(App) 66 | -------------------------------------------------------------------------------- /vue-example/src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* Mixins */ 2 | @mixin clearfix { 3 | &:after { 4 | content: ""; 5 | display: table; 6 | clear: both; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue-example/src/styles/_svgicon.scss: -------------------------------------------------------------------------------- 1 | /* Recommended css code for vue-svgicon */ 2 | .svg-icon { 3 | display: inline-block; 4 | width: 16px; 5 | height: 16px; 6 | color: inherit; 7 | fill: none; 8 | stroke: currentColor; 9 | vertical-align: -0.15em; 10 | } 11 | 12 | .svg-fill { 13 | fill: currentColor; 14 | stroke: none; 15 | } 16 | 17 | .svg-up { 18 | transform: rotate(0deg); 19 | } 20 | 21 | .svg-right { 22 | transform: rotate(90deg); 23 | } 24 | 25 | .svg-down { 26 | transform: rotate(180deg); 27 | } 28 | 29 | .svg-left { 30 | transform: rotate(-90deg); 31 | } 32 | -------------------------------------------------------------------------------- /vue-example/src/styles/_transition.scss: -------------------------------------------------------------------------------- 1 | /* Global transition */ 2 | // See https://vuejs.org/v2/guide/transitions.html for detail 3 | 4 | // fade 5 | .fade-enter-active, 6 | .fade-leave-active { 7 | transition: opacity 0.28s; 8 | } 9 | 10 | .fade-enter, 11 | .fade-leave-active { 12 | opacity: 0; 13 | } 14 | 15 | // fade-transform 16 | .fade-transform-leave-active, 17 | .fade-transform-enter-active { 18 | transition: all .5s; 19 | } 20 | 21 | .fade-transform-enter { 22 | opacity: 0; 23 | transform: translateX(-30px); 24 | } 25 | 26 | .fade-transform-leave-to { 27 | opacity: 0; 28 | transform: translateX(30px); 29 | } 30 | 31 | // breadcrumb 32 | .breadcrumb-enter-active, 33 | .breadcrumb-leave-active { 34 | transition: all .5s; 35 | } 36 | 37 | .breadcrumb-enter, 38 | .breadcrumb-leave-active { 39 | opacity: 0; 40 | transform: translateX(20px); 41 | } 42 | 43 | .breadcrumb-move { 44 | transition: all .5s; 45 | } 46 | 47 | .breadcrumb-leave-active { 48 | position: absolute; 49 | } 50 | -------------------------------------------------------------------------------- /vue-example/src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | 3 | // Base color 4 | $blue:#324157; 5 | $light-blue:#3A71A8; 6 | $red:#C03639; 7 | $pink: #E65D6E; 8 | $green: #30B08F; 9 | $tiffany: #4AB7BD; 10 | $yellow:#FEC171; 11 | $panGreen: #30B08F; 12 | 13 | // Sidebar 14 | $sideBarWidth: 210px; 15 | // $sideBarWidth: 0; 16 | $subMenuBg:#1f2d3d; 17 | $subMenuHover:#001528; 18 | $subMenuActiveText:#f4f4f5; 19 | $menuBg:#304156; 20 | $menuText:#bfcbd9; 21 | $menuActiveText:#409EFF; // Also see settings.sidebarTextTheme 22 | 23 | // Login page 24 | $lightGray: #eee; 25 | $darkGray:#889aa4; 26 | $loginBg: #2d3a4b; 27 | $loginCursorColor: #fff; 28 | 29 | // The :export directive is the magic sauce for webpack 30 | // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript 31 | :export { 32 | menuBg: $menuBg; 33 | menuText: $menuText; 34 | menuActiveText: $menuActiveText; 35 | } 36 | -------------------------------------------------------------------------------- /vue-example/src/styles/_variables.scss.d.ts: -------------------------------------------------------------------------------- 1 | export interface IScssVariables { 2 | menuBg: string 3 | menuText: string 4 | menuActiveText: string 5 | } 6 | 7 | export const variables: IScssVariables 8 | 9 | export default variables 10 | -------------------------------------------------------------------------------- /vue-example/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* Element Variables */ 2 | 3 | // Override Element UI variables 4 | $--color-primary: #1890ff; 5 | $--color-success: #13ce66; 6 | $--color-warning: #FFBA00; 7 | $--color-danger: #ff4949; 8 | $--color-info: #5d5d5d; 9 | $--button-font-weight: 400; 10 | $--color-text-regular: #1f2d3d; 11 | $--border-color-light: #dfe4ed; 12 | $--border-color-lighter: #e6ebf5; 13 | $--table-border:1px solid#dfe6ec; 14 | 15 | // Icon font path, required 16 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 17 | 18 | // Apply overrided variables in Element UI 19 | @import '~element-ui/packages/theme-chalk/src/index'; 20 | -------------------------------------------------------------------------------- /vue-example/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // @import './variables.scss'; // Already imported in style-resources-loader 2 | // @import './mixins.scss'; // Already imported in style-resources-loader 3 | @import './transition.scss'; 4 | @import './svgicon.scss'; 5 | 6 | /* Global scss */ 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | html { 16 | height: 100%; 17 | } 18 | 19 | #app { 20 | height: 100%; 21 | } 22 | 23 | *, 24 | *:before, 25 | *:after { 26 | box-sizing: border-box; 27 | } 28 | 29 | a, 30 | a:focus, 31 | a:hover { 32 | color: inherit; 33 | outline: none; 34 | text-decoration: none; 35 | } 36 | 37 | div:focus { 38 | outline: none; 39 | } 40 | 41 | .clearfix { 42 | @include clearfix; 43 | } 44 | 45 | .app-container { 46 | padding: 20px; 47 | } 48 | -------------------------------------------------------------------------------- /vue-example/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | // App 4 | const sidebarStatusKey = 'sidebar_status' 5 | export const getSidebarStatus = () => Cookies.get(sidebarStatusKey) 6 | export const setSidebarStatus = (sidebarStatus: string) => Cookies.set(sidebarStatusKey, sidebarStatus) 7 | 8 | // User 9 | const tokenKey = 'vue_typescript_admin_access_token' 10 | export const getToken = () => Cookies.get(tokenKey) 11 | export const setToken = (token: string) => Cookies.set(tokenKey, token) 12 | export const removeToken = () => Cookies.remove(tokenKey) 13 | -------------------------------------------------------------------------------- /vue-example/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import { UserModule } from '@/store/modules/user' 4 | 5 | const service = axios.create({ 6 | baseURL: process.env.VUE_APP_BASE_API, 7 | timeout: 5000 8 | }) 9 | 10 | // Request interceptors 11 | service.interceptors.request.use( 12 | (config) => { 13 | // Add X-Access-Token header to every request, you can add other custom headers here 14 | if (UserModule.token) { 15 | config.headers['X-Access-Token'] = UserModule.token 16 | } 17 | return config 18 | }, 19 | (error) => { 20 | Promise.reject(error) 21 | } 22 | ) 23 | 24 | // Response interceptors 25 | service.interceptors.response.use( 26 | (response) => { 27 | // Some example codes here: 28 | // code == 20000: success 29 | // code == 50001: invalid access token 30 | // code == 50002: already login in other place 31 | // code == 50003: access token expired 32 | // code == 50004: invalid user (user not exist) 33 | // code == 50005: username or password is incorrect 34 | // You can change this part for your own usage. 35 | const res = response.data 36 | if (res.code !== 20000) { 37 | Message({ 38 | message: res.message || 'Error', 39 | type: 'error', 40 | duration: 5 * 1000 41 | }) 42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 43 | MessageBox.confirm( 44 | 'You have been logged out, try to login again.', 45 | 'Log out', 46 | { 47 | confirmButtonText: 'Relogin', 48 | cancelButtonText: 'Cancel', 49 | type: 'warning' 50 | } 51 | ).then(() => { 52 | UserModule.ResetToken() 53 | location.reload() // To prevent bugs from vue-router 54 | }) 55 | } 56 | return Promise.reject(new Error(res.message || 'Error')) 57 | } else { 58 | return response.data 59 | } 60 | }, 61 | (error) => { 62 | Message({ 63 | message: error.message, 64 | type: 'error', 65 | duration: 5 * 1000 66 | }) 67 | return Promise.reject(error) 68 | } 69 | ) 70 | 71 | export default service 72 | -------------------------------------------------------------------------------- /vue-example/src/utils/validate.ts: -------------------------------------------------------------------------------- 1 | export const isValidUsername = (str: string) => ['admin', 'editor'].indexOf(str.trim()) >= 0 2 | 3 | export const isExternal = (path: string) => /^(https?:|mailto:|tel:)/.test(path) 4 | -------------------------------------------------------------------------------- /vue-example/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-example/src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /vue-example/src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 80 | -------------------------------------------------------------------------------- /vue-example/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-example/tests/unit/utils/validate.spec.ts: -------------------------------------------------------------------------------- 1 | import { isValidUsername, isExternal } from '@/utils/validate' 2 | 3 | describe('Utils:validate', () => { 4 | it('isValidUsername', () => { 5 | expect(isValidUsername('admin')).toBe(true) 6 | expect(isValidUsername('editor')).toBe(true) 7 | expect(isValidUsername('xxxx')).toBe(false) 8 | }) 9 | 10 | it('isExternal', () => { 11 | expect(isExternal('https://www.armour.com/')).toBe(true) 12 | expect(isExternal('mailto:someone@test.com')).toBe(true) 13 | expect(isExternal('123aBC')).toBe(false) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /vue-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "node", 16 | "jest", 17 | "webpack-env" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue-example/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { name } = require('./package') 3 | const cors = require('cors') 4 | const port = 5002 5 | 6 | module.exports = { 7 | devServer: { 8 | port, 9 | /* headers: { 10 | 'Access-Control-Allow-Origin': '*', 11 | }, */ 12 | before(app, server) { 13 | app.use(cors()) 14 | } 15 | }, 16 | publicPath: process.env.NODE_ENV === 'production' ? '/vue-typescript-admin-template/' : `//localhost:${port}`, // TODO: Remember to change this to fit your need 17 | lintOnSave: process.env.NODE_ENV === 'development', 18 | pwa: { 19 | name: name 20 | }, 21 | pluginOptions: { 22 | 'style-resources-loader': { 23 | preProcessor: 'scss', 24 | patterns: [ 25 | path.resolve(__dirname, 'src/styles/_variables.scss'), 26 | path.resolve(__dirname, 'src/styles/_mixins.scss') 27 | ] 28 | } 29 | }, 30 | /* configureWebpack: { 31 | output: { 32 | // 把子应用打包成 umd 库格式 33 | library: `${name}-[name]`, 34 | libraryTarget: 'umd', 35 | jsonpFunction: `webpackJsonp_${name}`, 36 | } 37 | }, */ 38 | chainWebpack(config) { 39 | // Provide the app's title in webpack's name field, so that 40 | // it can be accessed in index.html to inject the correct title. 41 | config.set('name', name) 42 | config.plugin('html').tap(args => { 43 | args[0].minify = false 44 | return args 45 | }) 46 | // 把子应用打包成 umd 库格式 47 | config.output 48 | .jsonpFunction(`webpackJsonp_${name}`) 49 | .library(`${name}-[name]`) 50 | .libraryTarget('umd') 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vue-main/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /vue-main/.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 | 7 | jobs: 8 | build: 9 | docker: 10 | - image: circleci/node:11 11 | 12 | working_directory: ~/vue-typescript-admin-template 13 | 14 | steps: 15 | - checkout 16 | 17 | # Download and cache dependencies 18 | - restore_cache: 19 | keys: 20 | - v1-dependencies-{{ checksum "package.json" }} 21 | # fallback to using the latest cache if no exact match is found 22 | - v1-dependencies- 23 | 24 | - run: yarn install 25 | 26 | - save_cache: 27 | paths: 28 | - node_modules 29 | key: v1-dependencies-{{ checksum "package.json" }} 30 | 31 | - run: yarn lint && yarn build 32 | -------------------------------------------------------------------------------- /vue-main/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # Indentation override for js(x), ts(x) and vue files 14 | [*.{js,jsx,ts,tsx,vue}] 15 | indent_size = 2 16 | indent_style = space 17 | 18 | # Indentation override for css related files 19 | [*.{css,styl,scss,less,sass}] 20 | indent_size = 2 21 | indent_style = space 22 | 23 | # Indentation override for html files 24 | [*.html] 25 | indent_size = 2 26 | indent_style = space 27 | 28 | # Trailing space override for markdown file 29 | [*.md] 30 | trim_trailing_whitespace = false 31 | 32 | # Indentation override for config files 33 | [*.{json,yml}] 34 | indent_size = 2 35 | indent_style = space 36 | -------------------------------------------------------------------------------- /vue-main/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_API = 'https://vue-typescript-admin-mock-server.armour.now.sh/mock-api/v1/' 2 | -------------------------------------------------------------------------------- /vue-main/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | src/assets 3 | tests/unit/coverage 4 | -------------------------------------------------------------------------------- /vue-main/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true 7 | }, 8 | parserOptions: { 9 | parser: '@typescript-eslint/parser', 10 | sourceType: 'module' 11 | }, 12 | plugins: [ 13 | 'vue' 14 | ], 15 | rules: { 16 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 18 | 'space-before-function-paren': [2, 'never'], 19 | 'vue/array-bracket-spacing': 'error', 20 | 'vue/arrow-spacing': 'error', 21 | 'vue/block-spacing': 'error', 22 | 'vue/brace-style': 'error', 23 | 'vue/camelcase': 'error', 24 | 'vue/comma-dangle': 'error', 25 | 'vue/component-name-in-template-casing': 'error', 26 | 'vue/eqeqeq': 'error', 27 | 'vue/key-spacing': 'error', 28 | 'vue/match-component-file-name': 'error', 29 | 'vue/object-curly-spacing': 'error' 30 | }, 31 | 'extends': [ 32 | 'eslint:recommended', 33 | 'plugin:vue/recommended', 34 | '@vue/standard', 35 | '@vue/typescript' 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /vue-main/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | # I'm submitting a 10 | 11 | 20 | 21 | ## Current behavior 22 | 23 | 24 | 25 | ## Expected behavior 26 | 27 | 28 | 29 | ## Minimal reproduction of the problem with instructions 30 | 31 | 34 | 35 | ## What is the motivation / use case for changing the behavior 36 | 37 | 38 | 39 | ## Environment 40 | 41 | 42 | -------------------------------------------------------------------------------- /vue-main/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | **Make sure the PR fulfills these requirements:** 9 | 10 | - When resolving a specific issue, make sure it's referenced in the PR's title (e.g. `Closes #xxx[,#xxx]`, where "xxx" is the issue number) 11 | 12 | - If adding a **new feature**, the PR's description includes: A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) 13 | 14 | - If this PR introduce a **breaking change**, please describe the impact and migration path for existing applications 15 | 16 | **What kind of change does this PR introduce?** 17 | 18 | 27 | 28 | **More information:** 29 | -------------------------------------------------------------------------------- /vue-main/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | /tests/**/coverage/ 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | -------------------------------------------------------------------------------- /vue-main/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chong Guo 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 | -------------------------------------------------------------------------------- /vue-main/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue-main/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vue-main/demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/demo/demo.gif -------------------------------------------------------------------------------- /vue-main/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue', 7 | 'ts', 8 | 'tsx' 9 | ], 10 | transform: { 11 | '^.+\\.vue$': 'vue-jest', 12 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 13 | '^.+\\.tsx?$': 'ts-jest' 14 | }, 15 | transformIgnorePatterns: [ 16 | '/node_modules/' 17 | ], 18 | moduleNameMapper: { 19 | '^@/(.*)$': '/src/$1' 20 | }, 21 | snapshotSerializers: [ 22 | 'jest-serializer-vue' 23 | ], 24 | testMatch: [ 25 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 26 | ], 27 | collectCoverage: true, 28 | collectCoverageFrom: [ 29 | 'src/utils/**/*.{ts,vue}', 30 | '!src/utils/auth.ts', 31 | '!src/utils/request.ts', 32 | 'src/components/**/*.{ts,vue}' 33 | ], 34 | coverageDirectory: '/tests/unit/coverage', 35 | coverageReporters: [ 36 | 'lcov', 37 | 'text-summary' 38 | ], 39 | testURL: 'http://localhost/', 40 | watchPlugins: [ 41 | 'jest-watch-typeahead/filename', 42 | 'jest-watch-typeahead/testname' 43 | ], 44 | globals: { 45 | 'ts-jest': { 46 | babelConfig: true 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vue-main/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-main/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/favicon.ico -------------------------------------------------------------------------------- /vue-main/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /vue-main/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /vue-main/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue-main/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue Typescript Admin", 3 | "short_name": "Vue Ts Admin", 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": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#fff", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /vue-main/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /vue-main/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /vue-main/src/api/articles.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getArticles = (params: any) => 4 | request({ 5 | url: '/articles', 6 | method: 'get', 7 | params 8 | }) 9 | -------------------------------------------------------------------------------- /vue-main/src/api/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface IArticleData { 2 | id: number 3 | status: string 4 | title: string 5 | abstractContent: string 6 | fullContent: string 7 | sourceURL: string 8 | imageURL: string 9 | timestamp: string | number 10 | platforms: string[] 11 | disableComment: boolean 12 | importance: number 13 | author: string 14 | reviewer: string 15 | type: string 16 | pageviews: number 17 | } 18 | -------------------------------------------------------------------------------- /vue-main/src/api/users.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const getUserInfo = (data: any) => 4 | request({ 5 | url: '/users/info', 6 | method: 'post', 7 | data 8 | }) 9 | 10 | export const login = (data: any) => 11 | request({ 12 | url: '/users/login', 13 | method: 'post', 14 | data 15 | }) 16 | 17 | export const logout = () => 18 | request({ 19 | url: '/users/logout', 20 | method: 'post' 21 | }) 22 | -------------------------------------------------------------------------------- /vue-main/src/assets/404-images/404-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/src/assets/404-images/404-cloud.png -------------------------------------------------------------------------------- /vue-main/src/assets/404-images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hades-li/vue-ts-admin-microApp/4f646fd288e3cb5e453f505fe6f8a17e17d81c42/vue-main/src/assets/404-images/404.png -------------------------------------------------------------------------------- /vue-main/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /vue-main/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 40 | 41 | 42 | 58 | -------------------------------------------------------------------------------- /vue-main/src/icons/README.md: -------------------------------------------------------------------------------- 1 | # vue-svgicon 2 | 3 | ## English 4 | 5 | * All svg components were generated by `vue-svgicon` using svg files 6 | * After you adding new svg files into `icons/svg` folder, run `yarn svg` to regerenrate all svg components (before this, you should have `vue-svgicon` installed globally or use `npx`) 7 | * See details at: [https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 8 | 9 | ## 中文 10 | 11 | * 所有的 svg 组件都是由 `vue-svgicon` 生成的 12 | * 每当在 `icons/svg` 文件夹内添加 icon 之后,可以通过执行 `yarn svg` 来重新生成所有组件 (在此之前需要全局安装 `vue-svgicon` 或使用 `npx`) 13 | * 详细文档请见:[https://github.com/MMF-FE/vue-svgicon](https://github.com/MMF-FE/vue-svgicon) 14 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/dashboard.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'dashboard': { 7 | width: 128, 8 | height: 100, 9 | viewBox: '0 0 128 100', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/example.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'example': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/eye-off.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-off': { 7 | width: 128, 8 | height: 64, 9 | viewBox: '0 0 128 64', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/eye-on.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'eye-on': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/form.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'form': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/hamburger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'hamburger': { 7 | width: 64, 8 | height: 64, 9 | viewBox: '0 0 1024 1024', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import './dashboard' 3 | import './example' 4 | import './eye-off' 5 | import './eye-on' 6 | import './form' 7 | import './hamburger' 8 | import './link' 9 | import './nested' 10 | import './password' 11 | import './table' 12 | import './tree' 13 | import './user' 14 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/link.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'link': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/nested.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'nested': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/password.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'password': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/table.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'table': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/tree.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'tree': { 7 | width: 128, 8 | height: 128, 9 | viewBox: '0 0 128 128', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/components/user.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | // @ts-ignore 4 | import icon from 'vue-svgicon' 5 | icon.register({ 6 | 'user': { 7 | width: 130, 8 | height: 130, 9 | viewBox: '0 0 130 130', 10 | data: '' 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/eye-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/eye-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/hamburger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vue-main/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /vue-main/src/layout/components/Sidebar/SidebarItemLink.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /vue-main/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | 54 | 80 | 81 | 92 | -------------------------------------------------------------------------------- /vue-main/src/layout/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain.vue' 2 | export { default as Navbar } from './Navbar/index.vue' 3 | export { default as Sidebar } from './Sidebar/index.vue' 4 | export { default as TagsView } from './TagsView/index.vue' 5 | -------------------------------------------------------------------------------- /vue-main/src/layout/mixin/resize.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue, Watch } from 'vue-property-decorator' 2 | import { AppModule, DeviceType } from '@/store/modules/app' 3 | 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | @Component({ 7 | name: 'ResizeMixin' 8 | }) 9 | export default class extends Vue { 10 | get device() { 11 | return AppModule.device 12 | } 13 | 14 | get sidebar() { 15 | return AppModule.sidebar 16 | } 17 | 18 | @Watch('$route') 19 | private onRouteChange() { 20 | if (this.device === DeviceType.Mobile && this.sidebar.opened) { 21 | AppModule.CloseSideBar(false) 22 | } 23 | } 24 | 25 | beforeMount() { 26 | window.addEventListener('resize', this.resizeHandler) 27 | } 28 | 29 | mounted() { 30 | const isMobile = this.isMobile() 31 | if (isMobile) { 32 | AppModule.ToggleDevice(DeviceType.Mobile) 33 | AppModule.CloseSideBar(true) 34 | } 35 | } 36 | 37 | beforeDestroy() { 38 | window.removeEventListener('resize', this.resizeHandler) 39 | } 40 | 41 | private isMobile() { 42 | const rect = document.body.getBoundingClientRect() 43 | return rect.width - 1 < WIDTH 44 | } 45 | 46 | private resizeHandler() { 47 | if (!document.hidden) { 48 | const isMobile = this.isMobile() 49 | AppModule.ToggleDevice(isMobile ? DeviceType.Mobile : DeviceType.Desktop) 50 | if (isMobile) { 51 | AppModule.CloseSideBar(true) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vue-main/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css' 4 | import ElementUI from 'element-ui' 5 | import SvgIcon from 'vue-svgicon' 6 | 7 | import '@/styles/element-variables.scss' 8 | import '@/styles/index.scss' 9 | 10 | import App from '@/App.vue' 11 | import store from '@/store' 12 | import router from '@/router' 13 | import '@/icons/components' 14 | import '@/permission' 15 | 16 | import QiankunVue from 'qiankun-vue' 17 | 18 | Vue.use(ElementUI) 19 | Vue.use(SvgIcon, { 20 | tagName: 'svg-icon', 21 | defaultWidth: '1em', 22 | defaultHeight: '1em' 23 | }) 24 | Vue.use(QiankunVue) 25 | // Vue.use(regApp) 26 | const qiankunVue = new QiankunVue([ 27 | { 28 | name: 'dashboard', 29 | entry: '//localhost:5001', 30 | activeRule: '/dashboard' 31 | }, 32 | { 33 | name: 'example', 34 | entry: '//localhost:5002', 35 | activeRule: '/example' 36 | } 37 | ]) 38 | 39 | Vue.config.productionTip = false 40 | 41 | new Vue({ 42 | router, 43 | store, 44 | qiankunVue, 45 | render: (h) => h(App) 46 | }).$mount('#main') 47 | -------------------------------------------------------------------------------- /vue-main/src/permission.ts: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import NProgress from 'nprogress' 3 | import 'nprogress/nprogress.css' 4 | import { Message } from 'element-ui' 5 | import { Route } from 'vue-router' 6 | import { UserModule } from '@/store/modules/user' 7 | 8 | NProgress.configure({ showSpinner: false }) 9 | 10 | const whiteList = ['/login'] 11 | 12 | router.beforeEach(async(to: Route, _: Route, next: any) => { 13 | // Start progress bar 14 | NProgress.start() 15 | 16 | // Determine whether the user has logged in 17 | if (UserModule.token) { 18 | if (to.path === '/login') { 19 | // If is logged in, redirect to the home page 20 | next({ path: '/' }) 21 | NProgress.done() 22 | } else { 23 | // Check whether the user has obtained his permission roles 24 | if (UserModule.roles.length === 0) { 25 | try { 26 | // Get user info, including roles 27 | await UserModule.GetUserInfo() 28 | // Set the replace: true, so the navigation will not leave a history record 29 | next({ ...to, replace: true }) 30 | } catch (err) { 31 | // Remove token and redirect to login page 32 | UserModule.ResetToken() 33 | Message.error(err || 'Has Error') 34 | next(`/login?redirect=${to.path}`) 35 | NProgress.done() 36 | } 37 | } else { 38 | next() 39 | } 40 | } 41 | } else { 42 | // Has no token 43 | if (whiteList.indexOf(to.path) !== -1) { 44 | // In the free login whitelist, go directly 45 | next() 46 | } else { 47 | // Other pages that do not have permission to access are redirected to the login page. 48 | next(`/login?redirect=${to.path}`) 49 | NProgress.done() 50 | } 51 | } 52 | }) 53 | 54 | router.afterEach((to: Route) => { 55 | // Finish progress bar 56 | NProgress.done() 57 | 58 | // set page title 59 | document.title = to.meta.title 60 | }) 61 | -------------------------------------------------------------------------------- /vue-main/src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.') 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline() { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error(error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /vue-main/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vue-main/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /vue-main/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { IAppState } from './modules/app' 4 | import { IUserState } from './modules/user' 5 | import { ITagsViewState } from './modules/tags-view' 6 | 7 | Vue.use(Vuex) 8 | 9 | export interface IRootState { 10 | app: IAppState 11 | user: IUserState 12 | tagsView: ITagsViewState 13 | } 14 | 15 | // Declare empty store first, dynamically register all modules later. 16 | export default new Vuex.Store({}) 17 | -------------------------------------------------------------------------------- /vue-main/src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators' 2 | import { getSidebarStatus, setSidebarStatus } from '@/utils/cookies' 3 | import store from '@/store' 4 | 5 | export enum DeviceType { 6 | Mobile, 7 | Desktop, 8 | } 9 | 10 | export interface IAppState { 11 | device: DeviceType 12 | sidebar: { 13 | opened: boolean 14 | withoutAnimation: boolean 15 | } 16 | } 17 | 18 | @Module({ dynamic: true, store, name: 'app' }) 19 | class App extends VuexModule implements IAppState { 20 | public sidebar = { 21 | opened: getSidebarStatus() !== 'closed', 22 | withoutAnimation: false 23 | } 24 | public device = DeviceType.Desktop 25 | 26 | @Mutation 27 | private TOGGLE_SIDEBAR(withoutAnimation: boolean) { 28 | this.sidebar.opened = !this.sidebar.opened 29 | this.sidebar.withoutAnimation = withoutAnimation 30 | if (this.sidebar.opened) { 31 | setSidebarStatus('opened') 32 | } else { 33 | setSidebarStatus('closed') 34 | } 35 | } 36 | 37 | @Mutation 38 | private CLOSE_SIDEBAR(withoutAnimation: boolean) { 39 | this.sidebar.opened = false 40 | this.sidebar.withoutAnimation = withoutAnimation 41 | setSidebarStatus('closed') 42 | } 43 | 44 | @Mutation 45 | private TOGGLE_DEVICE(device: DeviceType) { 46 | this.device = device 47 | } 48 | 49 | @Action 50 | public ToggleSideBar(withoutAnimation: boolean) { 51 | this.TOGGLE_SIDEBAR(withoutAnimation) 52 | } 53 | 54 | @Action 55 | public CloseSideBar(withoutAnimation: boolean) { 56 | this.CLOSE_SIDEBAR(withoutAnimation) 57 | } 58 | 59 | @Action 60 | public ToggleDevice(device: DeviceType) { 61 | this.TOGGLE_DEVICE(device) 62 | } 63 | } 64 | 65 | export const AppModule = getModule(App) 66 | -------------------------------------------------------------------------------- /vue-main/src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* Mixins */ 2 | @mixin clearfix { 3 | &:after { 4 | content: ""; 5 | display: table; 6 | clear: both; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue-main/src/styles/_svgicon.scss: -------------------------------------------------------------------------------- 1 | /* Recommended css code for vue-svgicon */ 2 | .svg-icon { 3 | display: inline-block; 4 | width: 16px; 5 | height: 16px; 6 | color: inherit; 7 | fill: none; 8 | stroke: currentColor; 9 | vertical-align: -0.15em; 10 | } 11 | 12 | .svg-fill { 13 | fill: currentColor; 14 | stroke: none; 15 | } 16 | 17 | .svg-up { 18 | transform: rotate(0deg); 19 | } 20 | 21 | .svg-right { 22 | transform: rotate(90deg); 23 | } 24 | 25 | .svg-down { 26 | transform: rotate(180deg); 27 | } 28 | 29 | .svg-left { 30 | transform: rotate(-90deg); 31 | } 32 | -------------------------------------------------------------------------------- /vue-main/src/styles/_transition.scss: -------------------------------------------------------------------------------- 1 | /* Global transition */ 2 | // See https://vuejs.org/v2/guide/transitions.html for detail 3 | 4 | // fade 5 | .fade-enter-active, 6 | .fade-leave-active { 7 | transition: opacity 0.28s; 8 | } 9 | 10 | .fade-enter, 11 | .fade-leave-active { 12 | opacity: 0; 13 | } 14 | 15 | // fade-transform 16 | .fade-transform-leave-active, 17 | .fade-transform-enter-active { 18 | transition: all .5s; 19 | } 20 | 21 | .fade-transform-enter { 22 | opacity: 0; 23 | transform: translateX(-30px); 24 | } 25 | 26 | .fade-transform-leave-to { 27 | opacity: 0; 28 | transform: translateX(30px); 29 | } 30 | 31 | // breadcrumb 32 | .breadcrumb-enter-active, 33 | .breadcrumb-leave-active { 34 | transition: all .5s; 35 | } 36 | 37 | .breadcrumb-enter, 38 | .breadcrumb-leave-active { 39 | opacity: 0; 40 | transform: translateX(20px); 41 | } 42 | 43 | .breadcrumb-move { 44 | transition: all .5s; 45 | } 46 | 47 | .breadcrumb-leave-active { 48 | position: absolute; 49 | } 50 | -------------------------------------------------------------------------------- /vue-main/src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | 3 | // Base color 4 | $blue:#324157; 5 | $light-blue:#3A71A8; 6 | $red:#C03639; 7 | $pink: #E65D6E; 8 | $green: #30B08F; 9 | $tiffany: #4AB7BD; 10 | $yellow:#FEC171; 11 | $panGreen: #30B08F; 12 | 13 | // Sidebar 14 | $sideBarWidth: 210px; 15 | $subMenuBg:#1f2d3d; 16 | $subMenuHover:#001528; 17 | $subMenuActiveText:#f4f4f5; 18 | $menuBg:#304156; 19 | $menuText:#bfcbd9; 20 | $menuActiveText:#409EFF; // Also see settings.sidebarTextTheme 21 | 22 | // Login page 23 | $lightGray: #eee; 24 | $darkGray:#889aa4; 25 | $loginBg: #2d3a4b; 26 | $loginCursorColor: #fff; 27 | 28 | // The :export directive is the magic sauce for webpack 29 | // https://mattferderer.com/use-sass-variables-in-typescript-and-javascript 30 | :export { 31 | menuBg: $menuBg; 32 | menuText: $menuText; 33 | menuActiveText: $menuActiveText; 34 | } 35 | -------------------------------------------------------------------------------- /vue-main/src/styles/_variables.scss.d.ts: -------------------------------------------------------------------------------- 1 | export interface IScssVariables { 2 | menuBg: string 3 | menuText: string 4 | menuActiveText: string 5 | } 6 | 7 | export const variables: IScssVariables 8 | 9 | export default variables 10 | -------------------------------------------------------------------------------- /vue-main/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* Element Variables */ 2 | 3 | // Override Element UI variables 4 | $--color-primary: #1890ff; 5 | $--color-success: #13ce66; 6 | $--color-warning: #FFBA00; 7 | $--color-danger: #ff4949; 8 | $--color-info: #5d5d5d; 9 | $--button-font-weight: 400; 10 | $--color-text-regular: #1f2d3d; 11 | $--border-color-light: #dfe4ed; 12 | $--border-color-lighter: #e6ebf5; 13 | $--table-border:1px solid#dfe6ec; 14 | 15 | // Icon font path, required 16 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 17 | 18 | // Apply overrided variables in Element UI 19 | @import '~element-ui/packages/theme-chalk/src/index'; 20 | -------------------------------------------------------------------------------- /vue-main/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // @import './variables.scss'; // Already imported in style-resources-loader 2 | // @import './mixins.scss'; // Already imported in style-resources-loader 3 | @import './transition.scss'; 4 | @import './svgicon.scss'; 5 | 6 | /* Global scss */ 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | html { 16 | height: 100%; 17 | } 18 | 19 | #app { 20 | height: 100%; 21 | } 22 | 23 | *, 24 | *:before, 25 | *:after { 26 | box-sizing: border-box; 27 | } 28 | 29 | a, 30 | a:focus, 31 | a:hover { 32 | color: inherit; 33 | outline: none; 34 | text-decoration: none; 35 | } 36 | 37 | div:focus { 38 | outline: none; 39 | } 40 | 41 | .clearfix { 42 | @include clearfix; 43 | } 44 | 45 | .app-container { 46 | padding: 20px; 47 | } 48 | -------------------------------------------------------------------------------- /vue-main/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | // App 4 | const sidebarStatusKey = 'sidebar_status' 5 | export const getSidebarStatus = () => Cookies.get(sidebarStatusKey) 6 | export const setSidebarStatus = (sidebarStatus: string) => Cookies.set(sidebarStatusKey, sidebarStatus) 7 | 8 | // User 9 | const tokenKey = 'vue_typescript_admin_access_token' 10 | export const getToken = () => Cookies.get(tokenKey) 11 | export const setToken = (token: string) => Cookies.set(tokenKey, token) 12 | export const removeToken = () => Cookies.remove(tokenKey) 13 | -------------------------------------------------------------------------------- /vue-main/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import { UserModule } from '@/store/modules/user' 4 | 5 | const service = axios.create({ 6 | baseURL: process.env.VUE_APP_BASE_API, 7 | timeout: 5000 8 | }) 9 | 10 | // Request interceptors 11 | service.interceptors.request.use( 12 | (config) => { 13 | // Add X-Access-Token header to every request, you can add other custom headers here 14 | if (UserModule.token) { 15 | config.headers['X-Access-Token'] = UserModule.token 16 | } 17 | return config 18 | }, 19 | (error) => { 20 | Promise.reject(error) 21 | } 22 | ) 23 | 24 | // Response interceptors 25 | service.interceptors.response.use( 26 | (response) => { 27 | // Some example codes here: 28 | // code == 20000: success 29 | // code == 50001: invalid access token 30 | // code == 50002: already login in other place 31 | // code == 50003: access token expired 32 | // code == 50004: invalid user (user not exist) 33 | // code == 50005: username or password is incorrect 34 | // You can change this part for your own usage. 35 | const res = response.data 36 | if (res.code !== 20000) { 37 | Message({ 38 | message: res.message || 'Error', 39 | type: 'error', 40 | duration: 5 * 1000 41 | }) 42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 43 | MessageBox.confirm( 44 | 'You have been logged out, try to login again.', 45 | 'Log out', 46 | { 47 | confirmButtonText: 'Relogin', 48 | cancelButtonText: 'Cancel', 49 | type: 'warning' 50 | } 51 | ).then(() => { 52 | UserModule.ResetToken() 53 | location.reload() // To prevent bugs from vue-router 54 | }) 55 | } 56 | return Promise.reject(new Error(res.message || 'Error')) 57 | } else { 58 | return response.data 59 | } 60 | }, 61 | (error) => { 62 | Message({ 63 | message: error.message, 64 | type: 'error', 65 | duration: 5 * 1000 66 | }) 67 | return Promise.reject(error) 68 | } 69 | ) 70 | 71 | export default service 72 | -------------------------------------------------------------------------------- /vue-main/src/utils/validate.ts: -------------------------------------------------------------------------------- 1 | export const isValidUsername = (str: string) => ['admin', 'editor'].indexOf(str.trim()) >= 0 2 | 3 | export const isExternal = (path: string) => /^(https?:|mailto:|tel:)/.test(path) 4 | -------------------------------------------------------------------------------- /vue-main/src/views/framework/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 25 | 26 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue-main/src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /vue-main/src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 80 | -------------------------------------------------------------------------------- /vue-main/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-main/tests/unit/utils/validate.spec.ts: -------------------------------------------------------------------------------- 1 | import { isValidUsername, isExternal } from '@/utils/validate' 2 | 3 | describe('Utils:validate', () => { 4 | it('isValidUsername', () => { 5 | expect(isValidUsername('admin')).toBe(true) 6 | expect(isValidUsername('editor')).toBe(true) 7 | expect(isValidUsername('xxxx')).toBe(false) 8 | }) 9 | 10 | it('isExternal', () => { 11 | expect(isExternal('https://www.armour.com/')).toBe(true) 12 | expect(isExternal('mailto:someone@test.com')).toBe(true) 13 | expect(isExternal('123aBC')).toBe(false) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /vue-main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "allowJs": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "jest", 18 | "node" 19 | ], 20 | "paths": { 21 | "@/*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx", 35 | "src/**/*.vue", 36 | "tests/**/*.ts", 37 | "tests/**/*.tsx" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /vue-main/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const name = 'vue admin framework' 3 | 4 | module.exports = { 5 | devServer: { 6 | port: 5000 7 | }, 8 | publicPath: process.env.NODE_ENV === 'production' ? '/vue-typescript-admin-template/' : '/', // TODO: Remember to change this to fit your need 9 | lintOnSave: process.env.NODE_ENV === 'development', 10 | pwa: { 11 | name: name 12 | }, 13 | pluginOptions: { 14 | 'style-resources-loader': { 15 | preProcessor: 'scss', 16 | patterns: [ 17 | path.resolve(__dirname, 'src/styles/_variables.scss'), 18 | path.resolve(__dirname, 'src/styles/_mixins.scss') 19 | ] 20 | } 21 | }, 22 | chainWebpack(config) { 23 | // Provide the app's title in webpack's name field, so that 24 | // it can be accessed in index.html to inject the correct title. 25 | config.set('name', name) 26 | } 27 | } 28 | --------------------------------------------------------------------------------