├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── auto-imports.d.ts ├── components.d.ts ├── index.html ├── package.json ├── public ├── mock │ ├── role.json │ ├── table.json │ └── user.json └── template.xlsx ├── screenshots ├── wms1.png └── wms3.png ├── src ├── App.vue ├── api │ └── index.ts ├── assets │ ├── css │ │ ├── icon.css │ │ └── main.css │ └── img │ │ ├── img.jpg │ │ ├── login-bg.jpg │ │ ├── logo.svg │ │ └── ucenter-bg.jpg ├── components │ ├── countup.vue │ ├── header.vue │ ├── menu.ts │ ├── sidebar.vue │ ├── table-custom.vue │ ├── table-detail.vue │ ├── table-edit.vue │ ├── table-search.vue │ └── tabs.vue ├── main.ts ├── router │ └── index.ts ├── store │ ├── permiss.ts │ ├── sidebar.ts │ ├── tabs.ts │ └── theme.ts ├── types │ ├── form-option.ts │ ├── menu.ts │ ├── role.ts │ ├── table.ts │ └── user.ts ├── utils │ ├── china.ts │ ├── index.ts │ └── request.ts ├── views │ ├── chart │ │ ├── echarts.vue │ │ ├── options.ts │ │ └── schart.vue │ ├── dashboard.vue │ ├── element │ │ ├── calendar.vue │ │ ├── carousel.vue │ │ ├── form.vue │ │ ├── statistic.vue │ │ ├── steps.vue │ │ ├── tabs.vue │ │ ├── tour.vue │ │ ├── upload.vue │ │ └── watermark.vue │ ├── home.vue │ ├── pages │ │ ├── 403.vue │ │ ├── 404.vue │ │ ├── editor.vue │ │ ├── icon.vue │ │ ├── login.vue │ │ ├── markdown.vue │ │ ├── register.vue │ │ ├── reset-pwd.vue │ │ ├── theme.vue │ │ └── ucenter.vue │ ├── system │ │ ├── menu.vue │ │ ├── role-permission.vue │ │ ├── role.vue │ │ └── user.vue │ └── table │ │ ├── basetable.vue │ │ ├── export.vue │ │ ├── import.vue │ │ └── table-editor.vue └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://lin-xin.github.io/images/weixin.jpg 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2023 vue-manage-system 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-manage-system 2 | 3 | 4 | GitHub release 5 | 6 | 7 | license 8 | 9 | 10 | 基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-manage-system/) 11 | 12 | > Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0),带后台功能请看 [tsrpc-manage-system](https://github.com/lin-xin/tsrpc-manage-system) 13 | 14 | [文档地址](https://lin-xin.github.io/example/vuems-doc/) 15 | [English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md) 16 | 17 | ## 赞助商 18 | 19 | ### 好问 20 | 21 | [](https://www.bestqa.net/home/index.html) 22 | 23 | 专业问卷服务,一对一客服,按需定制 24 | 25 | ## 支持作者 26 | 27 | 请作者喝杯咖啡吧!(微信号:linxin_20) 28 | 29 | ![微信扫一扫](https://lin-xin.github.io/images/weixin.jpg) 30 | 31 | ## 前言 32 | 33 | 该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。 34 | 35 | ## 功能 36 | 37 | - [x] Element Plus 38 | - [x] vite 3 39 | - [x] pinia 40 | - [x] typescript 41 | - [x] 登录/注册 42 | - [x] Dashboard 43 | - [x] 表格/表单 44 | - [x] 图表 :bar_chart: 45 | - [x] 富文本/markdown 编辑器 46 | - [x] 图片拖拽/裁剪上传 47 | - [x] 权限管理 48 | - [x] 三级菜单 49 | - [x] 自定义图标 50 | - [x] 主题切换 51 | 52 | ## 安装步骤 53 | 54 | > 因为使用 vite3,node 版本需要 14.18+ 55 | 56 | ``` 57 | git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地 58 | cd vue-manage-system // 进入模板目录 59 | npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn 60 | 61 | // 运行 62 | npm run dev 63 | 64 | // 执行构建命令,生成的dist文件夹放在服务器下即可访问 65 | npm run build 66 | ``` 67 | 68 | ## 项目截图 69 | 70 | ### 首页 71 | 72 | ![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png) 73 | 74 | ### 登录 75 | 76 | ![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png) 77 | 78 | ## License 79 | 80 | [MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE) 81 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # vue-manage-system 2 | 3 | 4 | vue 5 | 6 | 7 | element-ui 8 | 9 | 10 | license 11 | 12 | 13 | GitHub release 14 | 15 | 16 | donate 17 | 18 | 19 | The web management system solution based on Vue3 and ElementPlus。[live demo](https://lin-xin.gitee.io/example/work/) 20 | 21 | Please check the version of vue2 in [tag V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0) 22 | 23 | ## Donation 24 | 25 | ![WeChat](https://lin-xin.gitee.io/images/weixin.jpg) 26 | 27 | ## Preface 28 | 29 | The scheme as a set of multi-function background frame templates, suitable for most of the WEB management system development. Convenient development fast simple good components based on Vue3 and ElementPlus. Color separation of color style, support manual switch themes, and it is convenient to use a custom theme color. 30 | 31 | ## Function 32 | 33 | - [x] Element-UI 34 | - [x] Login/Logout 35 | - [x] Dashboard 36 | - [x] Table 37 | - [x] Tabs 38 | - [x] From 39 | - [x] Chart :bar_chart: 40 | - [x] Editor 41 | - [x] Markdown 42 | - [x] Upload pictures by clipping or dragging 43 | - [x] Permission 44 | - [x] Three level menu 45 | - [x] Custom icon 46 | 47 | ## Installation steps 48 | 49 | git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates 50 | cd vue-manage-system // Enter template directory 51 | npm install // Installation dependency 52 | 53 | ## Local development 54 | 55 | npm run dev 56 | 57 | ## Constructing production 58 | 59 | // Constructing project 60 | npm run build 61 | 62 | ## Component description and presentation 63 | 64 | ### vue-schart 65 | 66 | Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/vue-schart#/) 67 | 68 | ```html 69 | 74 | 99 | 105 | ``` 106 | 107 | ## Screenshot 108 | 109 | ### Default theme 110 | 111 | ![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png) 112 | 113 | ### Login 114 | 115 | ![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png) 116 | 117 | ## License 118 | 119 | [MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE) 120 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by 'unplugin-auto-import' 2 | export {} 3 | declare global { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/core/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | export {} 7 | 8 | declare module '@vue/runtime-core' { 9 | export interface GlobalComponents { 10 | Countup: typeof import('./src/components/countup.vue')['default'] 11 | ElAvatar: typeof import('element-plus/es')['ElAvatar'] 12 | ElButton: typeof import('element-plus/es')['ElButton'] 13 | ElCalendar: typeof import('element-plus/es')['ElCalendar'] 14 | ElCard: typeof import('element-plus/es')['ElCard'] 15 | ElCarousel: typeof import('element-plus/es')['ElCarousel'] 16 | ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] 17 | ElCascader: typeof import('element-plus/es')['ElCascader'] 18 | ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] 19 | ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] 20 | ElCol: typeof import('element-plus/es')['ElCol'] 21 | ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] 22 | ElCountdown: typeof import('element-plus/es')['ElCountdown'] 23 | ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] 24 | ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] 25 | ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] 26 | ElDialog: typeof import('element-plus/es')['ElDialog'] 27 | ElDivider: typeof import('element-plus/es')['ElDivider'] 28 | ElDropdown: typeof import('element-plus/es')['ElDropdown'] 29 | ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] 30 | ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] 31 | ElForm: typeof import('element-plus/es')['ElForm'] 32 | ElFormItem: typeof import('element-plus/es')['ElFormItem'] 33 | ElIcon: typeof import('element-plus/es')['ElIcon'] 34 | ElImage: typeof import('element-plus/es')['ElImage'] 35 | ElInput: typeof import('element-plus/es')['ElInput'] 36 | ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] 37 | ElLink: typeof import('element-plus/es')['ElLink'] 38 | ElMenu: typeof import('element-plus/es')['ElMenu'] 39 | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] 40 | ElOption: typeof import('element-plus/es')['ElOption'] 41 | ElPagination: typeof import('element-plus/es')['ElPagination'] 42 | ElProgress: typeof import('element-plus/es')['ElProgress'] 43 | ElRadio: typeof import('element-plus/es')['ElRadio'] 44 | ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] 45 | ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] 46 | ElRate: typeof import('element-plus/es')['ElRate'] 47 | ElResult: typeof import('element-plus/es')['ElResult'] 48 | ElRow: typeof import('element-plus/es')['ElRow'] 49 | ElSelect: typeof import('element-plus/es')['ElSelect'] 50 | ElSlider: typeof import('element-plus/es')['ElSlider'] 51 | ElSpace: typeof import('element-plus/es')['ElSpace'] 52 | ElStatistic: typeof import('element-plus/es')['ElStatistic'] 53 | ElStep: typeof import('element-plus/es')['ElStep'] 54 | ElSteps: typeof import('element-plus/es')['ElSteps'] 55 | ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] 56 | ElSwitch: typeof import('element-plus/es')['ElSwitch'] 57 | ElTable: typeof import('element-plus/es')['ElTable'] 58 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] 59 | ElTabPane: typeof import('element-plus/es')['ElTabPane'] 60 | ElTabs: typeof import('element-plus/es')['ElTabs'] 61 | ElTag: typeof import('element-plus/es')['ElTag'] 62 | ElTimeline: typeof import('element-plus/es')['ElTimeline'] 63 | ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] 64 | ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] 65 | ElTooltip: typeof import('element-plus/es')['ElTooltip'] 66 | ElTour: typeof import('element-plus/es')['ElTour'] 67 | ElTourStep: typeof import('element-plus/es')['ElTourStep'] 68 | ElTransfer: typeof import('element-plus/es')['ElTransfer'] 69 | ElUpload: typeof import('element-plus/es')['ElUpload'] 70 | ElWatermark: typeof import('element-plus/es')['ElWatermark'] 71 | Header: typeof import('./src/components/header.vue')['default'] 72 | RouterLink: typeof import('vue-router')['RouterLink'] 73 | RouterView: typeof import('vue-router')['RouterView'] 74 | Sidebar: typeof import('./src/components/sidebar.vue')['default'] 75 | TableCustom: typeof import('./src/components/table-custom.vue')['default'] 76 | TableDetail: typeof import('./src/components/table-detail.vue')['default'] 77 | TableEdit: typeof import('./src/components/table-edit.vue')['default'] 78 | TableSearch: typeof import('./src/components/table-search.vue')['default'] 79 | Tabs: typeof import('./src/components/tabs.vue')['default'] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-manage-system后台管理系统 9 | 10 | 11 | 12 | 13 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-manage-system", 3 | "version": "5.5.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vue-tsc --noEmit && vite build", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@element-plus/icons-vue": "*", 12 | "@wangeditor/editor": "^5.1.23", 13 | "@wangeditor/editor-for-vue": "^5.1.12", 14 | "axios": "^1.6.3", 15 | "countup.js": "^2.8.0", 16 | "echarts": "^5.5.0", 17 | "echarts-wordcloud": "^2.1.0", 18 | "element-plus": "^2.6.3", 19 | "md-editor-v3": "^2.11.2", 20 | "nprogress": "^0.2.0", 21 | "pinia": "^2.1.7", 22 | "vue": "^3.4.5", 23 | "vue-cropper": "1.1.1", 24 | "vue-echarts": "^6.6.9", 25 | "vue-router": "^4.2.5", 26 | "vue-schart": "^2.0.0", 27 | "xlsx": "^0.18.5" 28 | }, 29 | "devDependencies": { 30 | "@vitejs/plugin-vue": "^3.0.0", 31 | "@vue/compiler-sfc": "^3.1.2", 32 | "typescript": "^4.6.4", 33 | "unplugin-auto-import": "^0.11.2", 34 | "unplugin-vue-components": "^0.22.4", 35 | "vite": "^3.0.0", 36 | "vite-plugin-vue-setup-extend": "^0.4.0", 37 | "vue-tsc": "^0.38.4" 38 | }, 39 | "browserslist": [ 40 | "> 1%", 41 | "last 2 versions", 42 | "not dead" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /public/mock/role.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "id": 1, 5 | "name": "管理员", 6 | "key": "admin", 7 | "status": true, 8 | "permiss": [ 9 | "0", 10 | "1", 11 | "11", 12 | "12", 13 | "13", 14 | "2", 15 | "21", 16 | "22", 17 | "23", 18 | "24", 19 | "3", 20 | "31", 21 | "32", 22 | "33", 23 | "331", 24 | "332", 25 | "4", 26 | "41", 27 | "42", 28 | "5" 29 | ] 30 | }, 31 | { 32 | "id": 2, 33 | "name": "普通用户", 34 | "key": "user", 35 | "status": true, 36 | "permiss": [ 37 | "0", 38 | "1", 39 | "11", 40 | "12", 41 | "13" 42 | ] 43 | } 44 | ], 45 | "pageTotal": 2 46 | } -------------------------------------------------------------------------------- /public/mock/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "id": 1, 5 | "name": "张三", 6 | "money": 123, 7 | "address": "广东省东莞市长安镇", 8 | "state": true, 9 | "date": "2019-11-1", 10 | "thumb": "https://lin-xin.gitee.io/images/post/wms.png" 11 | }, 12 | { 13 | "id": 2, 14 | "name": "李四", 15 | "money": 456, 16 | "address": "广东省广州市白云区", 17 | "state": true, 18 | "date": "2019-10-11", 19 | "thumb": "https://lin-xin.gitee.io/images/post/node3.png" 20 | }, 21 | { 22 | "id": 3, 23 | "name": "王五", 24 | "money": 789, 25 | "address": "湖南省长沙市", 26 | "state": false, 27 | "date": "2019-11-11", 28 | "thumb": "https://lin-xin.gitee.io/images/post/parcel.png" 29 | }, 30 | { 31 | "id": 4, 32 | "name": "赵六", 33 | "money": 1011, 34 | "address": "福建省厦门市鼓浪屿", 35 | "state": true, 36 | "date": "2019-10-20", 37 | "thumb": "https://lin-xin.gitee.io/images/post/notice.png" 38 | } 39 | ], 40 | "pageTotal": 4 41 | } -------------------------------------------------------------------------------- /public/mock/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "id": 1, 5 | "name": "张三", 6 | "password": "123", 7 | "email": "123@qq.com", 8 | "phone": "12345678944", 9 | "date": "2024-01-01", 10 | "role": "管理员" 11 | }, 12 | { 13 | "id": 2, 14 | "name": "李四", 15 | "password": "123", 16 | "email": "1234@qq.com", 17 | "phone": "12345678945", 18 | "date": "2024-01-01", 19 | "role": "普通用户" 20 | } 21 | ], 22 | "pageTotal": 2 23 | } -------------------------------------------------------------------------------- /public/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/public/template.xlsx -------------------------------------------------------------------------------- /screenshots/wms1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/screenshots/wms1.png -------------------------------------------------------------------------------- /screenshots/wms3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/screenshots/wms3.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 18 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export const fetchData = () => { 4 | return request({ 5 | url: './mock/table.json', 6 | method: 'get' 7 | }); 8 | }; 9 | 10 | export const fetchUserData = () => { 11 | return request({ 12 | url: './mock/user.json', 13 | method: 'get' 14 | }); 15 | }; 16 | 17 | export const fetchRoleData = () => { 18 | return request({ 19 | url: './mock/role.json', 20 | method: 'get' 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/css/icon.css: -------------------------------------------------------------------------------- 1 | [class*=" el-icon-lx"], 2 | [class^=el-icon-lx] { 3 | font-family: lx-iconfont !important; 4 | } -------------------------------------------------------------------------------- /src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | outline: 0 !important; 5 | } 6 | 7 | 8 | body { 9 | font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif; 10 | } 11 | 12 | a { 13 | text-decoration: none; 14 | } 15 | i { 16 | font-style: normal; 17 | } 18 | 19 | .container { 20 | padding: 30px; 21 | background: #fff; 22 | border: 1px solid #ddd; 23 | border-radius: 5px; 24 | } 25 | 26 | .el-table th { 27 | background-color: #f5f7fa !important; 28 | } 29 | 30 | .plugins-tips { 31 | padding: 20px 10px; 32 | margin-bottom: 20px; 33 | background: #eef1f6; 34 | } 35 | 36 | .plugins-tips a { 37 | color: var(--el-color-primary); 38 | } 39 | 40 | .el-button + .el-tooltip { 41 | margin-left: 10px; 42 | } 43 | 44 | .mgb20 { 45 | margin-bottom: 20px; 46 | } 47 | .mgb10 { 48 | margin-bottom: 10px; 49 | } 50 | .mr10 { 51 | margin-right: 10px; 52 | } 53 | 54 | .move-enter-active, 55 | .move-leave-active { 56 | transition: opacity 0.1s ease; 57 | } 58 | 59 | .move-enter-from, 60 | .move-leave-to { 61 | opacity: 0; 62 | } 63 | 64 | .el-time-panel__content::after, 65 | .el-time-panel__content::before { 66 | margin-top: -7px; 67 | } 68 | 69 | .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) { 70 | padding-bottom: 0; 71 | } 72 | 73 | [hidden] { 74 | display: none !important; 75 | } 76 | 77 | .flex-center { 78 | display: flex; 79 | justify-content: center; 80 | align-items: center; 81 | } 82 | 83 | :root { 84 | --header-bg-color: #242f42; 85 | --header-text-color: #fff; 86 | --active-color: var(--el-color-primary); 87 | } 88 | -------------------------------------------------------------------------------- /src/assets/img/img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/src/assets/img/img.jpg -------------------------------------------------------------------------------- /src/assets/img/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/src/assets/img/login-bg.jpg -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 资源 82 -------------------------------------------------------------------------------- /src/assets/img/ucenter-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lin-xin/vue-manage-system/6a7019ec1a74cc05297d18647a5f944c242d468a/src/assets/img/ucenter-bg.jpg -------------------------------------------------------------------------------- /src/components/countup.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/header.vue: -------------------------------------------------------------------------------- 1 | 65 | 105 | 205 | -------------------------------------------------------------------------------- /src/components/menu.ts: -------------------------------------------------------------------------------- 1 | import { Menus } from '@/types/menu'; 2 | 3 | export const menuData: Menus[] = [ 4 | { 5 | id: '0', 6 | title: '系统首页', 7 | index: '/dashboard', 8 | icon: 'Odometer', 9 | }, 10 | { 11 | id: '1', 12 | title: '系统管理', 13 | index: '1', 14 | icon: 'HomeFilled', 15 | children: [ 16 | { 17 | id: '11', 18 | pid: '1', 19 | index: '/system-user', 20 | title: '用户管理', 21 | }, 22 | { 23 | id: '12', 24 | pid: '1', 25 | index: '/system-role', 26 | title: '角色管理', 27 | }, 28 | { 29 | id: '13', 30 | pid: '1', 31 | index: '/system-menu', 32 | title: '菜单管理', 33 | }, 34 | ], 35 | }, 36 | { 37 | id: '2', 38 | title: '组件', 39 | index: '2-1', 40 | icon: 'Calendar', 41 | children: [ 42 | { 43 | id: '21', 44 | pid: '3', 45 | index: '/form', 46 | title: '表单', 47 | }, 48 | { 49 | id: '22', 50 | pid: '3', 51 | index: '/upload', 52 | title: '上传', 53 | }, 54 | { 55 | id: '23', 56 | pid: '2', 57 | index: '/carousel', 58 | title: '走马灯', 59 | }, 60 | { 61 | id: '24', 62 | pid: '2', 63 | index: '/calendar', 64 | title: '日历', 65 | }, 66 | { 67 | id: '25', 68 | pid: '2', 69 | index: '/watermark', 70 | title: '水印', 71 | }, 72 | { 73 | id: '26', 74 | pid: '2', 75 | index: '/tour', 76 | title: '分布引导', 77 | }, 78 | { 79 | id: '27', 80 | pid: '2', 81 | index: '/steps', 82 | title: '步骤条', 83 | }, 84 | { 85 | id: '28', 86 | pid: '2', 87 | index: '/statistic', 88 | title: '统计', 89 | }, 90 | { 91 | id: '29', 92 | pid: '3', 93 | index: '29', 94 | title: '三级菜单', 95 | children: [ 96 | { 97 | id: '291', 98 | pid: '29', 99 | index: '/editor', 100 | title: '富文本编辑器', 101 | }, 102 | { 103 | id: '292', 104 | pid: '29', 105 | index: '/markdown', 106 | title: 'markdown编辑器', 107 | }, 108 | ], 109 | }, 110 | ], 111 | }, 112 | { 113 | id: '3', 114 | title: '表格', 115 | index: '3', 116 | icon: 'Calendar', 117 | children: [ 118 | { 119 | id: '31', 120 | pid: '3', 121 | index: '/table', 122 | title: '基础表格', 123 | }, 124 | { 125 | id: '32', 126 | pid: '3', 127 | index: '/table-editor', 128 | title: '可编辑表格', 129 | }, 130 | { 131 | id: '33', 132 | pid: '3', 133 | index: '/import', 134 | title: '导入Excel', 135 | }, 136 | { 137 | id: '34', 138 | pid: '3', 139 | index: '/export', 140 | title: '导出Excel', 141 | }, 142 | ], 143 | }, 144 | { 145 | id: '4', 146 | icon: 'PieChart', 147 | index: '4', 148 | title: '图表', 149 | children: [ 150 | { 151 | id: '41', 152 | pid: '4', 153 | index: '/schart', 154 | title: 'schart图表', 155 | }, 156 | { 157 | id: '42', 158 | pid: '4', 159 | index: '/echarts', 160 | title: 'echarts图表', 161 | }, 162 | ], 163 | }, 164 | { 165 | id: '5', 166 | icon: 'Guide', 167 | index: '/icon', 168 | title: '图标', 169 | permiss: '5', 170 | }, 171 | { 172 | id: '7', 173 | icon: 'Brush', 174 | index: '/theme', 175 | title: '主题', 176 | }, 177 | { 178 | id: '6', 179 | icon: 'DocumentAdd', 180 | index: '6', 181 | title: '附加页面', 182 | children: [ 183 | { 184 | id: '61', 185 | pid: '6', 186 | index: '/ucenter', 187 | title: '个人中心', 188 | }, 189 | { 190 | id: '62', 191 | pid: '6', 192 | index: '/login', 193 | title: '登录', 194 | }, 195 | { 196 | id: '63', 197 | pid: '6', 198 | index: '/register', 199 | title: '注册', 200 | }, 201 | { 202 | id: '64', 203 | pid: '6', 204 | index: '/reset-pwd', 205 | title: '重设密码', 206 | }, 207 | { 208 | id: '65', 209 | pid: '6', 210 | index: '/403', 211 | title: '403', 212 | }, 213 | { 214 | id: '66', 215 | pid: '6', 216 | index: '/404', 217 | title: '404', 218 | }, 219 | ], 220 | }, 221 | ]; 222 | -------------------------------------------------------------------------------- /src/components/sidebar.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 68 | 69 | 91 | -------------------------------------------------------------------------------- /src/components/table-custom.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 191 | 192 | 207 | -------------------------------------------------------------------------------- /src/components/table-detail.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /src/components/table-edit.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 89 | 90 | 112 | -------------------------------------------------------------------------------- /src/components/table-search.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 51 | 52 | 61 | -------------------------------------------------------------------------------- /src/components/tabs.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 107 | 108 | 149 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 4 | import App from './App.vue'; 5 | import router from './router'; 6 | import { usePermissStore } from './store/permiss'; 7 | import 'element-plus/dist/index.css'; 8 | import './assets/css/icon.css'; 9 | 10 | const app = createApp(App); 11 | app.use(createPinia()); 12 | app.use(router); 13 | 14 | // 注册elementplus图标 15 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 16 | app.component(key, component); 17 | } 18 | // 自定义权限指令 19 | const permiss = usePermissStore(); 20 | app.directive('permiss', { 21 | mounted(el, binding) { 22 | if (binding.value && !permiss.key.includes(String(binding.value))) { 23 | el['hidden'] = true; 24 | } 25 | }, 26 | }); 27 | 28 | app.mount('#app'); 29 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; 2 | import { usePermissStore } from '../store/permiss'; 3 | import Home from '../views/home.vue'; 4 | import NProgress from 'nprogress'; 5 | import 'nprogress/nprogress.css'; 6 | 7 | const routes: RouteRecordRaw[] = [ 8 | { 9 | path: '/', 10 | redirect: '/dashboard', 11 | }, 12 | { 13 | path: '/', 14 | name: 'Home', 15 | component: Home, 16 | children: [ 17 | { 18 | path: '/dashboard', 19 | name: 'dashboard', 20 | meta: { 21 | title: '系统首页', 22 | noAuth: true, 23 | }, 24 | component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'), 25 | }, 26 | { 27 | path: '/system-user', 28 | name: 'system-user', 29 | meta: { 30 | title: '用户管理', 31 | permiss: '11', 32 | }, 33 | component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'), 34 | }, 35 | { 36 | path: '/system-role', 37 | name: 'system-role', 38 | meta: { 39 | title: '角色管理', 40 | permiss: '12', 41 | }, 42 | component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'), 43 | }, 44 | { 45 | path: '/system-menu', 46 | name: 'system-menu', 47 | meta: { 48 | title: '菜单管理', 49 | permiss: '13', 50 | }, 51 | component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'), 52 | }, 53 | { 54 | path: '/table', 55 | name: 'basetable', 56 | meta: { 57 | title: '基础表格', 58 | permiss: '31', 59 | }, 60 | component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'), 61 | }, 62 | { 63 | path: '/table-editor', 64 | name: 'table-editor', 65 | meta: { 66 | title: '可编辑表格', 67 | permiss: '32', 68 | }, 69 | component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'), 70 | }, 71 | { 72 | path: '/schart', 73 | name: 'schart', 74 | meta: { 75 | title: 'schart图表', 76 | permiss: '41', 77 | }, 78 | component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'), 79 | }, 80 | { 81 | path: '/echarts', 82 | name: 'echarts', 83 | meta: { 84 | title: 'echarts图表', 85 | permiss: '42', 86 | }, 87 | component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'), 88 | }, 89 | 90 | { 91 | path: '/icon', 92 | name: 'icon', 93 | meta: { 94 | title: '图标', 95 | permiss: '5', 96 | }, 97 | component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'), 98 | }, 99 | { 100 | path: '/ucenter', 101 | name: 'ucenter', 102 | meta: { 103 | title: '个人中心', 104 | }, 105 | component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'), 106 | }, 107 | { 108 | path: '/editor', 109 | name: 'editor', 110 | meta: { 111 | title: '富文本编辑器', 112 | permiss: '291', 113 | }, 114 | component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'), 115 | }, 116 | { 117 | path: '/markdown', 118 | name: 'markdown', 119 | meta: { 120 | title: 'markdown编辑器', 121 | permiss: '292', 122 | }, 123 | component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'), 124 | }, 125 | { 126 | path: '/export', 127 | name: 'export', 128 | meta: { 129 | title: '导出Excel', 130 | permiss: '34', 131 | }, 132 | component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'), 133 | }, 134 | { 135 | path: '/import', 136 | name: 'import', 137 | meta: { 138 | title: '导入Excel', 139 | permiss: '33', 140 | }, 141 | component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'), 142 | }, 143 | { 144 | path: '/theme', 145 | name: 'theme', 146 | meta: { 147 | title: '主题设置', 148 | permiss: '7', 149 | }, 150 | component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'), 151 | }, 152 | { 153 | path: '/calendar', 154 | name: 'calendar', 155 | meta: { 156 | title: '日历', 157 | permiss: '24', 158 | }, 159 | component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'), 160 | }, 161 | { 162 | path: '/watermark', 163 | name: 'watermark', 164 | meta: { 165 | title: '水印', 166 | permiss: '25', 167 | }, 168 | component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'), 169 | }, 170 | { 171 | path: '/carousel', 172 | name: 'carousel', 173 | meta: { 174 | title: '走马灯', 175 | permiss: '23', 176 | }, 177 | component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'), 178 | }, 179 | { 180 | path: '/tour', 181 | name: 'tour', 182 | meta: { 183 | title: '分步引导', 184 | permiss: '26', 185 | }, 186 | component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'), 187 | }, 188 | { 189 | path: '/steps', 190 | name: 'steps', 191 | meta: { 192 | title: '步骤条', 193 | permiss: '27', 194 | }, 195 | component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'), 196 | }, 197 | { 198 | path: '/form', 199 | name: 'forms', 200 | meta: { 201 | title: '表单', 202 | permiss: '21', 203 | }, 204 | component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'), 205 | }, 206 | { 207 | path: '/upload', 208 | name: 'upload', 209 | meta: { 210 | title: '上传', 211 | permiss: '22', 212 | }, 213 | component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'), 214 | }, 215 | { 216 | path: '/statistic', 217 | name: 'statistic', 218 | meta: { 219 | title: '统计', 220 | permiss: '28', 221 | }, 222 | component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'), 223 | }, 224 | ], 225 | }, 226 | { 227 | path: '/login', 228 | meta: { 229 | title: '登录', 230 | noAuth: true, 231 | }, 232 | component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'), 233 | }, 234 | { 235 | path: '/register', 236 | meta: { 237 | title: '注册', 238 | noAuth: true, 239 | }, 240 | component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'), 241 | }, 242 | { 243 | path: '/reset-pwd', 244 | meta: { 245 | title: '重置密码', 246 | noAuth: true, 247 | }, 248 | component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'), 249 | }, 250 | { 251 | path: '/403', 252 | meta: { 253 | title: '没有权限', 254 | noAuth: true, 255 | }, 256 | component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'), 257 | }, 258 | { 259 | path: '/404', 260 | meta: { 261 | title: '找不到页面', 262 | noAuth: true, 263 | }, 264 | component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'), 265 | }, 266 | { path: '/:path(.*)', redirect: '/404' }, 267 | ]; 268 | 269 | const router = createRouter({ 270 | history: createWebHashHistory(), 271 | routes, 272 | }); 273 | 274 | router.beforeEach((to, from, next) => { 275 | NProgress.start(); 276 | const role = localStorage.getItem('vuems_name'); 277 | const permiss = usePermissStore(); 278 | 279 | if (!role && to.meta.noAuth !== true) { 280 | next('/login'); 281 | } else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) { 282 | // 如果没有权限,则进入403 283 | next('/403'); 284 | } else { 285 | next(); 286 | } 287 | }); 288 | 289 | router.afterEach(() => { 290 | NProgress.done(); 291 | }); 292 | 293 | export default router; 294 | -------------------------------------------------------------------------------- /src/store/permiss.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | interface ObjectList { 4 | [key: string]: string[]; 5 | } 6 | 7 | export const usePermissStore = defineStore('permiss', { 8 | state: () => { 9 | const defaultList: ObjectList = { 10 | admin: [ 11 | '0', 12 | '1', 13 | '11', 14 | '12', 15 | '13', 16 | '2', 17 | '21', 18 | '22', 19 | '23', 20 | '24', 21 | '25', 22 | '26', 23 | '27', 24 | '28', 25 | '29', 26 | '291', 27 | '292', 28 | '3', 29 | '31', 30 | '32', 31 | '33', 32 | '34', 33 | '4', 34 | '41', 35 | '42', 36 | '5', 37 | '7', 38 | '6', 39 | '61', 40 | '62', 41 | '63', 42 | '64', 43 | '65', 44 | '66', 45 | ], 46 | user: ['0', '1', '11', '12', '13'], 47 | }; 48 | const username = localStorage.getItem('vuems_name'); 49 | console.log(username); 50 | return { 51 | key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[], 52 | defaultList, 53 | }; 54 | }, 55 | actions: { 56 | handleSet(val: string[]) { 57 | this.key = val; 58 | }, 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /src/store/sidebar.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useSidebarStore = defineStore('sidebar', { 4 | state: () => { 5 | return { 6 | collapse: false, 7 | bgColor: localStorage.getItem('sidebar-bg-color') || '#324157', 8 | textColor: localStorage.getItem('sidebar-text-color') || '#bfcbd9' 9 | }; 10 | }, 11 | getters: {}, 12 | actions: { 13 | handleCollapse() { 14 | this.collapse = !this.collapse; 15 | }, 16 | setBgColor(color: string) { 17 | this.bgColor = color; 18 | localStorage.setItem('sidebar-bg-color', color); 19 | }, 20 | setTextColor(color: string) { 21 | this.textColor = color; 22 | localStorage.setItem('sidebar-text-color', color); 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/store/tabs.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | interface ListItem { 4 | name: string; 5 | path: string; 6 | title: string; 7 | } 8 | 9 | export const useTabsStore = defineStore('tabs', { 10 | state: () => { 11 | return { 12 | list: [] 13 | }; 14 | }, 15 | getters: { 16 | show: state => { 17 | return state.list.length > 0; 18 | }, 19 | nameList: state => { 20 | return state.list.map(item => item.name); 21 | } 22 | }, 23 | actions: { 24 | delTabsItem(index: number) { 25 | this.list.splice(index, 1); 26 | }, 27 | setTabsItem(data: ListItem) { 28 | this.list.push(data); 29 | }, 30 | clearTabs() { 31 | this.list = []; 32 | }, 33 | closeTabsOther(data: ListItem[]) { 34 | this.list = data; 35 | }, 36 | closeCurrentTag(data: any) { 37 | for (let i = 0, len = this.list.length; i < len; i++) { 38 | const item = this.list[i]; 39 | if (item.path === data.$route.fullPath) { 40 | if (i < len - 1) { 41 | data.$router.push(this.list[i + 1].path); 42 | } else if (i > 0) { 43 | data.$router.push(this.list[i - 1].path); 44 | } else { 45 | data.$router.push('/'); 46 | } 47 | this.list.splice(i, 1); 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /src/store/theme.ts: -------------------------------------------------------------------------------- 1 | import { mix, setProperty } from '@/utils'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export const useThemeStore = defineStore('theme', { 5 | state: () => { 6 | return { 7 | primary: '', 8 | success: '', 9 | warning: '', 10 | danger: '', 11 | info: '', 12 | headerBgColor: '#242f42', 13 | headerTextColor: '#fff', 14 | }; 15 | }, 16 | getters: {}, 17 | actions: { 18 | initTheme() { 19 | ['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => { 20 | const color = localStorage.getItem(`theme-${type}`) || ''; 21 | if (color) { 22 | this.setPropertyColor(color, type); // 设置主题色 23 | } 24 | }); 25 | const headerBgColor = localStorage.getItem('header-bg-color'); 26 | headerBgColor && this.setHeaderBgColor(headerBgColor); 27 | const headerTextColor = localStorage.getItem('header-text-color'); 28 | headerTextColor && this.setHeaderTextColor(headerTextColor); 29 | }, 30 | resetTheme() { 31 | ['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => { 32 | this.setPropertyColor('', type); // 重置主题色 33 | }); 34 | }, 35 | setPropertyColor(color: string, type: string = 'primary') { 36 | this[type] = color; 37 | setProperty(`--el-color-${type}`, color); 38 | localStorage.setItem(`theme-${type}`, color); 39 | this.setThemeLight(type); 40 | }, 41 | setThemeLight(type: string = 'primary') { 42 | [3, 5, 7, 8, 9].forEach((v) => { 43 | setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10)); 44 | }); 45 | setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2)); 46 | }, 47 | setHeaderBgColor(color: string) { 48 | this.headerBgColor = color; 49 | setProperty('--header-bg-color', color); 50 | localStorage.setItem(`header-bg-color`, color); 51 | }, 52 | setHeaderTextColor(color: string) { 53 | this.headerTextColor = color; 54 | setProperty('--header-text-color', color); 55 | localStorage.setItem(`header-text-color`, color); 56 | } 57 | } 58 | }); -------------------------------------------------------------------------------- /src/types/form-option.ts: -------------------------------------------------------------------------------- 1 | export interface FormOption { 2 | list: FormOptionList[]; 3 | labelWidth?: number | string; 4 | span?: number; 5 | 6 | } 7 | 8 | export interface FormOptionList { 9 | prop: string; 10 | label: string; 11 | type: string; 12 | placeholder?: string; 13 | disabled?: boolean; 14 | opts?: any[]; 15 | format?: string; 16 | activeValue?: any; 17 | inactiveValue?: any; 18 | activeText?: string; 19 | inactiveText?: string; 20 | required?: boolean; 21 | } -------------------------------------------------------------------------------- /src/types/menu.ts: -------------------------------------------------------------------------------- 1 | export interface Menus { 2 | id: string; 3 | pid?: string; 4 | icon?: string; 5 | index: string; 6 | title: string; 7 | permiss?: string; 8 | children?: Menus[]; 9 | } -------------------------------------------------------------------------------- /src/types/role.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Role { 3 | id: number; 4 | name: string; 5 | key: string; 6 | status: boolean; 7 | permiss: string[] 8 | } -------------------------------------------------------------------------------- /src/types/table.ts: -------------------------------------------------------------------------------- 1 | export interface TableItem { 2 | id: number; 3 | name: string; 4 | thumb: string; 5 | money: number; 6 | state: string; 7 | date: string; 8 | address: string; 9 | } -------------------------------------------------------------------------------- /src/types/user.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface User { 3 | id: number; 4 | name: string; 5 | password: string; 6 | email: string; 7 | phone: string; 8 | role: string; 9 | date: string; 10 | } 11 | 12 | export interface Register { 13 | username: string; 14 | password: string; 15 | email: string; 16 | } -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const setProperty = (prop: string, val: any, dom = document.documentElement) => { 2 | dom.style.setProperty(prop, val); 3 | }; 4 | 5 | export const mix = (color1: string, color2: string, weight: number = 0.5): string => { 6 | let color = '#'; 7 | for (let i = 0; i <= 2; i++) { 8 | const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16); 9 | const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16); 10 | const c = Math.round(c1 * weight + c2 * (1 - weight)); 11 | color += c.toString(16).padStart(2, '0'); 12 | } 13 | return color; 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; 2 | 3 | const service: AxiosInstance = axios.create({ 4 | timeout: 5000 5 | }); 6 | 7 | service.interceptors.request.use( 8 | (config: InternalAxiosRequestConfig) => { 9 | return config; 10 | }, 11 | (error: AxiosError) => { 12 | console.log(error); 13 | return Promise.reject(); 14 | } 15 | ); 16 | 17 | service.interceptors.response.use( 18 | (response: AxiosResponse) => { 19 | if (response.status === 200) { 20 | return response; 21 | } else { 22 | Promise.reject(); 23 | } 24 | }, 25 | (error: AxiosError) => { 26 | console.log(error); 27 | return Promise.reject(); 28 | } 29 | ); 30 | 31 | export default service; 32 | -------------------------------------------------------------------------------- /src/views/chart/echarts.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 75 | 76 | 88 | -------------------------------------------------------------------------------- /src/views/chart/options.ts: -------------------------------------------------------------------------------- 1 | import { graphic } from 'echarts/core'; 2 | export const barOptions = { 3 | xAxis: { 4 | type: 'category', 5 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 6 | }, 7 | yAxis: { 8 | type: 'value', 9 | }, 10 | tooltip: { 11 | trigger: 'axis', 12 | axisPointer: { 13 | type: 'shadow', 14 | }, 15 | }, 16 | color: ['#009688', '#f44336'], 17 | series: [ 18 | { 19 | data: [120, 200, 150, 80, 70, 110, 130], 20 | type: 'bar', 21 | }, 22 | { 23 | data: [180, 230, 190, 120, 110, 230, 235], 24 | type: 'bar', 25 | }, 26 | ], 27 | }; 28 | 29 | export const lineOptions = { 30 | tooltip: { 31 | trigger: 'axis', 32 | }, 33 | grid: { 34 | left: '3%', 35 | right: '4%', 36 | bottom: '3%', 37 | containLabel: true, 38 | }, 39 | xAxis: { 40 | type: 'category', 41 | boundaryGap: false, 42 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 43 | }, 44 | yAxis: { 45 | type: 'value', 46 | }, 47 | color: ['#009688', '#f44336'], 48 | series: [ 49 | { 50 | name: 'Email', 51 | type: 'line', 52 | stack: 'Total', 53 | areaStyle: {}, 54 | smooth: true, 55 | data: [120, 132, 101, 134, 90, 230, 210], 56 | }, 57 | { 58 | name: 'Union Ads', 59 | type: 'line', 60 | stack: 'Total', 61 | areaStyle: {}, 62 | smooth: true, 63 | data: [220, 182, 191, 234, 290, 330, 310], 64 | }, 65 | ], 66 | }; 67 | 68 | export const pieOptions = { 69 | title: { 70 | text: 'Referer of a Website', 71 | subtext: 'Fake Data', 72 | left: 'center', 73 | }, 74 | tooltip: { 75 | trigger: 'item', 76 | }, 77 | legend: { 78 | orient: 'vertical', 79 | left: 'left', 80 | }, 81 | series: [ 82 | { 83 | name: 'Access From', 84 | type: 'pie', 85 | radius: '50%', 86 | data: [ 87 | { value: 1048, name: 'Search Engine' }, 88 | { value: 735, name: 'Direct' }, 89 | { value: 580, name: 'Email' }, 90 | { value: 484, name: 'Union Ads' }, 91 | { value: 300, name: 'Video Ads' }, 92 | ], 93 | emphasis: { 94 | itemStyle: { 95 | shadowBlur: 10, 96 | shadowOffsetX: 0, 97 | shadowColor: 'rgba(0, 0, 0, 0.5)', 98 | }, 99 | }, 100 | }, 101 | ], 102 | }; 103 | 104 | export const wordOptions = { 105 | series: [ 106 | { 107 | type: 'wordCloud', 108 | rotationRange: [0, 0], 109 | autoSize: { 110 | enable: true, 111 | minSize: 14, 112 | }, 113 | textStyle: { 114 | fontFamily: '微软雅黑,sans-serif', 115 | color: function () { 116 | return ( 117 | 'rgb(' + 118 | [ 119 | Math.round(Math.random() * 160), 120 | Math.round(Math.random() * 160), 121 | Math.round(Math.random() * 160), 122 | ].join(',') + 123 | ')' 124 | ); 125 | }, 126 | }, 127 | data: [ 128 | { 129 | name: 'Vue', 130 | value: 10000, 131 | }, 132 | { 133 | name: 'React', 134 | value: 9000, 135 | }, 136 | { 137 | name: '图表', 138 | value: 4000, 139 | }, 140 | { 141 | name: '产品', 142 | value: 7000, 143 | }, 144 | { 145 | name: 'vue-manage-system', 146 | value: 2000, 147 | }, 148 | { 149 | name: 'element-plus', 150 | value: 6000, 151 | }, 152 | { 153 | name: '管理系统', 154 | value: 5000, 155 | }, 156 | { 157 | name: '前端', 158 | value: 4000, 159 | }, 160 | { 161 | name: '测试', 162 | value: 3000, 163 | }, 164 | { 165 | name: '后端', 166 | value: 8000, 167 | }, 168 | { 169 | name: '软件开发', 170 | value: 6000, 171 | }, 172 | { 173 | name: '程序员', 174 | value: 4000, 175 | }, 176 | ], 177 | }, 178 | ], 179 | }; 180 | 181 | export const ringOptions = { 182 | tooltip: { 183 | trigger: 'item', 184 | }, 185 | legend: { 186 | top: '5%', 187 | left: 'center', 188 | }, 189 | 190 | series: [ 191 | { 192 | name: 'Access From', 193 | type: 'pie', 194 | radius: ['40%', '70%'], 195 | avoidLabelOverlap: false, 196 | itemStyle: { 197 | borderRadius: 10, 198 | borderColor: '#fff', 199 | borderWidth: 2, 200 | }, 201 | label: { 202 | show: false, 203 | position: 'center', 204 | }, 205 | emphasis: { 206 | label: { 207 | show: true, 208 | fontSize: 40, 209 | fontWeight: 'bold', 210 | }, 211 | }, 212 | labelLine: { 213 | show: false, 214 | }, 215 | data: [ 216 | { value: 1048, name: 'Search Engine' }, 217 | { value: 735, name: 'Direct' }, 218 | { value: 580, name: 'Email' }, 219 | { value: 484, name: 'Union Ads' }, 220 | { value: 300, name: 'Video Ads' }, 221 | ], 222 | }, 223 | ], 224 | }; 225 | 226 | export const dashOpt1 = { 227 | xAxis: { 228 | type: 'category', 229 | boundaryGap: false, 230 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 231 | }, 232 | yAxis: { 233 | type: 'value', 234 | }, 235 | grid: { 236 | top: '2%', 237 | left: '2%', 238 | right: '3%', 239 | bottom: '2%', 240 | containLabel: true, 241 | }, 242 | color: ['#009688', '#f44336'], 243 | series: [ 244 | { 245 | type: 'line', 246 | areaStyle: { 247 | color: new graphic.LinearGradient(0, 0, 0, 1, [ 248 | { 249 | offset: 0, 250 | color: 'rgba(0, 150, 136,0.8)', 251 | }, 252 | { 253 | offset: 1, 254 | color: 'rgba(0, 150, 136,0.2)', 255 | }, 256 | ]), 257 | }, 258 | smooth: true, 259 | data: [120, 132, 301, 134, 90, 230, 210], 260 | }, 261 | { 262 | type: 'line', 263 | smooth: true, 264 | data: [220, 122, 191, 234, 190, 130, 310], 265 | }, 266 | ], 267 | }; 268 | 269 | export const dashOpt2 = { 270 | legend: { 271 | bottom: '1%', 272 | left: 'center', 273 | }, 274 | color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'], 275 | series: [ 276 | { 277 | type: 'pie', 278 | radius: ['40%', '70%'], 279 | avoidLabelOverlap: false, 280 | itemStyle: { 281 | borderRadius: 10, 282 | borderColor: '#fff', 283 | borderWidth: 2, 284 | }, 285 | data: [ 286 | { value: 1048, name: '数码' }, 287 | { value: 735, name: '食品' }, 288 | { value: 580, name: '母婴' }, 289 | { value: 484, name: '家电' }, 290 | { value: 300, name: '运动' }, 291 | ], 292 | }, 293 | ], 294 | }; 295 | 296 | export const mapOptions = { 297 | tooltip: { 298 | trigger: 'item', 299 | }, 300 | geo: { 301 | map: 'china', 302 | roam: false, 303 | emphasis: { 304 | label: { 305 | show: false, 306 | }, 307 | }, 308 | }, 309 | visualMap: { 310 | show: false, 311 | min: 0, 312 | max: 100, 313 | realtime: false, 314 | calculable: false, 315 | inRange: { 316 | color: ['#d2e0f5', '#71A9FF'], 317 | }, 318 | }, 319 | series: [ 320 | { 321 | geoIndex: 0, 322 | name: '地域分布', 323 | type: 'map', 324 | coordinateSystem: 'geo', 325 | map: 'china', 326 | data: [ 327 | { name: '北京', value: 100 }, 328 | { name: '上海', value: 100 }, 329 | { name: '广东', value: 100 }, 330 | { name: '浙江', value: 90 }, 331 | { name: '江西', value: 80 }, 332 | { name: '山东', value: 70 }, 333 | { name: '广西', value: 60 }, 334 | { name: '河南', value: 50 }, 335 | { name: '河南', value: 40 }, 336 | { name: '青海', value: 70 }, 337 | { name: '河南', value: 30 }, 338 | { name: '黑龙江', value: 20 }, 339 | { name: '新疆', value: 20 }, 340 | { name: '云南', value: 20 }, 341 | { name: '甘肃', value: 20 }, 342 | ], 343 | }, 344 | ], 345 | }; 346 | -------------------------------------------------------------------------------- /src/views/chart/schart.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 117 | 118 | 130 | -------------------------------------------------------------------------------- /src/views/dashboard.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 223 | 224 | 232 | 358 | -------------------------------------------------------------------------------- /src/views/element/calendar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 39 | 40 | 83 | -------------------------------------------------------------------------------- /src/views/element/carousel.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 45 | 46 | 67 | -------------------------------------------------------------------------------- /src/views/element/form.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | -------------------------------------------------------------------------------- /src/views/element/statistic.vue: -------------------------------------------------------------------------------- 1 | 233 | 234 | 261 | 262 | 286 | -------------------------------------------------------------------------------- /src/views/element/steps.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/element/tabs.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 106 | 107 | 117 | -------------------------------------------------------------------------------- /src/views/element/tour.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/element/upload.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 31 | 32 | 45 | -------------------------------------------------------------------------------- /src/views/element/watermark.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | -------------------------------------------------------------------------------- /src/views/home.vue: -------------------------------------------------------------------------------- 1 | 19 | 29 | 30 | 64 | -------------------------------------------------------------------------------- /src/views/pages/403.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 68 | -------------------------------------------------------------------------------- /src/views/pages/404.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 68 | -------------------------------------------------------------------------------- /src/views/pages/editor.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/pages/icon.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 205 | 206 | 258 | -------------------------------------------------------------------------------- /src/views/pages/login.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 105 | 106 | 172 | -------------------------------------------------------------------------------- /src/views/pages/markdown.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /src/views/pages/register.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 87 | 88 | 136 | -------------------------------------------------------------------------------- /src/views/pages/reset-pwd.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 55 | 56 | 103 | -------------------------------------------------------------------------------- /src/views/pages/theme.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 186 | 187 | 206 | -------------------------------------------------------------------------------- /src/views/pages/ucenter.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 143 | 144 | 265 | 266 | 271 | -------------------------------------------------------------------------------- /src/views/system/menu.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 143 | 144 | -------------------------------------------------------------------------------- /src/views/system/role-permission.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/views/system/role.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 161 | 162 | -------------------------------------------------------------------------------- /src/views/system/user.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 147 | 148 | -------------------------------------------------------------------------------- /src/views/table/basetable.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 161 | 162 | 170 | -------------------------------------------------------------------------------- /src/views/table/export.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 69 | 70 | 99 | -------------------------------------------------------------------------------- /src/views/table/import.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 98 | 99 | 110 | -------------------------------------------------------------------------------- /src/views/table/table-editor.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | 9 | declare module 'vue-schart'; 10 | declare module 'nprogress' -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": false, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "baseUrl": "./", 16 | "paths": { 17 | "@/*": ["src/*"] 18 | } 19 | }, 20 | "include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import VueSetupExtend from 'vite-plugin-vue-setup-extend'; 4 | import AutoImport from 'unplugin-auto-import/vite'; 5 | import Components from 'unplugin-vue-components/vite'; 6 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; 7 | export default defineConfig({ 8 | base: './', 9 | plugins: [ 10 | vue(), 11 | VueSetupExtend(), 12 | AutoImport({ 13 | resolvers: [ElementPlusResolver()] 14 | }), 15 | Components({ 16 | resolvers: [ElementPlusResolver()] 17 | }) 18 | ], 19 | optimizeDeps: { 20 | include: ['schart.js'] 21 | }, 22 | resolve: { 23 | alias: { 24 | '@': '/src', 25 | '~': '/src/assets' 26 | } 27 | }, 28 | define: { 29 | __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true", 30 | }, 31 | }); 32 | --------------------------------------------------------------------------------