├── .env.development ├── .env.production ├── .gitignore ├── README.md ├── dist ├── assets │ ├── element-icons.9c88a535.woff │ ├── element-icons.de5eb258.ttf │ ├── home.155038bb.svg │ ├── index.0282d2bf.js │ ├── index.0282d2bf.js.gz │ ├── index.1cb7eec7.css │ ├── index.1cb7eec7.css.gz │ ├── index.3d6b04f8.css │ ├── index.3d6b04f8.css.gz │ ├── index.710eea4b.js │ ├── index.710eea4b.js.gz │ ├── index.92421829.css │ ├── index.92421829.css.gz │ ├── index.930d31c9.js │ ├── index.930d31c9.js.gz │ ├── index.b06ca65d.css │ ├── index.b06ca65d.css.gz │ ├── index.cc670981.js │ ├── index.d41203df.js │ ├── index.d41203df.js.gz │ ├── index.e46c323d.js │ ├── index.efa6b091.css │ ├── index.efa6b091.css.gz │ ├── index.ffebf55c.js │ ├── index.ffebf55c.js.gz │ ├── login.a35c9f52.svg │ ├── logo.61ee5eb1.png │ ├── useTableData.6da28c31.js │ ├── useTableData.6da28c31.js.gz │ ├── vendor.f21cd016.js │ └── vendor.f21cd016.js.gz ├── favicon.ico └── index.html ├── index.html ├── package-lock.json ├── package.json ├── preview ├── logo.png ├── 个人信息.png ├── 右键菜单.png ├── 搜索.png ├── 更换主题色1.png ├── 标签切换.png ├── 注册.png ├── 用户管理.png ├── 登录.png ├── 移动端界面1.png ├── 移动端菜单.png ├── 移动端设置.png ├── 设置.png ├── 通知.png ├── 面包屑.png └── 首页.png ├── public └── favicon.ico ├── src ├── App.vue ├── assets │ └── images │ │ ├── arrow-down.svg │ │ ├── arrow-up.svg │ │ ├── empty-tips.svg │ │ ├── home.svg │ │ ├── login.svg │ │ ├── logo.png │ │ └── office-desk.svg ├── components │ └── CountDownBtn │ │ └── index.vue ├── hooks │ ├── useTableData.js │ └── useWindowSize.js ├── http │ ├── apis │ │ ├── role-management.js │ │ ├── user-management.js │ │ └── user.js │ ├── axios.js │ └── index.js ├── layouts │ ├── AdminLayout.vue │ ├── AppHeader │ │ └── index.vue │ ├── AppSideBar │ │ ├── MenuItem.vue │ │ └── index.vue │ ├── Breadcrumb │ │ └── index.vue │ ├── MobileAppHeader │ │ └── index.vue │ ├── MobileAppSideBar │ │ └── index.vue │ └── TagsViewSwitcher │ │ └── index.vue ├── main.js ├── mock │ ├── apiController │ │ ├── role-management.js │ │ ├── user-management.js │ │ └── user.js │ ├── index.js │ └── mock.js ├── others │ ├── options.js │ ├── utils.js │ └── validator.js ├── pages │ ├── 404 │ │ └── index.vue │ ├── home │ │ └── index.vue │ ├── login │ │ └── index.vue │ ├── role-management │ │ ├── components │ │ │ └── RoleEdit.vue │ │ └── index.vue │ └── user-management │ │ ├── components │ │ └── UserEdit.vue │ │ └── index.vue ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── app.js │ │ └── tags-view.js └── styles │ ├── animation.scss │ ├── color.scss │ ├── custom-default-browser-style.scss │ ├── element-variables.scss │ ├── global.scss │ └── mixin.scss ├── stats.html └── vite.config.js /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VITE_SERVER_HOST="" 3 | 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VITE_SERVER_HOST="" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist-ssr 4 | *.local -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 | ##

vue3-vite-elementplus-admin
管理系统快速开发模板

9 |
10 | ​ 基于Vue3.0前端开发框架和 ElementPlus UI框架,并使用由Vue 作者尤雨溪的前端工程化工具Vite进行构建。与Webpack相比,Vite极大的缩短了开发打包和热更新的效率。
此模板可用于现代化管理系统的初始开发模板,可节省一些重复性的工程初始化搭建工作。 11 |
12 | 13 | ## 特性 14 | 15 | - 包含简易的登录注册页、首页、404 提示页、用户管理页、角色权限页。 16 | - 封装好的经典管理端布局(包含头部组件、侧边栏菜单组件、面包屑组件、标签切换器组件)。 17 | - 默认使用`axios`请求数据和`mockjs`来模拟数据。 18 | - 封装的公共组件:倒计时按钮组件。 19 | - 使用`nprogress`插件来作为单页面切换的加载指示器。 20 | - 使用`VueRouter`动态生成不同角色菜单的路由权限。 21 | - 基本的兼容性提醒,由于vite和vue3不兼容IE浏览器,因此如检测为IE浏览器则提醒用户切换使用其他浏览器。 22 | - 界面设计简约,可根据自己的实际开发情况增加删除工具库或修改源码。 23 |
24 | 25 | ## 预览 26 | + github地址 27 | > https://github.com/FEZIRO/vue3-vite-elementplus-admin 28 | 29 | + 模板下载地址(template分支) 30 | > https://github.com/FEZIRO/vue3-vite-elementplus-admin/tree/template 31 | 32 | + 在线预览(账号admin,密码admin) 33 | > https://feziro.github.io/vue3-vite-elementplus-admin/ 34 | 35 | 36 | 37 |
38 | 登录 39 |

登录

40 |
41 |
42 | 注册 43 |

注册

44 |
45 |
46 | 首页 47 |

首页

48 |
49 |
50 | 用户管理 51 |

用户管理

52 |
53 | 54 |
55 | 移动端界面 56 |

移动端界面

57 |
58 | 59 |
60 | 移动端菜单 61 |

移动端菜单

62 |
63 |
64 | 65 | ## 使用库/工具 66 | 67 | - [Vue 3](https://v3.cn.vuejs.org/) 68 | - [VueRouter 4.x](https://next.router.vuejs.org/zh/)(有兼容`vue3`语法的`hook`写法) 69 | - [Vuex 4.x](https://next.vuex.vuejs.org/zh/index.html) (有兼容`vue3`语法的`hook`写法) 70 | - [Vite 2.x](https://cn.vitejs.dev/) 71 | - [ElementPlus 1.0.2-beta.55](https://element-plus.org/#/zh-CN/component/space)(整个库采用`vue3`语法编写) 72 | - [dayjs](https://github.com/iamkun/dayjs)(一个轻便的时间处理库) 73 | - [axios](http://www.axios-js.com/) (好用的 http 请求库) 74 | - [mockjs](http://mockjs.com/) (模拟请求) 75 | - [reset-css](https://www.npmjs.com/package/reset-css) (重置浏览器默认样式) 76 | - [nprogress](https://ricostacruz.com/nprogress/) (顶部加载指示器) 77 | - vite-plugin-compression(打包压缩 gzip 插件) 78 | - @rollup/plugin-strip (打包去除调试语句插件) 79 | - visualizer (打包文件大小占比分析可视化插件) 80 |

81 | 82 | ## 模板主要结构 83 | 84 | #### vite-vue3-elementplus-admin 85 | 86 | ├─ `public`(public 文件夹) 87 | │ └─ `favicon.ico` 浏览器标签前缀图标 88 | │ 89 | ├─ `src`(根目录 src-文件夹) 90 | │ ├─ `assets`(资源-文件夹) 91 | │ │ └─ `images`(图片-文件夹) 92 | │ │ ├─ `arrow-down.svg` 93 | │ │ ├─ `arrow-up.svg` 94 | │ │ ├─ `empty-tips.svg ` 95 | │ │ ├─ `home.svg` 96 | │ │ ├─ `login.svg` 97 | │ │ ├─ `logo.png` 98 | │ │ └─ `office-desk.svg` 99 | │ │ 100 | │ ├─ `components`(全局公共-文件夹) 101 | │ │ ├─ `CountDownBtn` (倒计时按钮组件) 102 | │ │ │ └─ `index.vue` 103 | │ │ 104 | │ ├─ `hooks`(自定义 hooks-文件夹) 105 | │ │ ├─ `useTableData.js` 106 | │ │ └─ `useWindowRect.js` 107 | │ │ 108 | │ ├─ `http`(http 请求-文件夹) 109 | │ │ ├─ `apis`(api 管理-文件夹) 110 | │ │ │ ├─ `role-management.js` 111 | │ │ │ ├─ `user-management.js` 112 | │ │ │ └─ `user.js` 113 | │ │ ├─ `axios.js` (axios 配置文件) 114 | │ │ └─ `index.js` (http 请求主入口文件) 115 | │ │ 116 | │ ├─ `layouts` (布局文件夹) 117 | │ │ ├─ `AppHeader`(头部组件-文件夹) 118 | │ │ │ └─ `index.vue` 119 | │ │ ├─ `MobileAppHeader`(移动端头部组件-文件夹) 120 | │ │ │ └─ `index.vue` 121 | │ │ ├─ `AppSideBar`(侧边栏菜单组件-文件夹) 122 | │ │ │ ├─ `index.vue` 123 | │ │ ├─ `MobileAppSideBar`(移动端侧边栏菜单组件-文件夹) 124 | │ │ │ ├─ `index.vue` 125 | │ │ │ └─ `MenuItem.vue` 126 | │ │ ├─ `Breadcrumb`(面包屑组件-文件夹) 127 | │ │ │ └─ `index.vue` 128 | │ │ ├─`TagsViewSwitcher`(标签切换组件-文件夹) 129 | │ │ │ └─ `index.vue` 130 | │ │ └─ `AdminLayout.vue` (管理端页面布局组件) 131 | │ │ 132 | │ ├─ `mock`(mock 模拟数据-文件夹) 133 | │ │ ├─ `apiController`(controller-文件夹) 134 | │ │ │ ├─ `role-management.js` 135 | │ │ │ ├─ `user-management.js` 136 | │ │ │ └─ `user.js` 137 | │ │ ├─ `index.js` (mock 主入口文件) 138 | │ │ └─ `mock.js` (mock 配置文件) 139 | │ │ 140 | │ ├─ `pages` 141 | │ │ ├─ `404`(404 页面-文件夹) 142 | │ │ │ └─ `index.vue` 143 | │ │ ├─ `home`(首页-文件夹) 144 | │ │ │ └─ `index.vue ` 145 | │ │ ├─ `login` (登录注册页面-文件夹) 146 | │ │ │ └─ `index.vue` 147 | │ │ ├─ `role-management`(角色管理页-文件夹) 148 | │ │ │ ├─ `components`(页面子组件-文件夹) 149 | │ │ │ │ └─ `RoleEdit.vue` 150 | │ │ │ └─ `index.vue` 151 | │ │ └─` user-management`(用户管理页-文件夹) 152 | │ │ ├─ `components` (页面子组件-文件夹) 153 | │ │ │ └─ `UserEdit.vue` 154 | │ │ └─ `index.vue` 155 | │ │ 156 | │ ├─ `router`(路由文件夹) 157 | │ │ └─ `index.js` vueRouter 主入口 158 | │ │ 159 | │ ├─ `store`(文件夹) 160 | │ │ ├─ `modules`(store 模块文件夹) 161 | │ │ │ ├─ `app.js` (app 配置模块) 162 | │ │ │ ├─ `options.js` (全局下拉值选项模块) 163 | │ │ │ └─ `tags-view.js` (页面标签切换模块) 164 | │ │ └─ `index.js` (store 主入口) 165 | │ │ 166 | │ ├─ `styles`(全局样式-文件夹) 167 | │ │ ├─ `animation.scss`(css 动画样式表)
168 | │ │ ├─ `color.scss`(颜色变量样式表)
169 | │ │ ├─ `custom-default-browser-style.scss` (自定义浏览器默认样式表)
170 | │ │ ├─ `elementui-variables.scss`(自定义 elementui 默认主题和样式表)
171 | │ │ ├─ `global.scss`(全局样式表) 172 | │ │ └─ `mixin.scss`(scss 的 mixin 样式表) 173 | │ │ 174 | │ ├─ `others`(其他-文件夹) 175 | │ │ ├─ `utils.js` (工具函数文件) 176 | │ │ ├─ `options.js` (下拉值文件) 177 | │ │ └─ `validator.js` (值校验函数文件) 178 | │ │ 179 | │ ├─ `App.vue` (项目主组件) 180 | │ └─ `main.js` (项目主入口) 181 | │ 182 | ├─ `index.html ` (页面挂载文件) 183 | ├─ `package.json` (npm 配置文件) 184 | ├─ `README.md` (项目文档) 185 | └─ `vite.config.js` (vite 配置文件) 186 |
187 | 188 | ## 关键点介绍 189 | 190 | - ##### 自定义 ElementUI 组件样式 191 | (修改主题方法可能会随着element版本而变化,此方法适用于<=ElementPlus1.0.2-beta.55版本) 192 | ​ 本模板已修改 elementUI 的默认主题色和部分表单组件样式,详细请参考`style`文件夹下的`elementui-variables.scss`样式文件。。 193 |
194 | ``` 195 | //修改 $--color-primary 变量可更换element主题色 196 | $--color-primary: #80cbc4; 197 | ``` 198 |
199 | 主题色1 200 |

主题色1

201 |
202 |
203 | 主题色2 204 |

主题色2

205 |

206 | 207 | - ##### 自定义 ElementUI 语言 208 | (修改语言方法可能会随着element版本而变化,此方法适用于<=ElementPlus1.0.2-beta.55版本)
209 | ``` 210 | // main.js 211 | import locale from "element-plus/lib/locale/lang/zh-cn"; 212 | app.use(ElementPlus, { locale }); 213 | ``` 214 |
215 | - ##### 重置浏览器默认样式 216 | 217 | ​ 请参考`style`文件夹下的`custom-default-browser-style.scss`文件,本文件修改浏览器默认滚动条样式。 218 | ​ 更多的浏览器样式重置则使用了 npm 插件`reset-css`,在项目主入口文件`main.js`中引入。 219 |

