├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── index.html ├── jsconfig.json ├── mock ├── _createProductionServer.js ├── login.js ├── menu.js └── test.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── login.js │ ├── menu.js │ └── test.js ├── assets │ ├── logo.svg │ ├── style │ │ ├── element-variables.scss │ │ └── global-variables.scss │ └── svg │ │ ├── bug.svg │ │ ├── error-icons │ │ ├── 403.svg │ │ ├── 404.svg │ │ └── 500.svg │ │ ├── home.svg │ │ ├── icon-home.svg │ │ └── language.svg ├── components │ ├── Avatar │ │ ├── hooks │ │ │ └── useUserinfo.js │ │ └── index.vue │ ├── ErrorLog │ │ └── index.vue │ ├── ProTable │ │ └── index.vue │ ├── SelectTree │ │ ├── Select.vue │ │ ├── Tree.vue │ │ └── index.vue │ └── SvgIcon │ │ └── index.vue ├── default-settings.js ├── directive │ └── index.js ├── error-log.js ├── global-components.js ├── hooks │ └── useCloseTag.js ├── i18n │ ├── index.js │ ├── locales │ │ ├── en │ │ │ ├── error.js │ │ │ ├── login.js │ │ │ ├── menu.js │ │ │ ├── public.js │ │ │ ├── tags.js │ │ │ ├── test │ │ │ │ └── list.js │ │ │ └── topbar.js │ │ └── zh-cn │ │ │ ├── error.js │ │ │ ├── login.js │ │ │ ├── menu.js │ │ │ ├── public.js │ │ │ ├── tags.js │ │ │ ├── test │ │ │ └── list.js │ │ │ └── topbar.js │ └── useLang.js ├── layout │ ├── components │ │ ├── Content │ │ │ └── index.vue │ │ ├── Sidebar │ │ │ ├── Item.vue │ │ │ ├── Logo.vue │ │ │ ├── Menus.vue │ │ │ ├── Submenu.vue │ │ │ ├── config │ │ │ │ └── menu.module.scss │ │ │ └── index.vue │ │ ├── Tagsbar │ │ │ ├── hooks │ │ │ │ ├── useContextMenu.js │ │ │ │ ├── useScrollbar.js │ │ │ │ └── useTags.js │ │ │ └── index.vue │ │ └── Topbar │ │ │ ├── Breadcrumbs.vue │ │ │ ├── ChangeLang.vue │ │ │ ├── Hamburger.vue │ │ │ ├── LockModal.vue │ │ │ ├── Userinfo.vue │ │ │ └── index.vue │ ├── hooks │ │ └── useResizeHandler.js │ └── index.vue ├── main.js ├── permission.js ├── pinia │ ├── index.js │ └── modules │ │ ├── account.js │ │ ├── app.js │ │ ├── errorLog.js │ │ ├── layoutSettings.js │ │ ├── menu.js │ │ └── tags.js ├── router │ ├── index.js │ └── modules │ │ ├── error.js │ │ ├── home.js │ │ ├── lock.js │ │ ├── login.js │ │ ├── redirect.js │ │ └── test.js ├── utils │ ├── encrypt.js │ ├── index.js │ ├── open-window.js │ ├── request.js │ ├── scroll-to.js │ ├── storage.js │ └── validate.js └── views │ ├── error │ └── index.vue │ ├── home │ └── index.vue │ ├── lock │ ├── Clock.vue │ ├── CurrentTime.vue │ ├── Unlock.vue │ ├── index.vue │ └── style │ │ └── style.css │ ├── login │ └── index.vue │ ├── redirect │ └── index.vue │ └── test │ ├── Add.vue │ ├── Auth.vue │ ├── Cache.vue │ ├── Edit.vue │ ├── Nest.vue │ ├── NoAuth.vue │ ├── Nocache.vue │ ├── error-log │ ├── components │ │ ├── ErrorTestA.vue │ │ └── ErrorTestB.vue │ └── index.vue │ ├── index.vue │ └── nest │ ├── Page1.vue │ └── Page2.vue └── vite.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | mock 4 | 5 | *.sh 6 | node_modules 7 | *.md 8 | *.woff 9 | *.ttf 10 | .vscode 11 | .idea 12 | dist 13 | /public 14 | /docs 15 | .husky 16 | .local 17 | /bin 18 | Dockerfile 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ___====-_ _-====___ 3 | * _--^^^#####// \\#####^^^--_ 4 | * _-^##########// ( ) \\##########^-_ 5 | * -############// |\^^/| \\############- 6 | * _/############// (@::@) \############\_ 7 | * /#############(( \\// ))#############\ 8 | * -###############\\ (oo) //###############- 9 | * -#################\\ / VV \ //#################- 10 | * -###################\\/ \//###################- 11 | * _#/|##########/\######( /\ )######/\##########|\#_ 12 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 13 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 14 | * ` ` ` ` / | | | | \ ' ' ' ' 15 | * ( | | | | ) 16 | * __\ | | | | /__ 17 | * (vvv(VVV)(VVV)vvv) 18 | * 19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | * 21 | * 神兽保佑 永无BUG 22 | * 23 | * @Descripttion: 24 | * @version: 25 | * @Date: 2021-04-20 11:06:21 26 | * @LastEditors: huzhushan@126.com 27 | * @LastEditTime: 2022-09-24 16:29:19 28 | * @Author: huzhushan@126.com 29 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 30 | * @Github: https://github.com/huzhushan/vue3-element-admin 31 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 32 | */ 33 | 34 | module.exports = { 35 | root: true, 36 | env: { 37 | browser: true, 38 | node: true, 39 | es6: true, 40 | }, 41 | extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/prettier'], 42 | parserOptions: { 43 | parser: 'babel-eslint', 44 | sourceType: 'module', 45 | ecmaVersion: 2020, 46 | }, 47 | rules: { 48 | 'no-console': 0, 49 | 'no-use-before-define': 'off', 50 | 'no-unused-vars': [ 51 | 'warn', 52 | { 53 | // 允许声明未使用变量 54 | vars: 'local', 55 | // 参数不检查 56 | args: 'none', 57 | }, 58 | ], 59 | 'vue/no-unused-vars': 'warn', 60 | 'no-prototype-builtins': 'off', 61 | 'no-irregular-whitespace': 'off', 62 | 'space-before-function-paren': 'off', 63 | 'vue/custom-event-name-casing': 'off', 64 | 'vue/attributes-order': 'off', 65 | 'vue/one-component-per-file': 'off', 66 | 'vue/html-closing-bracket-newline': 'off', 67 | 'vue/max-attributes-per-line': 'off', 68 | 'vue/multiline-html-element-content-newline': 'off', 69 | 'vue/singleline-html-element-content-newline': 'off', 70 | 'vue/attribute-hyphenation': 'off', 71 | 'vue/require-default-prop': 'off', 72 | 'vue/no-unused-components': 'warn', 73 | 'vue/no-setup-props-destructure': 'off', 74 | 'vue/script-setup-uses-vars': 'off', 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .DS_Store 4 | dist 5 | dist-ssr 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | package-lock.json 28 | yarn.lock 29 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, // 每行代码长度(默认80) 3 | tabWidth: 2, // 每个tab相当于多少个空格(默认2) 4 | useTabs: false, // 是否使用tab进行缩进(默认false) 5 | singleQuote: true, // 使用单引号(默认false) 6 | semi: false, // 声明结尾使用分号(默认true) 7 | trailingComma: 'es5', // 多行使用拖尾逗号(默认none) 8 | bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) 9 | jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false) 10 | arrowParens: 'avoid', // 只有一个参数的箭头函数的参数是否带括号(默认avoid) 11 | htmlWhitespaceSensitivity: 'ignore', // 空格不敏感。(开始标签、内容、结束标签各自单独一行) 12 | // vueIndentScriptAndStyle: false, // 是否给vue中的 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"], 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"], 9 | "include": ["src/**/*"] 10 | } -------------------------------------------------------------------------------- /mock/_createProductionServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐ 3 | * │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐ ┌┐ ┌┐ 4 | * └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘ └┘ └┘ 5 | * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐ 6 | * │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │ 7 | * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤ 8 | * │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │ │ 9 | * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │ 10 | * │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter │ │ 4 │ 5 │ 6 │ │ 11 | * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ ┌───┐ ├───┼───┼───┼───┤ 12 | * │ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│ Shift │ │ ↑ │ │ 1 │ 2 │ 3 │ │ 13 | * ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││ 14 | * │ Ctrl│ │Alt │ Space │ Alt│ │ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ . │←─┘│ 15 | * └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘ 16 | * 17 | * @Descripttion: 18 | * @version: 19 | * @Date: 2021-04-20 11:06:21 20 | * @LastEditors: huzhushan@126.com 21 | * @LastEditTime: 2021-04-21 12:44:16 22 | * @Author: huzhushan@126.com 23 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 24 | * @Github: https://github.com/huzhushan/vue3-element-admin 25 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 26 | */ 27 | 28 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' 29 | 30 | const modules = import.meta.globEager('./**/*.js') 31 | 32 | const mockModules = [] 33 | Object.keys(modules).forEach(key => { 34 | mockModules.push(...modules[key].default) 35 | }) 36 | 37 | /** 38 | * Used in a production environment. Need to manually import all modules 39 | */ 40 | export function setupProdMockServer() { 41 | createProdMockServer(mockModules) 42 | } 43 | -------------------------------------------------------------------------------- /mock/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | *   ┏┓   ┏┓+ + 4 | *  ┏┛┻━━━┛┻┓ + + 5 | *  ┃       ┃ 6 | *  ┃   ━   ┃ ++ + + + 7 | * ████━████ ┃+ 8 | *  ┃       ┃ + 9 | *  ┃   ┻   ┃ 10 | *  ┃       ┃ + + 11 | *  ┗━┓   ┏━┛ 12 | *    ┃   ┃ 13 | *    ┃   ┃ + + + + 14 | *    ┃   ┃ 15 | *    ┃   ┃ + 神兽保佑 16 | *    ┃   ┃ 代码无bug 17 | *    ┃   ┃  + 18 | *    ┃    ┗━━━┓ + + 19 | *    ┃        ┣┓ 20 | *    ┃        ┏┛ 21 | *    ┗┓┓┏━┳┓┏┛ + + + + 22 | *     ┃┫┫ ┃┫┫ 23 | *     ┗┻┛ ┗┻┛+ + + + 24 | * 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-04-20 11:06:21 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2021-07-26 13:06:50 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | export default [ 38 | { 39 | url: '/api/login', 40 | method: 'post', 41 | timeout: 1000, 42 | statusCode: 200, 43 | response: ({ body }) => { 44 | // 响应内容 45 | return +body.password === 123456 46 | ? { 47 | code: 200, 48 | message: '登录成功', 49 | data: { 50 | token: '@word(50, 100)', // @word()是mockjs的语法 51 | refresh_token: '@word(50, 100)', // refresh_token是用来重新生成token的 52 | }, 53 | } 54 | : { 55 | code: 400, 56 | message: '密码错误,请输入123456', 57 | } 58 | }, 59 | }, 60 | { 61 | url: '/api/userinfo', 62 | method: 'get', 63 | timeout: 100, 64 | response: { 65 | code: 200, 66 | message: '获取用户信息成功', 67 | data: { 68 | id: 1, 69 | name: 'zhangsan', 70 | 'role|1': ['admin', 'visitor'], // 随机返回一个角色admin或visitor 71 | avatar: "@image('48x48', '#fb0a2a')", 72 | }, 73 | }, 74 | }, 75 | ] 76 | -------------------------------------------------------------------------------- /mock/menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ___====-_ _-====___ 3 | * _--^^^#####// \\#####^^^--_ 4 | * _-^##########// ( ) \\##########^-_ 5 | * -############// |\^^/| \\############- 6 | * _/############// (@::@) \############\_ 7 | * /#############(( \\// ))#############\ 8 | * -###############\\ (oo) //###############- 9 | * -#################\\ / VV \ //#################- 10 | * -###################\\/ \//###################- 11 | * _#/|##########/\######( /\ )######/\##########|\#_ 12 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 13 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 14 | * ` ` ` ` / | | | | \ ' ' ' ' 15 | * ( | | | | ) 16 | * __\ | | | | /__ 17 | * (vvv(VVV)(VVV)vvv) 18 | * 19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | * 21 | * 神兽保佑 永无BUG 22 | * 23 | * @Descripttion: 24 | * @version: 25 | * @Date: 2021-04-20 11:06:21 26 | * @LastEditors: huzhushan@126.com 27 | * @LastEditTime: 2022-09-27 18:51:22 28 | * @Author: huzhushan@126.com 29 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 30 | * @Github: https://github.com/huzhushan/vue3-element-admin 31 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 32 | */ 33 | 34 | export default [ 35 | { 36 | url: '/api/menus', 37 | method: 'get', 38 | timeout: 100, 39 | response: ({ query }) => { 40 | // 响应内容 41 | const childs = [ 42 | { 43 | name: 'testList', 44 | title: '列表', 45 | children: [ 46 | { 47 | name: 'testAdd', 48 | title: '添加', 49 | }, 50 | { 51 | name: 'testEdit', 52 | title: '编辑', 53 | }, 54 | ] 55 | }, 56 | 57 | // { 58 | // name: 'testAuth', 59 | // title: '权限测试', 60 | // }, 61 | { 62 | name: 'test-cache', 63 | title: '该页面可缓存', 64 | }, 65 | { 66 | name: 'test-no-cache', 67 | title: '该页面不可缓存', 68 | }, 69 | { 70 | name: 'nest', 71 | title: '二级菜单', 72 | children: [ 73 | { 74 | name: 'nestPage1', 75 | title: 'page1', 76 | }, 77 | { 78 | name: 'nestPage2', 79 | title: 'page2', 80 | }, 81 | ], 82 | }, 83 | { 84 | name: 'test-error-log', 85 | title: '测试错误日志', 86 | }, 87 | ] 88 | 89 | return { 90 | code: 200, 91 | message: '获取菜单成功', 92 | data: [ 93 | { 94 | name: 'test', 95 | title: '测试页面', 96 | children: childs, 97 | }, 98 | ], 99 | } 100 | }, 101 | }, 102 | ] 103 | -------------------------------------------------------------------------------- /mock/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-25 12:27:50 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | export default [ 13 | { 14 | url: '/api/get', // 请求地址 15 | method: 'get', // 请求方法 16 | response: ({ query }) => { 17 | // 响应内容 18 | return { 19 | code: 0, 20 | data: { 21 | name: 'hello world', 22 | }, 23 | } 24 | }, 25 | }, 26 | { 27 | url: '/api/post', 28 | method: 'post', 29 | timeout: 2000, 30 | response: { 31 | code: 0, 32 | data: { 33 | name: 'hello world', 34 | }, 35 | }, 36 | }, 37 | { 38 | url: '/api/500', 39 | method: 'get', 40 | statusCode: 500, 41 | response: { 42 | code: 500, 43 | message: '内部错误', 44 | data: null, 45 | }, 46 | }, 47 | // 请求用户列表 48 | { 49 | url: '/api/test/users', 50 | method: 'post', 51 | timeout: 1000, 52 | response: () => { 53 | // 响应内容 54 | return { 55 | code: 200, 56 | message: '获取成功', 57 | data: { 58 | 'list|10': [ 59 | { 60 | 'id|+1': 1, 61 | nickName: '@cname()', 62 | userEmail: '@email()', 63 | 'status|1': [0, 1], 64 | }, 65 | ], 66 | 'total|50-1000': 1, 67 | }, 68 | } 69 | }, 70 | }, 71 | ] 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-element-admin", 3 | "version": "3.0.0", 4 | "author": { 5 | "name": "huzhushan", 6 | "email": "huzhushan@126.com", 7 | "url": "https://github.com/huzhushan" 8 | }, 9 | "scripts": { 10 | "start": "npm run dev:mock", 11 | "dev": "vite", 12 | "dev:mock": "vite --mode mock", 13 | "build": "vite build", 14 | "build:mock": "vite build --mode mock", 15 | "serve": "vite preview", 16 | "lint": "eslint --fix --ext .js,.vue src" 17 | }, 18 | "browserslist": [ 19 | "> 1%", 20 | "last 2 versions" 21 | ], 22 | "dependencies": { 23 | "axios": "^0.21.1", 24 | "vue": "^3.2.33", 25 | "vue-router": "^4.0.5", 26 | "vuex": "^4.0.0", 27 | "pinia": "^2.0.14" 28 | }, 29 | "devDependencies": { 30 | "@ehutch79/vite-eslint": "0.0.1", 31 | "@vitejs/plugin-vue": "^1.2.3", 32 | "@vue/compiler-sfc": "^3.1.2", 33 | "@vue/eslint-config-prettier": "^6.0.0", 34 | "autoprefixer": "^10.2.5", 35 | "babel-eslint": "^10.1.0", 36 | "crypto-js": "^4.0.0", 37 | "element-plus": "^2.2.13", 38 | "vue-i18n": "^9.0.0", 39 | "eslint": "^6.7.2", 40 | "eslint-plugin-prettier": "^3.1.3", 41 | "eslint-plugin-vue": "^7.0.0-0", 42 | "husky": "^1.3.1", 43 | "lint-staged": "^9.5.0", 44 | "mockjs": "^1.1.0", 45 | "prettier": "^1.19.1", 46 | "sass": "^1.41.1", 47 | "vite": "^2.3.7", 48 | "vite-plugin-mock": "^2.3.0", 49 | "vite-plugin-svg-icons": "^0.4.0" 50 | }, 51 | "lint-staged": { 52 | "src/**/*.{js,vue}": [ 53 | "npm run lint", 54 | "git add" 55 | ] 56 | }, 57 | "husky": { 58 | "hooks": { 59 | "pre-commit": "lint-staged" 60 | } 61 | }, 62 | "repository": { 63 | "type": "git", 64 | "url": "git+https://github.com/huzhushan/vue3-element-admin.git" 65 | }, 66 | "license": "MIT", 67 | "bugs": { 68 | "url": "https://github.com/huzhushan/vue3-element-admin/issues" 69 | }, 70 | "homepage": "https://github.com/huzhushan/vue3-element-admin" 71 | } 72 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huzhushan/vue3-element-admin/9b89d24d2eae3a1e56af70e14295ff56f0524336/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 33 | 38 | 39 | 62 | 63 | 76 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2021-04-21 09:36:55 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import request from '@/utils/request' 13 | 14 | // 登录接口 15 | export const Login = data => { 16 | return request({ 17 | url: '/api/login', 18 | method: 'post', 19 | data, 20 | }) 21 | } 22 | 23 | // 获取登录用户信息 24 | export const GetUserinfo = () => { 25 | return request({ 26 | url: '/api/userinfo', 27 | method: 'get', 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/api/menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2021-07-26 13:37:30 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import request from '@/utils/request' 13 | 14 | // 获取菜单 15 | export const GetMenus = params => { 16 | return request({ 17 | url: '/api/menus', 18 | method: 'get', 19 | params, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /src/api/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 16:35:04 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-25 11:50:39 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import request from '@/utils/request' 13 | 14 | // 测试 15 | export const TestError = () => { 16 | return request({ 17 | url: '/api/500', 18 | method: 'get', 19 | }) 20 | } 21 | 22 | // 用户列表 23 | export const getUsers = data => { 24 | return request({ 25 | url: '/api/test/users', 26 | method: 'post', 27 | data, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/style/element-variables.scss: -------------------------------------------------------------------------------- 1 | /**如果需要修改其它变量,可以在以下文件中查找 2 | * https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss 3 | */ 4 | 5 | @forward 'element-plus/theme-chalk/src/common/var.scss' with ( 6 | $colors: ( 7 | 'primary': ( 8 | 'base': $mainColor, 9 | ), 10 | ), 11 | ); 12 | 13 | @use "element-plus/theme-chalk/src/reset.scss" as *; 14 | @use "element-plus/theme-chalk/src/index.scss" as *; 15 | -------------------------------------------------------------------------------- /src/assets/style/global-variables.scss: -------------------------------------------------------------------------------- 1 | // 该文件中的变量是全局变量,在css文件和vue组件中可以直接使用 2 | 3 | $mainColor: #409eff; // 网站主题色 4 | 5 | // 侧边栏 6 | $menuBg: #304156; // 菜单背景颜色 7 | $menuTextColor: #fff; // 菜单文字颜色 8 | $menuActiveTextColor: $mainColor; // 已选中菜单文字颜色 9 | $menuActiveBg: none; // 已选中菜单背景颜色 10 | $menuHover: #263445; // 鼠标经过菜单时的背景颜色 11 | $subMenuBg: #1f2d3d; // 子菜单背景颜色 12 | $subMenuHover: #001528; // 鼠标经过子菜单时的背景颜色 13 | $collapseMenuActiveBg: #1f2d3d; // 菜单宽度折叠后,已选中菜单的背景颜色 14 | $collapseMenuActiveColor: $menuTextColor; // 菜单宽度折叠后,已选中菜单的文字颜色 15 | $collapseMenuActiveBorderColor: $mainColor; // 菜单宽度折叠后,已选中菜单的边框颜色 16 | $collapseMenuActiveBorderWidth: 2px; // 菜单宽度折叠后,已选中菜单的边框宽度 17 | $arrowColor: #909399; // 展开/收起箭头颜色 18 | $horizontalMenuHeight: 40px; // 菜单栏水平排列时候的高度 19 | -------------------------------------------------------------------------------- /src/assets/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/icon-home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Avatar/hooks/useUserinfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ┌─────────────────────────────────────────────────────────────┐ 3 | * │┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐│ 4 | * ││Esc│!1 │@2 │#3 │$4 │%5 │^6 │&7 │*8 │(9 │)0 │_- │+= │|\ │`~ ││ 5 | * │├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴───┤│ 6 | * ││ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{[ │}] │ BS ││ 7 | * │├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤│ 8 | * ││ Ctrl │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter ││ 9 | * │├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────┬───┤│ 10 | * ││ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│Shift │Fn ││ 11 | * │└─────┬──┴┬──┴──┬┴───┴───┴───┴───┴───┴──┬┴───┴┬──┴┬─────┴───┘│ 12 | * │ │Fn │ Alt │ Space │ Alt │Win│ HHKB │ 13 | * │ └───┴─────┴───────────────────────┴─────┴───┘ │ 14 | * └─────────────────────────────────────────────────────────────┘ 15 | * 16 | * @Descripttion: 17 | * @version: 18 | * @Date: 2021-04-23 14:56:06 19 | * @LastEditors: huzhushan@126.com 20 | * @LastEditTime: 2022-09-27 16:07:53 21 | * @Author: huzhushan@126.com 22 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 23 | * @Github: https://github.com/huzhushan/vue3-element-admin 24 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 25 | */ 26 | import { storeToRefs } from 'pinia' 27 | import { useAccount } from '@/pinia/modules/account' 28 | 29 | export const useUserinfo = () => { 30 | const { userinfo } = storeToRefs(useAccount()) 31 | return { userinfo } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Avatar/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 47 | -------------------------------------------------------------------------------- /src/components/ErrorLog/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 112 | 113 | 138 | 139 | 150 | -------------------------------------------------------------------------------- /src/components/SelectTree/Tree.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 45 | 117 | 131 | -------------------------------------------------------------------------------- /src/components/SelectTree/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 59 | 60 | 146 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 33 | 34 | 51 | 52 | 61 | -------------------------------------------------------------------------------- /src/default-settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * |~~~~~~~| 3 | * | | 4 | * | | 5 | * | | 6 | * | | 7 | * | | 8 | * |~.\\\_\~~~~~~~~~~~~~~xx~~~ ~~~~~~~~~~~~~~~~~~~~~/_//;~| 9 | * | \ o \_ ,XXXXX), _..-~ o / | 10 | * | ~~\ ~-. XXXXX`)))), _.--~~ .-~~~ | 11 | * ~~~~~~~`\ ~\~~~XXX' _/ ';)) |~~~~~~..-~ _.-~ ~~~~~~~ 12 | * `\ ~~--`_\~\, ;;;\)__.---.~~~ _.-~ 13 | * ~-. `:;;/;; \ _..-~~ 14 | * ~-._ `'' /-~-~ 15 | * `\ / / 16 | * | , | | 17 | * | ' / | 18 | * \/; | 19 | * ;; | 20 | * `; . | 21 | * |~~~-----.....| 22 | * | \ \ 23 | * | /\~~--...__ | 24 | * (| `\ __-\| 25 | * || \_ /~ | 26 | * |) \~-' | 27 | * | | \ ' 28 | * | | \ : 29 | * \ | | | 30 | * | ) ( ) 31 | * \ /; /\ | 32 | * | |/ | 33 | * | | | 34 | * \ .' || 35 | * | | | | 36 | * ( | | | 37 | * | \ \ | 38 | * || o `.)| 39 | * |`\\) | 40 | * | | 41 | * | | 42 | * 43 | * @Descripttion: 44 | * @version: 45 | * @Date: 2021-07-22 17:22:14 46 | * @LastEditors: huzhushan@126.com 47 | * @LastEditTime: 2021-09-18 15:02:01 48 | * @Author: huzhushan@126.com 49 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 50 | * @Github: https://github.com/huzhushan/vue3-element-admin 51 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 52 | */ 53 | 54 | export default { 55 | menus: { 56 | // 菜单栏是否显示 57 | isShow: true, 58 | // 菜单栏排列方式 59 | mode: 'vertical', // horizontal: 水平排列 vertical: 垂直排列 60 | }, 61 | tagsbar: { 62 | // 标签栏是否显示 63 | isShow: true, 64 | }, 65 | breadcrumbs: { 66 | // 面包屑导航是否显示 67 | isShow: true, 68 | }, 69 | topbar: { 70 | // 顶栏是否固定 71 | isFixed: true, 72 | }, 73 | layout: { 74 | // 是否流式布局 75 | isFluid: true, 76 | }, 77 | } 78 | -------------------------------------------------------------------------------- /src/directive/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | *   ┏┓   ┏┓+ + 4 | *  ┏┛┻━━━┛┻┓ + + 5 | *  ┃       ┃ 6 | *  ┃   ━   ┃ ++ + + + 7 | * ████━████ ┃+ 8 | *  ┃       ┃ + 9 | *  ┃   ┻   ┃ 10 | *  ┃       ┃ + + 11 | *  ┗━┓   ┏━┛ 12 | *    ┃   ┃ 13 | *    ┃   ┃ + + + + 14 | *    ┃   ┃ 15 | *    ┃   ┃ + 神兽保佑 16 | *    ┃   ┃ 代码无bug 17 | *    ┃   ┃  + 18 | *    ┃    ┗━━━┓ + + 19 | *    ┃        ┣┓ 20 | *    ┃        ┏┛ 21 | *    ┗┓┓┏━┳┓┏┛ + + + + 22 | *     ┃┫┫ ┃┫┫ 23 | *     ┗┻┛ ┗┻┛+ + + + 24 | * 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-09-01 13:58:08 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2022-09-27 18:31:22 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | import { useAccount } from '@/pinia/modules/account' 38 | 39 | export const Permission = app => { 40 | app.directive('permission', { 41 | mounted: function(el, binding) { 42 | const { permissionList } = useAccount() 43 | 44 | if ( 45 | binding.value && 46 | permissionList.every(item => item !== binding.value) 47 | ) { 48 | // 移除组件 49 | el.parentNode.removeChild(el) 50 | } 51 | }, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/error-log.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 佛曰: 3 | * 写字楼里写字间,写字间里程序员; 4 | * 程序人员写程序,又拿程序换酒钱。 5 | * 酒醒只在网上坐,酒醉还来网下眠; 6 | * 酒醉酒醒日复日,网上网下年复年。 7 | * 但愿老死电脑间,不愿鞠躬老板前; 8 | * 奔驰宝马贵者趣,公交自行程序员。 9 | * 别人笑我忒疯癫,我笑自己命太贱; 10 | * 不见满街漂亮妹,哪个归得程序员? 11 | * 12 | * @Descripttion: 13 | * @version: 14 | * @Date: 2021-04-21 09:18:32 15 | * @LastEditors: huzhushan@126.com 16 | * @LastEditTime: 2022-09-27 15:53:02 17 | * @Author: huzhushan@126.com 18 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 19 | * @Github: https://github.com/huzhushan/vue3-element-admin 20 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 21 | */ 22 | 23 | import { nextTick } from 'vue' 24 | import { useErrorlog } from './pinia/modules/errorLog' 25 | // import store from '@/store' 26 | 27 | // 判断环境,决定是否开启错误监控 28 | // - import.meta.env.DEV 布尔值,代表开发环境 29 | // - import.meta.env.PROD 布尔值,代表生产环境 30 | 31 | // const flag = import.meta.env.PROD // 生产环境才进行错误监控 32 | const flag = true // 为了演示,默认开启错误监控。如果你的项目不需要错误监控,请设为false 33 | 34 | export default app => { 35 | if (flag) { 36 | app.config.errorHandler = function(err, vm, info) { 37 | nextTick(() => { 38 | useErrorlog().addErrorLog({ 39 | err, 40 | // vm, // 这里不保存vm,否则渲染错误日志的时候控制台会有警告 41 | info, 42 | url: window.location.href, 43 | id: Date.now(), 44 | }) 45 | console.error(err, info) 46 | }) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/global-components.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 江城子 . 程序员之歌 3 | * 4 | * 十年生死两茫茫,写程序,到天亮。 5 | * 千行代码,Bug何处藏。 6 | * 纵使上线又怎样,朝令改,夕断肠。 7 | * 8 | * 领导每天新想法,天天改,日日忙。 9 | * 相顾无言,惟有泪千行。 10 | * 每晚灯火阑珊处,夜难寐,加班狂。 11 | * 12 | * 13 | * @Descripttion: 14 | * @version: 15 | * @Date: 2021-09-18 09:32:01 16 | * @LastEditors: huzhushan@126.com 17 | * @LastEditTime: 2022-09-24 14:43:31 18 | * @Author: huzhushan@126.com 19 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 20 | * @Github: https://github.com/huzhushan/vue3-element-admin 21 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 22 | */ 23 | 24 | export { default as SvgIcon } from '@/components/SvgIcon/index.vue' 25 | export { default as ProTable } from '@/components/ProTable/index.vue' 26 | -------------------------------------------------------------------------------- /src/hooks/useCloseTag.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | *   ┏┓   ┏┓+ + 4 | *  ┏┛┻━━━┛┻┓ + + 5 | *  ┃       ┃ 6 | *  ┃   ━   ┃ ++ + + + 7 | * ████━████ ┃+ 8 | *  ┃       ┃ + 9 | *  ┃   ┻   ┃ 10 | *  ┃       ┃ + + 11 | *  ┗━┓   ┏━┛ 12 | *    ┃   ┃ 13 | *    ┃   ┃ + + + + 14 | *    ┃   ┃ 15 | *    ┃   ┃ + 神兽保佑 16 | *    ┃   ┃ 代码无bug 17 | *    ┃   ┃  + 18 | *    ┃    ┗━━━┓ + + 19 | *    ┃        ┣┓ 20 | *    ┃        ┏┛ 21 | *    ┗┓┓┏━┳┓┏┛ + + + + 22 | *     ┃┫┫ ┃┫┫ 23 | *     ┗┻┛ ┗┻┛+ + + + 24 | * 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-08-20 11:15:27 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2022-09-27 16:15:56 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | import { useTags } from '@/pinia/modules/tags' 38 | import { reactive, toRefs, getCurrentInstance } from 'vue' 39 | import { useRoute, useRouter } from 'vue-router' 40 | 41 | // 关闭当前标签 42 | export default () => { 43 | const instance = getCurrentInstance() 44 | const router = useRouter() 45 | const route = useRoute() 46 | const { delTag } = useTags() 47 | const state = reactive({ 48 | /** 49 | * @param {String} fullPath 要跳转到那个页面的地址 50 | * @param {Boolean} reload 是否在跳转后重新渲染页面组件 51 | * @param {Boolean} f5 是否在跳转后刷新页面 52 | * @return {*} 53 | */ 54 | closeTag({ fullPath, reload, f5 } = {}) { 55 | delTag(route) 56 | fullPath ? router.push(fullPath) : router.back() 57 | reload && 58 | setTimeout(() => { 59 | instance.appContext.config.globalProperties.$tagsbar.refreshSelectedTag( 60 | route 61 | ) 62 | }, 500) 63 | 64 | f5 && setTimeout(() => window.location.reload(), 500) 65 | }, 66 | }) 67 | 68 | return toRefs(state) 69 | } 70 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | const getMessage = modules => { 4 | return Object.entries(modules).reduce((module, [path, mod]) => { 5 | const moduleName = path.replace(/^\.\/locales\/[\w-]+\/(.*)\.\w+$/, '$1') 6 | module[moduleName] = mod.default 7 | return module 8 | }, {}) 9 | } 10 | 11 | export default createI18n({ 12 | locale: localStorage.getItem('__VEA__lang') || 'zh-cn', 13 | messages: { 14 | 'zh-cn': getMessage(import.meta.globEager('./locales/zh-cn/**/*.js')), 15 | en: getMessage(import.meta.globEager('./locales/en/**/*.js')), 16 | }, 17 | legacy: false, 18 | globalInjection: true, 19 | }) 20 | -------------------------------------------------------------------------------- /src/i18n/locales/en/error.js: -------------------------------------------------------------------------------- 1 | export default { 2 | noauth: 'No access', 3 | servererror: 'Server error', 4 | notfound: 'Page not found', 5 | backhome: 'Back to Home', 6 | } 7 | -------------------------------------------------------------------------------- /src/i18n/locales/en/login.js: -------------------------------------------------------------------------------- 1 | export default { 2 | username: 'Username', 3 | password: 'Password', 4 | login: 'Login', 5 | logining: 'Login...', 6 | loginsuccess: 'Success', 7 | 'rules-username': 'Please input username', 8 | 'rules-password': 'Please input password', 9 | 'rules-regpassword': '6 to 12 characters in length', 10 | } 11 | -------------------------------------------------------------------------------- /src/i18n/locales/en/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | homepage: 'Homepage', 3 | dashboard: 'Dashboard', 4 | test: 'Test page', 5 | testList: 'List', 6 | testAdd: 'Add', 7 | testEdit: 'Edit', 8 | testAuth: 'Auth', 9 | testNoAuth: 'No auth', 10 | 'test-cache': 'Cache', 11 | 'test-no-cache': 'No Cache', 12 | nest: 'Nest page', 13 | nestPage1: 'Page1', 14 | nestPage2: 'Page2', 15 | 'test-error-log': 'Error log', 16 | } 17 | -------------------------------------------------------------------------------- /src/i18n/locales/en/public.js: -------------------------------------------------------------------------------- 1 | export default { 2 | sure: 'Sure', 3 | search: 'Search', 4 | reset: 'Reset', 5 | edit: 'Edit', 6 | add: 'Add', 7 | delete: 'Delete', 8 | save: 'Save', 9 | cancel: 'Cancel', 10 | yes: 'Yes', 11 | no: 'No', 12 | status: 'Status', 13 | operate: 'Operate', 14 | enabled: 'Enabled', 15 | disabled: 'Disabled', 16 | male: 'Male', 17 | female: 'Female', 18 | startdate: 'From', 19 | enddate: 'To', 20 | starttime: 'From', 21 | endtime: 'To', 22 | } 23 | -------------------------------------------------------------------------------- /src/i18n/locales/en/tags.js: -------------------------------------------------------------------------------- 1 | export default { 2 | refresh: 'Refresh', 3 | close: 'Close', 4 | other: 'Close other', 5 | left: 'Close left', 6 | right: 'Close right', 7 | all: 'Close all', 8 | } 9 | -------------------------------------------------------------------------------- /src/i18n/locales/en/test/list.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'List', 3 | batchDelete: 'Batch delete', 4 | add: 'Add one', 5 | refresh: 'Refresh', 6 | index: 'Index', 7 | name: 'Nickname', 8 | email: 'Email', 9 | desc: 'Description', 10 | publish: 'Published', 11 | nopublish: 'Unpublished', 12 | gender: 'Gender', 13 | city: 'City', 14 | bj: 'Beijing', 15 | sh: 'Shanghai', 16 | gz: 'Guangzhou', 17 | sz: 'Shenzhen', 18 | hobby: 'Hobby', 19 | eat: 'Eat', 20 | sleep: 'Sleep', 21 | bit: 'Beat', 22 | fruit: 'Fruit', 23 | apple: 'Apple', 24 | banana: 'Banana', 25 | orange: 'Orange', 26 | grape: 'Grape', 27 | date: 'Date', 28 | daterange: 'Date range', 29 | time: 'Time', 30 | timerange: 'Time range', 31 | num: 'Number', 32 | } 33 | -------------------------------------------------------------------------------- /src/i18n/locales/en/topbar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | center: 'User center', 3 | password: 'Modify password', 4 | logout: 'Logout', 5 | 'lock-title': 'Lock screen', 6 | 'lock-password': 'Password', 7 | 'lock-rules-password': 'Please input Screen password', 8 | 'lock-locked': 'Screen Locked', 9 | 'lock-lock': 'Unlock', 10 | 'lock-relogin': 'Re-login', 11 | 'lock-rules-password2': 'Screen password or User password', 12 | 'lock-rules-password3': 'Password error', 13 | 'lock-error': 'Your account has been logged out, please log in directly', 14 | 'lock-week0': 'Sunday', 15 | 'lock-week1': 'Monday', 16 | 'lock-week2': 'Tuesday', 17 | 'lock-week3': 'Wednesday', 18 | 'lock-week4': 'Thursday', 19 | 'lock-week5': 'Friday', 20 | 'lock-week6': 'Saturday', 21 | } 22 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/error.js: -------------------------------------------------------------------------------- 1 | export default { 2 | noauth: '您无权访问此页面', 3 | servererror: '服务器出错了', 4 | notfound: '您访问的页面不存在', 5 | backhome: '返回首页', 6 | } 7 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/login.js: -------------------------------------------------------------------------------- 1 | export default { 2 | username: '用户名', 3 | password: '密码', 4 | login: '登录', 5 | logining: '登录中...', 6 | loginsuccess: '登录成功', 7 | 'rules-username': '请输入用户名', 8 | 'rules-password': '请输入密码', 9 | 'rules-regpassword': '长度在 6 到 12 个字符', 10 | } 11 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | homepage: '首页', 3 | dashboard: '工作台', 4 | test: '测试页面', 5 | testList: '列表', 6 | testAdd: '添加', 7 | testEdit: '编辑', 8 | testAuth: '权限测试', 9 | testNoAuth: '权限页面', 10 | 'test-cache': '该页面可缓存', 11 | 'test-no-cache': '该页面不缓存', 12 | nest: '二级页面', 13 | nestPage1: 'Page1', 14 | nestPage2: 'Page2', 15 | 'test-error-log': '测试错误日志', 16 | } 17 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/public.js: -------------------------------------------------------------------------------- 1 | export default { 2 | sure: '确定', 3 | search: '搜索', 4 | reset: '重置', 5 | edit: '编辑', 6 | add: '新增', 7 | delete: '删除', 8 | save: '保存', 9 | cancel: '取消', 10 | yes: '是', 11 | no: '否', 12 | status: '状态', 13 | operate: '操作', 14 | enabled: '启用', 15 | disabled: '禁用', 16 | male: '男', 17 | female: '女', 18 | startdate: '开始日期', 19 | enddate: '结束日期', 20 | starttime: '开始时间', 21 | endtime: '结束时间', 22 | } 23 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/tags.js: -------------------------------------------------------------------------------- 1 | export default { 2 | refresh: '刷新', 3 | close: '关闭', 4 | other: '关闭其它', 5 | left: '关闭左侧', 6 | right: '关闭右侧', 7 | all: '关闭全部', 8 | } 9 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/test/list.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '列表', 3 | batchDelete: '批量删除', 4 | add: '添加一条', 5 | refresh: '刷新', 6 | index: '序号', 7 | name: '昵称', 8 | email: '邮箱', 9 | desc: '描述', 10 | publish: '已发布', 11 | nopublish: '未发布', 12 | gender: '性别', 13 | city: '城市', 14 | bj: '北京', 15 | sh: '上海', 16 | gz: '广州', 17 | sz: '深圳', 18 | hobby: '爱好', 19 | eat: '吃饭', 20 | sleep: '睡觉', 21 | bit: '打豆豆', 22 | fruit: '水果', 23 | apple: '苹果', 24 | banana: '香蕉', 25 | orange: '橘子', 26 | grape: '葡萄', 27 | date: '日期', 28 | daterange: '日期范围', 29 | time: '时间', 30 | timerange: '时间范围', 31 | num: '数量', 32 | } 33 | -------------------------------------------------------------------------------- /src/i18n/locales/zh-cn/topbar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | center: '个人中心', 3 | password: '修改密码', 4 | logout: '退出登录', 5 | 'lock-title': '锁定屏幕', 6 | 'lock-password': '锁屏密码', 7 | 'lock-rules-password': '请输入锁屏密码', 8 | 'lock-locked': '屏幕已锁定', 9 | 'lock-lock': '解锁', 10 | 'lock-relogin': '重新登录', 11 | 'lock-rules-password2': '请输入锁屏密码或登录密码', 12 | 'lock-rules-password3': '密码错误', 13 | 'lock-error': '您的账号已退出,请直接登录', 14 | 'lock-week0': '星期日', 15 | 'lock-week1': '星期一', 16 | 'lock-week2': '星期二', 17 | 'lock-week3': '星期三', 18 | 'lock-week4': '星期四', 19 | 'lock-week5': '星期五', 20 | 'lock-week6': '星期六', 21 | } 22 | -------------------------------------------------------------------------------- /src/i18n/useLang.js: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useI18n } from 'vue-i18n' 3 | 4 | export default function useLang() { 5 | const i18n = useI18n() 6 | const lang = computed(() => i18n.locale.value) 7 | const changeLang = value => { 8 | i18n.locale.value = value 9 | localStorage.setItem('__VEA__lang', value) 10 | } 11 | return { 12 | i18n, 13 | lang, 14 | changeLang, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/layout/components/Content/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 44 | 63 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 39 | 40 | 54 | 64 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 41 | 55 | 77 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Menus.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 54 | 95 | 157 | 168 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Submenu.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 39 | 59 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/config/menu.module.scss: -------------------------------------------------------------------------------- 1 | :export { 2 | menuBg: $menuBg; 3 | menuTextColor: $menuTextColor; 4 | menuActiveTextColor: $menuActiveTextColor; 5 | } 6 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 45 | 46 | 76 | 77 | 115 | -------------------------------------------------------------------------------- /src/layout/components/Tagsbar/hooks/useContextMenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * ┏┓   ┏┓ 4 | * ┏┛┻━━━┛┻┓ 5 | * ┃       ┃ 6 | * ┃   ━   ┃ 7 | * ┃ >   < ┃ 8 | * ┃       ┃ 9 | * ┃... ⌒ ... ┃ 10 | * ┃       ┃ 11 | * ┗━┓   ┏━┛ 12 | * ┃   ┃ 13 | * ┃   ┃ 14 | * ┃   ┃ 15 | * ┃   ┃ 神兽保佑 16 | * ┃   ┃ 代码无bug 17 | * ┃   ┃ 18 | * ┃   ┗━━━┓ 19 | * ┃       ┣┓ 20 | * ┃       ┏┛ 21 | * ┗┓┓┏━┳┓┏┛ 22 | * ┃┫┫ ┃┫┫ 23 | * ┗┻┛ ┗┻┛ 24 | * 25 | * @Descripttion: 26 | * @version: 27 | * @Date: 2021-04-20 11:06:21 28 | * @LastEditors: huzhushan@126.com 29 | * @LastEditTime: 2022-09-27 16:52:30 30 | * @Author: huzhushan@126.com 31 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 32 | * @Github: https://github.com/huzhushan/vue3-element-admin 33 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 34 | */ 35 | 36 | import { useTags } from '@/pinia/modules/tags' 37 | import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue' 38 | import { useRoute, useRouter } from 'vue-router' 39 | import { isAffix } from './useTags' 40 | 41 | export const useContextMenu = tagList => { 42 | const router = useRouter() 43 | const route = useRoute() 44 | 45 | const tagsStore = useTags() 46 | 47 | const state = reactive({ 48 | visible: false, 49 | top: 0, 50 | left: 0, 51 | selectedTag: {}, 52 | openMenu(tag, e) { 53 | state.visible = true 54 | state.left = e.clientX 55 | state.top = e.clientY 56 | state.selectedTag = tag 57 | }, 58 | closeMenu() { 59 | state.visible = false 60 | }, 61 | refreshSelectedTag(tag) { 62 | tagsStore.deCacheList(tag) 63 | const { fullPath } = tag 64 | nextTick(() => { 65 | router.replace({ 66 | path: '/redirect' + fullPath, 67 | }) 68 | }) 69 | }, 70 | closeTag(tag) { 71 | if (isAffix(tag)) return 72 | 73 | const closedTagIndex = tagList.value.findIndex( 74 | item => item.fullPath === tag.fullPath 75 | ) 76 | tagsStore.delTag(tag) 77 | if (isActive(tag)) { 78 | toLastTag(closedTagIndex - 1) 79 | } 80 | }, 81 | closeOtherTags() { 82 | tagsStore.delOtherTags(state.selectedTag) 83 | router.push(state.selectedTag) 84 | }, 85 | closeLeftTags() { 86 | state.closeSomeTags('left') 87 | }, 88 | closeRightTags() { 89 | state.closeSomeTags('right') 90 | }, 91 | closeSomeTags(direction) { 92 | const index = tagList.value.findIndex( 93 | item => item.fullPath === state.selectedTag.fullPath 94 | ) 95 | 96 | if ( 97 | (direction === 'left' && index <= 0) || 98 | (direction === 'right' && index >= tagList.value.length - 1) 99 | ) { 100 | return 101 | } 102 | 103 | const needToClose = 104 | direction === 'left' 105 | ? tagList.value.slice(0, index) 106 | : tagList.value.slice(index + 1) 107 | tagsStore.delSomeTags(needToClose) 108 | router.push(state.selectedTag) 109 | }, 110 | closeAllTags() { 111 | tagsStore.delAllTags() 112 | router.push('/') 113 | }, 114 | }) 115 | 116 | const isActive = tag => { 117 | return tag.fullPath === route.fullPath 118 | } 119 | 120 | const toLastTag = lastTagIndex => { 121 | const lastTag = tagList.value[lastTagIndex] 122 | if (lastTag) { 123 | router.push(lastTag.fullPath) 124 | } else { 125 | router.push('/') 126 | } 127 | } 128 | 129 | onMounted(() => { 130 | document.addEventListener('click', state.closeMenu) 131 | }) 132 | 133 | onBeforeUnmount(() => { 134 | document.removeEventListener('click', state.closeMenu) 135 | }) 136 | 137 | return toRefs(state) 138 | } 139 | -------------------------------------------------------------------------------- /src/layout/components/Tagsbar/hooks/useScrollbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ___====-_ _-====___ 3 | * _--^^^#####// \\#####^^^--_ 4 | * _-^##########// ( ) \\##########^-_ 5 | * -############// |\^^/| \\############- 6 | * _/############// (@::@) \############\_ 7 | * /#############(( \\// ))#############\ 8 | * -###############\\ (oo) //###############- 9 | * -#################\\ / VV \ //#################- 10 | * -###################\\/ \//###################- 11 | * _#/|##########/\######( /\ )######/\##########|\#_ 12 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 13 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 14 | * ` ` ` ` / | | | | \ ' ' ' ' 15 | * ( | | | | ) 16 | * __\ | | | | /__ 17 | * (vvv(VVV)(VVV)vvv) 18 | * 19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | * 21 | * 神兽保佑 永无BUG 22 | * 23 | * @Descripttion: 24 | * @version: 25 | * @Date: 2021-04-20 11:06:21 26 | * @LastEditors: huzhushan@126.com 27 | * @LastEditTime: 2022-08-13 14:50:23 28 | * @Author: huzhushan@126.com 29 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 30 | * @Github: https://github.com/huzhushan/vue3-element-admin 31 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 32 | */ 33 | 34 | import { ref } from 'vue' 35 | 36 | export const useScrollbar = tagsItem => { 37 | const scrollContainer = ref(null) 38 | const scrollLeft = ref(0) 39 | 40 | const doScroll = val => { 41 | scrollLeft.value = val 42 | scrollContainer.value.setScrollLeft(scrollLeft.value) 43 | } 44 | 45 | const handleScroll = e => { 46 | const $wrap = scrollContainer.value.wrap$ 47 | if ($wrap.offsetWidth + scrollLeft.value > $wrap.children[0].scrollWidth) { 48 | doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth) 49 | return 50 | } else if (scrollLeft.value < 0) { 51 | doScroll(0) 52 | return 53 | } 54 | const eventDelta = e.wheelDelta || -e.deltaY 55 | doScroll(scrollLeft.value - eventDelta / 4) 56 | } 57 | 58 | const moveToTarget = currentTag => { 59 | const $wrap = scrollContainer.value.wrap$ 60 | const tagList = tagsItem.value 61 | 62 | let firstTag = null 63 | let lastTag = null 64 | 65 | if (tagList.length > 0) { 66 | firstTag = tagList[0] 67 | lastTag = tagList[tagList.length - 1] 68 | } 69 | if (firstTag === currentTag) { 70 | doScroll(0) 71 | } else if (lastTag === currentTag) { 72 | doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth) 73 | } else { 74 | const el = currentTag.$el.nextElementSibling 75 | 76 | el.offsetLeft + el.offsetWidth > $wrap.offsetWidth 77 | ? doScroll(el.offsetLeft - el.offsetWidth) 78 | : doScroll(0) 79 | } 80 | } 81 | 82 | return { 83 | scrollContainer, 84 | handleScroll, 85 | moveToTarget, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/layout/components/Tagsbar/hooks/useTags.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ___====-_ _-====___ 3 | * _--^^^#####// \\#####^^^--_ 4 | * _-^##########// ( ) \\##########^-_ 5 | * -############// |\^^/| \\############- 6 | * _/############// (@::@) \############\_ 7 | * /#############(( \\// ))#############\ 8 | * -###############\\ (oo) //###############- 9 | * -#################\\ / VV \ //#################- 10 | * -###################\\/ \//###################- 11 | * _#/|##########/\######( /\ )######/\##########|\#_ 12 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 13 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 14 | * ` ` ` ` / | | | | \ ' ' ' ' 15 | * ( | | | | ) 16 | * __\ | | | | /__ 17 | * (vvv(VVV)(VVV)vvv) 18 | * 19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | * 21 | * 神兽保佑 永无BUG 22 | * 23 | * @Descripttion: 24 | * @version: 25 | * @Date: 2021-04-20 11:06:21 26 | * @LastEditors: huzhushan@126.com 27 | * @LastEditTime: 2022-09-27 18:28:33 28 | * @Author: huzhushan@126.com 29 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 30 | * @Github: https://github.com/huzhushan/vue3-element-admin 31 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 32 | */ 33 | import { storeToRefs } from 'pinia' 34 | import { useTags as useTagsbar } from '@/pinia/modules/tags' 35 | import { useScrollbar } from './useScrollbar' 36 | import { watch, computed, ref, nextTick, onBeforeMount } from 'vue' 37 | import { useRouter } from 'vue-router' 38 | 39 | export const isAffix = tag => { 40 | return !!tag.meta && !!tag.meta.affix 41 | } 42 | 43 | export const useTags = () => { 44 | const tagStore = useTagsbar() 45 | const { tagList } = storeToRefs(tagStore) 46 | const { addTag, delTag, saveActivePosition, updateTagList } = tagStore 47 | const router = useRouter() 48 | const route = router.currentRoute 49 | const routes = computed(() => router.getRoutes()) 50 | 51 | const tagsItem = ref([]) 52 | 53 | const setItemRef = (i, el) => { 54 | tagsItem.value[i] = el 55 | } 56 | 57 | const scrollbar = useScrollbar(tagsItem) 58 | 59 | watch( 60 | () => tagList.value.length, 61 | () => { 62 | tagsItem.value = [] 63 | } 64 | ) 65 | 66 | const filterAffixTags = routes => { 67 | return routes.filter(route => isAffix(route)) 68 | } 69 | 70 | const initTags = () => { 71 | const affixTags = filterAffixTags(routes.value) 72 | 73 | for (const tag of affixTags) { 74 | if (tag.name) { 75 | addTag(tag) 76 | } 77 | } 78 | // 不在路由中的所有标签,需要删除 79 | const noUseTags = tagList.value.filter(tag => 80 | routes.value.every(route => route.name !== tag.name) 81 | ) 82 | noUseTags.forEach(tag => { 83 | delTag(tag) 84 | }) 85 | } 86 | 87 | const addTagList = () => { 88 | const tag = route.value 89 | if (!!tag.name && tag.matched[0].components.default.name === 'layout') { 90 | addTag(tag) 91 | } 92 | } 93 | 94 | const saveTagPosition = tag => { 95 | const index = tagList.value.findIndex( 96 | item => item.fullPath === tag.fullPath 97 | ) 98 | 99 | saveActivePosition(Math.max(0, index)) 100 | } 101 | 102 | const moveToCurrentTag = () => { 103 | nextTick(() => { 104 | for (const tag of tagsItem.value) { 105 | if (!!tag && tag.to.path === route.value.path) { 106 | scrollbar.moveToTarget(tag) 107 | 108 | if (tag.to.fullPath !== route.value.fullPath) { 109 | updateTagList(route.value) 110 | } 111 | break 112 | } 113 | } 114 | }) 115 | } 116 | 117 | onBeforeMount(() => { 118 | initTags() 119 | addTagList() 120 | moveToCurrentTag() 121 | }) 122 | 123 | watch(route, (newRoute, oldRoute) => { 124 | saveTagPosition(oldRoute) // 保存标签的位置 125 | addTagList() 126 | moveToCurrentTag() 127 | }) 128 | 129 | return { 130 | tagList, 131 | setItemRef, 132 | isAffix, 133 | ...scrollbar, 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/layout/components/Tagsbar/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 74 | 75 | 108 | 109 | 203 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/Breadcrumbs.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 57 | 142 | 143 | 175 | 183 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/ChangeLang.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 65 | 79 | 80 | 95 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/Hamburger.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 38 | 58 | 68 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/LockModal.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 88 | 89 | 136 | 137 | 142 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/Userinfo.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 71 | 101 | 102 | 124 | -------------------------------------------------------------------------------- /src/layout/components/Topbar/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 65 | 115 | 151 | -------------------------------------------------------------------------------- /src/layout/hooks/useResizeHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ___====-_ _-====___ 3 | * _--^^^#####// \\#####^^^--_ 4 | * _-^##########// ( ) \\##########^-_ 5 | * -############// |\^^/| \\############- 6 | * _/############// (@::@) \############\_ 7 | * /#############(( \\// ))#############\ 8 | * -###############\\ (oo) //###############- 9 | * -#################\\ / VV \ //#################- 10 | * -###################\\/ \//###################- 11 | * _#/|##########/\######( /\ )######/\##########|\#_ 12 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 13 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 14 | * ` ` ` ` / | | | | \ ' ' ' ' 15 | * ( | | | | ) 16 | * __\ | | | | /__ 17 | * (vvv(VVV)(VVV)vvv) 18 | * 19 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | * 21 | * 神兽保佑 永无BUG 22 | * 23 | * @Descripttion: 24 | * @version: 25 | * @Date: 2021-04-20 11:06:21 26 | * @LastEditors: huzhushan@126.com 27 | * @LastEditTime: 2022-09-27 19:02:14 28 | * @Author: huzhushan@126.com 29 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 30 | * @Github: https://github.com/huzhushan/vue3-element-admin 31 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 32 | */ 33 | 34 | import { storeToRefs } from 'pinia' 35 | import { useApp } from '@/pinia/modules/app' 36 | import { onBeforeMount, onBeforeUnmount, computed } from 'vue' 37 | 38 | const WIDTH = 768 39 | export const useResizeHandler = () => { 40 | const appStore = useApp() 41 | const { sidebar } = storeToRefs(appStore) 42 | const { setDevice, setCollapse } = appStore 43 | const collapse = computed(() => sidebar.value.collapse) 44 | 45 | const isMobile = () => { 46 | return window.innerWidth < WIDTH 47 | } 48 | 49 | const resizeHandler = () => { 50 | if (isMobile()) { 51 | setDevice('mobile') 52 | setCollapse(1) 53 | } else { 54 | setDevice('desktop') 55 | setCollapse(collapse.value) 56 | } 57 | } 58 | 59 | onBeforeMount(() => { 60 | resizeHandler() 61 | window.addEventListener('resize', resizeHandler) 62 | }) 63 | 64 | onBeforeUnmount(() => { 65 | window.removeEventListener('resize', resizeHandler) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 56 | 105 | 106 | 139 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _oo0oo_ 3 | * o8888888o 4 | * 88" . "88 5 | * (| -_- |) 6 | * 0\ = /0 7 | * ___/`---'\___ 8 | * .' \\| |// '. 9 | * / \\||| : |||// \ 10 | * / _||||| -:- |||||- \ 11 | * | | \\\ - /// | | 12 | * | \_| ''\---/'' |_/ | 13 | * \ .-\__ '-' ___/-. / 14 | * ___'. .' /--.--\ `. .'___ 15 | * ."" '< `.___\_<|>_/___.' >' "". 16 | * | | : `- \`.;`\ _ /`;.`/ - ` : | | 17 | * \ \ `_. \_ __\ /__ _/ .-` / / 18 | * =====`-.____`.___ \_____/___.-`___.-'===== 19 | * `=---=' 20 | * 21 | * 22 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | * 24 | * 佛祖保佑 永不宕机 永无BUG 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-04-20 11:06:21 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2022-09-27 19:04:15 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | import { createApp } from 'vue' 38 | import App from './App.vue' 39 | 40 | const app = createApp(App) 41 | 42 | // 引入element-plus 43 | import ElementPlus from 'element-plus' 44 | import './assets/style/element-variables.scss' 45 | 46 | // 国际化 47 | import i18n from '@/i18n' 48 | 49 | // 全局注册element-plus/icons-vue 50 | import * as ICONS from '@element-plus/icons-vue' 51 | Object.entries(ICONS).forEach(([key, component]) => { 52 | // app.component(key === 'PieChart' ? 'PieChartIcon' : key, component) 53 | app.component(key, component) 54 | }) 55 | 56 | // 引入路由 57 | import router from './router' 58 | 59 | // 引入pinia 60 | import pinia from './pinia' 61 | 62 | // 权限控制 63 | import './permission' 64 | 65 | // 引入svg图标注册脚本 66 | import 'vite-plugin-svg-icons/register' 67 | 68 | // 注册全局组件 69 | import * as Components from './global-components' 70 | Object.entries(Components).forEach(([key, component]) => { 71 | app.component(key, component) 72 | }) 73 | 74 | // 注册自定义指令 75 | import * as Directives from '@/directive' 76 | Object.values(Directives).forEach(fn => fn(app)) 77 | 78 | // 错误日志 79 | import useErrorHandler from './error-log' 80 | useErrorHandler(app) 81 | 82 | app 83 | .use(i18n) 84 | .use(ElementPlus) 85 | .use(pinia) 86 | .use(router) 87 | .mount('#app') 88 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * ┏┓   ┏┓ 4 | * ┏┛┻━━━┛┻┓ 5 | * ┃       ┃ 6 | * ┃   ━   ┃ 7 | * ┃ >   < ┃ 8 | * ┃       ┃ 9 | * ┃... ⌒ ... ┃ 10 | * ┃       ┃ 11 | * ┗━┓   ┏━┛ 12 | * ┃   ┃ 13 | * ┃   ┃ 14 | * ┃   ┃ 15 | * ┃   ┃ 神兽保佑 16 | * ┃   ┃ 代码无bug 17 | * ┃   ┃ 18 | * ┃   ┗━━━┓ 19 | * ┃       ┣┓ 20 | * ┃       ┏┛ 21 | * ┗┓┓┏━┳┓┏┛ 22 | * ┃┫┫ ┃┫┫ 23 | * ┗┻┛ ┗┻┛ 24 | * 25 | * @Descripttion: 26 | * @version: 27 | * @Date: 2021-04-20 11:06:21 28 | * @LastEditors: huzhushan@126.com 29 | * @LastEditTime: 2022-09-27 16:35:06 30 | * @Author: huzhushan@126.com 31 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 32 | * @Github: https://github.com/huzhushan/vue3-element-admin 33 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 34 | */ 35 | 36 | import { ElLoading } from 'element-plus' 37 | import router from '@/router' 38 | import { TOKEN } from './pinia/modules/app' // TOKEN变量名 39 | import { nextTick } from 'vue' 40 | import { useApp } from './pinia/modules/app' 41 | import { useAccount } from './pinia/modules/account' 42 | import { useMenus } from './pinia/modules/menu' 43 | 44 | const getPageTitle = title => { 45 | const { title: appTitle } = useApp() 46 | if (title) { 47 | return `${title} - ${appTitle}` 48 | } 49 | return appTitle 50 | } 51 | 52 | // 白名单,里面是路由对象的name 53 | const WhiteList = ['login', 'lock'] 54 | 55 | let loadingInstance = null 56 | 57 | // vue-router4的路由守卫不再是通过next放行,而是通过return返回true或false或者一个路由地址 58 | router.beforeEach(async to => { 59 | loadingInstance = ElLoading.service({ 60 | lock: true, 61 | // text: '正在加载数据,请稍候~', 62 | background: 'rgba(0, 0, 0, 0.7)', 63 | }) 64 | 65 | if (WhiteList.includes(to.name)) { 66 | return true 67 | } 68 | if (!window.localStorage[TOKEN]) { 69 | return { 70 | name: 'login', 71 | query: { 72 | redirect: to.fullPath, // redirect是指登录之后可以跳回到redirect指定的页面 73 | }, 74 | replace: true, 75 | } 76 | } else { 77 | const { userinfo, getUserinfo } = useAccount() 78 | // 获取用户角色信息,根据角色判断权限 79 | if (!userinfo) { 80 | try { 81 | // 获取用户信息 82 | await getUserinfo() 83 | } catch (err) { 84 | loadingInstance.close() 85 | return false 86 | } 87 | 88 | return to.fullPath 89 | } 90 | 91 | // 生成菜单(如果你的项目有动态菜单,在此处会添加动态路由) 92 | const { menus, generateMenus } = useMenus() 93 | if (menus.length <= 0) { 94 | try { 95 | await generateMenus() 96 | return to.fullPath // 添加动态路由后,必须加这一句触发重定向,否则会404 97 | } catch (err) { 98 | loadingInstance.close() 99 | return false 100 | } 101 | } 102 | 103 | // 判断是否处于锁屏状态 104 | if (to.name !== 'lock') { 105 | const { authorization } = useApp() 106 | if (!!authorization && !!authorization.screenCode) { 107 | return { 108 | name: 'lock', 109 | query: { 110 | redirect: to.path, 111 | }, 112 | replace: true, 113 | } 114 | } 115 | } 116 | } 117 | }) 118 | 119 | router.afterEach(to => { 120 | loadingInstance.close() 121 | if (router.currentRoute.value.name === to.name) { 122 | nextTick(() => { 123 | document.title = getPageTitle(!!to.meta && to.meta.truetitle) 124 | }) 125 | } 126 | }) 127 | -------------------------------------------------------------------------------- /src/pinia/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 江城子 . 程序员之歌 3 | * 4 | * 十年生死两茫茫,写程序,到天亮。 5 | * 千行代码,Bug何处藏。 6 | * 纵使上线又怎样,朝令改,夕断肠。 7 | * 8 | * 领导每天新想法,天天改,日日忙。 9 | * 相顾无言,惟有泪千行。 10 | * 每晚灯火阑珊处,夜难寐,加班狂。 11 | * 12 | * 13 | * @Descripttion: 14 | * @version: 15 | * @Date: 2021-04-20 11:06:21 16 | * @LastEditors: huzhushan@126.com 17 | * @LastEditTime: 2022-09-27 14:52:09 18 | * @Author: huzhushan@126.com 19 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 20 | * @Github: https://github.com/huzhushan/vue3-element-admin 21 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 22 | */ 23 | 24 | import { createPinia } from 'pinia' 25 | 26 | const pinia = createPinia() 27 | 28 | export default pinia 29 | -------------------------------------------------------------------------------- /src/pinia/modules/account.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 14:57:06 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import { defineStore } from 'pinia' 13 | import { GetUserinfo } from '@/api/login' 14 | 15 | export const useAccount = defineStore('account', { 16 | state: () => ({ 17 | userinfo: null, 18 | permissionList: [], 19 | }), 20 | actions: { 21 | // 清除用户信息 22 | clearUserinfo() { 23 | this.userinfo = null 24 | }, 25 | // 获取用户信息 26 | async getUserinfo() { 27 | const { code, data } = await GetUserinfo() 28 | if (+code === 200) { 29 | this.userinfo = data 30 | return Promise.resolve(data) 31 | } 32 | }, 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /src/pinia/modules/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 15:42:35 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | 13 | import { defineStore } from 'pinia' 14 | import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法 15 | import { AesEncryption } from '@/utils/encrypt' 16 | import { toRaw } from 'vue' 17 | import { useAccount } from './account' 18 | import { useTags } from './tags' 19 | import { useMenus } from './menu' 20 | export const TOKEN = 'VEA-TOKEN' 21 | const COLLAPSE = 'VEA-COLLAPSE' 22 | 23 | export const useApp = defineStore('app', { 24 | state: () => ({ 25 | title: 'Vue3 Element Admin', 26 | authorization: getItem(TOKEN), 27 | sidebar: { 28 | collapse: getItem(COLLAPSE), 29 | }, 30 | device: 'desktop', 31 | matchedRoutes: [], 32 | }), 33 | actions: { 34 | setCollapse(data) { 35 | this.sidebar.collapse = data 36 | // 保存到localStorage 37 | setItem(COLLAPSE, data) 38 | }, 39 | clearCollapse() { 40 | this.sidebar.collapse = '' 41 | removeItem(COLLAPSE) 42 | }, 43 | setDevice(device) { 44 | this.device = device 45 | }, 46 | setMatchedRoutes(payload) { 47 | this.matchedRoutes = payload 48 | }, 49 | setToken(data) { 50 | this.authorization = data 51 | // 保存到localStorage 52 | setItem(TOKEN, data) 53 | }, 54 | initToken(data) { 55 | this.clearToken() 56 | this.setToken(data) 57 | }, 58 | clearToken() { 59 | // 清除token 60 | this.authorization = '' 61 | removeItem(TOKEN) 62 | // 清除用户信息 63 | useAccount().clearUserinfo() 64 | // 清除标签栏 65 | useTags().clearAllTags() 66 | // 清空menus 67 | useMenus().setMenus([]) 68 | }, 69 | setScreenCode(password) { 70 | const authorization = toRaw(this.authorization) 71 | 72 | if (!password) { 73 | try { 74 | delete authorization.screenCode 75 | } catch (err) { 76 | console.log(err) 77 | } 78 | 79 | this.authorization = authorization 80 | // 保存到localStorage 81 | setItem(TOKEN, authorization) 82 | 83 | return 84 | } 85 | 86 | // 对密码加密 87 | const screenCode = new AesEncryption().encryptByAES(password) 88 | 89 | const res = { 90 | ...authorization, 91 | screenCode, 92 | } 93 | this.authorization = res 94 | // 保存到localStorage 95 | setItem(TOKEN, res) 96 | }, 97 | }, 98 | }) 99 | -------------------------------------------------------------------------------- /src/pinia/modules/errorLog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-21 09:18:32 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 15:45:36 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import { defineStore } from 'pinia' 13 | 14 | export const useErrorlog = defineStore('errorLog', { 15 | state: () => ({ 16 | logs: [], 17 | }), 18 | actions: { 19 | addErrorLog(log) { 20 | // 可以根据需要将错误上报给服务器 21 | // ....code....... 22 | 23 | this.logs.push(log) 24 | }, 25 | clearErrorLog() { 26 | this.logs.splice(0) 27 | }, 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /src/pinia/modules/layoutSettings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _______________#########_______________________ 3 | * ______________############_____________________ 4 | * ______________#############____________________ 5 | * _____________##__###########___________________ 6 | * ____________###__######_#####__________________ 7 | * ____________###_#######___####_________________ 8 | * ___________###__##########_####________________ 9 | * __________####__###########_####_______________ 10 | * ________#####___###########__#####_____________ 11 | * _______######___###_########___#####___________ 12 | * _______#####___###___########___######_________ 13 | * ______######___###__###########___######_______ 14 | * _____######___####_##############__######______ 15 | * ____#######__#####################_#######_____ 16 | * ____#######__##############################____ 17 | * ___#######__######_#################_#######___ 18 | * ___#######__######_######_#########___######___ 19 | * ___#######____##__######___######_____######___ 20 | * ___#######________######____#####_____#####____ 21 | * ____######________#####_____#####_____####_____ 22 | * _____#####________####______#####_____###______ 23 | * ______#####______;###________###______#________ 24 | * ________##_______####________####______________ 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-07-23 16:10:49 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2022-09-27 15:47:50 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | import { defineStore } from 'pinia' 37 | import { getItem, setItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法 38 | import defaultSettings from '@/default-settings' 39 | 40 | export const useLayoutsettings = defineStore('layoutSettings', { 41 | state: () => getItem('defaultSettings') || defaultSettings, 42 | actions: { 43 | saveSettings(data) { 44 | Object.entries(data).forEach(([key, value]) => { 45 | this[key] = value 46 | }) 47 | setItem('defaultSettings', data) 48 | }, 49 | }, 50 | }) 51 | -------------------------------------------------------------------------------- /src/pinia/modules/menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 16:41:46 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import { defineStore } from 'pinia' 13 | import { fixedRoutes, asyncRoutes } from '@/router' 14 | import { GetMenus } from '@/api/menu' 15 | import router from '@/router' 16 | import { ref } from 'vue' 17 | 18 | export const useMenus = defineStore('menu', () => { 19 | const generateUrl = (path, parentPath) => { 20 | return path.startsWith('/') 21 | ? path 22 | : path 23 | ? `${parentPath}/${path}` 24 | : parentPath 25 | } 26 | 27 | const getFilterRoutes = (targetRoutes, ajaxRoutes) => { 28 | const filterRoutes = [] 29 | 30 | ajaxRoutes.forEach(item => { 31 | const target = targetRoutes.find(target => target.name === item.name) 32 | 33 | if (target) { 34 | const { children: targetChildren, ...rest } = target 35 | const route = { 36 | ...rest, 37 | } 38 | 39 | if (item.children) { 40 | route.children = getFilterRoutes(targetChildren, item.children) 41 | } 42 | 43 | filterRoutes.push(route) 44 | } 45 | }) 46 | 47 | return filterRoutes 48 | } 49 | 50 | const getFilterMenus = (arr, parentPath = '') => { 51 | const menus = [] 52 | 53 | arr.forEach(item => { 54 | if (!item.hidden) { 55 | const menu = { 56 | url: generateUrl(item.path, parentPath), 57 | title: item.meta.title, 58 | icon: item.icon, 59 | } 60 | if (item.children) { 61 | if (item.children.filter(child => !child.hidden).length <= 0) { 62 | delete menu.children 63 | } else { 64 | menu.children = getFilterMenus(item.children, menu.url) 65 | } 66 | } 67 | menus.push(menu) 68 | } 69 | }) 70 | 71 | return menus 72 | } 73 | 74 | // 扁平化树形数据 75 | const flattenTree = tree => { 76 | let result = [] 77 | tree.forEach(node => { 78 | result.push(node) // 将当前节点添加到结果数组中 79 | if (node.children && node.children.length) { 80 | // 如果有子节点,将子节点添加到结果数组中 81 | result = result.concat(flattenTree(node.children)) 82 | delete node.children 83 | } 84 | }) 85 | return result 86 | } 87 | 88 | const optimizeRoutes = (arr, parentPath = '', parentName = '') => { 89 | return arr.map(obj => { 90 | const item = { 91 | ...obj, 92 | } 93 | item.path = item.path.startsWith('/') 94 | ? item.path 95 | : parentPath 96 | ? `${parentPath}/${item.path}` 97 | : item.path 98 | if (parentName) { 99 | item.meta.parent = parentName 100 | } 101 | if (item.children) { 102 | item.children = optimizeRoutes(item.children, item.path, item.name) 103 | } 104 | return item 105 | }) 106 | } 107 | 108 | const formatRoutes = routes => { 109 | return routes.map(route => ({ 110 | ...route, 111 | children: flattenTree(optimizeRoutes(route.children || [])), 112 | })) 113 | } 114 | 115 | const menus = ref([]) 116 | const setMenus = data => { 117 | menus.value = data 118 | } 119 | const generateMenus = async () => { 120 | // // 方式一:只有固定菜单 121 | // const menus = getFilterMenus(fixedRoutes) 122 | // commit('SET_MENUS', menus) 123 | 124 | // 方式二:有动态菜单 125 | // 从后台获取菜单 126 | const { code, data } = await GetMenus() 127 | 128 | if (+code === 200) { 129 | // 添加路由之前先删除所有动态路由 130 | asyncRoutes.forEach(item => { 131 | router.removeRoute(item.name) 132 | }) 133 | // 过滤出需要添加的动态路由 134 | const filterRoutes = getFilterRoutes(asyncRoutes, data) 135 | 136 | // 生成菜单 137 | const menus = getFilterMenus([...fixedRoutes, ...filterRoutes]) 138 | setMenus(menus) 139 | 140 | // 添加动态路由,由于只做了二级路由,所以需要将三级之后的children提到二级 141 | const arr = formatRoutes(filterRoutes) 142 | arr.forEach(route => router.addRoute(route)) 143 | } 144 | } 145 | return { 146 | menus, 147 | setMenus, 148 | generateMenus, 149 | } 150 | }) 151 | -------------------------------------------------------------------------------- /src/pinia/modules/tags.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 16:49:31 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | import { defineStore } from 'pinia' 13 | import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法 14 | const TAGLIST = 'VEA-TAGLIST' 15 | 16 | export const useTags = defineStore('tags', { 17 | state: () => ({ 18 | tagList: getItem(TAGLIST) || [], 19 | cacheList: [], 20 | activePosition: -1, 21 | }), 22 | actions: { 23 | saveActivePosition(index) { 24 | this.activePosition = index 25 | }, 26 | addTag({ path, fullPath, name, meta, params, query }) { 27 | if (this.tagList.some(v => v.path === path)) return false 28 | // 添加tagList 29 | const target = Object.assign( 30 | {}, 31 | { path, fullPath, name, meta, params, query }, 32 | { 33 | title: meta.title || '未命名', 34 | fullPath: fullPath || path, 35 | } 36 | ) 37 | if (this.activePosition === -1) { 38 | if (name === 'home') { 39 | this.tagList.unshift(target) 40 | } else { 41 | this.tagList.push(target) 42 | } 43 | } else { 44 | this.tagList.splice(this.activePosition + 1, 0, target) 45 | } 46 | // 保存到localStorage 47 | setItem(TAGLIST, this.tagList) 48 | 49 | // 添加cacheList 50 | if (this.cacheList.includes(name)) return 51 | if (!meta.noCache) { 52 | this.cacheList.push(name) 53 | } 54 | }, 55 | deTagList(tag) { 56 | // 删除tagList 57 | this.tagList = this.tagList.filter(v => v.path !== tag.path) 58 | // 保存到localStorage 59 | setItem(TAGLIST, this.tagList) 60 | }, 61 | deCacheList(tag) { 62 | // 删除cacheList 63 | this.cacheList = this.cacheList.filter(v => v !== tag.name) 64 | }, 65 | delTag(tag) { 66 | // 删除tagList 67 | this.deTagList(tag) 68 | 69 | // 删除cacheList 70 | this.deCacheList(tag) 71 | }, 72 | delOtherTags(tag) { 73 | this.tagList = this.tagList.filter( 74 | v => !!v.meta.affix || v.path === tag.path 75 | ) 76 | // 保存到localStorage 77 | setItem(TAGLIST, this.tagList) 78 | 79 | this.cacheList = this.cacheList.filter(v => v === tag.name) 80 | }, 81 | delSomeTags(tags) { 82 | this.tagList = this.tagList.filter( 83 | v => !!v.meta.affix || tags.every(tag => tag.path !== v.path) 84 | ) 85 | // 保存到localStorage 86 | setItem(TAGLIST, this.tagList) 87 | 88 | this.cacheList = this.cacheList.filter(v => 89 | tags.every(tag => tag.name !== v) 90 | ) 91 | }, 92 | delAllTags() { 93 | this.tagList = this.tagList.filter(v => !!v.meta.affix) 94 | // 保存到localStorage 95 | removeItem(TAGLIST) 96 | this.cacheList = [] 97 | }, 98 | updateTagList(tag) { 99 | const index = this.tagList.findIndex(v => v.path === tag.path) 100 | if (index > -1) { 101 | this.tagList[index] = Object.assign({}, this.tagList[index], tag) 102 | // 保存到localStorage 103 | setItem(TAGLIST, this.tagList) 104 | } 105 | }, 106 | clearAllTags() { 107 | this.cacheList = [] 108 | this.tagList = [] 109 | // 保存到localStorage 110 | removeItem(TAGLIST) 111 | }, 112 | }, 113 | }) 114 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐ 3 | * │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐ ┌┐ ┌┐ 4 | * └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘ └┘ └┘ 5 | * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐ 6 | * │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │ 7 | * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤ 8 | * │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │ │ 9 | * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │ 10 | * │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter │ │ 4 │ 5 │ 6 │ │ 11 | * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ ┌───┐ ├───┼───┼───┼───┤ 12 | * │ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│ Shift │ │ ↑ │ │ 1 │ 2 │ 3 │ │ 13 | * ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││ 14 | * │ Ctrl│ │Alt │ Space │ Alt│ │ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ . │←─┘│ 15 | * └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘ 16 | * 17 | * @Descripttion: 18 | * @version: 19 | * @Date: 2021-04-20 11:06:21 20 | * @LastEditors: huzhushan@126.com 21 | * @LastEditTime: 2021-07-26 16:16:36 22 | * @Author: huzhushan@126.com 23 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 24 | * @Github: https://github.com/huzhushan/vue3-element-admin 25 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 26 | */ 27 | 28 | import { createRouter, createWebHashHistory } from 'vue-router' 29 | 30 | import redirect from './modules/redirect' 31 | import error from './modules/error' 32 | import login from './modules/login' 33 | import lock from './modules/lock' 34 | import home from './modules/home' 35 | import test from './modules/test' 36 | 37 | /* 菜单栏的路由 */ 38 | // 固定菜单 39 | export const fixedRoutes = [...home] 40 | // 动态菜单 41 | export const asyncRoutes = [...test] 42 | 43 | const router = createRouter({ 44 | history: createWebHashHistory(), 45 | routes: [ 46 | { 47 | path: '/', 48 | redirect: '/home', 49 | }, 50 | ...redirect, // 统一的重定向配置 51 | ...login, 52 | ...lock, 53 | ...fixedRoutes, 54 | ...error, 55 | ], 56 | scrollBehavior(to, from, savedPosition) { 57 | if (savedPosition) { 58 | return savedPosition 59 | } else { 60 | return { left: 0, top: 0 } 61 | } 62 | }, 63 | }) 64 | 65 | export default router 66 | -------------------------------------------------------------------------------- /src/router/modules/error.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 18:14:03 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | 13 | import { useAccount } from '@/pinia/modules/account' 14 | 15 | const checkUserinfo = (code, fullPath) => { 16 | const { userinfo } = useAccount() 17 | if (userinfo) { 18 | return `/error/${code === '404' ? fullPath : code}` 19 | } 20 | return true 21 | } 22 | 23 | const Layout = () => import('@/layout/index.vue') 24 | const Error = () => import('@/views/error/index.vue') 25 | 26 | export default [ 27 | { 28 | path: '/error', 29 | component: Layout, 30 | children: [ 31 | { 32 | path: '403', 33 | name: 'error-forbidden', 34 | component: Error, 35 | meta: { title: '403' }, 36 | props: { 37 | error: '403', 38 | }, 39 | }, 40 | { 41 | path: '500', 42 | name: 'error-server-error', 43 | component: Error, 44 | meta: { title: '500' }, 45 | props: { 46 | error: '500', 47 | }, 48 | }, 49 | { 50 | path: ':pathMatch(.*)', 51 | name: 'error-not-found', 52 | component: Error, 53 | meta: { title: '404' }, 54 | props: { 55 | error: '404', 56 | }, 57 | }, 58 | ], 59 | }, 60 | { 61 | path: '/403', 62 | name: 'forbidden', 63 | component: Error, 64 | props: { 65 | error: '403', 66 | }, 67 | beforeEnter() { 68 | return checkUserinfo('403') 69 | }, 70 | }, 71 | { 72 | path: '/500', 73 | name: 'server-error', 74 | component: Error, 75 | props: { 76 | error: '500', 77 | }, 78 | beforeEnter() { 79 | return checkUserinfo('500') 80 | }, 81 | }, 82 | { 83 | path: '/:pathMatch(.*)', 84 | name: 'not-found', 85 | component: Error, 86 | props: { 87 | error: '404', 88 | }, 89 | beforeEnter(to) { 90 | return checkUserinfo('404', to.fullPath.replace('/', '')) 91 | }, 92 | }, 93 | ] 94 | -------------------------------------------------------------------------------- /src/router/modules/home.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-24 19:27:21 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | // home.js 13 | const Layout = () => import('@/layout/index.vue') 14 | const Home = () => import('@/views/home/index.vue') 15 | 16 | export default [ 17 | { 18 | path: '/home', 19 | component: Layout, 20 | name: 'Dashboard', 21 | meta: { 22 | title: 'menu.dashboard', 23 | }, 24 | icon: 'icon-home', 25 | children: [ 26 | { 27 | path: '', 28 | name: 'home', 29 | component: Home, 30 | meta: { 31 | title: 'menu.homepage', 32 | affix: true, 33 | }, 34 | }, 35 | ], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/router/modules/lock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _______________#########_______________________ 3 | * ______________############_____________________ 4 | * ______________#############____________________ 5 | * _____________##__###########___________________ 6 | * ____________###__######_#####__________________ 7 | * ____________###_#######___####_________________ 8 | * ___________###__##########_####________________ 9 | * __________####__###########_####_______________ 10 | * ________#####___###########__#####_____________ 11 | * _______######___###_########___#####___________ 12 | * _______#####___###___########___######_________ 13 | * ______######___###__###########___######_______ 14 | * _____######___####_##############__######______ 15 | * ____#######__#####################_#######_____ 16 | * ____#######__##############################____ 17 | * ___#######__######_#################_#######___ 18 | * ___#######__######_######_#########___######___ 19 | * ___#######____##__######___######_____######___ 20 | * ___#######________######____#####_____#####____ 21 | * ____######________#####_____#####_____####_____ 22 | * _____#####________####______#####_____###______ 23 | * ______#####______;###________###______#________ 24 | * ________##_______####________####______________ 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-04-23 15:25:15 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2021-04-23 15:29:50 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | const Lock = () => import('@/views/lock/index.vue') 38 | 39 | export default [ 40 | { 41 | path: '/lock', 42 | name: 'lock', 43 | component: Lock, 44 | meta: { 45 | title: '屏幕已锁定', 46 | }, 47 | }, 48 | ] 49 | -------------------------------------------------------------------------------- /src/router/modules/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2021-04-21 09:34:37 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | // login.js 13 | const Login = () => import('@/views/login/index.vue') 14 | 15 | export default [ 16 | { 17 | path: '/login', 18 | name: 'login', 19 | component: Login, 20 | }, 21 | ] 22 | -------------------------------------------------------------------------------- /src/router/modules/redirect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 11:06:21 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2021-04-21 09:34:40 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | const Layout = () => import('@/layout/index.vue') 13 | const Redirect = () => import('@/views/redirect/index.vue') 14 | 15 | export default [ 16 | { 17 | path: '/redirect/:path(.*)', 18 | component: Layout, 19 | children: [ 20 | { 21 | path: '', 22 | component: Redirect, 23 | }, 24 | ], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /src/router/modules/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-21 09:18:32 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2022-09-27 18:51:35 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | const Layout = () => import('@/layout/index.vue') 13 | const List = () => import('@/views/test/index.vue') 14 | const Add = () => import('@/views/test/Add.vue') 15 | const Edit = () => import('@/views/test/Edit.vue') 16 | const Auth = () => import('@/views/test/Auth.vue') 17 | const NoAuth = () => import('@/views/test/NoAuth.vue') 18 | const Nest = () => import('@/views/test/Nest.vue') 19 | const NestPage1 = () => import('@/views/test/nest/Page1.vue') 20 | const NestPage2 = () => import('@/views/test/nest/Page2.vue') 21 | const Iscache = () => import('@/views/test/Cache.vue') 22 | const Nocache = () => import('@/views/test/Nocache.vue') 23 | const ErrorLog = () => import('@/views/test/error-log/index.vue') 24 | 25 | export default [ 26 | { 27 | path: '/test', 28 | component: Layout, 29 | name: 'test', 30 | meta: { 31 | title: 'menu.test', 32 | }, 33 | icon: 'Location', 34 | children: [ 35 | { 36 | path: '', 37 | name: 'testList', 38 | component: List, 39 | meta: { 40 | title: 'menu.testList', 41 | }, 42 | children: [ 43 | { 44 | path: 'add', 45 | name: 'testAdd', 46 | component: Add, 47 | meta: { 48 | title: 'menu.testAdd', 49 | }, 50 | hidden: true, // 不在菜单中显示 51 | }, 52 | { 53 | path: 'edit/:id', 54 | name: 'testEdit', 55 | component: Edit, 56 | meta: { 57 | title: 'menu.testEdit', 58 | }, 59 | hidden: true, // 不在菜单中显示 60 | }, 61 | ], 62 | }, 63 | 64 | // { 65 | // path: 'auth', 66 | // name: 'testAuth', 67 | // component: Auth, 68 | // meta: { 69 | // title: 'menu.testAuth', 70 | // }, 71 | // }, 72 | // { 73 | // path: 'noauth', 74 | // name: 'testNoAuth', 75 | // component: NoAuth, 76 | // meta: { 77 | // title: 'menu.testNoAuth', 78 | // }, 79 | // hidden: true, 80 | // }, 81 | { 82 | path: 'cache', 83 | name: 'test-cache', 84 | component: Iscache, 85 | meta: { 86 | title: 'menu.test-cache', 87 | }, 88 | }, 89 | { 90 | path: 'nocache', 91 | name: 'test-no-cache', 92 | component: Nocache, 93 | meta: { 94 | title: 'menu.test-no-cache', 95 | noCache: true, // 不缓存页面 96 | }, 97 | }, 98 | { 99 | path: 'nest', 100 | name: 'nest', 101 | component: Nest, 102 | redirect: '/test/nest/page1', 103 | meta: { 104 | title: 'menu.nest', 105 | }, 106 | children: [ 107 | { 108 | path: 'page1', 109 | name: 'nestPage1', 110 | component: NestPage1, 111 | meta: { 112 | title: 'menu.nestPage1', 113 | }, 114 | }, 115 | { 116 | path: 'page2', 117 | name: 'nestPage2', 118 | component: NestPage2, 119 | meta: { 120 | title: 'menu.nestPage2', 121 | }, 122 | }, 123 | ], 124 | }, 125 | { 126 | path: 'error-log', 127 | name: 'test-error-log', 128 | component: ErrorLog, 129 | meta: { 130 | title: 'menu.test-error-log', 131 | }, 132 | }, 133 | ], 134 | }, 135 | ] 136 | -------------------------------------------------------------------------------- /src/utils/encrypt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | *   ┏┓   ┏┓+ + 4 | *  ┏┛┻━━━┛┻┓ + + 5 | *  ┃       ┃ 6 | *  ┃   ━   ┃ ++ + + + 7 | * ████━████ ┃+ 8 | *  ┃       ┃ + 9 | *  ┃   ┻   ┃ 10 | *  ┃       ┃ + + 11 | *  ┗━┓   ┏━┛ 12 | *    ┃   ┃ 13 | *    ┃   ┃ + + + + 14 | *    ┃   ┃ 15 | *    ┃   ┃ + 神兽保佑 16 | *    ┃   ┃ 代码无bug 17 | *    ┃   ┃  + 18 | *    ┃    ┗━━━┓ + + 19 | *    ┃        ┣┓ 20 | *    ┃        ┏┛ 21 | *    ┗┓┓┏━┳┓┏┛ + + + + 22 | *     ┃┫┫ ┃┫┫ 23 | *     ┗┻┛ ┗┻┛+ + + + 24 | * 25 | * 26 | * @Descripttion: 27 | * @version: 28 | * @Date: 2021-04-23 12:35:44 29 | * @LastEditors: huzhushan@126.com 30 | * @LastEditTime: 2021-04-23 13:59:04 31 | * @Author: huzhushan@126.com 32 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 33 | * @Github: https://github.com/huzhushan/vue3-element-admin 34 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 35 | */ 36 | 37 | import { encrypt, decrypt } from 'crypto-js/aes' 38 | import { parse } from 'crypto-js/enc-utf8' 39 | import pkcs7 from 'crypto-js/pad-pkcs7' 40 | import ECB from 'crypto-js/mode-ecb' 41 | import md5 from 'crypto-js/md5' 42 | import UTF8 from 'crypto-js/enc-utf8' 43 | import Base64 from 'crypto-js/enc-base64' 44 | 45 | // 16位密钥 46 | export const KEY_IV = { 47 | key: '_11111000001111@', 48 | iv: '@11111000001111_', 49 | } 50 | 51 | // AES加密和解密 52 | export class AesEncryption { 53 | constructor(opt = KEY_IV) { 54 | const { key, iv } = opt 55 | if (key) { 56 | this.key = parse(key) 57 | } 58 | if (iv) { 59 | this.iv = parse(iv) 60 | } 61 | } 62 | 63 | get getOptions() { 64 | return { 65 | mode: ECB, 66 | padding: pkcs7, 67 | iv: this.iv, 68 | } 69 | } 70 | 71 | encryptByAES(cipherText) { 72 | return encrypt(cipherText, this.key, this.getOptions).toString() 73 | } 74 | 75 | decryptByAES(cipherText) { 76 | return decrypt(cipherText, this.key, this.getOptions).toString(UTF8) 77 | } 78 | } 79 | 80 | // Base64加密 81 | export function encryptByBase64(cipherText) { 82 | return Base64.stringify(UTF8.parse(cipherText)) 83 | } 84 | 85 | // Base64解密 86 | export function decryptByBase64(cipherText) { 87 | return Base64.parse(cipherText).toString(UTF8) 88 | } 89 | 90 | // MD5加密,不可逆 91 | export function encryptByMd5(password) { 92 | return md5(password).toString() 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/open-window.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Sting} url 3 | * @param {Sting} title 4 | * @param {Number} w 5 | * @param {Number} h 6 | */ 7 | export default function openWindow(url, title, w, h) { 8 | // Fixes dual-screen position Most browsers Firefox 9 | const dualScreenLeft = 10 | window.screenLeft !== undefined ? window.screenLeft : screen.left 11 | const dualScreenTop = 12 | window.screenTop !== undefined ? window.screenTop : screen.top 13 | 14 | const width = window.innerWidth 15 | ? window.innerWidth 16 | : document.documentElement.clientWidth 17 | ? document.documentElement.clientWidth 18 | : screen.width 19 | const height = window.innerHeight 20 | ? window.innerHeight 21 | : document.documentElement.clientHeight 22 | ? document.documentElement.clientHeight 23 | : screen.height 24 | 25 | const left = width / 2 - w / 2 + dualScreenLeft 26 | const top = height / 2 - h / 2 + dualScreenTop 27 | const newWindow = window.open( 28 | url, 29 | title, 30 | 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + 31 | w + 32 | ', height=' + 33 | h + 34 | ', top=' + 35 | top + 36 | ', left=' + 37 | left 38 | ) 39 | 40 | // Puts focus on the newWindow 41 | if (window.focus) { 42 | newWindow.focus() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | /* 2 | * .::::. 3 | * .::::::::. 4 | * ::::::::::: 5 | * ..:::::::::::' 6 | * '::::::::::::' 7 | * .:::::::::: 8 | * '::::::::::::::.. 9 | * ..::::::::::::. 10 | * ``:::::::::::::::: 11 | * ::::``:::::::::' .:::. 12 | * ::::' ':::::' .::::::::. 13 | * .::::' :::: .:::::::'::::. 14 | * .:::' ::::: .:::::::::' ':::::. 15 | * .::' :::::.:::::::::' ':::::. 16 | * .::' ::::::::::::::' ``::::. 17 | * ...::: ::::::::::::' ``::. 18 | * ````':. ':::::::::' ::::.. 19 | * '.:::::' ':'````.. 20 | * 21 | * @Descripttion: 22 | * @version: 23 | * @Date: 2021-04-20 11:06:21 24 | * @LastEditors: huzhushan@126.com 25 | * @LastEditTime: 2022-09-27 18:17:20 26 | * @Author: huzhushan@126.com 27 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 28 | * @Github: https://github.com/huzhushan/vue3-element-admin 29 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 30 | */ 31 | 32 | import axios from 'axios' 33 | import { ElMessage } from 'element-plus' 34 | import router from '@/router' 35 | import { useApp } from '@/pinia/modules/app' 36 | 37 | const service = axios.create({ 38 | baseURL: '/', 39 | timeout: 10000, 40 | withCredentials: true, 41 | }) 42 | 43 | // 拦截请求 44 | service.interceptors.request.use( 45 | config => { 46 | const { authorization } = useApp() 47 | if (authorization) { 48 | config.headers.Authorization = `Bearer ${authorization.token}` 49 | } 50 | return config 51 | }, 52 | error => { 53 | // console.log(error); 54 | return Promise.reject(error) 55 | } 56 | ) 57 | 58 | // 拦截响应 59 | service.interceptors.response.use( 60 | // 响应成功进入第1个函数,该函数的参数是响应对象 61 | response => { 62 | return response.data 63 | }, 64 | // 响应失败进入第2个函数,该函数的参数是错误对象 65 | async error => { 66 | // 如果响应码是 401 ,则请求获取新的 token 67 | // 响应拦截器中的 error 就是那个响应的错误对象 68 | if (error.response && error.response.status === 401) { 69 | // 校验是否有 refresh_token 70 | const { authorization, clearToken, setToken } = useApp() 71 | if (!authorization || !authorization.refresh_token) { 72 | if (router.currentRoute.value.name === 'login') { 73 | return Promise.reject(error) 74 | } 75 | const redirect = encodeURIComponent(window.location.href) 76 | router.push(`/login?redirect=${redirect}`) 77 | // 清除token 78 | clearToken() 79 | setTimeout(() => { 80 | ElMessage.closeAll() 81 | try { 82 | ElMessage.error(error.response.data.msg) 83 | } catch (err) { 84 | ElMessage.error(error.message) 85 | } 86 | }) 87 | // 代码不要往后执行了 88 | return Promise.reject(error) 89 | } 90 | // 如果有refresh_token,则请求获取新的 token 91 | try { 92 | const res = await axios({ 93 | method: 'PUT', 94 | url: '/api/authorizations', 95 | timeout: 10000, 96 | headers: { 97 | Authorization: `Bearer ${authorization.refresh_token}`, 98 | }, 99 | }) 100 | // 如果获取成功,则把新的 token 更新到容器中 101 | // console.log('刷新 token 成功', res) 102 | setToken({ 103 | token: res.data.data.token, // 最新获取的可用 token 104 | refresh_token: authorization.refresh_token, // 还是原来的 refresh_token 105 | }) 106 | // 把之前失败的用户请求继续发出去 107 | // config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有 108 | // return 把 request 的请求结果继续返回给发请求的具体位置 109 | return service(error.config) 110 | } catch (err) { 111 | // 如果获取失败,直接跳转 登录页 112 | // console.log('请求刷新 token 失败', err) 113 | const redirect = encodeURIComponent(window.location.href) 114 | router.push(`/login?redirect=${redirect}`) 115 | // 清除token 116 | clearToken() 117 | return Promise.reject(error) 118 | } 119 | } 120 | 121 | // console.dir(error) // 可在此进行错误上报 122 | ElMessage.closeAll() 123 | try { 124 | ElMessage.error(error.response.data.msg) 125 | } catch (err) { 126 | ElMessage.error(error.message) 127 | } 128 | 129 | return Promise.reject(error) 130 | } 131 | ) 132 | 133 | export default service 134 | -------------------------------------------------------------------------------- /src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return (c / 2) * t * t + b 5 | } 6 | t-- 7 | return (-c / 2) * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return ( 13 | window.requestAnimationFrame || 14 | window.webkitRequestAnimationFrame || 15 | window.mozRequestAnimationFrame || 16 | function(callback) { 17 | window.setTimeout(callback, 1000 / 60) 18 | } 19 | ) 20 | })() 21 | 22 | /** 23 | * Because it's so fucking difficult to detect the scrolling element, just move them all 24 | * @param {number} amount 25 | */ 26 | function move(amount) { 27 | document.documentElement.scrollTop = amount 28 | document.body.parentNode.scrollTop = amount 29 | document.body.scrollTop = amount 30 | } 31 | 32 | function position() { 33 | return ( 34 | document.documentElement.scrollTop || 35 | document.body.parentNode.scrollTop || 36 | document.body.scrollTop 37 | ) 38 | } 39 | 40 | /** 41 | * @param {number} to 42 | * @param {number} duration 43 | * @param {Function} callback 44 | */ 45 | export function scrollTo(to, duration, callback) { 46 | const start = position() 47 | const change = to - start 48 | const increment = 20 49 | let currentTime = 0 50 | duration = typeof duration === 'undefined' ? 500 : duration 51 | var animateScroll = function() { 52 | // increment the time 53 | currentTime += increment 54 | // find the value with the quadratic in-out easing function 55 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 56 | // move the document.body 57 | move(val) 58 | // do the animation unless its over 59 | if (currentTime < duration) { 60 | requestAnimFrame(animateScroll) 61 | } else { 62 | if (callback && typeof callback === 'function') { 63 | // the animation is done so lets callback 64 | callback() 65 | } 66 | } 67 | } 68 | animateScroll() 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ┌─────────────────────────────────────────────────────────────┐ 3 | * │┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐│ 4 | * ││Esc│!1 │@2 │#3 │$4 │%5 │^6 │&7 │*8 │(9 │)0 │_- │+= │|\ │`~ ││ 5 | * │├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴───┤│ 6 | * ││ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{[ │}] │ BS ││ 7 | * │├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤│ 8 | * ││ Ctrl │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter ││ 9 | * │├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────┬───┤│ 10 | * ││ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│Shift │Fn ││ 11 | * │└─────┬──┴┬──┴──┬┴───┴───┴───┴───┴───┴──┬┴───┴┬──┴┬─────┴───┘│ 12 | * │ │Fn │ Alt │ Space │ Alt │Win│ HHKB │ 13 | * │ └───┴─────┴───────────────────────┴─────┴───┘ │ 14 | * └─────────────────────────────────────────────────────────────┘ 15 | * 16 | * @Descripttion: 17 | * @version: 18 | * @Date: 2021-04-20 11:06:21 19 | * @LastEditors: huzhushan@126.com 20 | * @LastEditTime: 2021-04-21 12:48:37 21 | * @Author: huzhushan@126.com 22 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 23 | * @Github: https://github.com/huzhushan/vue3-element-admin 24 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 25 | */ 26 | 27 | export const getItem = name => { 28 | const data = window.localStorage.getItem(name) 29 | try { 30 | return JSON.parse(data) 31 | } catch (err) { 32 | return data 33 | } 34 | } 35 | 36 | export const setItem = (name, value) => { 37 | if (typeof value === 'object') { 38 | value = JSON.stringify(value) 39 | } 40 | 41 | window.localStorage.setItem(name, value) 42 | } 43 | 44 | export const removeItem = name => { 45 | window.localStorage.removeItem(name) 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Descripttion: 3 | * @version: 4 | * @Date: 2021-04-20 13:05:47 5 | * @LastEditors: huzhushan@126.com 6 | * @LastEditTime: 2021-04-21 09:32:36 7 | * @Author: huzhushan@126.com 8 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 9 | * @Github: https://github.com/huzhushan/vue3-element-admin 10 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 11 | */ 12 | /** 13 | * @param {string} path 14 | * @returns {Boolean} 15 | */ 16 | export function isExternal(path) { 17 | return /^(https?:|mailto:|tel:)/.test(path) 18 | } 19 | 20 | /** 21 | * @param {string} str 22 | * @returns {Boolean} 23 | */ 24 | export function validUsername(str) { 25 | const valid_map = ['admin', 'editor'] 26 | return valid_map.indexOf(str.trim()) >= 0 27 | } 28 | 29 | /** 30 | * @param {string} url 31 | * @returns {Boolean} 32 | */ 33 | export function validURL(url) { 34 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 35 | return reg.test(url) 36 | } 37 | 38 | /** 39 | * @param {string} str 40 | * @returns {Boolean} 41 | */ 42 | export function validLowerCase(str) { 43 | const reg = /^[a-z]+$/ 44 | return reg.test(str) 45 | } 46 | 47 | /** 48 | * @param {string} str 49 | * @returns {Boolean} 50 | */ 51 | export function validUpperCase(str) { 52 | const reg = /^[A-Z]+$/ 53 | return reg.test(str) 54 | } 55 | 56 | /** 57 | * @param {string} str 58 | * @returns {Boolean} 59 | */ 60 | export function validAlphabets(str) { 61 | const reg = /^[A-Za-z]+$/ 62 | return reg.test(str) 63 | } 64 | 65 | /** 66 | * @param {string} email 67 | * @returns {Boolean} 68 | */ 69 | export function validEmail(email) { 70 | const reg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 71 | return reg.test(email) 72 | } 73 | 74 | /** 75 | * @param {string} str 76 | * @returns {Boolean} 77 | */ 78 | export function isString(str) { 79 | if (typeof str === 'string' || str instanceof String) { 80 | return true 81 | } 82 | return false 83 | } 84 | 85 | /** 86 | * @param {Array} arg 87 | * @returns {Boolean} 88 | */ 89 | export function isArray(arg) { 90 | if (typeof Array.isArray === 'undefined') { 91 | return Object.prototype.toString.call(arg) === '[object Array]' 92 | } 93 | return Array.isArray(arg) 94 | } 95 | -------------------------------------------------------------------------------- /src/views/error/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 33 | 34 | 41 | 42 | 66 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/lock/Clock.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 86 | 87 | 119 | 120 | 127 | -------------------------------------------------------------------------------- /src/views/lock/CurrentTime.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 45 | 46 | 70 | 87 | -------------------------------------------------------------------------------- /src/views/lock/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 44 | 45 | 61 | 62 | 74 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 52 | 53 | 153 | 154 | 221 | -------------------------------------------------------------------------------- /src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 26 | -------------------------------------------------------------------------------- /src/views/test/Add.vue: -------------------------------------------------------------------------------- 1 | 12 | 20 | -------------------------------------------------------------------------------- /src/views/test/Auth.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /src/views/test/Cache.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | -------------------------------------------------------------------------------- /src/views/test/Edit.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 47 | 60 | -------------------------------------------------------------------------------- /src/views/test/Nest.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/views/test/NoAuth.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/views/test/Nocache.vue: -------------------------------------------------------------------------------- 1 | 8 | 18 | -------------------------------------------------------------------------------- /src/views/test/error-log/components/ErrorTestA.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/views/test/error-log/components/ErrorTestB.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/views/test/error-log/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 29 | 30 | 46 | 47 | 52 | -------------------------------------------------------------------------------- /src/views/test/nest/Page1.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/views/test/nest/Page2.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 江城子 . 程序员之歌 3 | * 4 | * 十年生死两茫茫,写程序,到天亮。 5 | * 千行代码,Bug何处藏。 6 | * 纵使上线又怎样,朝令改,夕断肠。 7 | * 8 | * 领导每天新想法,天天改,日日忙。 9 | * 相顾无言,惟有泪千行。 10 | * 每晚灯火阑珊处,夜难寐,加班狂。 11 | * 12 | * 13 | * @Descripttion: 14 | * @version: 15 | * @Date: 2021-04-20 11:06:21 16 | * @LastEditors: huzhushan@126.com 17 | * @LastEditTime: 2022-09-24 14:33:17 18 | * @Author: huzhushan@126.com 19 | * @HomePage: https://huzhushan.gitee.io/vue3-element-admin 20 | * @Github: https://github.com/huzhushan/vue3-element-admin 21 | * @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/ 22 | */ 23 | 24 | import { defineConfig } from 'vite' 25 | import vue from '@vitejs/plugin-vue' 26 | import path from 'path' 27 | import { viteMockServe } from 'vite-plugin-mock' 28 | import viteSvgIcons from 'vite-plugin-svg-icons' 29 | import viteESLint from '@ehutch79/vite-eslint' 30 | 31 | // https://vitejs.dev/config/ 32 | export default env => { 33 | // console.log(111, env); 34 | 35 | return defineConfig({ 36 | // base: '/vue3-element-admin-site/', 37 | plugins: [ 38 | vue(), 39 | viteMockServe({ 40 | ignore: /^_/, // 忽略以下划线`_`开头的文件 41 | mockPath: 'mock', // 指定mock目录中的文件全部是mock接口 42 | supportTs: false, // mockPath目录中的文件是否支持ts文件,现在我们不使用ts,所以设为false 43 | localEnabled: env.mode === 'mock', // 开发环境是否开启mock功能(可以在package.json的启动命令中指定mode为mock) 44 | prodEnabled: env.mode === 'mock', // 生产环境是否开启mock功能 45 | injectCode: ` 46 | import { setupProdMockServer } from '../mock/_createProductionServer'; 47 | setupProdMockServer(); 48 | `, 49 | }), 50 | viteSvgIcons({ 51 | // 指定需要缓存的图标文件夹 52 | iconDirs: [path.resolve(__dirname, 'src/assets/svg')], 53 | // 指定symbolId格式 54 | symbolId: 'icon-[dir]-[name]', 55 | }), 56 | viteESLint({ 57 | include: ['src/**/*.vue', 'src/**/*.js'], 58 | }), 59 | ], 60 | css: { 61 | preprocessorOptions: { 62 | scss: { 63 | // 全局变量 64 | // additionalData: '@import "./src/assets/style/global-variables.scss";', 65 | // element-plus升级到v2需要改成以下写法 66 | additionalData: `@use "./src/assets/style/global-variables.scss" as *;`, 67 | }, 68 | }, 69 | }, 70 | resolve: { 71 | alias: { 72 | '@': path.resolve(__dirname, 'src'), 73 | }, 74 | }, 75 | server: { 76 | port: 3001, 77 | open: true, 78 | proxy: { 79 | '/api': { 80 | target: 'http://dev.api.xxx.com', // 后端接口的域名 81 | changeOrigin: true, 82 | rewrite: path => path.replace(/^\/api/, ''), 83 | }, 84 | }, 85 | }, 86 | esbuild: false, 87 | build: { 88 | terserOptions: { 89 | compress: { 90 | keep_infinity: true, 91 | // 删除console 92 | drop_console: true, 93 | }, 94 | }, 95 | // 禁用该功能可能会提高大型项目的构建性能 96 | brotliSize: false, 97 | rollupOptions: { 98 | output: { 99 | // 拆分单独模块 100 | manualChunks: { 101 | 'element-plus': ['element-plus'], 102 | mockjs: ['mockjs'], 103 | }, 104 | }, 105 | }, 106 | }, 107 | }) 108 | } 109 | --------------------------------------------------------------------------------