220 | 221 | - ##### useTableData 222 | 223 | ​ 自定义 hook,用于封装表格查询的可复用逻辑和操作,在需要表格查询操作的页面或组件中引入即可。详细请查看`hooks`文件夹下的`useTableData.js`文件。 224 | 225 | ``` 226 | //使用举例 227 | import useTableData from "@/hooks/useTableData.js"; 228 | 229 | //传入以Promise封装的http请求api 230 | const table = useTableData(fetchTableDataApi); 231 | 232 | //设置参数,发起请求 233 | table.setParams({ 234 | keyword: keyword, 235 | ... 236 | }); 237 | 238 | //重置请求 239 | table.reset(); 240 | 241 | //刷新请求 242 | table.refresh() 243 | 244 | //表格请求参数(默认字段 currentPage,pageSize ) 245 | console.log(table.tableParams); 246 | 247 | //表格分页器分页组配置(默认值 [20, 50, 100] ) 248 | console.log(table.PAGE_SIZES); 249 | 250 | //表格数据(默认字段 list,totalCount,isLoading ) 251 | console.log(table.tableData); 252 | ``` 253 | 254 | - ##### useWindowSize 与动态表格高度 255 | 256 | ​ 当固定表头时需要限制 elementUI 表格高度,数据则会表现为溢出滚动,如页面缩放的时候需要占满屏幕,则可监测浏览器高度`clientHeight`动态赋值表格高度,以达到缩放浏览器窗口表格大小不变的效果。 257 | ​ `useWindowRect`封装了监听浏览器宽高的可复用逻辑,此 hook 将动态的`clientHeight`和`clientWidth`值设置存储到 vuex 的`store/modules/app.js`文件内的`windowRect`字段,因此每个页面可引入动态的宽高值来设置表格高度。 258 |

259 | - ##### 菜单权限和动态路由 260 | ​如管理系统的权限是根据菜单管理页面和权限配置页面来设置的并且通过后端返回菜单区分账号权限,则前端可在登录时根据返回的菜单渲染侧边栏菜单和使用`VueRouter`的`router.addRoute`API 来动态添加路由。 261 | 动态添加路由会在浏览器刷新时丢失,可把菜单用`localStorage`缓存,每次刷新浏览器都动态添加一次路由即可解决。 262 | 退出登录时需要清除动态添加的路由,可使用 resetRouter 进行重置路由。 263 | ``` 264 | function resetRouter() { 265 | const newRouter = createRouter({ 266 | routes: constantRoute, 267 | history: createWebHashHistory(), 268 | }); 269 | router.matcher = newRouter.matcher; 270 | } 271 | ``` 272 |
273 | - ##### 菜单检索 274 | 275 | 根据后端返回的菜单结构的页面名称进行递归检索。 276 | 277 |
278 | 检索菜单 279 |

检索菜单

280 |
281 |
282 | - ##### 面包屑 283 | 284 | 最简单的处理方式就是根据后端返回的菜单设置个`breadcrumb`数组字段处理即可,如有其它需求可根据实际数据情况而定。 285 | 286 | ``` 287 | { 288 | id: "1", 289 | name: "系统管理", 290 | icon: "el-icon-setting", 291 | url: "/system", 292 | breadcrumb: ["系统管理"], 293 | type: "group", 294 | children: [{ 295 | id: "1-1", 296 | name: "用户管理", 297 | icon: "", 298 | type: "page", 299 | url: "/system/user-management", 300 | children: null, 301 | breadcrumb: ["系统管理", "用户管理"], 302 | }] 303 | } 304 | ``` 305 | 306 |
307 | 面包屑 308 |

面包屑

309 |
310 |
311 | - ##### 移动端适配 312 | 313 | 使用 CSS 媒体查询、Javascript的`clientWidth`客户端宽度检测来区分移动端或桌面端。管理端类型界面不建议在小屏设备进行操作。 314 |
全局设备类型字段(`/store/modules/app`中的`device`字段): 315 | 316 | - desktop:大于 750px(大屏设备) 317 | - mobile:小于 750px(小屏设备) 318 | 319 | 如有更多断点检测需求可自行在`app.vue`中设置。 320 |

321 | - ##### 系统设置 322 | 可配置化或用户自行选择。 323 |
324 | - 页面指示: 325 | 可选`标签切换`/`面包屑` 326 |
327 | - 页面状态缓存: 328 | 当页面指示为`标签切换`时可选页面切换时是否缓存页面状态,使用``组件缓存。 329 |
330 | - 菜单栏折叠。 331 | 332 |
333 |
334 | 系统设置 335 |

系统设置

336 |
337 |
338 | - ##### 右键自定义菜单与Teleport 339 | 当指示器为`标签切换`时,标签切换栏区域点击右键出现自定义菜单,可删除全部已打开的标签; 340 | 自定义右键菜单采用Vue3新特性`teleport`的方式加载到`body`元素里面,防止父元素定位干扰。 341 | 342 | ``` 343 | 右键自定义菜单 344 | 345 |
346 | xxx 347 |
348 |
349 | ``` 350 | 351 |
352 | 系统设置 353 |

系统设置

354 |
355 |
356 | 357 | - ##### http请求与axios全局请求配置 358 | 全局请求配置文件在`/src/http/axios.js`下配置;请求接口按页面模块进行分类,页面名称对应api接口文件名称,存放在`/src/http/apis/`文件夹下,再通过`/src/http/index.js`文件进行统一导出。 359 | ``` 360 | // http接口请求使用 361 | import http from "@/http"; 362 | http.[页面模块名称].xxx() 363 | 364 | 如 http.roleManagement.tableList() 365 | ``` 366 |
367 | 368 | - ##### Mockjs模拟数据 369 | 所有数据均来自本地mock模拟数据,位于`/src/mock`文件夹下。 370 | `/src/mock/mock.js`为mock全局配置
371 | `/src/mock/apiController`文件夹为所有模拟接口请求处理
372 |
373 | 在`/src/main.js`下引入mock文件即可使用接口,如`import "./mock/index";` 374 |

375 | - ##### 环境切换 376 | 根据项目的根目录下的`.env.xxx.`文件区分环境,以`VITE_`开头的字段可在`import.meta.env`中获取。如有更多需求可自行添加。 377 | + `.env.development`开发环境 378 | + `.env.production`正式环境 379 |

380 | - ##### Vite基本命令 381 | ``` 382 | // package.json 383 | "scripts": { 384 | "dev": "vite", //开启热更新服务器 385 | "build": "vite build", //打包项目 386 | "preview/dist": "vite preview" //本地预览打包的项目 387 | } 388 | ``` 389 |
390 | - ##### Vite基本配置 391 | ``` 392 | // vite.config.js配置文件 393 | 394 | import vue from "@vitejs/plugin-vue"; 395 | import { visualizer } from "rollup-plugin-visualizer"; 396 | import strip from "@rollup/plugin-strip"; 397 | import viteCompression from "vite-plugin-compression"; 398 | const path = require("path"); 399 | 400 | export default { 401 | base: './', 402 | plugins: [ 403 | vue(), 404 | //正式环境打包查看各文件大小占比 405 | visualizer({ 406 | open: true, 407 | gzipSize: true, 408 | brotliSize: true, 409 | }), 410 | //正式环境打包去除调试语句 411 | { 412 | ...strip({ 413 | include: ["**/*.js", "**/*.vue", "**/*.ts", "**/*.jsx"], 414 | }), 415 | apply: "build", 416 | }, 417 | //打包开启gzip压缩 418 | viteCompression(), 419 | ], 420 | resolve: { 421 | alias: { 422 | // 键必须以斜线开始和结束 423 | "@": path.resolve(__dirname, "./src"), 424 | }, 425 | }, 426 | css: { 427 | preprocessorOptions: { 428 | scss: { 429 | //添加scss全局变量样式 430 | additionalData: "@import './src/styles/global.scss';", 431 | }, 432 | }, 433 | }, 434 | server: { 435 | // 配置调试服务器主机名,如果允许外部访问,可设置为"0.0.0.0" 436 | host: "0.0.0.0", 437 | port: 3000, // 服务器端口号 438 | open: true, // 是否自动打开浏览器 439 | }, 440 | }; 441 | ``` 442 |
443 | ## 注意 444 | 445 | - Vite 工具打包不兼容任何版本的 IE 浏览器(和 Webpack 打包机制不同)。 446 | - Vue 3 不兼容IE浏览器。 447 | - 本模板的ElementPlus使用的是1.0.2-beta.55测试版,由于是测试版缘故,功能设置随时会变,故当更新到某个新版本时某些设置可能会不正确不生效,一切以Element官方文档为准! 448 |
449 |
450 |

By FEZIRO

451 | -------------------------------------------------------------------------------- /dist/assets/element-icons.9c88a535.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/element-icons.9c88a535.woff -------------------------------------------------------------------------------- /dist/assets/element-icons.de5eb258.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/element-icons.de5eb258.ttf -------------------------------------------------------------------------------- /dist/assets/index.0282d2bf.js: -------------------------------------------------------------------------------- 1 | var e=Object.defineProperty,a=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,t=Object.prototype.propertyIsEnumerable,o=(a,l,t)=>l in a?e(a,l,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[l]=t;import{r as n,b as i,e as r,f as d,h as s,g as m,m as c,A as p,u,w as f,o as b,v as g,c as h,C,D as y,E as k,G as w,J as v,z}from"./vendor.f21cd016.js";import{h as _}from"./index.ffebf55c.js";import{u as T}from"./useTableData.6da28c31.js";import"./index.710eea4b.js";const M={name:"RoleTableEdit",emits:["confirm"],setup(e,i){const r=n({type:"",title:"",visible:!1,confirmBtnLoading:!1}),d=n({form:{name:"",remark:"",permission:""}}),s=()=>{r.visible=!1,d.form={name:"",remark:"",permission:""}};return{modalConfig:r,data:d,menuTree:[{id:2,label:"系统管理",children:[{id:5,label:"用户管理"},{id:6,label:"角色管理"}]},{id:3,label:"数据统计"}],menuTreeDefaultProps:{children:"children",label:"label"},hideModal:s,showModal:(e={type:"add",title:"",data:{}})=>{r.type=e.type,r.visible=!0,"add"===e.type&&(r.title="添加"),"edit"===e.type&&(r.title="编辑"),r.title=e.title||"",e.data&&(d.form=((e,n)=>{for(var i in n||(n={}))l.call(n,i)&&o(e,i,n[i]);if(a)for(var i of a(n))t.call(n,i)&&o(e,i,n[i]);return e})({},e.data))},onModalConfirmClick:()=>{s(),i.emit("confirm")},onModalCloseClick:()=>{s()}}}},S={key:0,class:"edit-page"},P={class:"confirm-btn-group"},x=p("关闭"),E=p("保存");M.render=function(e,a,l,t,o,n){const p=i("el-page-header"),u=i("el-input"),f=i("el-form-item"),b=i("el-col"),g=i("el-tree"),h=i("el-button"),C=i("el-row"),y=i("el-form");return t.modalConfig.visible?(r(),d("div",S,[s(p,{onBack:t.onModalCloseClick,content:"添加角色"},null,8,["onBack"]),s(y,{size:"medium",model:t.data.form,"label-position":"top",style:{width:"50%",margin:"0 auto","margin-top":"20px"}},{default:m((()=>[s(C,{gutter:20},{default:m((()=>[s(b,{span:24},{default:m((()=>[s(f,{label:"角色名称:"},{default:m((()=>[s(u,{size:"medium",modelValue:t.data.form.name,"onUpdate:modelValue":a[1]||(a[1]=e=>t.data.form.name=e),placeholder:"请输入",clearable:"",style:{width:"100%"}},null,8,["modelValue"])])),_:1})])),_:1}),s(b,{span:24},{default:m((()=>[s(f,{label:"备注:"},{default:m((()=>[s(u,{type:"textarea",size:"medium",modelValue:t.data.form.reamrk,"onUpdate:modelValue":a[2]||(a[2]=e=>t.data.form.reamrk=e),placeholder:"请输入",clearable:"",style:{width:"100%"}},null,8,["modelValue"])])),_:1})])),_:1}),s(b,{span:24},{default:m((()=>[s(f,{label:"页面权限:"},{default:m((()=>[s(g,{data:t.menuTree,"show-checkbox":"","node-key":"id",props:t.menuTreeDefaultProps},null,8,["data","props"])])),_:1})])),_:1}),s(b,{span:24},{default:m((()=>[s("div",P,[s(h,{size:"medium",type:"danger",onClick:t.onModalCloseClick},{default:m((()=>[x])),_:1},8,["onClick"]),s(h,{size:"medium",type:"primary",onClick:t.onModalConfirmClick},{default:m((()=>[E])),_:1},8,["onClick"])])])),_:1})])),_:1})])),_:1},8,["model"])])):c("",!0)};const D={name:"role-management",components:{RoleEdit:M},setup(){const e=T(_.roleManagement.list),a=u();let l=n({keyword:""});f((()=>l.keyword),(()=>{e.setParams({keyword:l.keyword})}));b((()=>{e.setParams({keyword:l.keyword})}));const t=g([]),o=g(null);return{roleManagementTable:e,filterForm:l,tableSelection:t,roleEdit:o,onTableAddClick:()=>{o.value.showModal({type:"add",title:"添加"})},onTableEditClick:e=>{o.value.showModal({type:"edit",title:"添加",data:{id:e.id,name:e.roleName,remark:e.remark}})},onTableDeleteClick:()=>{},onTableSelectionChange:e=>{t.value=e},onSizeChange:a=>{e.setParams({pageSize:a})},onCurrentChange:a=>{e.setParams({currentPage:a})},windowRect:h((()=>a.state.app.windowRect)),device:h((()=>a.state.app.device))}}},V=z();C("data-v-6275459d");const j={class:"role-management-page","element-loading-text":"拼命加载中"},O=p("添加"),R=p("删除"),A=p("编辑"),B={class:"table-pager"};y();const F=V(((e,a,l,t,o,n)=>{const m=i("el-input"),c=i("el-button"),p=i("el-space"),u=i("el-table-column"),f=i("el-empty"),b=i("el-table"),g=i("el-pagination"),h=i("RoleEdit"),C=k("loading");return w((r(),d("div",j,[s(p,{wrap:"",style:{width:"100%"}},{default:V((()=>[s(m,{modelValue:t.filterForm.keyword,"onUpdate:modelValue":a[1]||(a[1]=e=>t.filterForm.keyword=e),class:"search-input","prefix-icon":"el-icon-search",size:"small",placeholder:"角色名称"},null,8,["modelValue"]),s(c,{type:"primary",size:"small",icon:"el-icon-circle-plus-outline",onClick:t.onTableAddClick},{default:V((()=>[O])),_:1},8,["onClick"]),s(c,{type:"danger",size:"small",icon:"el-icon-delete",disabled:0==t.tableSelection.length,onClick:t.onTableDeleteClick},{default:V((()=>[R])),_:1},8,["disabled","onClick"])])),_:1}),s(b,{data:t.roleManagementTable.tableData.list,"tooltip-effect":"dark",height:t.windowRect.clientHeight-250,"row-style":{height:"65px"},onSelectionChange:t.onTableSelectionChange},{empty:V((()=>[s(f,{description:"暂无数据"})])),default:V((()=>[s(u,{type:"selection",width:"50","show-overflow-tooltip":"","header-align":"center",align:"center"}),s(u,{prop:"roleName",label:"角色名称","min-width":"120","show-overflow-tooltip":""}),s(u,{prop:"remark",label:"备注","show-overflow-tooltip":""}),s(u,{label:"操作","show-overflow-tooltip":"","min-width":"95",fixed:"right"},{default:V((e=>[s(c,{type:"warning",plain:"",size:"small",onClick:a=>t.onTableEditClick(e.row)},{default:V((()=>[A])),_:2},1032,["onClick"])])),_:1})])),_:1},8,["data","height","onSelectionChange"]),s("div",B,[s(g,{onSizeChange:t.onSizeChange,onCurrentChange:t.onCurrentChange,"page-size":t.roleManagementTable.tableParams.pageSize,"page-sizes":t.roleManagementTable.PAGE_SIZES,layout:"total,prev,pager,next,"+("mobile"===t.device?"":"sizes"),total:t.roleManagementTable.tableData.totalCount,small:"mobile"===t.device},null,8,["onSizeChange","onCurrentChange","page-size","page-sizes","layout","total","small"])]),s(v,{name:"fade"},{default:V((()=>[s(h,{ref:"roleEdit"},null,512)])),_:1})],512)),[[C,t.roleManagementTable.tableData.isPageLoading]])}));D.render=F,D.__scopeId="data-v-6275459d";export{D as default}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.0282d2bf.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.0282d2bf.js.gz -------------------------------------------------------------------------------- /dist/assets/index.1cb7eec7.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";*{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}:focus{outline:0}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active,.fade-leave-active{transition:all .3s ease}.fade-enter-from,.fade-leave-to{opacity:0;transform:translateY(-1000px)}@keyframes slideUp{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar{background:#36ad6a;height:3px}body{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.login-page{width:100%;height:100vh;background:#f6f7f9;display:flex;justify-content:center;align-items:center}.login-page .panel-container{position:relative;display:flex;justify-content:center;align-items:center;width:auto}.login-page .panel-container .login-intro{width:500px;height:500px;flex-shrink:0;background-color:#36ad6a;padding:50px;color:#fff!important;box-sizing:border-box;padding-right:60px;border-top-left-radius:6px;border-bottom-left-radius:6px}@media screen and (max-width:750px){.login-page .panel-container .login-intro{display:none}}.login-page .panel-container .login-intro>img{width:100%;height:100%}.login-page .panel-container .login-intro .intro-subtitle,.login-page .panel-container .login-intro .intro-title{text-align:left}.login-page .panel-container .login-intro .intro-title{font-weight:700;font-size:22px}.login-page .panel-container .login-intro .intro-subtitle{font-size:14px;margin-top:10px}.login-page .panel-container .login-panel{width:400px;min-height:500px;padding:50px;background-color:#fff;box-sizing:border-box;transition:all .5s;flex-shrink:0;border-top-right-radius:6px;border-bottom-right-radius:6px}@media screen and (max-width:750px){.login-page .panel-container .login-panel{width:90vw;padding:50px 30px}}.login-page .panel-container .login-panel h1{text-align:center;font-size:24px;width:100%;margin:0 auto}.login-page .panel-container .login-panel .login-form{margin-top:40px}.login-page .panel-container .login-panel .login-form .login-btn{width:100%;height:40px;margin-top:30px}.login-page .panel-container .login-panel .login-form .form-bottom{margin-top:10px;display:flex;justify-content:space-between;align-items:center}.login-page .panel-container .code-by{position:absolute;bottom:-50px;left:50%;transform:translateX(-50%);color:#ccc} -------------------------------------------------------------------------------- /dist/assets/index.1cb7eec7.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.1cb7eec7.css.gz -------------------------------------------------------------------------------- /dist/assets/index.3d6b04f8.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";*{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}:focus{outline:0}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active,.fade-leave-active{transition:all .3s ease}.fade-enter-from,.fade-leave-to{opacity:0;transform:translateY(-1000px)}@keyframes slideUp{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar{background:#36ad6a;height:3px}body{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.edit-page{background:#fff;position:absolute;left:0;right:0;bottom:0;top:0;z-index:10;overflow:auto;box-sizing:border-box;padding:20px}.edit-page .confirm-btn-group{margin-top:100px;text-align:right}.edit-page .el-form-item__label{width:100%;font-weight:700}[data-v-6275459d]{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}[data-v-6275459d]:focus{outline:0}[data-v-6275459d]::-webkit-scrollbar{width:8px;height:8px}[data-v-6275459d]::-webkit-scrollbar-track{background:0 0}[data-v-6275459d]::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}[data-v-6275459d]::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active[data-v-6275459d],.fade-leave-active[data-v-6275459d]{transition:all .3s ease}.fade-enter-from[data-v-6275459d],.fade-leave-to[data-v-6275459d]{opacity:0;transform:translateY(-1000px)}@keyframes slideUp-6275459d{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown-6275459d{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn-6275459d{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut-6275459d{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar[data-v-6275459d]{background:#36ad6a;height:3px}body[data-v-6275459d]{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.role-management-page[data-v-6275459d]{width:100%;box-sizing:border-box;padding:10px;background:#fff;overflow:auto;position:relative}.role-management-page .table-pager[data-v-6275459d]{width:100%;display:flex;justify-content:center;align-items:center;padding:20px 0;box-sizing:border-box} -------------------------------------------------------------------------------- /dist/assets/index.3d6b04f8.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.3d6b04f8.css.gz -------------------------------------------------------------------------------- /dist/assets/index.710eea4b.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.710eea4b.js.gz -------------------------------------------------------------------------------- /dist/assets/index.92421829.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.92421829.css.gz -------------------------------------------------------------------------------- /dist/assets/index.930d31c9.js: -------------------------------------------------------------------------------- 1 | import{d as e,v as l,a as o,b as a,e as t,f as n,g as s,A as i,y as u,q as r,u as d,r as c,c as m,I as p,h as g,G as f,L as C,m as v}from"./vendor.f21cd016.js";import{h as b}from"./index.ffebf55c.js";import{c as y}from"./index.710eea4b.js";const h=e({name:"CountDownBtn",props:{defaultTime:{type:Number,default:10},defaultText:{type:String,default:"获取验证码"}},setup(e,a){let t=l(0);t.value=e.defaultTime;let n=l(!1),s=l("");s.value=e.defaultText;let i=null;return o((()=>{i&&clearInterval(i)})),{onGetCodeClick:()=>{a.emit("getCode"),n.value=!0,s.value=t.value+"s后获取",i=setInterval((()=>{--t.value,s.value=t.value+"s后获取",0==t.value&&(s.value=e.defaultText,t.value=e.defaultTime,n.value=!1,clearInterval(i))}),1e3)},waitingCodeTips:s,disableBtn:n}}});h.render=function(e,l,o,r,d,c){const m=a("el-button");return t(),n(m,{type:"primary",disabled:e.disableBtn,onClick:e.onGetCodeClick},{default:s((()=>[i(u(e.waitingCodeTips),1)])),_:1},8,["disabled","onClick"])};const k={name:"login",components:{CountDownBtn:h},setup(){const e=r(),o=d(),a=l(!0),t=c({account:"",password:""});let n=l(!1);const s=c({phone:"",password:"",code:""});return{loginForm:t,loginImg:"./assets/login.a35c9f52.svg",loginStatus:a,loginBtnLoading:n,registerForm:s,onRegisterClick:()=>{},onLoginClick:async()=>{n.value=!0;let l=await b.user.login({username:t.account,password:t.password}).catch((()=>{n.value=!1}));n.value=!1,l?(l.menu.unshift({name:"首页",icon:"el-icon-s-home",type:"page",url:"/home",children:null,path:[],tagsViewAffix:!0,breadcrumb:["首页"]}),localStorage.setItem("userInfo",JSON.stringify(l.userInfo)),localStorage.setItem("menu",JSON.stringify(l.menu)),y(l.menu),p.success("登录成功!"),e.push("/home")):p.error("登录失败!")},onGetCodeClick:()=>{},onTogglePanelStatus:()=>{a.value=!a.value},appName:m((()=>o.state.app.appName)),device:m((()=>o.state.app.device))}}},w={class:"login-page"},_={class:"panel-container"},V={class:"login-intro"},F={class:"intro-title"},S=g("p",{class:"intro-subtitle"},"欢迎登录!",-1),x={class:"login-panel"},T={key:0,style:{"margin-bottom":"10px"}},I=i(" 登录 "),B={class:"form-bottom"},G=i(" 注册 "),N=i(" 第三方登录 "),L=i(" 注册 "),U={class:"form-bottom"},j=i(" 密码登录 "),D=i(" 第三方登录 "),O=g("span",{class:"code-by"},"Code By FEZIRO",-1);k.render=function(e,l,o,i,r,d){const c=a("el-input"),m=a("el-form-item"),p=a("el-button"),b=a("el-form"),y=a("CountDownBtn"),h=a("el-space");return t(),n("div",w,[g("div",_,[f(g("section",V,[g("h1",F,u(i.appName),1),S,g("img",{src:i.loginImg,alt:"login"},null,8,["src"])],512),[[C,i.loginStatus]]),g("section",x,["mobile"==i.device?(t(),n("h1",T,u(i.appName),1)):v("",!0),g("h1",null,u(i.loginStatus?"登录":"注册"),1),f(g(b,{model:i.loginForm,class:"login-form","label-position":"top"},{default:s((()=>[g(m,{label:"账号"},{default:s((()=>[g(c,{placeholder:"请输入账号",modelValue:i.loginForm.account,"onUpdate:modelValue":l[1]||(l[1]=e=>i.loginForm.account=e)},null,8,["modelValue"])])),_:1}),g(m,{label:"密码"},{default:s((()=>[g(c,{type:"password",placeholder:"请输入密码",modelValue:i.loginForm.password,"onUpdate:modelValue":l[2]||(l[2]=e=>i.loginForm.password=e)},null,8,["modelValue"])])),_:1}),g(m,null,{default:s((()=>[g(p,{type:"primary",class:"login-btn",onClick:i.onLoginClick,loading:i.loginBtnLoading},{default:s((()=>[I])),_:1},8,["onClick","loading"]),g("div",B,[g(p,{type:"text",onClick:i.onTogglePanelStatus},{default:s((()=>[G])),_:1},8,["onClick"]),g(p,{type:"text"},{default:s((()=>[N])),_:1})])])),_:1})])),_:1},8,["model"]),[[C,i.loginStatus]]),f(g(b,{model:i.registerForm,class:"login-form","label-position":"top"},{default:s((()=>[g(m,{label:"手机号码",name:"phone"},{default:s((()=>[g(c,{placeholder:"请输入手机号码",modelValue:i.registerForm.phone,"onUpdate:modelValue":l[3]||(l[3]=e=>i.registerForm.phone=e)},null,8,["modelValue"])])),_:1}),g(m,{label:"密码"},{default:s((()=>[g(c,{type:"password",placeholder:"请设置密码",modelValue:i.registerForm.password,"onUpdate:modelValue":l[4]||(l[4]=e=>i.registerForm.password=e)},null,8,["modelValue"])])),_:1}),g(m,{label:"验证码"},{default:s((()=>[g(h,null,{default:s((()=>[g(c,{placeholder:"请输入验证码",modelValue:i.registerForm.code,"onUpdate:modelValue":l[5]||(l[5]=e=>i.registerForm.code=e)},null,8,["modelValue"]),g(y,{defaultTime:4,onGetCode:i.onGetCodeClick},null,8,["onGetCode"])])),_:1})])),_:1}),g(m,null,{default:s((()=>[g(p,{type:"primary",class:"login-btn",onClick:i.onRegisterClick},{default:s((()=>[L])),_:1},8,["onClick"]),g("div",U,[g(p,{type:"text",onClick:i.onTogglePanelStatus},{default:s((()=>[j])),_:1},8,["onClick"]),g(p,{type:"text"},{default:s((()=>[D])),_:1})])])),_:1})])),_:1},8,["model"]),[[C,!i.loginStatus]])]),O])])};export{k as default}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.930d31c9.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.930d31c9.js.gz -------------------------------------------------------------------------------- /dist/assets/index.b06ca65d.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";[data-v-4898c2bf]{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}[data-v-4898c2bf]:focus{outline:0}[data-v-4898c2bf]::-webkit-scrollbar{width:8px;height:8px}[data-v-4898c2bf]::-webkit-scrollbar-track{background:0 0}[data-v-4898c2bf]::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}[data-v-4898c2bf]::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active[data-v-4898c2bf],.fade-leave-active[data-v-4898c2bf]{transition:all .3s ease}.fade-enter-from[data-v-4898c2bf],.fade-leave-to[data-v-4898c2bf]{opacity:0;transform:translateY(-1000px)}@keyframes slideUp-4898c2bf{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown-4898c2bf{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn-4898c2bf{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut-4898c2bf{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar[data-v-4898c2bf]{background:#36ad6a;height:3px}body[data-v-4898c2bf]{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.avatar-uploader[data-v-4898c2bf]{margin-bottom:30px;text-align:center}.avatar-uploader .avatar-upload-wrap[data-v-4898c2bf]{position:relative;height:100px;border-radius:100%;overflow:hidden}.avatar-uploader .avatar-upload-wrap .uploader-bottom[data-v-4898c2bf]{width:100%;padding:4px 0;background:rgba(0,0,0,.4);position:absolute;z-index:99;bottom:0;left:50%;transform:translateX(-50%);text-align:center}.avatar-uploader .avatar-upload-wrap .uploader-bottom .icon-upload[data-v-4898c2bf]{font-size:16px;color:#fff}[data-v-0e832da3]{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}[data-v-0e832da3]:focus{outline:0}[data-v-0e832da3]::-webkit-scrollbar{width:8px;height:8px}[data-v-0e832da3]::-webkit-scrollbar-track{background:0 0}[data-v-0e832da3]::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}[data-v-0e832da3]::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active[data-v-0e832da3],.fade-leave-active[data-v-0e832da3]{transition:all .3s ease}.fade-enter-from[data-v-0e832da3],.fade-leave-to[data-v-0e832da3]{opacity:0;transform:translateY(-1000px)}@keyframes slideUp-0e832da3{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown-0e832da3{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn-0e832da3{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut-0e832da3{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar[data-v-0e832da3]{background:#36ad6a;height:3px}body[data-v-0e832da3]{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.user-management-page[data-v-0e832da3]{width:100%;box-sizing:border-box;padding:10px;background:#fff;overflow:auto;position:relative}.user-management-page .table-pager[data-v-0e832da3]{width:100%;display:flex;justify-content:center;align-items:center;padding:20px 0;box-sizing:border-box} -------------------------------------------------------------------------------- /dist/assets/index.b06ca65d.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.b06ca65d.css.gz -------------------------------------------------------------------------------- /dist/assets/index.cc670981.js: -------------------------------------------------------------------------------- 1 | import{u as s,C as a,D as e,b as t,e as p,f as c,h as o,y as i,z as d}from"./vendor.f21cd016.js";const m={name:"home",setup:()=>({appName:s().state.app.appName})},r=d();a("data-v-657f9024");const l=o("div",{class:"home-img"},[o("img",{src:"./assets/home.155038bb.svg"})],-1),n={class:"tips"},v=o("div",{class:"tips-title"},"欢迎登陆!",-1),f={class:"tips-subtitle"};e();const u=r(((s,a,e,d,m,u)=>{const h=t("el-space");return p(),c(h,{wrap:"",class:"home-container"},{default:r((()=>[l,o("div",n,[v,o("div",f,i(d.appName),1)])])),_:1})}));m.render=u,m.__scopeId="data-v-657f9024";export{m as default}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.d41203df.js: -------------------------------------------------------------------------------- 1 | var e=Object.defineProperty,l=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,t=(l,a,o)=>a in l?e(l,a,{enumerable:!0,configurable:!0,writable:!0,value:o}):l[a]=o;import{u as i}from"./useTableData.6da28c31.js";import{C as n,D as s,u as d,r,b as m,e as u,f as p,h as c,F as f,x as b,z as h,A as g,w,o as y,v as C,c as v,E as k,G as _,y as x}from"./vendor.f21cd016.js";import{a as z}from"./index.710eea4b.js";import{h as V}from"./index.ffebf55c.js";const T=[{id:1,name:"普通人员"},{id:2,name:"管理员"}];z(T,{keyName:"id"});const M=[{id:1,name:"男"},{id:2,name:"女"}];z(M,{keyName:"id"});const S={name:"UserEdit",emits:["confirm"],setup(e,i){d();const n=r({type:"",title:"",visible:!1,confirmBtnLoading:!1}),s=r({form:{userName:"",sex:"",phone:"",role:""}}),m=()=>{n.visible=!1,s.form={userName:"",sex:"",phone:"",role:""}};return{modalConfig:n,data:s,sexOptions:M,roleOptions:T,hideModal:m,showModal:(e={type:"add",title:"",data:{}})=>{n.type=e.type,n.visible=!0,"add"===e.type&&(n.title="添加"),"edit"===e.type&&(n.title="编辑"),n.title=e.title||"",e.data&&(s.form=((e,i)=>{for(var n in i||(i={}))a.call(i,n)&&t(e,n,i[n]);if(l)for(var n of l(i))o.call(i,n)&&t(e,n,i[n]);return e})({},e.data))},onModalConfirmClick:()=>{m(),i.emit("confirm")},onModalCloseClick:()=>{m()}}}},E=h();n("data-v-4898c2bf");const O={class:"avatar-upload-wrap"},P=c("div",{class:"uploader-bottom"},[c("i",{class:"icon-upload el-icon-camera"})],-1),U=g("取 消"),j=g("确 定");s();const N=E(((e,l,a,o,t,i)=>{const n=m("el-avatar"),s=m("el-upload"),d=m("el-col"),r=m("el-input"),h=m("el-form-item"),g=m("el-option"),w=m("el-select"),y=m("el-row"),C=m("el-form"),v=m("el-button"),k=m("el-dialog");return u(),p(k,{title:o.modalConfig.title,modelValue:o.modalConfig.visible,"onUpdate:modelValue":l[5]||(l[5]=e=>o.modalConfig.visible=e),width:"40%","close-on-click-modal":!1},{footer:E((()=>[c(v,{onClick:o.onModalCloseClick,size:"medium"},{default:E((()=>[U])),_:1},8,["onClick"]),c(v,{type:"primary",onClick:o.onModalConfirmClick,size:"medium",loading:o.modalConfig.confirmBtnLoading},{default:E((()=>[j])),_:1},8,["onClick","loading"])])),default:E((()=>[c(C,{size:"medium",style:{"min-height":"200px"},"label-width":"100px","label-position":"top"},{default:E((()=>[c(y,{gutter:20},{default:E((()=>[c(d,{span:24},{default:E((()=>[c(s,{class:"avatar-uploader",action:"https://jsonplaceholder.typicode.com/posts/","show-file-list":!1},{default:E((()=>[c("div",O,[c(n,{size:100,style:{"margin-bottom":"30px"},src:"https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"}),P])])),_:1})])),_:1}),c(d,{span:12},{default:E((()=>[c(h,{label:"用户名:"},{default:E((()=>[c(r,{size:"medium",modelValue:o.data.form.userName,"onUpdate:modelValue":l[1]||(l[1]=e=>o.data.form.userName=e),placeholder:"请输入用户名",clearable:"",style:{width:"100%"}},null,8,["modelValue"])])),_:1})])),_:1}),c(d,{span:12},{default:E((()=>[c(h,{label:"性别:"},{default:E((()=>[c(w,{modelValue:o.data.form.sex,"onUpdate:modelValue":l[2]||(l[2]=e=>o.data.form.sex=e),placeholder:"请选择性别",clearable:"",size:"medium",style:{width:"100%"}},{default:E((()=>[(u(!0),p(f,null,b(o.sexOptions,(e=>(u(),p(g,{key:e.id,label:e.name,value:e.id},null,8,["label","value"])))),128))])),_:1},8,["modelValue"])])),_:1})])),_:1}),c(d,{span:12},{default:E((()=>[c(h,{label:"联系电话:"},{default:E((()=>[c(r,{modelValue:o.data.form.phone,"onUpdate:modelValue":l[3]||(l[3]=e=>o.data.form.phone=e),placeholder:"请输入联系电话",size:"medium",style:{width:"100%"}},null,8,["modelValue"])])),_:1})])),_:1}),c(d,{span:12},{default:E((()=>[c(h,{label:"角色:"},{default:E((()=>[c(w,{modelValue:o.data.form.role,"onUpdate:modelValue":l[4]||(l[4]=e=>o.data.form.role=e),placeholder:"请选择角色",size:"medium",style:{width:"100%"}},{default:E((()=>[(u(!0),p(f,null,b(o.roleOptions,(e=>(u(),p(g,{key:e.id,label:e.name,value:e.id},null,8,["label","value"])))),128))])),_:1},8,["modelValue"])])),_:1})])),_:1})])),_:1})])),_:1})])),_:1},8,["title","modelValue"])}));S.render=N,S.__scopeId="data-v-4898c2bf";const D={name:"user-management",components:{UserEdit:S},setup(){const e=d(),l=i(V.userManagement.list);let a=r({keyword:"",sex:""});w([()=>a.keyword,()=>a.sex],(()=>{l.setParams({keyword:a.keyword,sex:a.sex})}));y((()=>{l.setParams({keyword:a.keyword})}));const o=C([]),t=C(null);return{filterForm:a,userManagementTable:l,tableSelection:o,userTableEdit:t,onTableAddClick:()=>{t.value.showModal({type:"add",title:"添加"})},onTableEditClick:e=>{t.value.showModal({type:"edit",title:"添加",data:{userName:e.userName,sex:e.sex,phone:e.phone,role:e.role}})},onTableDeleteClick:()=>{},onTableSelectionChange:e=>{o.value=e},onSizeChange:e=>{l.setParams({pageSize:e})},onCurrentChange:e=>{l.setParams({currentPage:e})},windowRect:v((()=>e.state.app.windowRect)),device:v((()=>e.state.app.device))}}},F=h();n("data-v-0e832da3");const A={class:"user-management-page","element-loading-text":"拼命加载中"},I=g("添加"),L=g("删除"),R=g("编辑"),B=g(" 暂无数据 "),G={class:"table-pager"};s();const H=F(((e,l,a,o,t,i)=>{const n=m("el-input"),s=m("el-option"),d=m("el-select"),r=m("el-button"),f=m("el-space"),b=m("el-table-column"),h=m("el-avatar"),w=m("el-tag"),y=m("el-table"),C=m("el-pagination"),v=m("UserEdit"),z=k("loading");return _((u(),p("div",A,[c(f,{wrap:"",style:{width:"100%"}},{default:F((()=>[c(n,{modelValue:o.filterForm.keyword,"onUpdate:modelValue":l[1]||(l[1]=e=>o.filterForm.keyword=e),class:"search-input","prefix-icon":"el-icon-search",size:"small",placeholder:"用户名/姓名",clearable:"",style:{width:"200px"}},null,8,["modelValue"]),c(d,{style:{width:"100px"},modelValue:o.filterForm.sex,"onUpdate:modelValue":l[2]||(l[2]=e=>o.filterForm.sex=e),class:"search-input","prefix-icon":"el-icon-search",size:"small",placeholder:"性别",clearable:""},{default:F((()=>[c(s,{label:"男",value:"1"}),c(s,{label:"女",value:"2"})])),_:1},8,["modelValue"]),c(r,{type:"primary",size:"small",icon:"el-icon-circle-plus-outline",onClick:o.onTableAddClick},{default:F((()=>[I])),_:1},8,["onClick"]),c(r,{type:"danger",size:"small",icon:"el-icon-delete",disabled:0==o.tableSelection.length,onClick:o.onTableDeleteClick},{default:F((()=>[L])),_:1},8,["disabled","onClick"])])),_:1}),c(y,{data:o.userManagementTable.tableData.list,"tooltip-effect":"dark",height:o.windowRect.clientHeight-240,"row-style":{height:"65px"},onSelectionChange:o.onTableSelectionChange},{empty:F((()=>[B])),default:F((()=>[c(b,{type:"selection",width:"50","show-overflow-tooltip":"","header-align":"center",align:"center"}),c(b,{label:"头像",width:"80","show-overflow-tooltip":""},{default:F((e=>[c(h,{size:32,src:e.row.img},null,8,["src"])])),_:1}),c(b,{prop:"userName",label:"用户名","show-overflow-tooltip":""}),c(b,{prop:"sex",label:"性别","show-overflow-tooltip":""}),c(b,{label:"角色","show-overflow-tooltip":""},{default:F((e=>[c(w,{effect:"dark",size:"small"},{default:F((()=>[g(x(e.row.role),1)])),_:2},1024)])),_:1}),c(b,{prop:"phone",label:"联系电话","min-width":"120","show-overflow-tooltip":""}),c(b,{prop:"loginTime",label:"登录次数","show-overflow-tooltip":""}),c(b,{prop:"lastLoginTime",label:"最后登录时间","min-width":"200","show-overflow-tooltip":""}),c(b,{label:"操作","show-overflow-tooltip":"","min-width":"95",fixed:"right"},{default:F((e=>[c(r,{type:"warning",size:"small",onClick:l=>o.onTableEditClick(e.row)},{default:F((()=>[R])),_:2},1032,["onClick"])])),_:1})])),_:1},8,["data","height","onSelectionChange"]),c("div",G,[c(C,{onSizeChange:o.onSizeChange,onCurrentChange:o.onCurrentChange,"page-size":o.userManagementTable.tableParams.pageSize,"page-sizes":o.userManagementTable.PAGE_SIZES,layout:"total,prev,pager,next,"+("mobile"===o.device?"":"sizes"),total:o.userManagementTable.tableData.totalCount,small:"mobile"===o.device},null,8,["onSizeChange","onCurrentChange","page-size","page-sizes","layout","total","small"])]),c(v,{ref:"userTableEdit",onConfirm:o.userManagementTable.refresh},null,8,["onConfirm"])],512)),[[z,o.userManagementTable.tableData.isLoading]])}));D.render=H,D.__scopeId="data-v-0e832da3";export{D as default}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.d41203df.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.d41203df.js.gz -------------------------------------------------------------------------------- /dist/assets/index.e46c323d.js: -------------------------------------------------------------------------------- 1 | import{d as e,q as t,b as a,e as s,f as r,g as n,h as o,A as i}from"./vendor.f21cd016.js";const c=e({name:"404",setup(){const e=t();return{onNavigateBack:()=>{e.back()}}}}),l=i("返回");c.render=function(e,t,i,c,u,d){const f=a("el-button"),m=a("el-result");return s(),r(m,{icon:"error",title:"404",subTitle:"页面不存在!",style:{width:"100%",height:"100vh"}},{extra:n((()=>[o(f,{type:"primary",size:"medium",onClick:e.onNavigateBack},{default:n((()=>[l])),_:1},8,["onClick"])])),_:1})};export{c as default}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.efa6b091.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";[data-v-657f9024]{scrollbar-color:rgba(0,0,0,0.3) transparent;scrollbar-width:thin}[data-v-657f9024]:focus{outline:0}[data-v-657f9024]::-webkit-scrollbar{width:8px;height:8px}[data-v-657f9024]::-webkit-scrollbar-track{background:0 0}[data-v-657f9024]::-webkit-scrollbar-thumb{background-color:rgba(153,153,153,.5);background-clip:padding-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;transition:all 1s;cursor:pointer}[data-v-657f9024]::-webkit-scrollbar-thumb:hover{background-color:#999}.fade-enter-active[data-v-657f9024],.fade-leave-active[data-v-657f9024]{transition:all .3s ease}.fade-enter-from[data-v-657f9024],.fade-leave-to[data-v-657f9024]{opacity:0;transform:translateY(-1000px)}@keyframes slideUp-657f9024{0%{opacity:0;transform:translateY(60px)}100%{opacity:1;transform:translateY(0)}}@keyframes upAndDown-657f9024{0%{transform:translateY(0)}50%{transform:translateY(10px)}100%{transform:translateY(0)}}@keyframes sideBarSlipIn-657f9024{0%{transform:translateX(0)}100%{transform:translateX(-500px)}}@keyframes sideBarSlipOut-657f9024{0%{transform:translateX(-500px)}100%{transform:translateX(0)}}#nprogress .bar[data-v-657f9024]{background:#36ad6a;height:3px}body[data-v-657f9024]{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#000;width:100%;height:100%}.home-container[data-v-657f9024]{display:flex;align-items:center;justify-content:center;box-sizing:border-box;flex-wrap:wrap;height:100%;width:100%;background:#fff}.home-container .home-img[data-v-657f9024]{width:400px;height:auto;padding:0 50px;box-sizing:border-box;animation:upAndDown-657f9024 2s infinite linear}.home-container .home-img>img[data-v-657f9024]{transform:rotateY(180deg)}.home-container .tips[data-v-657f9024]{position:relative;overflow:hidden;display:flex;flex-direction:column;text-align:center;margin:0 50px}.home-container .tips .tips-title[data-v-657f9024]{margin-bottom:30px;font-size:40px;font-weight:700;line-height:40px;color:#000;opacity:0;animation-name:slideUp-657f9024;animation-duration:.5s;animation-fill-mode:forwards}.home-container .tips .tips-subtitle[data-v-657f9024]{margin-bottom:30px;font-size:25px;line-height:36px;color:rgba(0,0,0,.8);opacity:0;animation-name:slideUp-657f9024;animation-duration:.5s;animation-delay:.2s;animation-fill-mode:forwards} -------------------------------------------------------------------------------- /dist/assets/index.efa6b091.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.efa6b091.css.gz -------------------------------------------------------------------------------- /dist/assets/index.ffebf55c.js: -------------------------------------------------------------------------------- 1 | var e={exports:{}},t=function(e,t){return function(){for(var r=new Array(arguments.length),n=0;n=0)return;o[t]="set-cookie"===t?(o[t]?o[t]:[]).concat([r]):o[t]?o[t]+", "+r:r}})),o):o},_=A,D=S,F=function(e){return new Promise((function(t,r){var n=e.data,o=e.headers;U.isFormData(n)&&delete o["Content-Type"];var s=new XMLHttpRequest;if(e.auth){var a=e.auth.username||"",i=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";o.Authorization="Basic "+btoa(a+":"+i)}var u=k(e.baseURL,e.url);if(s.open(e.method.toUpperCase(),B(u,e.params,e.paramsSerializer),!0),s.timeout=e.timeout,s.onreadystatechange=function(){if(s&&4===s.readyState&&(0!==s.status||s.responseURL&&0===s.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in s?q(s.getAllResponseHeaders()):null,o={data:e.responseType&&"text"!==e.responseType?s.response:s.responseText,status:s.status,statusText:s.statusText,headers:n,config:e,request:s};L(t,r,o),s=null}},s.onabort=function(){s&&(r(D("Request aborted",e,"ECONNABORTED",s)),s=null)},s.onerror=function(){r(D("Network Error",e,null,s)),s=null},s.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),r(D(t,e,"ECONNABORTED",s)),s=null},U.isStandardBrowserEnv()){var c=(e.withCredentials||_(u))&&e.xsrfCookieName?N.read(e.xsrfCookieName):void 0;c&&(o[e.xsrfHeaderName]=c)}if("setRequestHeader"in s&&U.forEach(o,(function(e,t){void 0===n&&"content-type"===t.toLowerCase()?delete o[t]:s.setRequestHeader(t,e)})),U.isUndefined(e.withCredentials)||(s.withCredentials=!!e.withCredentials),e.responseType)try{s.responseType=e.responseType}catch(f){if("json"!==e.responseType)throw f}"function"==typeof e.onDownloadProgress&&s.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&s.upload&&s.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){s&&(s.abort(),r(e),s=null)})),n||(n=null),s.send(n)}))},M=f,z=function(e,t){w.forEach(e,(function(r,n){n!==t&&n.toUpperCase()===t.toUpperCase()&&(e[t]=r,delete e[n])}))},H={"Content-Type":"application/x-www-form-urlencoded"};function I(e,t){!M.isUndefined(e)&&M.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var X,$={adapter:(("undefined"!=typeof XMLHttpRequest||"undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process))&&(X=F),X),transformRequest:[function(e,t){return z(t,"Accept"),z(t,"Content-Type"),M.isFormData(e)||M.isArrayBuffer(e)||M.isBuffer(e)||M.isStream(e)||M.isFile(e)||M.isBlob(e)?e:M.isArrayBufferView(e)?e.buffer:M.isURLSearchParams(e)?(I(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):M.isObject(e)?(I(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(t){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(e){return e>=200&&e<300}};$.headers={common:{Accept:"application/json, text/plain, */*"}},M.forEach(["delete","get","head"],(function(e){$.headers[e]={}})),M.forEach(["post","put","patch"],(function(e){$.headers[e]=M.merge(H)}));var J=$,V=f,K=function(e,t,r){return y.forEach(r,(function(r){e=r(e,t)})),e},G=v,Q=J;function W(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var Y=f,Z=function(e,t){t=t||{};var r={},n=["url","method","data"],o=["headers","auth","proxy","params"],s=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],a=["validateStatus"];function i(e,t){return Y.isPlainObject(e)&&Y.isPlainObject(t)?Y.merge(e,t):Y.isPlainObject(t)?Y.merge({},t):Y.isArray(t)?t.slice():t}function u(n){Y.isUndefined(t[n])?Y.isUndefined(e[n])||(r[n]=i(void 0,e[n])):r[n]=i(e[n],t[n])}Y.forEach(n,(function(e){Y.isUndefined(t[e])||(r[e]=i(void 0,t[e]))})),Y.forEach(o,u),Y.forEach(s,(function(n){Y.isUndefined(t[n])?Y.isUndefined(e[n])||(r[n]=i(void 0,e[n])):r[n]=i(void 0,t[n])})),Y.forEach(a,(function(n){n in t?r[n]=i(e[n],t[n]):n in e&&(r[n]=i(void 0,e[n]))}));var c=n.concat(o).concat(s).concat(a),f=Object.keys(e).concat(Object.keys(t)).filter((function(e){return-1===c.indexOf(e)}));return Y.forEach(f,u),r},ee=f,te=p,re=g,ne=function(e){return W(e),e.headers=e.headers||{},e.data=K(e.data,e.headers,e.transformRequest),e.headers=V.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),V.forEach(["delete","get","head","post","put","patch","common"],(function(t){delete e.headers[t]})),(e.adapter||Q.adapter)(e).then((function(t){return W(e),t.data=K(t.data,t.headers,e.transformResponse),t}),(function(t){return G(t)||(W(e),t&&t.response&&(t.response.data=K(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)}))},oe=Z;function se(e){this.defaults=e,this.interceptors={request:new re,response:new re}}se.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{}).url=arguments[0]:e=e||{},(e=oe(this.defaults,e)).method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[ne,void 0],r=Promise.resolve(e);for(this.interceptors.request.forEach((function(e){t.unshift(e.fulfilled,e.rejected)})),this.interceptors.response.forEach((function(e){t.push(e.fulfilled,e.rejected)}));t.length;)r=r.then(t.shift(),t.shift());return r},se.prototype.getUri=function(e){return e=oe(this.defaults,e),te(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},ee.forEach(["delete","get","head","options"],(function(e){se.prototype[e]=function(t,r){return this.request(oe(r||{},{method:e,url:t,data:(r||{}).data}))}})),ee.forEach(["post","put","patch"],(function(e){se.prototype[e]=function(t,r,n){return this.request(oe(n||{},{method:e,url:t,data:r}))}}));var ae=se;function ie(e){this.message=e}ie.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},ie.prototype.__CANCEL__=!0;var ue=ie,ce=ue;function fe(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise((function(e){t=e}));var r=this;e((function(e){r.reason||(r.reason=new ce(e),t(r.reason))}))}fe.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},fe.source=function(){var e;return{token:new fe((function(t){e=t})),cancel:e}};var de=fe,le=f,pe=t,he=ae,me=Z;function ge(e){var t=new he(e),r=pe(he.prototype.request,t);return le.extend(r,he.prototype,t),le.extend(r,t),r}var ye=ge(J);ye.Axios=he,ye.create=function(e){return ge(me(ye.defaults,e))},ye.Cancel=ue,ye.CancelToken=de,ye.isCancel=v,ye.all=function(e){return Promise.all(e)},ye.spread=function(e){return function(t){return e.apply(null,t)}},ye.isAxiosError=function(e){return"object"==typeof e&&!0===e.isAxiosError},e.exports=ye,e.exports.default=ye;var ve=e.exports;ve.defaults.timeout=36e6,ve.interceptors.request.use((e=>e),(e=>Promise.reject(e))),ve.interceptors.response.use((e=>{if(e.data.success)return e.data.data;throw new Error(e.data.msg)}),(e=>{if(e&&e.response)switch(e.response.status){case 400:return e.message="请求出错(400)",Promise.reject(e);case 401:return e.message="未授权(401)",Promise.reject(e);case 403:e.message="禁止访问(403)";break;case 404:return e.message="资源未找到(404)",Promise.reject(e);case 500:return Promise.reject(e);case 501:return e.message="服务未实现(501)",Promise.reject(e);case 502:return e.message="网络错误(502)",Promise.reject(e);case 503:return e.message="服务不可用(503)",Promise.reject(e);case 504:return e.message="网络超时(504)",Promise.reject(e);case 505:return e.message="HTTP版本不受支持(505)",Promise.reject(e);default:return e.message=`请求出错(${e.response.status})!`,Promise.reject(e)}else e.message="连接服务器失败!"}));var we={user:Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",login:e=>ve({url:"/login",method:"POST",data:e})}),roleManagement:Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",list:e=>ve({url:"/roleList",method:"POST",data:e}),addUser:e=>ve({url:"/roleList/add",method:"POST",data:e}),deleteUser:e=>ve({url:"/roleList/delete",method:"POST",data:e}),editUser:e=>ve({url:"/roleList/edit",method:"POST",data:e})}),userManagement:Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",list:e=>ve({url:"/userList",method:"POST",data:e}),addUser:e=>ve({url:"/userList/add",method:"POST",data:e}),deleteUser:e=>ve({url:"/userList/delete",method:"POST",data:e}),editUser:e=>ve({url:"/userList/edit",method:"POST",data:e})})};export{we as h}; 2 | -------------------------------------------------------------------------------- /dist/assets/index.ffebf55c.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/index.ffebf55c.js.gz -------------------------------------------------------------------------------- /dist/assets/logo.61ee5eb1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/logo.61ee5eb1.png -------------------------------------------------------------------------------- /dist/assets/useTableData.6da28c31.js: -------------------------------------------------------------------------------- 1 | var t=Object.defineProperty,e=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(e,a,r)=>a in e?t(e,a,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[a]=r,i=(t,i)=>{for(var n in i||(i={}))a.call(i,n)&&o(t,n,i[n]);if(e)for(var n of e(i))r.call(i,n)&&o(t,n,i[n]);return t};import{g as n}from"./index.710eea4b.js";import{r as s,I as l}from"./vendor.f21cd016.js";function g(t){if("function"!=typeof t&&"Promise"!==n(t))throw new Error("请传入Promise类型的api请求");const e=[20,50,100],a={list:[],totalCount:0,isLoading:!1,currentPage:1,pageSize:e[0]};let r=s({list:a.list,totalCount:a.totalCount,isLoading:a.isLoading}),o={currentPage:a.currentPage,pageSize:a.pageSize};const g=async()=>{r.isLoading=!0;let e=await t(o).catch((t=>{r.isLoading=!1}));e?(r.isLoading=!1,r.list=e.list,r.totalCount=e.totalCount):l.error("数据获取失败!")};return{PAGE_SIZES:e,tableData:r,tableParams:o,setParams:(t={})=>{o=i(i({},o),t),g()},reset:()=>{r.list=a.list,r.totalCount=a.totalCount,r.isLoading=a.isLoading,o={currentPage:a.currentPage,pageSize:a.pageSize},g()},refresh:()=>{g()}}}export{g as u}; 2 | -------------------------------------------------------------------------------- /dist/assets/useTableData.6da28c31.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/useTableData.6da28c31.js.gz -------------------------------------------------------------------------------- /dist/assets/vendor.f21cd016.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/assets/vendor.f21cd016.js.gz -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/dist/favicon.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | Vite-Vue3-Elementplus-Admin 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | Vite-Vue3-Elementplus-Admin 11 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vue3-elementplus-admin", 3 | "version": "1.0.0", 4 | "author": "FEZIRO", 5 | "email": "FEZIRO@foxmail.com", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview/dist": "vite preview" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "dayjs": "^1.10.6", 14 | "element-plus": "^1.0.2-beta.55", 15 | "mockjs": "^1.1.0", 16 | "nprogress": "^0.2.0", 17 | "reset-css": "^5.0.1", 18 | "vue": "^3.1.4", 19 | "vue-router": "^4.0.10", 20 | "vuex": "^4.0.2" 21 | }, 22 | "devDependencies": { 23 | "@rollup/plugin-strip": "^2.1.0", 24 | "@vitejs/plugin-vue": "^1.4.0", 25 | "@vue/compiler-sfc": "^3.1.5", 26 | "babel-plugin-component": "^1.1.1", 27 | "rollup-plugin-visualizer": "^5.5.2", 28 | "sass": "^1.37.5", 29 | "vite": "^2.4.4", 30 | "vite-plugin-compression": "^0.3.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /preview/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/logo.png -------------------------------------------------------------------------------- /preview/个人信息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/个人信息.png -------------------------------------------------------------------------------- /preview/右键菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/右键菜单.png -------------------------------------------------------------------------------- /preview/搜索.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/搜索.png -------------------------------------------------------------------------------- /preview/更换主题色1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/更换主题色1.png -------------------------------------------------------------------------------- /preview/标签切换.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/标签切换.png -------------------------------------------------------------------------------- /preview/注册.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/注册.png -------------------------------------------------------------------------------- /preview/用户管理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/用户管理.png -------------------------------------------------------------------------------- /preview/登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/登录.png -------------------------------------------------------------------------------- /preview/移动端界面1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/移动端界面1.png -------------------------------------------------------------------------------- /preview/移动端菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/移动端菜单.png -------------------------------------------------------------------------------- /preview/移动端设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/移动端设置.png -------------------------------------------------------------------------------- /preview/设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/设置.png -------------------------------------------------------------------------------- /preview/通知.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/通知.png -------------------------------------------------------------------------------- /preview/面包屑.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/面包屑.png -------------------------------------------------------------------------------- /preview/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/preview/首页.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 44 | 45 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/empty-tips.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 22 | 26 | 27 | 28 | 35 | 46 | 47 | 48 | 50 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 65 | 67 | 69 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 84 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FEZIRO/vue3-vite-elementplus-admin/092b3a8d46bb68809770feda8c7589893da885ab/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/components/CountDownBtn/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 57 | 58 | -------------------------------------------------------------------------------- /src/hooks/useTableData.js: -------------------------------------------------------------------------------- 1 | import { reactive } from "vue"; 2 | import { getType } from "@/others/utils"; 3 | import { ElMessage } from "element-plus"; 4 | 5 | export default function useTableData(api) { 6 | if (typeof api !== "function" && getType(api) !== "Promise") { 7 | throw new Error("请传入Promise类型的api请求"); 8 | } 9 | 10 | //分页器每页个数配置组 11 | const PAGE_SIZES = [20, 50, 100]; 12 | 13 | //初始化数据 14 | const DEFAULT_TABLE_DATA = { 15 | list: [], 16 | totalCount: 0, 17 | isLoading: false, 18 | currentPage: 1, 19 | pageSize: PAGE_SIZES[0], 20 | }; 21 | 22 | //数据表格 23 | let tableData = reactive({ 24 | list: DEFAULT_TABLE_DATA.list, 25 | totalCount: DEFAULT_TABLE_DATA.totalCount, 26 | isLoading: DEFAULT_TABLE_DATA.isLoading, 27 | }); 28 | 29 | //数据表格基本请求参数 30 | let tableParams = { 31 | currentPage: DEFAULT_TABLE_DATA.currentPage, 32 | pageSize: DEFAULT_TABLE_DATA.pageSize, 33 | }; 34 | 35 | //获取数据 36 | const fetchData = async () => { 37 | tableData.isLoading = true; 38 | let res = await api(tableParams).catch(err => { 39 | tableData.isLoading = false; 40 | }); 41 | if (res) { 42 | console.log(api.toString() + "请求成功\n", res); 43 | tableData.isLoading = false; 44 | tableData.list = res.list; 45 | tableData.totalCount = res.totalCount; 46 | } else { 47 | ElMessage.error("数据获取失败!"); 48 | } 49 | }; 50 | 51 | //重置请求 52 | const reset = () => { 53 | tableData.list = DEFAULT_TABLE_DATA.list; 54 | tableData.totalCount = DEFAULT_TABLE_DATA.totalCount; 55 | tableData.isLoading = DEFAULT_TABLE_DATA.isLoading; 56 | tableParams = { 57 | currentPage: DEFAULT_TABLE_DATA.currentPage, 58 | pageSize: DEFAULT_TABLE_DATA.pageSize, 59 | }; 60 | fetchData(); 61 | }; 62 | 63 | //刷新 64 | const refresh = () => { 65 | fetchData(); 66 | }; 67 | 68 | //设置查询参数 69 | const setParams = (params = {}) => { 70 | tableParams = { ...tableParams, ...params }; 71 | fetchData(); 72 | }; 73 | 74 | return { 75 | PAGE_SIZES, 76 | tableData, 77 | tableParams, 78 | setParams, 79 | reset, 80 | refresh, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/hooks/useWindowSize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 实时监听获取浏览器窗口宽度、高度 3 | */ 4 | 5 | import { toRefs, onMounted, onBeforeUnmount, reactive } from "vue"; 6 | import { debounce, getClientRect } from "@/others/utils"; 7 | 8 | export default function useWindowSize() { 9 | const windowRect = reactive({ 10 | clientHeight: getClientRect().clientHeight, 11 | clientWidth: getClientRect().clientWidth, 12 | }); 13 | 14 | const onWindowsResize = debounce(() => { 15 | windowRect.clientHeight = getClientRect().clientHeight; 16 | windowRect.clientWidth = getClientRect().clientWidth; 17 | }, 300); 18 | 19 | onMounted(() => { 20 | window.addEventListener("resize", onWindowsResize); 21 | }); 22 | 23 | onBeforeUnmount(() => { 24 | window.removeEventListener("resize", onWindowsResize); 25 | }); 26 | return { 27 | ...toRefs(windowRect), 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/http/apis/role-management.js: -------------------------------------------------------------------------------- 1 | import axios from "../axios"; 2 | 3 | export const list = data => axios({ url: "/roleList", method: "POST", data }); 4 | 5 | export const addUser = data => 6 | axios({ url: "/roleList/add", method: "POST", data }); 7 | 8 | export const deleteUser = data => 9 | axios({ url: "/roleList/delete", method: "POST", data }); 10 | 11 | export const editUser = data => 12 | axios({ url: "/roleList/edit", method: "POST", data }); 13 | -------------------------------------------------------------------------------- /src/http/apis/user-management.js: -------------------------------------------------------------------------------- 1 | import axios from "../axios"; 2 | 3 | export const list = data => axios({ url: "/userList", method: "POST", data }); 4 | 5 | export const addUser = data => 6 | axios({ url: "/userList/add", method: "POST", data }); 7 | 8 | export const deleteUser = data => 9 | axios({ url: "/userList/delete", method: "POST", data }); 10 | 11 | export const editUser = data => 12 | axios({ url: "/userList/edit", method: "POST", data }); 13 | -------------------------------------------------------------------------------- /src/http/apis/user.js: -------------------------------------------------------------------------------- 1 | import axios from "../axios"; 2 | 3 | //登录操作 4 | export const login = data => axios({ url: "/login", method: "POST", data }); 5 | -------------------------------------------------------------------------------- /src/http/axios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc:axios请求库全局配置 3 | */ 4 | import axios from "axios"; 5 | 6 | //根据.env文件切换环境 7 | // axios.defaults.baseURL = process.env.VITE_APP_SERVER_HOST; 8 | 9 | axios.defaults.timeout = 36000000; 10 | axios.interceptors.request.use( 11 | config => { 12 | // let token = localStorage.getItem("userInfo").token; 13 | // config.headers["accessToken"] = token; 14 | return config; 15 | }, 16 | error => { 17 | return Promise.reject(error); 18 | } 19 | ); 20 | 21 | axios.interceptors.response.use( 22 | response => { 23 | // console.log("response", response); 24 | 25 | if (response.data.success) { 26 | return response.data.data; 27 | } else { 28 | // message.error(response.data.msg); 29 | throw new Error(response.data.msg); 30 | } 31 | }, 32 | error => { 33 | console.error("请求error", error); 34 | 35 | if (error && error.response) { 36 | switch (error.response.status) { 37 | case 400: 38 | error.message = "请求出错(400)"; 39 | return Promise.reject(error); 40 | case 401: 41 | error.message = "未授权(401)"; 42 | //跳登录页 43 | return Promise.reject(error); 44 | case 403: 45 | error.message = "禁止访问(403)"; 46 | break; 47 | case 404: 48 | error.message = "资源未找到(404)"; 49 | return Promise.reject(error); 50 | case 500: 51 | return Promise.reject(error); 52 | case 501: 53 | error.message = "服务未实现(501)"; 54 | return Promise.reject(error); 55 | case 502: 56 | error.message = "网络错误(502)"; 57 | return Promise.reject(error); 58 | case 503: 59 | error.message = "服务不可用(503)"; 60 | return Promise.reject(error); 61 | case 504: 62 | error.message = "网络超时(504)"; 63 | return Promise.reject(error); 64 | case 505: 65 | error.message = "HTTP版本不受支持(505)"; 66 | return Promise.reject(error); 67 | default: 68 | error.message = `请求出错(${error.response.status})!`; 69 | return Promise.reject(error); 70 | } 71 | } else { 72 | error.message = "连接服务器失败!"; 73 | } 74 | } 75 | ); 76 | 77 | export default axios; 78 | -------------------------------------------------------------------------------- /src/http/index.js: -------------------------------------------------------------------------------- 1 | import * as user from "./apis/user.js"; 2 | import * as roleManagement from "./apis/role-management"; 3 | import * as userManagement from "./apis/user-management"; 4 | 5 | export default { 6 | user, 7 | roleManagement, 8 | userManagement, 9 | }; 10 | -------------------------------------------------------------------------------- /src/layouts/AdminLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 54 | 55 | -------------------------------------------------------------------------------- /src/layouts/AppHeader/index.vue: -------------------------------------------------------------------------------- 1 | 182 | 183 | 336 | 337 | 544 | -------------------------------------------------------------------------------- /src/layouts/AppSideBar/MenuItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 58 | -------------------------------------------------------------------------------- /src/layouts/AppSideBar/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 50 | 51 | 139 | -------------------------------------------------------------------------------- /src/layouts/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /src/layouts/MobileAppHeader/index.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 177 | 178 | 354 | -------------------------------------------------------------------------------- /src/layouts/MobileAppSideBar/index.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 178 | 179 | 294 | -------------------------------------------------------------------------------- /src/layouts/TagsViewSwitcher/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 164 | 165 | 250 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import ElementPlus from "element-plus"; 3 | import "element-plus/lib/theme-chalk/index.css"; 4 | import locale from "element-plus/lib/locale/lang/zh-cn"; 5 | import App from "./App.vue"; 6 | import router from "@/router"; 7 | import "reset-css"; 8 | import store from "@/store"; 9 | import "./mock/index"; 10 | import "./styles/element-variables.scss"; 11 | 12 | //全局注册Nprogress加载指示器 13 | import NProgress from "nprogress"; 14 | import "nprogress/nprogress.css"; 15 | NProgress.configure({ showSpinner: false, easing: "ease", speed: 500 }); 16 | 17 | const app = createApp(App); 18 | console.warn("环境", import.meta.env); 19 | 20 | // 开发环境开启devtool 21 | if (import.meta.env.MODE === "development") { 22 | app.config.devtools = true; 23 | } 24 | 25 | app.config.errorHandler = (err, vm, info) => { 26 | console.error("Vue错误:", err, vm, info); 27 | }; 28 | 29 | app.config.warnHandler = function (msg, vm, trace) { 30 | console.warn("Vue警告:", msg, vm, trace); 31 | }; 32 | app.config.performance = true; 33 | 34 | //全局注册组件 35 | import AdminLayout from "@/layouts/AdminLayout.vue"; 36 | app.component("AdminLayout", AdminLayout); 37 | 38 | import AppTagsViewSwitcher from "@/layouts/TagsViewSwitcher/index.vue"; 39 | app.component("AppTagsViewSwitcher", AppTagsViewSwitcher); 40 | 41 | import AppSideBar from "@/layouts/AppSideBar/index.vue"; 42 | app.component("AppSideBar", AppSideBar); 43 | 44 | import AppBreadcrumb from "@/layouts/Breadcrumb/index.vue"; 45 | app.component("AppBreadcrumb", AppBreadcrumb); 46 | 47 | import AppHeader from "@/layouts/AppHeader/index.vue"; 48 | app.component("AppHeader", AppHeader); 49 | 50 | import MobileAppHeader from "@/layouts/MobileAppHeader/index.vue"; 51 | app.component("MobileAppHeader", MobileAppHeader); 52 | 53 | import MobileAppSideBar from "@/layouts/MobileAppSideBar/index.vue"; 54 | app.component("MobileAppSideBar", MobileAppSideBar); 55 | 56 | app.use(ElementPlus, { locale }); 57 | app.use(router); 58 | app.use(store); 59 | app.mount("#app"); 60 | -------------------------------------------------------------------------------- /src/mock/apiController/role-management.js: -------------------------------------------------------------------------------- 1 | import Mock from "../mock"; 2 | let roleList = [ 3 | { 4 | id: 1, 5 | roleName: "管理员", 6 | remark: "--", 7 | }, 8 | { 9 | id: 2, 10 | roleName: "超级管理员", 11 | remark: "--", 12 | }, 13 | { 14 | id: 3, 15 | roleName: "普通用户", 16 | remark: "--", 17 | }, 18 | ]; 19 | 20 | const pager = (list, pageSize) => { 21 | let newlist = []; 22 | for (let i = 0; i < list.length; i += pageSize) { 23 | newlist.push(list.slice(i, i + pageSize)); 24 | } 25 | return newlist; 26 | }; 27 | 28 | const parserBody = bodyStr => { 29 | if (bodyStr) { 30 | return JSON.parse(bodyStr); 31 | } else { 32 | return {}; 33 | } 34 | }; 35 | 36 | Mock.mock("/roleList", "post", config => { 37 | let body = parserBody(config.body); 38 | console.log("/roleList 请求", config); 39 | const pageSize = body.pageSize; 40 | const currentPage = body.currentPage; 41 | const keyword = body.keyword; 42 | 43 | let filterList = roleList.filter(item => new RegExp(keyword).test(item.name)); 44 | return { 45 | success: true, 46 | msg: "", 47 | data: { 48 | list: pager(filterList, pageSize)[currentPage - 1], 49 | currentPage: currentPage, 50 | totalCount: filterList.length, 51 | totalPage: pager(filterList, pageSize).length, 52 | pageSize: pageSize, 53 | }, 54 | }; 55 | }); 56 | 57 | Mock.mock("/roleList/add", "post", config => { 58 | let body = parserBody(config.body); 59 | console.log("/roleList/add 请求", config); 60 | roleList.unshift({ 61 | id: Math.random(), 62 | name: body.name, 63 | }); 64 | return { 65 | success: true, 66 | msg: "", 67 | data: {}, 68 | }; 69 | }); 70 | 71 | Mock.mock("/roleList/delete", "post", config => { 72 | let body = parserBody(config.body); 73 | console.log("/roleList/delete 请求", config); 74 | roleList = roleList.filter(item => item.id !== body.id); 75 | return { 76 | success: true, 77 | msg: "", 78 | data: {}, 79 | }; 80 | }); 81 | 82 | // Mock.mock("/roleList/edit", "post", config => { 83 | // let body = parserBody(config.body); 84 | // console.log("/roleList/add 请求", config); 85 | // roleList = roleList.filter(item => item.id !== body.id); 86 | // return { 87 | // success: true, 88 | // msg: "", 89 | // data: {}, 90 | // }; 91 | // }); 92 | -------------------------------------------------------------------------------- /src/mock/apiController/user-management.js: -------------------------------------------------------------------------------- 1 | import Mock from "../mock"; 2 | let userList = new Array(100) 3 | .fill({ 4 | id: "", 5 | name: "", 6 | }) 7 | .map((item, index) => { 8 | return { 9 | id: index, 10 | img: "https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3305298991,2024211813&fm=26&gp=0.jpg", 11 | userName: "张三" + index, 12 | sex: "男", 13 | role: "管理员", 14 | phone: "13323131321", 15 | loginTime: "32", 16 | lastLoginTime: "2020-12-12 01:00:00", 17 | }; 18 | }); 19 | 20 | const pager = (list, pageSize) => { 21 | let newlist = []; 22 | for (let i = 0; i < list.length; i += pageSize) { 23 | newlist.push(list.slice(i, i + pageSize)); 24 | } 25 | return newlist; 26 | }; 27 | 28 | const parserBody = bodyStr => { 29 | if (bodyStr) { 30 | return JSON.parse(bodyStr); 31 | } else { 32 | return {}; 33 | } 34 | }; 35 | 36 | Mock.mock("/userList", "post", config => { 37 | let body = parserBody(config.body); 38 | console.log("/userList 请求", config); 39 | const pageSize = body.pageSize; 40 | const currentPage = body.currentPage; 41 | const keyword = body.keyword; 42 | 43 | let filterList = userList.filter(item => 44 | new RegExp(keyword).test(item.userName) 45 | ); 46 | return { 47 | success: true, 48 | msg: "", 49 | data: { 50 | list: pager(filterList, pageSize)[currentPage - 1], 51 | currentPage: currentPage, 52 | totalCount: filterList.length, 53 | totalPage: pager(filterList, pageSize).length, 54 | pageSize: pageSize, 55 | }, 56 | }; 57 | }); 58 | 59 | Mock.mock("/userList/add", "post", config => { 60 | let body = parserBody(config.body); 61 | console.log("/userList/add 请求", config); 62 | userList.unshift({ 63 | id: Math.random(), 64 | name: body.username, 65 | }); 66 | return { 67 | success: true, 68 | msg: "", 69 | data: {}, 70 | }; 71 | }); 72 | 73 | Mock.mock("/userList/delete", "post", config => { 74 | let body = parserBody(config.body); 75 | console.log("/userList/delete 请求", config); 76 | userList = userList.filter(item => item.id !== body.id); 77 | return { 78 | success: true, 79 | msg: "", 80 | data: {}, 81 | }; 82 | }); 83 | 84 | // Mock.mock("/userList/edit", "post", config => { 85 | // let body = parserBody(config.body); 86 | // console.log("/userList/add 请求", config); 87 | // userList = userList.filter(item => item.id !== body.id); 88 | // return { 89 | // success: true, 90 | // msg: "", 91 | // data: {}, 92 | // }; 93 | // }); 94 | -------------------------------------------------------------------------------- /src/mock/apiController/user.js: -------------------------------------------------------------------------------- 1 | import Mock from "../mock"; 2 | 3 | Mock.mock("/login", "post", config => { 4 | console.log("mockconfig", config); 5 | let body = config.body && JSON.parse(config.body); 6 | console.log("body", body); 7 | 8 | if (body.username === "admin" && body.password === "admin") { 9 | return { 10 | success: true, 11 | msg: "登录成功", 12 | data: { 13 | userInfo: { 14 | id: "32hnhszj22872hwkjae", 15 | name: "系统管理员", 16 | role: "管理员", 17 | avatar: 18 | "https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3305298991,2024211813&fm=26&gp=0.jpg", 19 | }, 20 | menu: [ 21 | { 22 | id: "1", 23 | name: "系统管理", 24 | icon: "el-icon-setting", 25 | url: "/system", 26 | breadcrumb: ["系统管理"], 27 | type: "group", 28 | children: [ 29 | { 30 | id: "1-1", 31 | name: "用户管理", 32 | icon: "", 33 | type: "page", 34 | url: "/system/user-management", 35 | children: null, 36 | breadcrumb: ["系统管理", "用户管理"], 37 | }, 38 | { 39 | id: "1-2", 40 | name: "角色管理", 41 | icon: "", 42 | type: "page", 43 | url: "/system/role-management", 44 | children: null, 45 | breadcrumb: ["系统管理", "角色管理"], 46 | }, 47 | ], 48 | }, 49 | ], 50 | }, 51 | }; 52 | } 53 | if (body.username === "2" && body.password === "2") { 54 | return { 55 | success: true, 56 | msg: "登录成功", 57 | data: { 58 | userInfo: { 59 | id: "ashwh2872dkjsahhwkjae", 60 | name: "张三", 61 | role: "普通用户", 62 | avatar: 63 | "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2448425926,3379370176&fm=11&gp=0.jpg", 64 | }, 65 | menu: [ 66 | { 67 | id: "2", 68 | name: "统计", 69 | icon: "", 70 | type: "page", 71 | url: "/chart", 72 | children: null, 73 | breadcrumb: [ 74 | { 75 | name: "统计", 76 | url: "/chart", 77 | }, 78 | ], 79 | }, 80 | ], 81 | }, 82 | }; 83 | } 84 | 85 | return { 86 | success: false, 87 | msg: "密码错误!", 88 | data: {}, 89 | }; 90 | }); 91 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import "./apiController/user"; 2 | import "./apiController/user-management"; 3 | import "./apiController/role-management"; 4 | -------------------------------------------------------------------------------- /src/mock/mock.js: -------------------------------------------------------------------------------- 1 | import Mock from "mockjs"; 2 | 3 | Mock.setup({ 4 | timeout: 500, // 设置延迟响应,模拟向后端请求数据 5 | }); 6 | 7 | 8 | export default Mock; -------------------------------------------------------------------------------- /src/others/options.js: -------------------------------------------------------------------------------- 1 | import { arrayToObject } from "@/others/utils.js"; 2 | 3 | /** 4 | * 全局下拉值选项配置 5 | */ 6 | 7 | // 用户类型选项 8 | export const userTypeOptions = [ 9 | { 10 | id: 1, 11 | name: "普通人员", 12 | }, 13 | { 14 | id: 2, 15 | name: "管理员", 16 | }, 17 | ]; 18 | 19 | // 用户类型映射(可用于表格类型回显) 20 | // [{ id: 1, name: "张三" }] => { 1 : { id: 1, name: "张三" } } 21 | export const userTypeOptionsMap = arrayToObject(userTypeOptions, { 22 | keyName: "id", 23 | }); 24 | 25 | 26 | //性别类型选项 27 | export const sexOptions = [ 28 | { 29 | id: 1, 30 | name: "男", 31 | }, 32 | { 33 | id: 2, 34 | name: "女", 35 | }, 36 | ]; 37 | // 性别类型选项映射 38 | export const sexOptionsMap = arrayToObject(sexOptions, { 39 | keyName: "id", 40 | }); 41 | -------------------------------------------------------------------------------- /src/others/utils.js: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | 3 | /** 4 | * 对象数组映射成对象Map结构 5 | * @param {Array} 6 | * @return {Object} 7 | */ 8 | export function arrayToObject( 9 | data = [], 10 | format = { keyName: "id", valueName: "name" } 11 | ) { 12 | let map = {}; 13 | if (data.length == 0) return; 14 | if (format && format.keyName && format.valueName) { 15 | // 例如以id为key名称,以name为value名称 16 | // [{ id: "xx", name: "yy" }] => { xx: yy } 17 | data.forEach(item => { 18 | map[item[format.keyName]] = item[format.valueName]; 19 | }); 20 | return map; 21 | } else if (format && format.keyName) { 22 | // 例如以id为key名称 23 | // [{ id: "xx", name: "yy" }] => { xx: { id: "xx", name: "yy" } } 24 | data.forEach(item => { 25 | map[item[format.keyName]] = item; 26 | }); 27 | return map; 28 | } else { 29 | console.error("请输入keyName或valueName"); 30 | return; 31 | } 32 | } 33 | 34 | /** 35 | * 时间格式化 36 | * @param {Number} num 37 | * @return {Date} 38 | */ 39 | export function timeFormat(time, formatStr = "YYYY-MM-DD HH:mm") { 40 | if (!time) return "--"; 41 | return time ? dayjs(time).format(formatStr) : ""; 42 | } 43 | 44 | /** 45 | * 类型检测 46 | * @param {Any} value 47 | * @return {String} 48 | * 返回类型字符如: Array String Number 49 | */ 50 | export function getType(value) { 51 | let type = Object.prototype.toString.call(value); 52 | type = type.replace(/^\[object\s/, ""); 53 | type = type.replace(/\]$/, ""); 54 | return type; 55 | } 56 | 57 | /** 58 | * 遍历树节点 59 | * @param {Array} data 60 | * @param {Function} callBack 61 | * @return {null} 62 | * [{ 63 | * id:1, 64 | * name:"1", 65 | * children:[{ 66 | * id:1, 67 | * name:"1", 68 | * }] 69 | * }] 70 | * **/ 71 | export function traverseArrayTree( 72 | data = [], 73 | childNodeKey = "children", 74 | callBack 75 | ) { 76 | if (Array.isArray(data)) { 77 | data.forEach(item => { 78 | typeof callBack == "function" && callBack(item); 79 | if (item[childNodeKey] && Array.isArray(item[childNodeKey])) { 80 | traverseArrayTree(item[childNodeKey], childNodeKey, callBack); 81 | } 82 | }); 83 | } else { 84 | throw new error("请传入数组"); 85 | } 86 | } 87 | 88 | /** 89 | * 函数节流 90 | * @param {Function} fun 91 | * @param {Number} wait 92 | * @return {Function} 93 | */ 94 | export function throttle(fn, wait) { 95 | var pre = Date.now(); 96 | return function () { 97 | var context = this; 98 | var args = arguments; 99 | var now = Date.now(); 100 | if (now - pre >= wait) { 101 | fn.apply(context, args); 102 | pre = Date.now(); 103 | } 104 | }; 105 | } 106 | 107 | /** 108 | * 函数防抖 109 | * @param {Function} fun 110 | * @param {Number} delay 111 | * @return {Function} 112 | */ 113 | export function debounce(fn, delay) { 114 | // 记录上一次的延时器 115 | var timer = null; 116 | return function () { 117 | // 清除上一次延时器 118 | var context = this; 119 | var args = arguments; 120 | clearTimeout(timer); 121 | timer = setTimeout(function () { 122 | fn.apply(context, args); 123 | }, delay); 124 | }; 125 | } 126 | 127 | /** 128 | * 获取客户端浏览器内容窗口宽高 129 | * @param null 130 | * @return {Object} 131 | */ 132 | export const getClientRect = () => { 133 | let currentClientHeight = 134 | document.documentElement.clientHeight || document.body.clientHeight; 135 | let currentClientWidth = 136 | document.documentElement.clientWidth || document.body.clientWidth; 137 | 138 | return { 139 | clientWidth: currentClientWidth, 140 | clientHeight: currentClientHeight, 141 | }; 142 | }; 143 | -------------------------------------------------------------------------------- /src/others/validator.js: -------------------------------------------------------------------------------- 1 | import { Message } from "element-ui"; 2 | /** 3 | * 数据正则验证器 4 | */ 5 | 6 | /** 7 | * 验证电话号码 8 | * @param {Number/String} phone 9 | * @return {Boolean} 10 | */ 11 | export const validPhone = (phone, errMsg = "") => { 12 | const phoneRule = 13 | /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/; 14 | let status = phoneRule.test(phone); 15 | if (!status) { 16 | if (errMsg) Message({ type: "error", message: errMsg }); 17 | } 18 | 19 | return status; 20 | }; 21 | 22 | /** 23 | * 验证邮箱 24 | * @param {String} email 25 | * @return {Boolean} 26 | */ 27 | export const validEmail = (email, errMsg = "") => { 28 | const emailRule = 29 | /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; 30 | let status = emailRule.test(email); 31 | if (!status) { 32 | if (errMsg) Message({ type: "error", message: errMsg }); 33 | } 34 | 35 | return status; 36 | }; 37 | 38 | /** 39 | * 验证内容不为空 40 | * @param {String} content 41 | * @return {Boolean} 42 | */ 43 | export const validNotEmpty = (content, errMsg = "") => { 44 | if (content == null) { 45 | if (errMsg) Message({ type: "error", message: errMsg }); 46 | return false; 47 | } 48 | const notEmptyRule = /\S/; 49 | let status = notEmptyRule.test(content); 50 | if (!status) { 51 | if (errMsg) Message({ type: "error", message: errMsg }); 52 | } 53 | 54 | return status; 55 | }; 56 | 57 | /** 58 | * 验证数组不为空 59 | * @param {Array} contentList 60 | * @return {Boolean} 61 | */ 62 | export const validArrayNotEmpty = (contentList = [], errMsg = "") => { 63 | let status = contentList.length == 0 ? false : true; 64 | if (!status) { 65 | if (errMsg) Message({ type: "error", message: errMsg }); 66 | } 67 | 68 | return status; 69 | }; 70 | -------------------------------------------------------------------------------- /src/pages/404/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /src/pages/home/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 31 | 91 | -------------------------------------------------------------------------------- /src/pages/login/index.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 101 | 197 | 198 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /src/pages/role-management/components/RoleEdit.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 166 | 167 | -------------------------------------------------------------------------------- /src/pages/role-management/index.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 189 | 190 | -------------------------------------------------------------------------------- /src/pages/user-management/components/UserEdit.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 189 | 190 | -------------------------------------------------------------------------------- /src/pages/user-management/index.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 222 | 223 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | import NProgress from "nprogress"; 3 | 4 | //路径映射页面组件 5 | const pageUrlMap = { 6 | "/home": () => import("@/pages/home/index.vue"), 7 | "/system/user-management": () => import("@/pages/user-management/index.vue"), 8 | "/system/role-management": () => import("@/pages/role-management/index.vue"), 9 | }; 10 | 11 | //静态路由 12 | const constantRoute = [ 13 | { 14 | name: "登录", 15 | path: "/login", 16 | component: () => import("@/pages/login/index.vue"), 17 | meta: { 18 | title: "登录", 19 | auth: false, 20 | tagsView: false, 21 | tagsViewAffix: false, 22 | }, 23 | }, 24 | { 25 | name: "404", 26 | path: "/404", 27 | component: () => import("@/pages/404/index.vue"), 28 | meta: { 29 | title: "404页面未找到", 30 | auth: false, 31 | tagsView: false, 32 | tagsViewAffix: false, 33 | }, 34 | }, 35 | { 36 | path: "/:patchAll(\\S+)", 37 | redirect: "/404", 38 | }, 39 | ]; 40 | 41 | const router = createRouter({ 42 | routes: constantRoute, 43 | history: createWebHashHistory(), 44 | }); 45 | 46 | //路由拦截 47 | router.beforeEach((to, from, next) => { 48 | NProgress.start(); 49 | if (to.meta && to.meta.title) { 50 | document.title = to.meta.title; 51 | } 52 | let userInfo = localStorage.getItem("userInfo") || null; 53 | 54 | if (to.fullPath == "/" && !userInfo) { 55 | router.replace("/login"); 56 | return; 57 | } 58 | 59 | if (to.meta.auth) { 60 | if (!userInfo) { 61 | router.push({ path: "/login" }); 62 | } else { 63 | next(); 64 | } 65 | } else { 66 | next(); 67 | } 68 | }); 69 | 70 | router.afterEach(() => { 71 | NProgress.done(); 72 | }); 73 | 74 | //动态路由 75 | export function createRoute(menu) { 76 | if (!menu || menu.length == 0) return; 77 | for (let i = 0; i < menu.length; i++) { 78 | let menuItem = menu[i]; 79 | if (menuItem.type === "group") { 80 | createRoute(menuItem.children); 81 | } else { 82 | router.addRoute({ 83 | name: menuItem.name, 84 | path: menuItem.url || "", 85 | component: menuItem.url ? pageUrlMap[menuItem.url] : "", 86 | meta: { 87 | title: menuItem.name || "", 88 | auth: true, 89 | layout: menuItem.layout || "admin", 90 | tagsView: menuItem.tagsView || true, 91 | tagsViewAffix: menuItem.tagsViewAffix || false, 92 | breadcrumb: menuItem.breadcrumb || [], 93 | }, 94 | }); 95 | } 96 | } 97 | router.addRoute({ 98 | path: "/", 99 | redirect: "/home", 100 | }); 101 | } 102 | //刷新时浏览器路由不丢失 103 | let menu = localStorage.getItem("menu"); 104 | menu && createRoute(JSON.parse(menu)); 105 | 106 | //重置路由(用于退出登录时重置路由) 107 | export function resetRouter() { 108 | const newRouter = createRouter({ 109 | routes: constantRoute, 110 | history: createWebHashHistory(), 111 | }); 112 | router.matcher = newRouter.matcher; 113 | } 114 | 115 | export default router; 116 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "vuex"; 2 | import app from "@/store/modules/app.js"; 3 | import tagsView from "@/store/modules/tags-view.js"; 4 | 5 | export default createStore({ 6 | modules: { 7 | app, 8 | tagsView, 9 | }, 10 | devtools: true, 11 | }); 12 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app全局配置 3 | */ 4 | 5 | import logo from "../../assets/images/logo.png"; 6 | import { getClientRect } from "@/others/utils"; 7 | const getCachePageIndicator = () => { 8 | let val = localStorage.getItem("pageIndicator"); 9 | return val; 10 | }; 11 | const getCacheMenuCollapse = () => { 12 | let val = localStorage.getItem("menuCollapse"); 13 | return val; 14 | }; 15 | const getCachePageKeepAlive = () => { 16 | let val = localStorage.getItem("getCachePageKeepAlive"); 17 | return val; 18 | }; 19 | 20 | export default { 21 | namespaced: true, 22 | state: () => ({ 23 | //平台名称 24 | appName: "Vue3-Element+管理系统", 25 | //平台logo 26 | appLogo: logo, 27 | //端标识(移动端"mobile"/ 桌面端"desktop") 28 | device: "desktop", 29 | //页面指示器("面包屑"/"标签切换") 30 | pageIndicator: getCachePageIndicator() || "面包屑", 31 | //菜单折叠 32 | menuCollapse: getCacheMenuCollapse() || 0, 33 | //页面缓存 34 | pageKeepAlive: getCachePageKeepAlive() || 0, 35 | //客户端窗口尺寸宽高(resize动态赋值) 36 | windowRect: { 37 | clientHeight: getClientRect().clientHeight, 38 | clientWidth: getClientRect().clientWidth, 39 | }, 40 | }), 41 | 42 | getters: { 43 | userInfo() { 44 | let userInfo = localStorage.getItem("userInfo"); 45 | return userInfo ? JSON.parse(userInfo) : {}; 46 | }, 47 | menu() { 48 | let menu = localStorage.getItem("menu"); 49 | return menu ? JSON.parse(menu) : []; 50 | }, 51 | }, 52 | 53 | //用于同步操作state 54 | mutations: { 55 | SET_DEVICE(state, device) { 56 | state.device = device; 57 | if (device === "mobile") this.commit("app/TOGGLE_MENU_COLLAPSE", 1); 58 | if (device === "desktop") this.commit("app/TOGGLE_MENU_COLLAPSE", 0); 59 | }, 60 | 61 | TOGGLE_MENU_COLLAPSE(state, collapse) { 62 | if (state.menuCollapse === collapse) return; 63 | state.menuCollapse = collapse; 64 | localStorage.setItem("menuCollapse", state.menuCollapse); 65 | }, 66 | 67 | SET_MENU_TAG_SWITCHER(state, val = "面包屑") { 68 | state.pageIndicator = val; 69 | localStorage.setItem("pageIndicator", state.pageIndicator); 70 | }, 71 | 72 | SET_PAGE_KEEP_ALIVE(state, val = 0) { 73 | state.pageKeepAlive = val; 74 | localStorage.setItem("pageKeepAlive", state.pageKeepAlive); 75 | }, 76 | 77 | SET_WINDOW_RECT(state, val) { 78 | state.windowRect = { 79 | clientHeight: val.clientHeight || 0, 80 | clientWidth: val.clientWidth || 0, 81 | }; 82 | }, 83 | }, 84 | 85 | //用于异步操作state 86 | actions: {}, 87 | }; 88 | -------------------------------------------------------------------------------- /src/store/modules/tags-view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * tagsView标签切换页面 3 | * 4 | */ 5 | 6 | // visitedRoutes本地缓存(localStorage) 7 | const getCacheVisitedRoutes = () => { 8 | let visitedRoutes = localStorage.getItem("visitedRoutes"); 9 | return visitedRoutes ? JSON.parse(visitedRoutes) : []; 10 | }; 11 | const setCacheVisitedRoutes = visitedRoutes => { 12 | localStorage.setItem("visitedRoutes", JSON.stringify(visitedRoutes)); 13 | }; 14 | 15 | export default { 16 | namespaced: true, 17 | state: () => ({ 18 | visitedRoutes: getCacheVisitedRoutes(), 19 | }), 20 | mutations: { 21 | //添加 22 | ADD_VISITED_ROUTE(state, route) { 23 | let target = state.visitedRoutes.find(item => item.path === route.path); 24 | if (target) { 25 | if (route.fullPath !== target.fullPath) Object.assign(target, route); 26 | return; 27 | } 28 | state.visitedRoutes.push({ ...target, ...route }); 29 | setCacheVisitedRoutes(state.visitedRoutes); 30 | }, 31 | //删除 32 | DELETE_VISITED_ROUTE(state, route) { 33 | state.visitedRoutes.forEach((item, index) => { 34 | if (item.path === route.path) state.visitedRoutes.splice(index, 1); 35 | }); 36 | setCacheVisitedRoutes(state.visitedRoutes); 37 | }, 38 | //删除除了本身以外的 39 | DELETE_OTHERS_VISITED_ROUTE(state, route) { 40 | state.visitedRoutes = state.visitedRoutes.filter( 41 | item => item.path == route.path 42 | ); 43 | setCacheVisitedRoutes(state.visitedRoutes); 44 | console.log("state", state); 45 | }, 46 | //更新当前 47 | UPDATE_VISITED_ROUTE(state, route) { 48 | state.visitedRoutes.forEach(item => { 49 | if (item.path === route.path) { 50 | console.log("refresh", item); 51 | item = Object.assign(item, route); 52 | } 53 | }); 54 | }, 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/styles/animation.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 动画 3 | */ 4 | 5 | /************** Vue 组件动画 ***************/ 6 | 7 | .fade { 8 | 9 | &-enter-active, 10 | &-leave-active { 11 | transition: all 0.3s ease; 12 | } 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(-1000px); 18 | } 19 | } 20 | 21 | 22 | 23 | /************** Css动画 ***************/ 24 | 25 | //上划出 26 | @keyframes slideUp { 27 | 0% { 28 | opacity: 0; 29 | transform: translateY(60px); 30 | } 31 | 32 | 100% { 33 | opacity: 1; 34 | transform: translateY(0); 35 | } 36 | } 37 | 38 | 39 | //上下循环跳动 40 | @keyframes upAndDown { 41 | 0% { 42 | transform: translateY(0); 43 | } 44 | 45 | 50% { 46 | transform: translateY(10px); 47 | } 48 | 49 | 100% { 50 | transform: translateY(0px); 51 | } 52 | } 53 | 54 | //移动端侧边栏滑入 55 | @keyframes sideBarSlipIn { 56 | 0% { 57 | transform: translateX(0px); 58 | } 59 | 60 | 100% { 61 | transform: translateX(-500px); 62 | } 63 | } 64 | 65 | //移动端侧边栏滑出 66 | @keyframes sideBarSlipOut { 67 | 0% { 68 | transform: translateX(-500px); 69 | } 70 | 71 | 100% { 72 | transform: translateX(0px); 73 | } 74 | } -------------------------------------------------------------------------------- /src/styles/color.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 颜色 3 | */ 4 | 5 | $primaryColor:#36ad6a; 6 | $secondaryColor:rgb($primaryColor, 0.1); 7 | $bgColor:#f6f7f9; 8 | 9 | $blue:#409EFF; 10 | $red: #F56C6C; 11 | $green:#67C23A; 12 | $yellow:#E6A23C; 13 | 14 | //app背景色 15 | $appBgColor:#fff; 16 | -------------------------------------------------------------------------------- /src/styles/custom-default-browser-style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义浏览器默认样式 3 | */ 4 | 5 | //优化滚动条样式 6 | * { 7 | /* for Firefox: */ 8 | scrollbar-color: rgba(#000, 0.3) transparent; 9 | scrollbar-width: thin; 10 | 11 | &:focus { 12 | outline: none; 13 | } 14 | 15 | } 16 | 17 | 18 | ::-webkit-scrollbar { 19 | width: 8px; 20 | height: 8px; 21 | } 22 | 23 | //滚动条轨道 24 | ::-webkit-scrollbar-track { 25 | background: transparent; 26 | } 27 | 28 | //滚动条 29 | ::-webkit-scrollbar-thumb { 30 | background-color: rgba(#999, 0.5); 31 | background-clip: padding-box; 32 | -webkit-border-radius: 2px; 33 | -moz-border-radius: 2px; 34 | border-radius: 2px; 35 | transition: all 1s; 36 | cursor: pointer; 37 | } 38 | 39 | //滚动条鼠标悬停 40 | ::-webkit-scrollbar-thumb:hover { 41 | background-color: rgba(#999, 1); 42 | } -------------------------------------------------------------------------------- /src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | @import "./color.scss"; 2 | 3 | /** 4 | * 自定义elementUI主题色 5 | */ 6 | 7 | $--color-primary:$primaryColor; 8 | /* 改变 icon 字体路径变量,必需 */ 9 | $--font-path: 'element-plus/lib/theme-chalk/fonts'; 10 | 11 | @import "element-plus/packages/theme-chalk/src/index"; 12 | 13 | 14 | /********************重置elementUI组件某些样式******************/ 15 | 16 | //全局设置tab的细线 17 | .el-tabs__nav-wrap::after { 18 | height: 1px; 19 | } 20 | 21 | 22 | .el-overlay { 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | } 27 | 28 | //全局弹窗居中, 内容溢出滚动 29 | 30 | .el-dialog { 31 | display: flex; 32 | flex-direction: column; 33 | margin: 0 !important; 34 | max-height: calc(100% - 30px); 35 | max-width: calc(100% - 30px); 36 | 37 | @media screen and (max-width:1024px) { 38 | width: 90% !important; 39 | } 40 | } 41 | 42 | .el-dialog__wrapper { 43 | transition-duration: 0.3s; 44 | } 45 | 46 | .el-dialog .el-dialog__body { 47 | flex: 1; 48 | overflow: auto; 49 | padding-top: 10px; 50 | padding-bottom: 10px; 51 | } 52 | 53 | .el-dialog__header { 54 | text-align: left; 55 | } 56 | 57 | .el-dialog__wrapper { 58 | background: rgba(#000, 0.3); 59 | } 60 | 61 | .el-radio-group { 62 | white-space: nowrap; 63 | } 64 | 65 | .el-image-viewer__mask { 66 | opacity: 0.8; 67 | } 68 | 69 | 70 | .el-image { 71 | .el-icon-circle-close::before { 72 | color: #fff; 73 | } 74 | } 75 | 76 | .el-tree-node__content { 77 | padding: 4px 0; 78 | } 79 | 80 | .el-cascader__tags { 81 | input { 82 | &::placeholder { 83 | color: transparent; 84 | } 85 | } 86 | } 87 | 88 | .el-textarea__inner, 89 | .el-input__inner { 90 | border-radius: 2px; 91 | background-color: rgba(#000, 0.03); 92 | border: 1px solid transparent; 93 | transition: all 0.5s; 94 | box-sizing: border-box; 95 | } 96 | 97 | .el-range-input { 98 | background: transparent; 99 | } 100 | 101 | .el-input-group__append { 102 | border: none; 103 | } 104 | 105 | .el-tag { 106 | border-radius: 2px; 107 | } 108 | 109 | 110 | .el-button { 111 | border-radius: 2px; 112 | } 113 | 114 | .el-button--default { 115 | border: none; 116 | outline: none; 117 | background-color: rgba(#000, 0.05); 118 | } 119 | 120 | .el-menu.el-menu--horizontal { 121 | border-bottom: 0; 122 | } 123 | 124 | .el-form--label-top .el-form-item__label { 125 | padding-bottom: 0 !important; 126 | } 127 | 128 | .el-form-item { 129 | margin-bottom: 15px !important; 130 | } 131 | 132 | .el-message { 133 | border: none; 134 | box-shadow: 0 5px 5px 0 rgba(#000, 0.02); 135 | 136 | @media screen and (max-width:750px) { 137 | min-width: 80% !important; 138 | } 139 | } -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局样式表 3 | */ 4 | 5 | @import "./color.scss"; 6 | @import "./custom-default-browser-style.scss"; 7 | @import "./mixin.scss"; 8 | @import "./animation.scss"; 9 | 10 | 11 | //头部导航栏高度 12 | $appHeaderHeight:62px; 13 | //侧边导航栏宽度 14 | $sideMenuWidth:220px; 15 | 16 | 17 | //管理端面包屑高度 18 | $breadcrumbHeight:50px; 19 | //管理端主内容高度 20 | $adminContentHeight:calc(100vh - #{$appHeaderHeight} - #{$breadcrumbHeight} - 2px); 21 | 22 | 23 | #nprogress .bar { 24 | background: $primaryColor; 25 | height: 3px; 26 | } 27 | 28 | 29 | body { 30 | font-family: "Avenir", Helvetica, Arial, sans-serif; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | color: #000; 34 | width: 100%; 35 | height: 100%; 36 | } -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局scss的mixin 3 | */ 4 | @mixin flex-row-center-center { 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | @mixin flex-col-center-center { 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | 18 | //单行溢出 19 | @mixin ellipsis-singe-line { 20 | white-space: nowrap; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | } 24 | 25 | //多行溢出 26 | @mixin ellipsis-multi-line($line) { 27 | display: -webkit-box; 28 | -webkit-box-orient: vertical; 29 | -webkit-line-clamp: $line; 30 | overflow: hidden; 31 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from "@vitejs/plugin-vue"; 2 | import { visualizer } from "rollup-plugin-visualizer"; 3 | import strip from "@rollup/plugin-strip"; 4 | import viteCompression from "vite-plugin-compression"; 5 | const path = require("path"); 6 | 7 | export default { 8 | base: './', 9 | plugins: [ 10 | vue(), 11 | //正式环境打包查看各文件大小占比 12 | visualizer({ 13 | open: true, 14 | gzipSize: true, 15 | brotliSize: true, 16 | }), 17 | //正式环境打包去除调试语句 18 | { 19 | ...strip({ 20 | include: ["**/*.js", "**/*.vue", "**/*.ts", "**/*.jsx"], 21 | }), 22 | apply: "build", 23 | }, 24 | //打包开启gzip压缩 25 | viteCompression(), 26 | ], 27 | resolve: { 28 | alias: { 29 | // 键必须以斜线开始和结束 30 | "@": path.resolve(__dirname, "./src"), 31 | }, 32 | }, 33 | css: { 34 | preprocessorOptions: { 35 | scss: { 36 | //添加scss全局变量样式 37 | additionalData: "@import './src/styles/global.scss';", 38 | }, 39 | }, 40 | }, 41 | server: { 42 | // 配置调试服务器主机名,如果允许外部访问,可设置为"0.0.0.0" 43 | host: "0.0.0.0", 44 | port: 3000, // 服务器端口号 45 | open: true, // 是否自动打开浏览器 46 | }, 47 | }; 48 | --------------------------------------------------------------------------------