diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..1723ef5
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,8 @@
+# 页面标题
+VITE_APP_TITLE = AgileBoot管理系统
+
+# 开发环境配置
+VITE_APP_ENV = 'development'
+
+# AgileBoot管理系统/开发环境
+VITE_APP_BASE_API = '/dev-api'
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..c081c62
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,11 @@
+# 页面标题
+VITE_APP_TITLE = AgileBoot管理系统
+
+# 生产环境配置
+VITE_APP_ENV = 'production'
+
+# AgileBoot管理系统/生产环境
+VITE_APP_BASE_API = '/prod-api'
+
+# 是否在打包时开启压缩,支持 gzip 和 brotli
+VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
diff --git a/.env.staging b/.env.staging
new file mode 100644
index 0000000..1c58f78
--- /dev/null
+++ b/.env.staging
@@ -0,0 +1,11 @@
+# 页面标题
+VITE_APP_TITLE = AgileBoot管理系统
+
+# 生产环境配置
+VITE_APP_ENV = 'staging'
+
+# AgileBoot管理系统/生产环境
+VITE_APP_BASE_API = '/stage-api'
+
+# 是否在打包时开启压缩,支持 gzip 和 brotli
+VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..840c164
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true,
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/vue3-essential",
+ "airbnb-base"
+ ],
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": [
+ "vue"
+ ],
+ "rules": {
+ }
+}
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..fbcab77
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..78a752d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..05ac595
--- /dev/null
+++ b/README.md
@@ -0,0 +1,109 @@
+
+
+
+RuoYi v3.8.2
+基于SpringBoot+Vue3前后端分离的Java快速开发框架
+
+
+
+
+
+
+## 平台简介
+
+* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
+* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast) 版本。
+* 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui)。
+* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)
+* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)
+
+## 前端运行
+
+```bash
+# 克隆项目
+git clone https://github.com/yangzongzhuan/RuoYi-Vue3.git
+
+# 进入项目目录
+cd RuoYi-Vue3
+
+# 安装依赖
+yarn --registry=https://registry.npmmirror.com
+
+# 启动服务
+yarn dev
+
+# 构建测试环境 yarn build:stage
+# 构建生产环境 yarn build:prod
+# 前端访问地址 http://localhost:80
+```
+
+## 内置功能
+
+1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
+2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
+3. 岗位管理:配置系统用户所属担任职务。
+4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
+5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
+6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
+7. 参数管理:对系统动态配置常用参数。
+8. 通知公告:系统通知公告信息发布维护。
+9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
+10. 登录日志:系统登录日志记录查询包含登录异常。
+11. 在线用户:当前系统中活跃用户状态监控。
+12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
+13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。
+14. 系统接口:根据业务代码自动生成相关的api接口文档。
+15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
+16. 缓存监控:对系统的缓存信息查询,命令统计等。
+17. 在线构建器:拖动表单元素生成相应的HTML代码。
+18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。
+
+## 在线体验
+
+- admin/admin123
+- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
+
+演示地址:http://vue.ruoyi.vip
+文档地址:http://doc.ruoyi.vip
+
+## 演示图
+
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+
+
+## AgileBoot前后端分离交流群
+
+QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) 点击按钮入群。
\ No newline at end of file
diff --git a/bin/build.bat b/bin/build.bat
new file mode 100644
index 0000000..21a1dea
--- /dev/null
+++ b/bin/build.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 打包Web工程,生成dist文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+yarn build:prod
+
+pause
\ No newline at end of file
diff --git a/bin/package.bat b/bin/package.bat
new file mode 100644
index 0000000..28ce921
--- /dev/null
+++ b/bin/package.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 安装Web工程,生成node_modules文件。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+yarn --registry=https://registry.npmmirror.com
+
+pause
\ No newline at end of file
diff --git a/bin/run-web.bat b/bin/run-web.bat
new file mode 100644
index 0000000..b7041f0
--- /dev/null
+++ b/bin/run-web.bat
@@ -0,0 +1,12 @@
+@echo off
+echo.
+echo [信息] 使用 Vite 命令运行 Web 工程。
+echo.
+
+%~d0
+cd %~dp0
+
+cd ..
+yarn dev
+
+pause
\ No newline at end of file
diff --git a/html/ie.html b/html/ie.html
new file mode 100644
index 0000000..052ffcd
--- /dev/null
+++ b/html/ie.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+ 请升级您的浏览器
+
+
+
+
+
+
+请升级您的浏览器,以便我们更好的为您提供服务!
+您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。
+
+请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束
+自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明 。
+
+您可以选择更先进的浏览器
+推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f4a5732
--- /dev/null
+++ b/index.html
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+ AgileBoot管理系统
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..bcc953c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "ruoyi",
+ "version": "3.8.2",
+ "description": "AgileBoot管理系统",
+ "author": "valarchie",
+ "license": "MIT",
+ "scripts": {
+ "dev": "vite",
+ "build:prod": "vite build",
+ "build:stage": "vite build --mode staging",
+ "preview": "vite preview",
+ "fix": "eslint src/**/*.* --fix"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://gitee.com/y_project/RuoYi-Vue.git"
+ },
+ "dependencies": {
+ "@element-plus/icons-vue": "1.1.4",
+ "axios": "0.26.1",
+ "echarts": "5.3.2",
+ "element-plus": "2.1.8",
+ "file-saver": "2.0.5",
+ "fuse.js": "6.5.3",
+ "js-cookie": "3.0.1",
+ "jsencrypt": "3.2.1",
+ "nprogress": "0.2.0",
+ "vue": "3.2.31",
+ "vue-cropper": "1.0.3",
+ "vue-router": "4.0.14",
+ "vuex": "4.0.2"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "2.3.1",
+ "@vue/compiler-sfc": "3.2.31",
+ "eslint": "^8.21.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-standard": "^17.0.0",
+ "eslint-plugin-import": "^2.26.0",
+ "eslint-plugin-n": "^15.2.4",
+ "eslint-plugin-promise": "^6.0.0",
+ "eslint-plugin-vue": "^9.3.0",
+ "sass": "1.50.0",
+ "unplugin-auto-import": "0.6.9",
+ "vite": "2.6.14",
+ "vite-plugin-compression": "0.5.1",
+ "vite-plugin-svg-icons": "1.0.5",
+ "vite-plugin-vue-setup-extend": "0.4.0"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e263760
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..98240ae
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/api/login.js b/src/api/login.js
new file mode 100644
index 0000000..724411e
--- /dev/null
+++ b/src/api/login.js
@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { encrypt, decrypt } from '@/utils/jsencrypt';
+
+// 登录方法
+export function login(username, password, code, uuid) {
+ const passwordEncrypt = encrypt(password);
+
+ // console.log(passwordEncrypt);
+ const data = {
+ username,
+ password: passwordEncrypt,
+ code,
+ uuid,
+ };
+ return request({
+ url: '/login',
+ headers: {
+ isToken: false,
+ },
+ method: 'post',
+ data,
+ });
+}
+
+// 注册方法
+export function register(data) {
+ return request({
+ url: '/register',
+ headers: {
+ isToken: false,
+ },
+ method: 'post',
+ data,
+ });
+}
+
+// 获取用户详细信息
+export function getLoginUserInfo() {
+ return request({
+ url: '/getLoginUserInfo',
+ method: 'get',
+ });
+}
+
+// 退出方法
+export function logout() {
+ return request({
+ url: '/logout',
+ method: 'post',
+ });
+}
+
+// 获取验证码
+export function getCodeImg() {
+ return request({
+ url: '/captchaImage',
+ headers: {
+ isToken: false,
+ },
+ method: 'get',
+ timeout: 20000,
+ });
+}
diff --git a/src/api/menu.js b/src/api/menu.js
new file mode 100644
index 0000000..9b9b979
--- /dev/null
+++ b/src/api/menu.js
@@ -0,0 +1,7 @@
+import request from '@/utils/request';
+
+// 获取路由
+export const getRouters = () => request({
+ url: '/getRouters',
+ method: 'get',
+});
diff --git a/src/api/monitor/cache.js b/src/api/monitor/cache.js
new file mode 100644
index 0000000..6fd2a58
--- /dev/null
+++ b/src/api/monitor/cache.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+// 查询缓存详细
+export function getCacheInfo() {
+ return request({
+ url: '/monitor/cacheInfo',
+ method: 'get',
+ });
+}
diff --git a/src/api/monitor/logininfor.js b/src/api/monitor/logininfor.js
new file mode 100644
index 0000000..31b6b67
--- /dev/null
+++ b/src/api/monitor/logininfor.js
@@ -0,0 +1,26 @@
+import request from '@/utils/request';
+
+// 查询登录日志列表
+export function list(query) {
+ return request({
+ url: '/loginInfo/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 删除登录日志
+export function deleteLoginInfo(infoId) {
+ return request({
+ url: `/loginInfo/${infoId}`,
+ method: 'delete',
+ });
+}
+
+// 清空登录日志
+export function cleanLoginInfo() {
+ return request({
+ url: '/loginInfo/clean',
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/online.js b/src/api/monitor/online.js
new file mode 100644
index 0000000..e10984e
--- /dev/null
+++ b/src/api/monitor/online.js
@@ -0,0 +1,18 @@
+import request from '@/utils/request';
+
+// 查询在线用户列表
+export function list(query) {
+ return request({
+ url: '/monitor/onlineUser/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 强退用户
+export function forceLogout(tokenId) {
+ return request({
+ url: `/monitor/onlineUser/${tokenId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/operlog.js b/src/api/monitor/operlog.js
new file mode 100644
index 0000000..63718a6
--- /dev/null
+++ b/src/api/monitor/operlog.js
@@ -0,0 +1,26 @@
+import request from '@/utils/request';
+
+// 查询操作日志列表
+export function list(query) {
+ return request({
+ url: '/operationLog/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 删除操作日志
+export function deleteOperationLog(operationId) {
+ return request({
+ url: `/operationLog/${operationId}`,
+ method: 'delete',
+ });
+}
+
+// 清空操作日志
+export function cleanOperlog() {
+ return request({
+ url: '/operationLog/clean',
+ method: 'delete',
+ });
+}
diff --git a/src/api/monitor/server.js b/src/api/monitor/server.js
new file mode 100644
index 0000000..1572c06
--- /dev/null
+++ b/src/api/monitor/server.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+// 获取服务信息
+export function getServerInfo() {
+ return request({
+ url: '/monitor/serverInfo',
+ method: 'get',
+ });
+}
diff --git a/src/api/system/config.js b/src/api/system/config.js
new file mode 100644
index 0000000..88374a6
--- /dev/null
+++ b/src/api/system/config.js
@@ -0,0 +1,45 @@
+import request from '@/utils/request';
+
+// 查询参数列表
+export function listConfig(query) {
+ return request({
+ url: '/system/config/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询参数详细
+export function getConfig(configId) {
+ return request({
+ url: `/system/config/${configId}`,
+ method: 'get',
+ });
+}
+
+// 根据字典类型查询字典数据信息
+export function getDictType(dictType) {
+ return request({
+ url: `/system/config/dict/${dictType}`,
+ method: 'get',
+ });
+}
+
+
+// 修改参数配置
+export function updateConfig(data) {
+ return request({
+ url: '/system/config',
+ method: 'put',
+ data,
+ });
+}
+
+
+// 刷新参数缓存
+export function refreshCache() {
+ return request({
+ url: '/system/config/refreshCache',
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/dept.js b/src/api/system/dept.js
new file mode 100644
index 0000000..672d8b9
--- /dev/null
+++ b/src/api/system/dept.js
@@ -0,0 +1,68 @@
+import request from '@/utils/request';
+
+// 查询部门列表
+export function listDept(query) {
+ return request({
+ url: '/system/dept/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询部门列表(排除当前自身节点)
+export function listDeptExcludeCurrentDeptItself(deptId) {
+ return request({
+ url: `/system/dept/list/exclude/${deptId}`,
+ method: 'get',
+ });
+}
+
+// 查询部门详细
+export function getDept(deptId) {
+ return request({
+ url: `/system/dept/${deptId}`,
+ method: 'get',
+ });
+}
+
+// 查询部门下拉树结构
+export function getDeptSelectTree() {
+ return request({
+ url: '/system/dept/dropdownList',
+ method: 'get',
+ });
+}
+
+// 根据角色ID查询部门树结构
+export function getDeptTreeSelectByRole(roleId) {
+ return request({
+ url: `/system/dept/dropdownList/role/${roleId}`,
+ method: 'get',
+ });
+}
+
+// 新增部门
+export function addDept(data) {
+ return request({
+ url: '/system/dept',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改部门
+export function updateDept(data) {
+ return request({
+ url: '/system/dept',
+ method: 'put',
+ data,
+ });
+}
+
+// 删除部门
+export function deleteDept(deptId) {
+ return request({
+ url: `/system/dept/${deptId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/menu.js b/src/api/system/menu.js
new file mode 100644
index 0000000..c07deee
--- /dev/null
+++ b/src/api/system/menu.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request';
+
+// 查询菜单列表
+export function listMenu(query) {
+ return request({
+ url: '/system/menu/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询菜单详细
+export function getMenu(menuId) {
+ return request({
+ url: `/system/menu/${menuId}`,
+ method: 'get',
+ });
+}
+
+// 查询菜单下拉树结构
+export function getMenuSelectTree() {
+ return request({
+ url: '/system/menu/dropdownList',
+ method: 'get',
+ });
+}
+
+// 根据角色ID查询菜单下拉树结构
+export function getMenuSelectTreeByRole(roleId) {
+ return request({
+ url: `/system/menu/roleMenuTreeSelect/${roleId}`,
+ method: 'get',
+ });
+}
+
+// 新增菜单
+export function addMenu(data) {
+ return request({
+ url: '/system/menu',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改菜单
+export function updateMenu(data) {
+ return request({
+ url: '/system/menu',
+ method: 'put',
+ data,
+ });
+}
+
+// 删除菜单
+export function deleteMenu(menuId) {
+ return request({
+ url: `/system/menu/${menuId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/notice.js b/src/api/system/notice.js
new file mode 100644
index 0000000..ee6284e
--- /dev/null
+++ b/src/api/system/notice.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request';
+
+// 查询公告列表
+export function listNotice(query) {
+ return request({
+ url: '/system/notice/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询公告详细
+export function getNotice(noticeId) {
+ return request({
+ url: `/system/notice/${noticeId}`,
+ method: 'get',
+ });
+}
+
+// 新增公告
+export function addNotice(data) {
+ return request({
+ url: '/system/notice',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改公告
+export function updateNotice(data) {
+ return request({
+ url: '/system/notice',
+ method: 'put',
+ data,
+ });
+}
+
+// 删除公告
+export function deleteNotice(noticeId) {
+ return request({
+ url: `/system/notice/${noticeId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/post.js b/src/api/system/post.js
new file mode 100644
index 0000000..7b93454
--- /dev/null
+++ b/src/api/system/post.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request';
+
+// 查询岗位列表
+export function listPost(query) {
+ return request({
+ url: '/system/post/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询岗位详细
+export function getPost(postId) {
+ return request({
+ url: `/system/post/${postId}`,
+ method: 'get',
+ });
+}
+
+// 新增岗位
+export function addPost(data) {
+ return request({
+ url: '/system/post',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改岗位
+export function updatePost(data) {
+ return request({
+ url: '/system/post',
+ method: 'put',
+ data,
+ });
+}
+
+// 删除岗位
+export function deletePost(postId) {
+ return request({
+ url: `/system/post/${postId}`,
+ method: 'delete',
+ });
+}
diff --git a/src/api/system/role.js b/src/api/system/role.js
new file mode 100644
index 0000000..e76a471
--- /dev/null
+++ b/src/api/system/role.js
@@ -0,0 +1,117 @@
+import request from '@/utils/request';
+
+// 查询角色列表
+export function listRole(query) {
+ return request({
+ url: '/system/role/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询角色详细
+export function getRole(roleId) {
+ return request({
+ url: `/system/role/${roleId}`,
+ method: 'get',
+ });
+}
+
+// 新增角色
+export function addRole(data) {
+ return request({
+ url: '/system/role',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改角色
+export function updateRole(data) {
+ return request({
+ url: '/system/role',
+ method: 'put',
+ data,
+ });
+}
+
+// 角色数据权限
+export function changeDataScope(data) {
+ const { roleId } = data;
+ return request({
+ url: `/system/role/${roleId}/dataScope`,
+ method: 'put',
+ data,
+ });
+}
+
+// 角色状态修改
+export function changeRoleStatus(roleId, status) {
+ const data = {
+ roleId,
+ status,
+ };
+ return request({
+ url: `/system/role/${roleId}/status`,
+ method: 'put',
+ data,
+ });
+}
+
+// 删除角色
+export function deleteRole(roleId) {
+ return request({
+ url: `/system/role/${roleId}`,
+ method: 'delete',
+ });
+}
+
+// 查询角色已授权用户列表
+export function allocatedUserList(query) {
+ const { roleId } = query;
+ return request({
+ url: `/system/role/${roleId}/allocated/list`,
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询角色未授权用户列表
+export function unallocatedUserList(query) {
+ const { roleId } = query;
+ return request({
+ url: `/system/role/${roleId}/unallocated/list`,
+ method: 'get',
+ params: query,
+ });
+}
+
+// 取消用户授权角色 弃用
+export function deleteRoleOfUser(data) {
+ const { roleId, userIds } = data;
+ return request({
+ url: `/system/role/users/${userIds}/grant`,
+ method: 'delete',
+ data,
+ });
+}
+
+// 批量取消用户授权角色
+export function deleteRoleOfSomeUser(data) {
+ const { roleId, userIds } = data;
+ return request({
+ url: `/system/role/users/${userIds}/grant/bulk`,
+ method: 'delete',
+ params: data,
+ });
+}
+
+// 授权用户选择
+export function addRoleOfAllUser(data) {
+ const { roleId, userIds } = data;
+ return request({
+ url: `/system/role/${roleId}/users/${userIds}/grant/bulk`,
+ method: 'post',
+ data,
+ });
+}
diff --git a/src/api/system/user.js b/src/api/system/user.js
new file mode 100644
index 0000000..968bbe0
--- /dev/null
+++ b/src/api/system/user.js
@@ -0,0 +1,128 @@
+import request from '@/utils/request';
+import { parseStrEmpty } from '@/utils/ruoyi';
+
+// 查询用户列表
+export function listUser(query) {
+ return request({
+ url: '/system/user/list',
+ method: 'get',
+ params: query,
+ });
+}
+
+// 查询用户详细
+export function getUser(userId) {
+ return request({
+ url: `/system/user/${parseStrEmpty(userId)}`,
+ method: 'get',
+ });
+}
+
+// 新增用户
+export function addUser(data) {
+ return request({
+ url: '/system/user',
+ method: 'post',
+ data,
+ });
+}
+
+// 修改用户
+export function updateUser(data) {
+ return request({
+ url: '/system/user',
+ method: 'put',
+ data,
+ });
+}
+
+// 删除用户
+export function delUser(userId) {
+ return request({
+ url: `/system/user/${userId}`,
+ method: 'delete',
+ });
+}
+
+// 用户密码重置
+export function resetUserPwd(userId, password) {
+ const data = {
+ userId,
+ password,
+ };
+ return request({
+ url: `/system/user/${userId}/password/reset`,
+ method: 'put',
+ data,
+ });
+}
+
+// 用户状态修改
+export function changeUserStatus(userId, status) {
+ const data = {
+ userId,
+ status,
+ };
+ return request({
+ url: `/system/user/${userId}/status`,
+ method: 'put',
+ data,
+ });
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+ return request({
+ url: '/system/user/profile',
+ method: 'get',
+ });
+}
+
+// 修改用户个人信息
+export function updateUserProfile(data) {
+ return request({
+ url: '/system/user/profile',
+ method: 'put',
+ data,
+ });
+}
+
+// 用户密码重置
+export function updateUserPwd(oldPassword, newPassword) {
+ const data = {
+ oldPassword,
+ newPassword,
+ };
+ return request({
+ url: '/system/user/profile/password',
+ method: 'put',
+ data,
+ });
+}
+
+// 用户头像上传
+export function uploadAvatar(data) {
+ return request({
+ url: '/system/user/profile/avatar',
+ method: 'post',
+ data,
+ });
+}
+
+// 查询授权角色
+export function getAuthRole(userId) {
+ return request({
+ url: `/system/user/${userId}/role/`,
+ method: 'get',
+ });
+}
+
+// 保存授权角色
+export function updateAuthRole(data) {
+ const { userId } = data.userId;
+ return request({
+ url: `/system/user/${userId}/role/`,
+ method: 'put',
+ params: data,
+ });
+}
diff --git a/src/assets/401_images/401.gif b/src/assets/401_images/401.gif
new file mode 100644
index 0000000..cd6e0d9
Binary files /dev/null and b/src/assets/401_images/401.gif differ
diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
Binary files /dev/null and b/src/assets/404_images/404.png differ
diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ
diff --git a/src/assets/icons/svg/404.svg b/src/assets/icons/svg/404.svg
new file mode 100644
index 0000000..c69b73a
--- /dev/null
+++ b/src/assets/icons/svg/404.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/bug.svg b/src/assets/icons/svg/bug.svg
new file mode 100644
index 0000000..545445f
--- /dev/null
+++ b/src/assets/icons/svg/bug.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/build.svg b/src/assets/icons/svg/build.svg
new file mode 100644
index 0000000..df50fa0
--- /dev/null
+++ b/src/assets/icons/svg/build.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/button.svg b/src/assets/icons/svg/button.svg
new file mode 100644
index 0000000..b6c7cc0
--- /dev/null
+++ b/src/assets/icons/svg/button.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/cascader.svg b/src/assets/icons/svg/cascader.svg
new file mode 100644
index 0000000..bd68954
--- /dev/null
+++ b/src/assets/icons/svg/cascader.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/chart.svg b/src/assets/icons/svg/chart.svg
new file mode 100644
index 0000000..4f233ad
--- /dev/null
+++ b/src/assets/icons/svg/chart.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/checkbox.svg b/src/assets/icons/svg/checkbox.svg
new file mode 100644
index 0000000..aed629d
--- /dev/null
+++ b/src/assets/icons/svg/checkbox.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/clipboard.svg b/src/assets/icons/svg/clipboard.svg
new file mode 100644
index 0000000..9cc81c4
--- /dev/null
+++ b/src/assets/icons/svg/clipboard.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/code.svg b/src/assets/icons/svg/code.svg
new file mode 100644
index 0000000..547e244
--- /dev/null
+++ b/src/assets/icons/svg/code.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/color.svg b/src/assets/icons/svg/color.svg
new file mode 100644
index 0000000..8b3f86a
--- /dev/null
+++ b/src/assets/icons/svg/color.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/component.svg b/src/assets/icons/svg/component.svg
new file mode 100644
index 0000000..6db5b80
--- /dev/null
+++ b/src/assets/icons/svg/component.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/dashboard.svg b/src/assets/icons/svg/dashboard.svg
new file mode 100644
index 0000000..5b1db14
--- /dev/null
+++ b/src/assets/icons/svg/dashboard.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/date-range.svg b/src/assets/icons/svg/date-range.svg
new file mode 100644
index 0000000..d3080ee
--- /dev/null
+++ b/src/assets/icons/svg/date-range.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/date.svg b/src/assets/icons/svg/date.svg
new file mode 100644
index 0000000..9ff2dea
--- /dev/null
+++ b/src/assets/icons/svg/date.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/dict.svg b/src/assets/icons/svg/dict.svg
new file mode 100644
index 0000000..7e40a53
--- /dev/null
+++ b/src/assets/icons/svg/dict.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/documentation.svg b/src/assets/icons/svg/documentation.svg
new file mode 100644
index 0000000..c6be674
--- /dev/null
+++ b/src/assets/icons/svg/documentation.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/download.svg b/src/assets/icons/svg/download.svg
new file mode 100644
index 0000000..f40ad45
--- /dev/null
+++ b/src/assets/icons/svg/download.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/drag.svg b/src/assets/icons/svg/drag.svg
new file mode 100644
index 0000000..58547aa
--- /dev/null
+++ b/src/assets/icons/svg/drag.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/druid.svg b/src/assets/icons/svg/druid.svg
new file mode 100644
index 0000000..ea95efd
--- /dev/null
+++ b/src/assets/icons/svg/druid.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/edit.svg b/src/assets/icons/svg/edit.svg
new file mode 100644
index 0000000..82779ab
--- /dev/null
+++ b/src/assets/icons/svg/edit.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/education.svg b/src/assets/icons/svg/education.svg
new file mode 100644
index 0000000..ac71946
--- /dev/null
+++ b/src/assets/icons/svg/education.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/email.svg b/src/assets/icons/svg/email.svg
new file mode 100644
index 0000000..b43ec51
--- /dev/null
+++ b/src/assets/icons/svg/email.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/example.svg b/src/assets/icons/svg/example.svg
new file mode 100644
index 0000000..2c6e66b
--- /dev/null
+++ b/src/assets/icons/svg/example.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/excel.svg b/src/assets/icons/svg/excel.svg
new file mode 100644
index 0000000..3ab9dfd
--- /dev/null
+++ b/src/assets/icons/svg/excel.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/exit-fullscreen.svg b/src/assets/icons/svg/exit-fullscreen.svg
new file mode 100644
index 0000000..fc45add
--- /dev/null
+++ b/src/assets/icons/svg/exit-fullscreen.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/eye-open.svg b/src/assets/icons/svg/eye-open.svg
new file mode 100644
index 0000000..b400e4b
--- /dev/null
+++ b/src/assets/icons/svg/eye-open.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/eye.svg b/src/assets/icons/svg/eye.svg
new file mode 100644
index 0000000..421b122
--- /dev/null
+++ b/src/assets/icons/svg/eye.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/form.svg b/src/assets/icons/svg/form.svg
new file mode 100644
index 0000000..635962f
--- /dev/null
+++ b/src/assets/icons/svg/form.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/fullscreen.svg b/src/assets/icons/svg/fullscreen.svg
new file mode 100644
index 0000000..7b253b7
--- /dev/null
+++ b/src/assets/icons/svg/fullscreen.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/github.svg b/src/assets/icons/svg/github.svg
new file mode 100644
index 0000000..ed7b6ae
--- /dev/null
+++ b/src/assets/icons/svg/github.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/guide.svg b/src/assets/icons/svg/guide.svg
new file mode 100644
index 0000000..64d9db4
--- /dev/null
+++ b/src/assets/icons/svg/guide.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/icon.svg b/src/assets/icons/svg/icon.svg
new file mode 100644
index 0000000..80e445e
--- /dev/null
+++ b/src/assets/icons/svg/icon.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/input.svg b/src/assets/icons/svg/input.svg
new file mode 100644
index 0000000..01fcfab
--- /dev/null
+++ b/src/assets/icons/svg/input.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/international.svg b/src/assets/icons/svg/international.svg
new file mode 100644
index 0000000..a0fe705
--- /dev/null
+++ b/src/assets/icons/svg/international.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/job.svg b/src/assets/icons/svg/job.svg
new file mode 100644
index 0000000..96caee8
--- /dev/null
+++ b/src/assets/icons/svg/job.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/language.svg b/src/assets/icons/svg/language.svg
new file mode 100644
index 0000000..e3109a4
--- /dev/null
+++ b/src/assets/icons/svg/language.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/link.svg b/src/assets/icons/svg/link.svg
new file mode 100644
index 0000000..78dc9c9
--- /dev/null
+++ b/src/assets/icons/svg/link.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/list.svg b/src/assets/icons/svg/list.svg
new file mode 100644
index 0000000..326a3a1
--- /dev/null
+++ b/src/assets/icons/svg/list.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/lock.svg b/src/assets/icons/svg/lock.svg
new file mode 100644
index 0000000..81bd860
--- /dev/null
+++ b/src/assets/icons/svg/lock.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/log.svg b/src/assets/icons/svg/log.svg
new file mode 100644
index 0000000..0bc3742
--- /dev/null
+++ b/src/assets/icons/svg/log.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/logininfor.svg b/src/assets/icons/svg/logininfor.svg
new file mode 100644
index 0000000..53076d1
--- /dev/null
+++ b/src/assets/icons/svg/logininfor.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/message.svg b/src/assets/icons/svg/message.svg
new file mode 100644
index 0000000..f6c3319
--- /dev/null
+++ b/src/assets/icons/svg/message.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/money.svg b/src/assets/icons/svg/money.svg
new file mode 100644
index 0000000..f7889f8
--- /dev/null
+++ b/src/assets/icons/svg/money.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/monitor.svg b/src/assets/icons/svg/monitor.svg
new file mode 100644
index 0000000..c688f4f
--- /dev/null
+++ b/src/assets/icons/svg/monitor.svg
@@ -0,0 +1,2 @@
+
diff --git a/src/assets/icons/svg/nested.svg b/src/assets/icons/svg/nested.svg
new file mode 100644
index 0000000..a59364a
--- /dev/null
+++ b/src/assets/icons/svg/nested.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/number.svg b/src/assets/icons/svg/number.svg
new file mode 100644
index 0000000..9b9bbdf
--- /dev/null
+++ b/src/assets/icons/svg/number.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/online.svg b/src/assets/icons/svg/online.svg
new file mode 100644
index 0000000..9e3e124
--- /dev/null
+++ b/src/assets/icons/svg/online.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/password.svg b/src/assets/icons/svg/password.svg
new file mode 100644
index 0000000..9df95c8
--- /dev/null
+++ b/src/assets/icons/svg/password.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/pdf.svg b/src/assets/icons/svg/pdf.svg
new file mode 100644
index 0000000..7613534
--- /dev/null
+++ b/src/assets/icons/svg/pdf.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/people.svg b/src/assets/icons/svg/people.svg
new file mode 100644
index 0000000..bcf4088
--- /dev/null
+++ b/src/assets/icons/svg/people.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/peoples.svg b/src/assets/icons/svg/peoples.svg
new file mode 100644
index 0000000..739e953
--- /dev/null
+++ b/src/assets/icons/svg/peoples.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/phone.svg b/src/assets/icons/svg/phone.svg
new file mode 100644
index 0000000..55c9ada
--- /dev/null
+++ b/src/assets/icons/svg/phone.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/post.svg b/src/assets/icons/svg/post.svg
new file mode 100644
index 0000000..068c286
--- /dev/null
+++ b/src/assets/icons/svg/post.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/qq.svg b/src/assets/icons/svg/qq.svg
new file mode 100644
index 0000000..53eab84
--- /dev/null
+++ b/src/assets/icons/svg/qq.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/question.svg b/src/assets/icons/svg/question.svg
new file mode 100644
index 0000000..64b4501
--- /dev/null
+++ b/src/assets/icons/svg/question.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/radio.svg b/src/assets/icons/svg/radio.svg
new file mode 100644
index 0000000..4d8a3fc
--- /dev/null
+++ b/src/assets/icons/svg/radio.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/rate.svg b/src/assets/icons/svg/rate.svg
new file mode 100644
index 0000000..2ff2587
--- /dev/null
+++ b/src/assets/icons/svg/rate.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/redis.svg b/src/assets/icons/svg/redis.svg
new file mode 100644
index 0000000..d321520
--- /dev/null
+++ b/src/assets/icons/svg/redis.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/row.svg b/src/assets/icons/svg/row.svg
new file mode 100644
index 0000000..1aee520
--- /dev/null
+++ b/src/assets/icons/svg/row.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/search.svg b/src/assets/icons/svg/search.svg
new file mode 100644
index 0000000..3c629ee
--- /dev/null
+++ b/src/assets/icons/svg/search.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/select.svg b/src/assets/icons/svg/select.svg
new file mode 100644
index 0000000..1138495
--- /dev/null
+++ b/src/assets/icons/svg/select.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/server.svg b/src/assets/icons/svg/server.svg
new file mode 100644
index 0000000..6455f0c
--- /dev/null
+++ b/src/assets/icons/svg/server.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/shopping.svg b/src/assets/icons/svg/shopping.svg
new file mode 100644
index 0000000..0aad3b5
--- /dev/null
+++ b/src/assets/icons/svg/shopping.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/size.svg b/src/assets/icons/svg/size.svg
new file mode 100644
index 0000000..135d059
--- /dev/null
+++ b/src/assets/icons/svg/size.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/skill.svg b/src/assets/icons/svg/skill.svg
new file mode 100644
index 0000000..0dded9e
--- /dev/null
+++ b/src/assets/icons/svg/skill.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/slider.svg b/src/assets/icons/svg/slider.svg
new file mode 100644
index 0000000..a7df892
--- /dev/null
+++ b/src/assets/icons/svg/slider.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/star.svg b/src/assets/icons/svg/star.svg
new file mode 100644
index 0000000..a9363ad
--- /dev/null
+++ b/src/assets/icons/svg/star.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/swagger.svg b/src/assets/icons/svg/swagger.svg
new file mode 100644
index 0000000..367d4d1
--- /dev/null
+++ b/src/assets/icons/svg/swagger.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/switch.svg b/src/assets/icons/svg/switch.svg
new file mode 100644
index 0000000..232713a
--- /dev/null
+++ b/src/assets/icons/svg/switch.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/system.svg b/src/assets/icons/svg/system.svg
new file mode 100644
index 0000000..513f82c
--- /dev/null
+++ b/src/assets/icons/svg/system.svg
@@ -0,0 +1,2 @@
+
diff --git a/src/assets/icons/svg/tab.svg b/src/assets/icons/svg/tab.svg
new file mode 100644
index 0000000..0840bd9
--- /dev/null
+++ b/src/assets/icons/svg/tab.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/table.svg b/src/assets/icons/svg/table.svg
new file mode 100644
index 0000000..6fef9f7
--- /dev/null
+++ b/src/assets/icons/svg/table.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/textarea.svg b/src/assets/icons/svg/textarea.svg
new file mode 100644
index 0000000..6b1ffc0
--- /dev/null
+++ b/src/assets/icons/svg/textarea.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/theme.svg b/src/assets/icons/svg/theme.svg
new file mode 100644
index 0000000..b27d237
--- /dev/null
+++ b/src/assets/icons/svg/theme.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/time-range.svg b/src/assets/icons/svg/time-range.svg
new file mode 100644
index 0000000..333437b
--- /dev/null
+++ b/src/assets/icons/svg/time-range.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/time.svg b/src/assets/icons/svg/time.svg
new file mode 100644
index 0000000..355c82a
--- /dev/null
+++ b/src/assets/icons/svg/time.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/tool.svg b/src/assets/icons/svg/tool.svg
new file mode 100644
index 0000000..021b4c7
--- /dev/null
+++ b/src/assets/icons/svg/tool.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/tree-table.svg b/src/assets/icons/svg/tree-table.svg
new file mode 100644
index 0000000..9e0015c
--- /dev/null
+++ b/src/assets/icons/svg/tree-table.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/tree.svg b/src/assets/icons/svg/tree.svg
new file mode 100644
index 0000000..9112dfb
--- /dev/null
+++ b/src/assets/icons/svg/tree.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/upload.svg b/src/assets/icons/svg/upload.svg
new file mode 100644
index 0000000..80fef66
--- /dev/null
+++ b/src/assets/icons/svg/upload.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/user.svg b/src/assets/icons/svg/user.svg
new file mode 100644
index 0000000..8f4b10a
--- /dev/null
+++ b/src/assets/icons/svg/user.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/validCode.svg b/src/assets/icons/svg/validCode.svg
new file mode 100644
index 0000000..6136ca5
--- /dev/null
+++ b/src/assets/icons/svg/validCode.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/wechat.svg b/src/assets/icons/svg/wechat.svg
new file mode 100644
index 0000000..bd3c8a9
--- /dev/null
+++ b/src/assets/icons/svg/wechat.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/icons/svg/zip.svg b/src/assets/icons/svg/zip.svg
new file mode 100644
index 0000000..ab2dd37
--- /dev/null
+++ b/src/assets/icons/svg/zip.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/images/dark.svg b/src/assets/images/dark.svg
new file mode 100644
index 0000000..fffb76c
--- /dev/null
+++ b/src/assets/images/dark.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/src/assets/images/light.svg b/src/assets/images/light.svg
new file mode 100644
index 0000000..800c174
--- /dev/null
+++ b/src/assets/images/light.svg
@@ -0,0 +1,39 @@
+
+
diff --git a/src/assets/images/login-background.jpg b/src/assets/images/login-background.jpg
new file mode 100644
index 0000000..8a89eb8
Binary files /dev/null and b/src/assets/images/login-background.jpg differ
diff --git a/src/assets/images/profile.jpg b/src/assets/images/profile.jpg
new file mode 100644
index 0000000..b3a940b
Binary files /dev/null and b/src/assets/images/profile.jpg differ
diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png
new file mode 100644
index 0000000..e263760
Binary files /dev/null and b/src/assets/logo/logo.png differ
diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss
new file mode 100644
index 0000000..3590d8d
--- /dev/null
+++ b/src/assets/styles/btn.scss
@@ -0,0 +1,99 @@
+@import './variables.module.scss';
+
+@mixin colorBtn($color) {
+ background: $color;
+
+ &:hover {
+ color: $color;
+
+ &:before,
+ &:after {
+ background: $color;
+ }
+ }
+}
+
+.blue-btn {
+ @include colorBtn($blue)
+}
+
+.light-blue-btn {
+ @include colorBtn($light-blue)
+}
+
+.red-btn {
+ @include colorBtn($red)
+}
+
+.pink-btn {
+ @include colorBtn($pink)
+}
+
+.green-btn {
+ @include colorBtn($green)
+}
+
+.tiffany-btn {
+ @include colorBtn($tiffany)
+}
+
+.yellow-btn {
+ @include colorBtn($yellow)
+}
+
+.pan-btn {
+ font-size: 14px;
+ color: #fff;
+ padding: 14px 36px;
+ border-radius: 8px;
+ border: none;
+ outline: none;
+ transition: 600ms ease all;
+ position: relative;
+ display: inline-block;
+
+ &:hover {
+ background: #fff;
+
+ &:before,
+ &:after {
+ width: 100%;
+ transition: 600ms ease all;
+ }
+ }
+
+ &:before,
+ &:after {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 2px;
+ width: 0;
+ transition: 400ms ease all;
+ }
+
+ &::after {
+ right: inherit;
+ top: inherit;
+ left: 0;
+ bottom: 0;
+ }
+}
+
+.custom-button {
+ display: inline-block;
+ line-height: 1;
+ white-space: nowrap;
+ cursor: pointer;
+ background: #fff;
+ color: #fff;
+ -webkit-appearance: none;
+ text-align: center;
+ box-sizing: border-box;
+ outline: 0;
+ margin: 0;
+ padding: 10px 15px;
+ font-size: 14px;
+ border-radius: 4px;
+}
diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss
new file mode 100644
index 0000000..0f175f2
--- /dev/null
+++ b/src/assets/styles/element-ui.scss
@@ -0,0 +1,96 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+ font-weight: 400 !important;
+}
+
+.el-upload {
+ input[type="file"] {
+ display: none !important;
+ }
+}
+
+.el-upload__input {
+ display: none;
+}
+
+.cell {
+ .el-tag {
+ margin-right: 0px;
+ }
+}
+
+.small-padding {
+ .cell {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+}
+
+.fixed-width {
+ .el-button--mini {
+ padding: 7px 10px;
+ width: 60px;
+ }
+}
+
+.status-col {
+ .cell {
+ padding: 0 10px;
+ text-align: center;
+
+ .el-tag {
+ margin-right: 0px;
+ }
+ }
+}
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+ transform: none;
+ left: 0;
+ position: relative;
+ margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+ .el-upload {
+ width: 100%;
+
+ .el-upload-dragger {
+ width: 100%;
+ height: 200px;
+ }
+ }
+}
+
+// dropdown
+.el-dropdown-menu {
+ a {
+ display: block
+ }
+}
+
+// fix date-picker ui bug in filter-item
+.el-range-editor.el-input__inner {
+ display: inline-flex !important;
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+ box-sizing: content-box;
+}
+
+.el-menu--collapse
+ > div
+ > .el-submenu
+ > .el-submenu__title
+ .el-submenu__icon-arrow {
+ display: none;
+}
+
+.el-dropdown .el-dropdown-link{
+ color: var(--el-color-primary) !important;
+}
\ No newline at end of file
diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss
new file mode 100644
index 0000000..29dd2b9
--- /dev/null
+++ b/src/assets/styles/index.scss
@@ -0,0 +1,193 @@
+@import './variables.module.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+@import './btn.scss';
+@import './ruoyi.scss';
+
+body {
+ height: 100%;
+ margin: 0;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+ font-weight: 700;
+}
+
+html {
+ height: 100%;
+ box-sizing: border-box;
+}
+
+#app {
+ height: 100%;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+.no-padding {
+ padding: 0px !important;
+}
+
+.padding-content {
+ padding: 4px 0;
+}
+
+a:focus,
+a:active {
+ outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+}
+
+div:focus {
+ outline: none;
+}
+
+.fr {
+ float: right;
+}
+
+.fl {
+ float: left;
+}
+
+.pr-5 {
+ padding-right: 5px;
+}
+
+.pl-5 {
+ padding-left: 5px;
+}
+
+.block {
+ display: block;
+}
+
+.pointer {
+ cursor: pointer;
+}
+
+.inlineBlock {
+ display: block;
+}
+
+.clearfix {
+ &:after {
+ visibility: hidden;
+ display: block;
+ font-size: 0;
+ content: " ";
+ clear: both;
+ height: 0;
+ }
+}
+
+aside {
+ background: #eef1f6;
+ padding: 8px 24px;
+ margin-bottom: 20px;
+ border-radius: 2px;
+ display: block;
+ line-height: 32px;
+ font-size: 16px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+ color: #2c3e50;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ a {
+ color: #337ab7;
+ cursor: pointer;
+
+ &:hover {
+ color: rgb(32, 160, 255);
+ }
+ }
+}
+
+//main-container全局样式
+.app-container {
+ padding: 20px;
+}
+
+.components-container {
+ margin: 30px 50px;
+ position: relative;
+}
+
+.pagination-container {
+ margin-top: 30px;
+}
+
+.text-center {
+ text-align: center
+}
+
+.sub-navbar {
+ height: 50px;
+ line-height: 50px;
+ position: relative;
+ width: 100%;
+ text-align: right;
+ padding-right: 20px;
+ transition: 600ms ease position;
+ background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
+
+ .subtitle {
+ font-size: 20px;
+ color: #fff;
+ }
+
+ &.draft {
+ background: #d0d0d0;
+ }
+
+ &.deleted {
+ background: #d0d0d0;
+ }
+}
+
+.link-type,
+.link-type:focus {
+ color: #337ab7;
+ cursor: pointer;
+
+ &:hover {
+ color: rgb(32, 160, 255);
+ }
+}
+
+.filter-container {
+ padding-bottom: 10px;
+
+ .filter-item {
+ display: inline-block;
+ vertical-align: middle;
+ margin-bottom: 10px;
+ }
+}
+
+//refine vue-multiselect plugin
+.multiselect {
+ line-height: 16px;
+}
+
+.multiselect--active {
+ z-index: 1000 !important;
+}
diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss
new file mode 100644
index 0000000..06fa061
--- /dev/null
+++ b/src/assets/styles/mixin.scss
@@ -0,0 +1,66 @@
+@mixin clearfix {
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+}
+
+@mixin scrollBar {
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+}
+
+@mixin relative {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+@mixin pct($pct) {
+ width: #{$pct};
+ position: relative;
+ margin: 0 auto;
+}
+
+@mixin triangle($width, $height, $color, $direction) {
+ $width: $width/2;
+ $color-border-style: $height solid $color;
+ $transparent-border-style: $width solid transparent;
+ height: 0;
+ width: 0;
+
+ @if $direction==up {
+ border-bottom: $color-border-style;
+ border-left: $transparent-border-style;
+ border-right: $transparent-border-style;
+ }
+
+ @else if $direction==right {
+ border-left: $color-border-style;
+ border-top: $transparent-border-style;
+ border-bottom: $transparent-border-style;
+ }
+
+ @else if $direction==down {
+ border-top: $color-border-style;
+ border-left: $transparent-border-style;
+ border-right: $transparent-border-style;
+ }
+
+ @else if $direction==left {
+ border-right: $color-border-style;
+ border-top: $transparent-border-style;
+ border-bottom: $transparent-border-style;
+ }
+}
diff --git a/src/assets/styles/ruoyi.scss b/src/assets/styles/ruoyi.scss
new file mode 100644
index 0000000..68c7d90
--- /dev/null
+++ b/src/assets/styles/ruoyi.scss
@@ -0,0 +1,274 @@
+ /**
+ * 通用css样式布局处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+ /** 基础通用 **/
+.pt5 {
+ padding-top: 5px;
+}
+.pr5 {
+ padding-right: 5px;
+}
+.pb5 {
+ padding-bottom: 5px;
+}
+.mt5 {
+ margin-top: 5px;
+}
+.mr5 {
+ margin-right: 5px;
+}
+.mb5 {
+ margin-bottom: 5px;
+}
+.mb8 {
+ margin-bottom: 8px;
+}
+.ml5 {
+ margin-left: 5px;
+}
+.mt10 {
+ margin-top: 10px;
+}
+.mr10 {
+ margin-right: 10px;
+}
+.mb10 {
+ margin-bottom: 10px;
+}
+.ml10 {
+ margin-left: 10px;
+}
+.mt20 {
+ margin-top: 20px;
+}
+.mr20 {
+ margin-right: 20px;
+}
+.mb20 {
+ margin-bottom: 20px;
+}
+.ml20 {
+ margin-left: 20px;
+}
+
+.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
+ font-family: inherit;
+ font-weight: 500;
+ line-height: 1.1;
+ color: inherit;
+}
+
+.el-dialog:not(.is-fullscreen) {
+ margin-top: 6vh !important;
+}
+
+.el-dialog.scrollbar .el-dialog__body {
+ overflow: auto;
+ overflow-x: hidden;
+ max-height: 70vh;
+ padding: 10px 20px 0;
+}
+
+.el-table {
+ .el-table__header-wrapper, .el-table__fixed-header-wrapper {
+ th {
+ word-break: break-word;
+ background-color: #f8f8f9 !important;
+ color: #515a6e;
+ height: 40px !important;
+ font-size: 13px;
+ }
+ }
+ .el-table__body-wrapper {
+ .el-button [class*="el-icon-"] + span {
+ margin-left: 1px;
+ }
+ }
+}
+
+/** 表单布局 **/
+.form-header {
+ font-size:15px;
+ color:#6379bb;
+ border-bottom:1px solid #ddd;
+ margin:8px 10px 25px 10px;
+ padding-bottom:5px
+}
+
+/** 表格布局 **/
+.pagination-container {
+ // position: relative;
+ height: 25px;
+ margin-bottom: 10px;
+ margin-top: 15px;
+ padding: 10px 20px !important;
+}
+
+/* tree border */
+.tree-border {
+ margin-top: 5px;
+ border: 1px solid #e5e6e7;
+ background: #FFFFFF none;
+ border-radius:4px;
+ width: 100%;
+}
+
+.pagination-container .el-pagination {
+ right: 0;
+ position: absolute;
+}
+
+@media ( max-width : 768px) {
+ .pagination-container .el-pagination > .el-pagination__jump {
+ display: none !important;
+ }
+ .pagination-container .el-pagination > .el-pagination__sizes {
+ display: none !important;
+ }
+}
+
+.el-table .fixed-width .el-button--small {
+ padding-left: 0;
+ padding-right: 0;
+ width: inherit;
+}
+
+/** 表格更多操作下拉样式 */
+.el-table .el-dropdown-link {
+ cursor: pointer;
+ color: #409EFF;
+ margin-left: 10px;
+}
+
+.el-table .el-dropdown, .el-icon-arrow-down {
+ font-size: 12px;
+}
+
+.el-tree-node__content > .el-checkbox {
+ margin-right: 8px;
+}
+
+.list-group-striped > .list-group-item {
+ border-left: 0;
+ border-right: 0;
+ border-radius: 0;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.list-group {
+ padding-left: 0px;
+ list-style: none;
+}
+
+.list-group-item {
+ border-bottom: 1px solid #e7eaec;
+ border-top: 1px solid #e7eaec;
+ margin-bottom: -1px;
+ padding: 11px 0px;
+ font-size: 13px;
+}
+
+.pull-right {
+ float: right !important;
+}
+
+.el-card__header {
+ padding: 14px 15px 7px !important;
+ min-height: 40px;
+}
+
+.el-card__body {
+ padding: 15px 20px 20px 20px !important;
+}
+
+.card-box {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-bottom: 10px;
+}
+
+/* button color */
+.el-button--cyan.is-active,
+.el-button--cyan:active {
+ background: #20B2AA;
+ border-color: #20B2AA;
+ color: #FFFFFF;
+}
+
+.el-button--cyan:focus,
+.el-button--cyan:hover {
+ background: #48D1CC;
+ border-color: #48D1CC;
+ color: #FFFFFF;
+}
+
+.el-button--cyan {
+ background-color: #20B2AA;
+ border-color: #20B2AA;
+ color: #FFFFFF;
+}
+
+/* text color */
+.text-navy {
+ color: #1ab394;
+}
+
+.text-primary {
+ color: inherit;
+}
+
+.text-success {
+ color: #1c84c6;
+}
+
+.text-info {
+ color: #23c6c8;
+}
+
+.text-warning {
+ color: #f8ac59;
+}
+
+.text-danger {
+ color: #ed5565;
+}
+
+.text-muted {
+ color: #888888;
+}
+
+/* image */
+.img-circle {
+ border-radius: 50%;
+}
+
+.img-lg {
+ width: 120px;
+ height: 120px;
+}
+
+.avatar-upload-preview {
+ position: absolute;
+ top: 50%;
+ transform: translate(50%, -50%);
+ width: 200px;
+ height: 200px;
+ border-radius: 50%;
+ box-shadow: 0 0 4px #ccc;
+ overflow: hidden;
+}
+
+/* 拖拽列样式 */
+.sortable-ghost{
+ opacity: .8;
+ color: #fff!important;
+ background: #42b983!important;
+}
+
+/* 表格右侧工具栏样式 */
+.top-right-btn {
+ margin-left: auto;
+}
diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss
new file mode 100644
index 0000000..0808812
--- /dev/null
+++ b/src/assets/styles/sidebar.scss
@@ -0,0 +1,238 @@
+#app {
+
+ .main-container {
+ min-height: 100%;
+ transition: margin-left .28s;
+ margin-left: $base-sidebar-width;
+ position: relative;
+ }
+
+ .sidebarHide {
+ margin-left: 0!important;
+ }
+
+ .sidebar-container {
+ -webkit-transition: width .28s;
+ transition: width 0.28s;
+ width: $base-sidebar-width !important;
+ background-color: $base-menu-background;
+ height: 100%;
+ position: fixed;
+ font-size: 0px;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1001;
+ overflow: hidden;
+ -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
+ box-shadow: 2px 0 6px rgba(0,21,41,.35);
+
+ // reset element-ui css
+ .horizontal-collapse-transition {
+ transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+ }
+
+ .scrollbar-wrapper {
+ overflow-x: hidden !important;
+ }
+
+ .el-scrollbar__bar.is-vertical {
+ right: 0px;
+ }
+
+ .el-scrollbar {
+ height: 100%;
+ }
+
+ &.has-logo {
+ .el-scrollbar {
+ height: calc(100% - 50px);
+ }
+ }
+
+ .is-horizontal {
+ display: none;
+ }
+
+ a {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ }
+
+ .svg-icon {
+ margin-right: 16px;
+ }
+
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ }
+
+ .el-menu-item, .menu-title {
+ overflow: hidden !important;
+ text-overflow: ellipsis !important;
+ white-space: nowrap !important;
+ }
+
+ .el-menu-item .el-menu-tooltip__trigger {
+ display: inline-block !important;
+ }
+
+ // menu hover
+ .sub-menu-title-noDropdown,
+ .el-sub-menu__title {
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.06) !important;
+ }
+ }
+
+ & .theme-dark .is-active > .el-sub-menu__title {
+ color: $base-menu-color-active !important;
+ }
+
+ & .nest-menu .el-sub-menu>.el-sub-menu__title,
+ & .el-sub-menu .el-menu-item {
+ min-width: $base-sidebar-width !important;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.06) !important;
+ }
+ }
+
+ & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
+ & .theme-dark .el-sub-menu .el-menu-item {
+ background-color: $base-sub-menu-background !important;
+
+ &:hover {
+ background-color: $base-sub-menu-hover !important;
+ }
+ }
+ }
+
+ .hideSidebar {
+ .sidebar-container {
+ width: 54px !important;
+ }
+
+ .main-container {
+ margin-left: 54px;
+ }
+
+ .sub-menu-title-noDropdown {
+ padding: 0 !important;
+ position: relative;
+
+ .el-tooltip {
+ padding: 0 !important;
+
+ .svg-icon {
+ margin-left: 20px;
+ }
+ }
+ }
+
+ .el-sub-menu {
+ overflow: hidden;
+
+ &>.el-sub-menu__title {
+ padding: 0 !important;
+
+ .svg-icon {
+ margin-left: 20px;
+ }
+
+ }
+ }
+
+ .el-menu--collapse {
+ .el-sub-menu {
+ &>.el-sub-menu__title {
+ &>span {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
+ display: inline-block;
+ }
+ &>i {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
+ display: inline-block;
+ }
+ }
+ }
+ }
+ }
+
+ .el-menu--collapse .el-menu .el-sub-menu {
+ min-width: $base-sidebar-width !important;
+ }
+
+ // mobile responsive
+ .mobile {
+ .main-container {
+ margin-left: 0px;
+ }
+
+ .sidebar-container {
+ transition: transform .28s;
+ width: $base-sidebar-width !important;
+ }
+
+ &.hideSidebar {
+ .sidebar-container {
+ pointer-events: none;
+ transition-duration: 0.3s;
+ transform: translate3d(-$base-sidebar-width, 0, 0);
+ }
+ }
+ }
+
+ .withoutAnimation {
+
+ .main-container,
+ .sidebar-container {
+ transition: none;
+ }
+ }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+ &>.el-menu {
+ .svg-icon {
+ margin-right: 16px;
+ }
+ }
+
+ .nest-menu .el-sub-menu>.el-sub-menu__title,
+ .el-menu-item {
+ &:hover {
+ // you can use $sub-menuHover
+ background-color: rgba(0, 0, 0, 0.06) !important;
+ }
+ }
+
+ // the scroll bar appears when the sub-menu is too long
+ >.el-menu--popup {
+ max-height: 100vh;
+ overflow-y: auto;
+
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+ }
+}
diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss
new file mode 100644
index 0000000..4cb27cc
--- /dev/null
+++ b/src/assets/styles/transition.scss
@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+ opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+ transition: all .5s;
+}
+
+.fade-transform-enter {
+ opacity: 0;
+ transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+ opacity: 0;
+ transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+ transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.breadcrumb-move {
+ transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+ position: absolute;
+}
diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss
new file mode 100644
index 0000000..3dbfaa7
--- /dev/null
+++ b/src/assets/styles/variables.module.scss
@@ -0,0 +1,65 @@
+// base color
+$blue: #324157;
+$light-blue: #3A71A8;
+$red: #C03639;
+$pink: #E65D6E;
+$green: #30B08F;
+$tiffany: #4AB7BD;
+$yellow: #FEC171;
+$panGreen: #30B08F;
+
+// 默认菜单主题风格
+$base-menu-color: #bfcbd9;
+$base-menu-color-active: #f4f4f5;
+$base-menu-background: #304156;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color: rgba(0, 0, 0, 0.7);
+$base-menu-light-background: #ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background: #1f2d3d;
+$base-sub-menu-hover: #001528;
+
+// 自定义暗色菜单风格
+/**
+$base-menu-color:hsla(0,0%,100%,.65);
+$base-menu-color-active:#fff;
+$base-menu-background:#001529;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#000c17;
+$base-sub-menu-hover:#001528;
+*/
+
+$--color-primary: #409EFF;
+$--color-success: #67C23A;
+$--color-warning: #E6A23C;
+$--color-danger: #F56C6C;
+$--color-info: #909399;
+
+$base-sidebar-width: 200px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+ menuColor: $base-menu-color;
+ menuLightColor: $base-menu-light-color;
+ menuColorActive: $base-menu-color-active;
+ menuBackground: $base-menu-background;
+ menuLightBackground: $base-menu-light-background;
+ subMenuBackground: $base-sub-menu-background;
+ subMenuHover: $base-sub-menu-hover;
+ sideBarWidth: $base-sidebar-width;
+ logoTitleColor: $base-logo-title-color;
+ logoLightTitleColor: $base-logo-light-title-color;
+ primaryColor: $--color-primary;
+ successColor: $--color-success;
+ dangerColor: $--color-danger;
+ infoColor: $--color-info;
+ warningColor: $--color-warning;
+}
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..d14e864
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,66 @@
+
+
+
+
+ {{ item.meta.title }}
+ {{ item.meta.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue
new file mode 100644
index 0000000..35357c9
--- /dev/null
+++ b/src/components/DictTag/index.vue
@@ -0,0 +1,48 @@
+
+
+
+
+ {{ item.label }}
+ {{ item.label }}
+
+
+
+
+
+
+
+
diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue
new file mode 100644
index 0000000..48ea8c0
--- /dev/null
+++ b/src/components/FileUpload/index.vue
@@ -0,0 +1,198 @@
+
+
+
+
+ 选取文件
+
+
+
+ 请上传
+ 大小不超过 {{ fileSize }}MB
+ 格式为 {{ fileType.join("/") }}
+ 的文件
+
+
+
+
+
+ {{ getFileName(file.name) }}
+
+
+ 删除
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..6f9b61e
--- /dev/null
+++ b/src/components/Hamburger/index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue
new file mode 100644
index 0000000..9f7e993
--- /dev/null
+++ b/src/components/HeaderSearch/index.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue
new file mode 100644
index 0000000..b2e1c81
--- /dev/null
+++ b/src/components/IconSelect/index.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
diff --git a/src/components/IconSelect/requireIcons.js b/src/components/IconSelect/requireIcons.js
new file mode 100644
index 0000000..517458f
--- /dev/null
+++ b/src/components/IconSelect/requireIcons.js
@@ -0,0 +1,8 @@
+const icons = [];
+const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
+for (const path in modules) {
+ const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
+ icons.push(p);
+}
+
+export default icons;
diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue
new file mode 100644
index 0000000..cdee8a7
--- /dev/null
+++ b/src/components/ImagePreview/index.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
new file mode 100644
index 0000000..c6e9d72
--- /dev/null
+++ b/src/components/ImageUpload/index.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+ 请上传
+
+ 大小不超过 {{ fileSize }}MB
+
+
+ 格式为 {{ fileType.join("/") }}
+
+ 的文件
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue
new file mode 100644
index 0000000..301cab5
--- /dev/null
+++ b/src/components/Pagination/index.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
diff --git a/src/components/ParentView/index.vue b/src/components/ParentView/index.vue
new file mode 100644
index 0000000..7bf6148
--- /dev/null
+++ b/src/components/ParentView/index.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue
new file mode 100644
index 0000000..e57d5f9
--- /dev/null
+++ b/src/components/RightToolbar/index.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/RuoYi/Doc/index.vue b/src/components/RuoYi/Doc/index.vue
new file mode 100644
index 0000000..5ca5948
--- /dev/null
+++ b/src/components/RuoYi/Doc/index.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/src/components/RuoYi/Git/index.vue b/src/components/RuoYi/Git/index.vue
new file mode 100644
index 0000000..9934089
--- /dev/null
+++ b/src/components/RuoYi/Git/index.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue
new file mode 100644
index 0000000..89c3db3
--- /dev/null
+++ b/src/components/Screenfull/index.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue
new file mode 100644
index 0000000..7331ea3
--- /dev/null
+++ b/src/components/SizeSelect/index.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..e8ba736
--- /dev/null
+++ b/src/components/SvgIcon/index.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/svgicon.js b/src/components/SvgIcon/svgicon.js
new file mode 100644
index 0000000..1035468
--- /dev/null
+++ b/src/components/SvgIcon/svgicon.js
@@ -0,0 +1,10 @@
+import * as components from '@element-plus/icons-vue';
+
+export default {
+ install: (app) => {
+ for (const key in components) {
+ const componentConfig = components[key];
+ app.component(componentConfig.name, componentConfig);
+ }
+ },
+};
diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue
new file mode 100644
index 0000000..8c4f62b
--- /dev/null
+++ b/src/components/TopNav/index.vue
@@ -0,0 +1,174 @@
+
+
+
+
+ {{ item.meta.title }}
+
+
+
+
+ 更多菜单
+
+
+ {{ item.meta.title }}
+
+
+
+
+
+
+
+
diff --git a/src/components/TreeSelect/index.vue b/src/components/TreeSelect/index.vue
new file mode 100644
index 0000000..a90adf7
--- /dev/null
+++ b/src/components/TreeSelect/index.vue
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/iFrame/index.vue b/src/components/iFrame/index.vue
new file mode 100644
index 0000000..3f51bad
--- /dev/null
+++ b/src/components/iFrame/index.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/src/directive/common/copyText.js b/src/directive/common/copyText.js
new file mode 100644
index 0000000..5924bde
--- /dev/null
+++ b/src/directive/common/copyText.js
@@ -0,0 +1,66 @@
+/**
+* v-copyText 复制文本内容
+* Copyright (c) 2022 ruoyi
+*/
+
+export default {
+ beforeMount(el, { value, arg }) {
+ if (arg === 'callback') {
+ el.$copyCallback = value;
+ } else {
+ el.$copyValue = value;
+ const handler = () => {
+ copyTextToClipboard(el.$copyValue);
+ if (el.$copyCallback) {
+ el.$copyCallback(el.$copyValue);
+ }
+ };
+ el.addEventListener('click', handler);
+ el.$destroyCopy = () => el.removeEventListener('click', handler);
+ }
+ },
+};
+
+function copyTextToClipboard(input, { target = document.body } = {}) {
+ const element = document.createElement('textarea');
+ const previouslyFocusedElement = document.activeElement;
+
+ element.value = input;
+
+ // Prevent keyboard from showing on mobile
+ element.setAttribute('readonly', '');
+
+ element.style.contain = 'strict';
+ element.style.position = 'absolute';
+ element.style.left = '-9999px';
+ element.style.fontSize = '12pt'; // Prevent zooming on iOS
+
+ const selection = document.getSelection();
+ const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
+
+ target.append(element);
+ element.select();
+
+ // Explicit selection workaround for iOS
+ element.selectionStart = 0;
+ element.selectionEnd = input.length;
+
+ let isSuccess = false;
+ try {
+ isSuccess = document.execCommand('copy');
+ } catch { }
+
+ element.remove();
+
+ if (originalRange) {
+ selection.removeAllRanges();
+ selection.addRange(originalRange);
+ }
+
+ // Get the focus back on the previously focused element, if any
+ if (previouslyFocusedElement) {
+ previouslyFocusedElement.focus();
+ }
+
+ return isSuccess;
+}
diff --git a/src/directive/index.js b/src/directive/index.js
new file mode 100644
index 0000000..b91904d
--- /dev/null
+++ b/src/directive/index.js
@@ -0,0 +1,9 @@
+import hasRole from './permission/hasRole';
+import hasPermi from './permission/hasPermi';
+import copyText from './common/copyText';
+
+export default function directive(app) {
+ app.directive('hasRole', hasRole);
+ app.directive('hasPermi', hasPermi);
+ app.directive('copyText', copyText);
+}
diff --git a/src/directive/permission/hasPermi.js b/src/directive/permission/hasPermi.js
new file mode 100644
index 0000000..1f13384
--- /dev/null
+++ b/src/directive/permission/hasPermi.js
@@ -0,0 +1,26 @@
+/**
+ * v-hasPermi 操作权限处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+import store from '@/store';
+
+export default {
+ mounted(el, binding, vnode) {
+ const { value } = binding;
+ const all_permission = '*:*:*';
+ const permissions = store.getters && store.getters.permissions;
+
+ if (value && value instanceof Array && value.length > 0) {
+ const permissionFlag = value;
+
+ const hasPermissions = permissions.some((permission) => all_permission === permission || permissionFlag.includes(permission));
+
+ if (!hasPermissions) {
+ el.parentNode && el.parentNode.removeChild(el);
+ }
+ } else {
+ throw new Error('请设置操作权限标签值');
+ }
+ },
+};
diff --git a/src/directive/permission/hasRole.js b/src/directive/permission/hasRole.js
new file mode 100644
index 0000000..2fae193
--- /dev/null
+++ b/src/directive/permission/hasRole.js
@@ -0,0 +1,26 @@
+/**
+ * v-hasRole 角色权限处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+import store from '@/store';
+
+export default {
+ mounted(el, binding, vnode) {
+ const { value } = binding;
+ const super_admin = 'admin';
+ const currentRole = store.getters && store.getters.role;
+
+ if (value && value instanceof Array && value.length > 0) {
+ const roleFlag = value;
+
+ const hasRole = super_admin === currentRole || roleFlag.includes(currentRole);
+
+ if (!hasRole) {
+ el.parentNode && el.parentNode.removeChild(el);
+ }
+ } else {
+ throw new Error('请设置角色权限标签值"');
+ }
+ },
+};
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..8d3ff99
--- /dev/null
+++ b/src/layout/components/AppMain.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/InnerLink/index.vue b/src/layout/components/InnerLink/index.vue
new file mode 100644
index 0000000..8c43f78
--- /dev/null
+++ b/src/layout/components/InnerLink/index.vue
@@ -0,0 +1,29 @@
+
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..1a41f7e
--- /dev/null
+++ b/src/layout/components/Navbar.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..c9eeb8e
--- /dev/null
+++ b/src/layout/components/Settings/index.vue
@@ -0,0 +1,255 @@
+
+
+
+
主题风格设置
+
+
+
+

+
+
+
+

+
+
+
+
+ 主题颜色
+
+
+
+
+
+
+ 系统布局配置
+
+
+ 开启 TopNav
+
+
+
+
+
+
+ 开启 Tags-Views
+
+
+
+
+
+
+ 固定 Header
+
+
+
+
+
+
+ 显示 Logo
+
+
+
+
+
+
+ 动态标题
+
+
+
+
+
+
+
+ 保存配置
+ 重置配置
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..2dc67b3
--- /dev/null
+++ b/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..b16d539
--- /dev/null
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..0b175a3
--- /dev/null
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..1276eb3
--- /dev/null
+++ b/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..99d6ea9
--- /dev/null
+++ b/src/layout/components/TagsView/ScrollPane.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue
new file mode 100644
index 0000000..105aba7
--- /dev/null
+++ b/src/layout/components/TagsView/index.vue
@@ -0,0 +1,330 @@
+
+
+
+
+ {{ tag.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/index.js b/src/layout/components/index.js
new file mode 100644
index 0000000..9c8c93c
--- /dev/null
+++ b/src/layout/components/index.js
@@ -0,0 +1,4 @@
+export { default as AppMain } from './AppMain';
+export { default as Navbar } from './Navbar';
+export { default as Settings } from './Settings';
+export { default as TagsView } from './TagsView/index.vue';
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..75e8d53
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..98c7bd3
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,82 @@
+import { createApp } from 'vue';
+
+import Cookies from 'js-cookie';
+
+import ElementPlus from 'element-plus';
+import locale from 'element-plus/lib/locale/lang/zh-cn'; // 中文语言
+
+import '@/assets/styles/index.scss'; // global css
+
+import App from './App';
+import store from './store';
+import router from './router';
+import directive from './directive'; // directive
+
+// 注册指令
+import plugins from './plugins'; // plugins
+import { download } from '@/utils/request';
+
+// svg图标
+import 'virtual:svg-icons-register';
+import SvgIcon from '@/components/SvgIcon';
+import elementIcons from '@/components/SvgIcon/svgicon';
+
+import './permission'; // permission control
+
+import { useDict } from '@/utils/dict';
+import {
+ parseTime, resetForm, addDateRange, addTimeRange, handleTree, selectDictLabel,
+} from '@/utils/ruoyi';
+
+// 分页组件
+import Pagination from '@/components/Pagination';
+// 自定义表格工具组件
+import RightToolbar from '@/components/RightToolbar';
+// 文件上传组件
+import FileUpload from '@/components/FileUpload';
+// 图片上传组件
+import ImageUpload from '@/components/ImageUpload';
+// 图片预览组件
+import ImagePreview from '@/components/ImagePreview';
+// 自定义树选择组件
+import TreeSelect from '@/components/TreeSelect';
+// 字典标签组件
+import DictTag from '@/components/DictTag';
+
+const app = createApp(App);
+
+// 全局方法挂载
+app.config.globalProperties.useDict = useDict;
+app.config.globalProperties.download = download;
+app.config.globalProperties.parseTime = parseTime;
+app.config.globalProperties.resetForm = resetForm;
+app.config.globalProperties.handleTree = handleTree;
+app.config.globalProperties.addDateRange = addDateRange;
+app.config.globalProperties.addTimeRange = addTimeRange;
+app.config.globalProperties.selectDictLabel = selectDictLabel;
+
+// 全局组件挂载
+app.component('DictTag', DictTag);
+app.component('Pagination', Pagination);
+app.component('TreeSelect', TreeSelect);
+app.component('FileUpload', FileUpload);
+app.component('ImageUpload', ImageUpload);
+app.component('ImagePreview', ImagePreview);
+app.component('RightToolbar', RightToolbar);
+
+app.use(router);
+app.use(store);
+app.use(plugins);
+app.use(elementIcons);
+app.component('svg-icon', SvgIcon);
+
+directive(app);
+
+// 使用element-plus 并且设置全局的大小
+app.use(ElementPlus, {
+ locale,
+ // 支持 large、default、small
+ size: Cookies.get('size') || 'default',
+});
+
+app.mount('#app');
diff --git a/src/permission.js b/src/permission.js
new file mode 100644
index 0000000..53bcf62
--- /dev/null
+++ b/src/permission.js
@@ -0,0 +1,62 @@
+import { ElMessage } from 'element-plus';
+import NProgress from 'nprogress';
+import router from './router';
+import store from './store';
+import 'nprogress/nprogress.css';
+import { getToken } from '@/utils/auth';
+import { isHttp } from '@/utils/validate';
+import { isRelogin } from '@/utils/request';
+
+NProgress.configure({ showSpinner: false });
+
+const whiteList = ['/login', '/auth-redirect', '/bind', '/register'];
+
+router.beforeEach((to, from, next) => {
+ NProgress.start();
+ if (getToken()) {
+ to.meta.title && store.dispatch('settings/setTitle', to.meta.title);
+
+ console.log(store.getters.role);
+ /* has token */
+ if (to.path === '/login') {
+ next({ path: '/' });
+ NProgress.done();
+ } else if (!store.getters.role) {
+ console.log("重复获取")
+ isRelogin.show = true;
+ // 判断当前用户是否已拉取完user_info信息
+ store.dispatch('GetInfo').then(() => {
+ isRelogin.show = false;
+ store.dispatch('GenerateRoutes').then((accessRoutes) => {
+ // 根据roles权限生成可访问的路由表
+ accessRoutes.forEach((route) => {
+ if (!isHttp(route.path)) {
+ router.addRoute(route); // 动态添加可访问路由表
+ }
+ });
+ next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
+ });
+ }).catch((err) => {
+ store.dispatch('LogOut').then(() => {
+ ElMessage.error(err);
+ next({ path: '/' });
+ });
+ });
+ } else {
+ next();
+ }
+ } else {
+ // 没有token
+ if (whiteList.indexOf(to.path) !== -1) {
+ // 在免登录白名单,直接进入
+ next();
+ } else {
+ next(`/login?redirect=${to.fullPath}`); // 否则全部重定向到登录页
+ NProgress.done();
+ }
+ }
+});
+
+router.afterEach(() => {
+ NProgress.done();
+});
diff --git a/src/plugins/auth.js b/src/plugins/auth.js
new file mode 100644
index 0000000..ffe2d09
--- /dev/null
+++ b/src/plugins/auth.js
@@ -0,0 +1,46 @@
+import store from '@/store';
+
+function authPermission(permission) {
+ const all_permission = '*:*:*';
+ const permissions = store.getters && store.getters.permissions;
+ if (permission && permission.length > 0) {
+ return permissions.some((v) => all_permission === v || v === permission);
+ }
+ return false;
+}
+
+function authRole(role) {
+ const super_admin = 'admin';
+ const currentRole = store.getters && store.getters.role;
+ if (role && !currentRole) {
+ return super_admin === currentRole || currentRole === role;
+ }
+ return false;
+}
+
+export default {
+ // 验证用户是否具备某权限
+ hasPermi(permission) {
+ return authPermission(permission);
+ },
+ // 验证用户是否含有指定权限,只需包含其中一个
+ hasPermiOr(permissions) {
+ return permissions.some((item) => authPermission(item));
+ },
+ // 验证用户是否含有指定权限,必须全部拥有
+ hasPermiAnd(permissions) {
+ return permissions.every((item) => authPermission(item));
+ },
+ // 验证用户是否具备某角色
+ hasRole(role) {
+ return authRole(role);
+ },
+ // 验证用户是否含有指定角色,只需包含其中一个
+ hasRoleOr(roles) {
+ return roles.some((item) => authRole(item));
+ },
+ // 验证用户是否含有指定角色,必须全部拥有
+ hasRoleAnd(roles) {
+ return roles.every((item) => authRole(item));
+ },
+};
diff --git a/src/plugins/cache.js b/src/plugins/cache.js
new file mode 100644
index 0000000..04a33e7
--- /dev/null
+++ b/src/plugins/cache.js
@@ -0,0 +1,77 @@
+const sessionCache = {
+ set(key, value) {
+ if (!sessionStorage) {
+ return;
+ }
+ if (key != null && value != null) {
+ sessionStorage.setItem(key, value);
+ }
+ },
+ get(key) {
+ if (!sessionStorage) {
+ return null;
+ }
+ if (key == null) {
+ return null;
+ }
+ return sessionStorage.getItem(key);
+ },
+ setJSON(key, jsonValue) {
+ if (jsonValue != null) {
+ this.set(key, JSON.stringify(jsonValue));
+ }
+ },
+ getJSON(key) {
+ const value = this.get(key);
+ if (value != null) {
+ return JSON.parse(value);
+ }
+ },
+ remove(key) {
+ sessionStorage.removeItem(key);
+ },
+};
+const localCache = {
+ set(key, value) {
+ if (!localStorage) {
+ return;
+ }
+ if (key != null && value != null) {
+ localStorage.setItem(key, value);
+ }
+ },
+ get(key) {
+ if (!localStorage) {
+ return null;
+ }
+ if (key == null) {
+ return null;
+ }
+ return localStorage.getItem(key);
+ },
+ setJSON(key, jsonValue) {
+ if (jsonValue != null) {
+ this.set(key, JSON.stringify(jsonValue));
+ }
+ },
+ getJSON(key) {
+ const value = this.get(key);
+ if (value != null) {
+ return JSON.parse(value);
+ }
+ },
+ remove(key) {
+ localStorage.removeItem(key);
+ },
+};
+
+export default {
+ /**
+ * 会话级缓存
+ */
+ session: sessionCache,
+ /**
+ * 本地缓存
+ */
+ local: localCache,
+};
diff --git a/src/plugins/download.js b/src/plugins/download.js
new file mode 100644
index 0000000..bddba27
--- /dev/null
+++ b/src/plugins/download.js
@@ -0,0 +1,54 @@
+import axios from 'axios';
+import { ElMessage } from 'element-plus';
+import { saveAs } from 'file-saver';
+import { getToken } from '@/utils/auth';
+import errorCode from '@/utils/errorCode';
+import { blobValidate } from '@/utils/ruoyi';
+
+const baseURL = import.meta.env.VITE_APP_BASE_API;
+
+export default {
+ name(name, isDelete = true) {
+ const url = `${baseURL}/file/download?fileName=${encodeURI(name)}&delete=${isDelete}`;
+ axios({
+ method: 'get',
+ url,
+ responseType: 'blob',
+ headers: { Authorization: `Bearer ${getToken()}` },
+ }).then(async (res) => {
+ const isLogin = await blobValidate(res.data);
+ if (isLogin) {
+ const blob = new Blob([res.data]);
+ this.saveAs(blob, decodeURI(res.headers['download-filename']));
+ } else {
+ this.printErrMsg(res.data);
+ }
+ });
+ },
+ zip(url, name) {
+ var url = baseURL + url;
+ axios({
+ method: 'get',
+ url,
+ responseType: 'blob',
+ headers: { Authorization: `Bearer ${getToken()}` },
+ }).then(async (res) => {
+ const isLogin = await blobValidate(res.data);
+ if (isLogin) {
+ const blob = new Blob([res.data], { type: 'application/zip' });
+ this.saveAs(blob, name);
+ } else {
+ this.printErrMsg(res.data);
+ }
+ });
+ },
+ saveAs(text, name, opts) {
+ saveAs(text, name, opts);
+ },
+ async printErrMsg(data) {
+ const resText = await data.text();
+ const rspObj = JSON.parse(resText);
+ const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode.default;
+ ElMessage.error(errMsg);
+ },
+};
diff --git a/src/plugins/index.js b/src/plugins/index.js
new file mode 100644
index 0000000..a19b303
--- /dev/null
+++ b/src/plugins/index.js
@@ -0,0 +1,18 @@
+import tab from './tab';
+import auth from './auth';
+import cache from './cache';
+import modal from './modal';
+import download from './download';
+
+export default function installPlugins(app) {
+ // 页签操作
+ app.config.globalProperties.$tab = tab;
+ // 认证对象
+ app.config.globalProperties.$auth = auth;
+ // 缓存对象
+ app.config.globalProperties.$cache = cache;
+ // 模态框对象
+ app.config.globalProperties.$modal = modal;
+ // 下载文件
+ app.config.globalProperties.$download = download;
+}
diff --git a/src/plugins/modal.js b/src/plugins/modal.js
new file mode 100644
index 0000000..0bd71b1
--- /dev/null
+++ b/src/plugins/modal.js
@@ -0,0 +1,84 @@
+import {
+ ElMessage, ElMessageBox, ElNotification, ElLoading,
+} from 'element-plus';
+
+let loadingInstance;
+
+export default {
+ // 消息提示
+ msg(content) {
+ ElMessage.info(content);
+ },
+ // 错误消息
+ msgError(content) {
+ ElMessage.error(content);
+ },
+ // 成功消息
+ msgSuccess(content) {
+ ElMessage.success(content);
+ },
+ // 警告消息
+ msgWarning(content) {
+ ElMessage.warning(content);
+ },
+ // 弹出提示
+ alert(content) {
+ ElMessageBox.alert(content, '系统提示');
+ },
+ // 错误提示
+ alertError(content) {
+ ElMessageBox.alert(content, '系统提示', { type: 'error' });
+ },
+ // 成功提示
+ alertSuccess(content) {
+ ElMessageBox.alert(content, '系统提示', { type: 'success' });
+ },
+ // 警告提示
+ alertWarning(content) {
+ ElMessageBox.alert(content, '系统提示', { type: 'warning' });
+ },
+ // 通知提示
+ notify(content) {
+ ElNotification.info(content);
+ },
+ // 错误通知
+ notifyError(content) {
+ ElNotification.error(content);
+ },
+ // 成功通知
+ notifySuccess(content) {
+ ElNotification.success(content);
+ },
+ // 警告通知
+ notifyWarning(content) {
+ ElNotification.warning(content);
+ },
+ // 确认窗体
+ confirm(content) {
+ return ElMessageBox.confirm(content, '系统提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ });
+ },
+ // 提交内容
+ prompt(content) {
+ return ElMessageBox.prompt(content, '系统提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ });
+ },
+ // 打开遮罩层
+ loading(content) {
+ loadingInstance = ElLoading.service({
+ lock: true,
+ text: content,
+ background: 'rgba(0, 0, 0, 0.7)',
+ });
+ },
+ // 关闭遮罩层
+ closeLoading() {
+ loadingInstance.close();
+ },
+};
diff --git a/src/plugins/tab.js b/src/plugins/tab.js
new file mode 100644
index 0000000..bb1ab9c
--- /dev/null
+++ b/src/plugins/tab.js
@@ -0,0 +1,63 @@
+import store from '@/store';
+import router from '@/router';
+
+export default {
+ // 刷新当前tab页签
+ refreshPage(obj) {
+ const { path, query, matched } = router.currentRoute.value;
+ if (obj === undefined) {
+ matched.forEach((m) => {
+ if (m.components && m.components.default && m.components.default.name) {
+ if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
+ obj = { name: m.components.default.name, path, query };
+ }
+ }
+ });
+ }
+ return store.dispatch('tagsView/delCachedView', obj).then(() => {
+ const { path, query } = obj;
+ router.replace({
+ path: `/redirect${path}`,
+ query,
+ });
+ });
+ },
+ // 关闭当前tab页签,打开新页签
+ closeOpenPage(obj) {
+ store.dispatch('tagsView/delView', router.currentRoute.value);
+ if (obj !== undefined) {
+ return router.push(obj);
+ }
+ },
+ // 关闭指定tab页签
+ closePage(obj) {
+ if (obj === undefined) {
+ return store.dispatch('tagsView/delView', router.currentRoute.value).then(({ lastPath }) => router.push(lastPath || '/index'));
+ }
+ return store.dispatch('tagsView/delView', obj);
+ },
+ // 关闭所有tab页签
+ closeAllPage() {
+ return store.dispatch('tagsView/delAllViews');
+ },
+ // 关闭左侧tab页签
+ closeLeftPage(obj) {
+ return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute.value);
+ },
+ // 关闭右侧tab页签
+ closeRightPage(obj) {
+ return store.dispatch('tagsView/delRightTags', obj || router.currentRoute.value);
+ },
+ // 关闭其他tab页签
+ closeOtherPage(obj) {
+ return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute.value);
+ },
+ // 打开tab页签
+ openPage(url) {
+ return router.push(url);
+ },
+ // 修改tab页签
+ updatePage(obj) {
+ return store.dispatch('tagsView/updateVisitedView', obj);
+ },
+};
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..dafb9d0
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,136 @@
+import { createWebHistory, createRouter } from 'vue-router';
+import Layout from '@/layout';
+
+/**
+ * Note: 路由配置项
+ *
+ * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
+ * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
+ * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
+ * // 若你想不管路由下面的 children 声明的个数都显示你的根路由
+ * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
+ * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
+ * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题
+ * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
+ * meta : {
+ noCache: true // 如果设置为true,则不会被 缓存(默认 false)
+ title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
+ icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
+ breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
+ activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
+ }
+ */
+
+// 公共路由
+export const constantRoutes = [
+ {
+ path: '/redirect',
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: '/redirect/:path(.*)',
+ component: () => import('@/views/redirect/index.vue'),
+ },
+ ],
+ },
+ {
+ path: '/login',
+ component: () => import('@/views/login'),
+ hidden: true,
+ },
+ {
+ path: '/register',
+ component: () => import('@/views/register'),
+ hidden: true,
+ },
+ {
+ path: '/:pathMatch(.*)*',
+ component: () => import('@/views/error/404'),
+ hidden: true,
+ },
+ {
+ path: '/401',
+ component: () => import('@/views/error/401'),
+ hidden: true,
+ },
+ {
+ path: '',
+ component: Layout,
+ redirect: '/index',
+ children: [
+ {
+ path: '/index',
+ component: () => import('@/views/index'),
+ name: 'Index',
+ meta: { title: '首页', icon: 'dashboard', affix: true },
+ },
+ ],
+ },
+ {
+ path: '/user',
+ component: Layout,
+ hidden: true,
+ redirect: 'noredirect',
+ children: [
+ {
+ path: 'profile',
+ component: () => import('@/views/system/user/profile/index'),
+ name: 'Profile',
+ meta: { title: '个人中心', icon: 'user' },
+ },
+ ],
+ },
+ {
+ path: '/system/user-auth',
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: 'role/:userId(\\d+)',
+ component: () => import('@/views/system/user/authRole'),
+ name: 'AuthRole',
+ meta: { title: '分配角色', activeMenu: '/system/user' },
+ },
+ ],
+ },
+ {
+ path: '/system/role-auth',
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: 'user/:roleId(\\d+)',
+ component: () => import('@/views/system/role/authUser'),
+ name: 'AuthUser',
+ meta: { title: '分配用户', activeMenu: '/system/role' },
+ },
+ ],
+ },
+ {
+ path: '/system/dict-data',
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: 'index/:dictId(\\d+)',
+ component: () => import('@/views/system/dict/data'),
+ name: 'Data',
+ meta: { title: '字典数据', activeMenu: '/system/dict' },
+ },
+ ],
+ },
+];
+
+const router = createRouter({
+ history: createWebHistory(),
+ routes: constantRoutes,
+ scrollBehavior(to, from, savedPosition) {
+ if (savedPosition) {
+ return savedPosition;
+ }
+ return { top: 0 };
+ },
+});
+
+export default router;
diff --git a/src/settings.js b/src/settings.js
new file mode 100644
index 0000000..5b4cdce
--- /dev/null
+++ b/src/settings.js
@@ -0,0 +1,47 @@
+export default {
+ /**
+ * 网页标题
+ */
+ title: import.meta.env.VITE_APP_TITLE,
+ /**
+ * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
+ */
+ sideTheme: 'theme-dark',
+ /**
+ * 是否系统布局配置
+ */
+ showSettings: false,
+
+ /**
+ * 是否显示顶部导航
+ */
+ topNav: false,
+
+ /**
+ * 是否显示 tagsView
+ */
+ tagsView: true,
+
+ /**
+ * 是否固定头部
+ */
+ fixedHeader: false,
+
+ /**
+ * 是否显示logo
+ */
+ sidebarLogo: true,
+
+ /**
+ * 是否显示动态标题
+ */
+ dynamicTitle: false,
+
+ /**
+ * @type {string | array} 'production' | ['production', 'development']
+ * @description Need show err logs component.
+ * The default is only used in the production env
+ * If you want to also use it in dev, you can pass ['production', 'development']
+ */
+ errorLog: 'production',
+};
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 0000000..090cdee
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,19 @@
+const getters = {
+ sidebar: (state) => state.app.sidebar,
+ size: (state) => state.app.size,
+ device: (state) => state.app.device,
+ visitedViews: (state) => state.tagsView.visitedViews,
+ cachedViews: (state) => state.tagsView.cachedViews,
+ token: (state) => state.user.token,
+ avatar: (state) => state.user.avatar,
+ name: (state) => state.user.name,
+ introduction: (state) => state.user.introduction,
+ role: (state) => state.user.role,
+ permissions: (state) => state.user.permissions,
+ dictTypes: (state) => state.user.dictTypes,
+ permission_routes: (state) => state.permission.routes,
+ topbarRouters: (state) => state.permission.topbarRouters,
+ defaultRoutes: (state) => state.permission.defaultRoutes,
+ sidebarRouters: (state) => state.permission.sidebarRouters,
+};
+export default getters;
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..7089b9a
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,20 @@
+import { createStore } from 'vuex';
+import app from './modules/app';
+import user from './modules/user';
+import tagsView from './modules/tagsView';
+import permission from './modules/permission';
+import settings from './modules/settings';
+import getters from './getters';
+
+const store = createStore({
+ modules: {
+ app,
+ user,
+ tagsView,
+ permission,
+ settings,
+ },
+ getters,
+});
+
+export default store;
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
new file mode 100644
index 0000000..a648966
--- /dev/null
+++ b/src/store/modules/app.js
@@ -0,0 +1,66 @@
+import Cookies from 'js-cookie';
+
+const state = {
+ sidebar: {
+ opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+ withoutAnimation: false,
+ hide: false,
+ },
+ device: 'desktop',
+ size: Cookies.get('size') || 'default',
+};
+
+const mutations = {
+ TOGGLE_SIDEBAR: (state) => {
+ if (state.sidebar.hide) {
+ return false;
+ }
+ state.sidebar.opened = !state.sidebar.opened;
+ state.sidebar.withoutAnimation = false;
+ if (state.sidebar.opened) {
+ Cookies.set('sidebarStatus', 1);
+ } else {
+ Cookies.set('sidebarStatus', 0);
+ }
+ },
+ CLOSE_SIDEBAR: (state, withoutAnimation) => {
+ Cookies.set('sidebarStatus', 0);
+ state.sidebar.opened = false;
+ state.sidebar.withoutAnimation = withoutAnimation;
+ },
+ TOGGLE_DEVICE: (state, device) => {
+ state.device = device;
+ },
+ SET_SIZE: (state, size) => {
+ state.size = size;
+ Cookies.set('size', size);
+ },
+ SET_SIDEBAR_HIDE: (state, status) => {
+ state.sidebar.hide = status;
+ },
+};
+
+const actions = {
+ toggleSideBar({ commit }) {
+ commit('TOGGLE_SIDEBAR');
+ },
+ closeSideBar({ commit }, { withoutAnimation }) {
+ commit('CLOSE_SIDEBAR', withoutAnimation);
+ },
+ toggleDevice({ commit }, device) {
+ commit('TOGGLE_DEVICE', device);
+ },
+ setSize({ commit }, size) {
+ commit('SET_SIZE', size);
+ },
+ toggleSideBarHide({ commit }, status) {
+ commit('SET_SIDEBAR_HIDE', status);
+ },
+};
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions,
+};
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
new file mode 100644
index 0000000..6846e28
--- /dev/null
+++ b/src/store/modules/permission.js
@@ -0,0 +1,119 @@
+import { constantRoutes } from '@/router';
+import { getRouters } from '@/api/menu';
+import Layout from '@/layout/index';
+import ParentView from '@/components/ParentView';
+import InnerLink from '@/layout/components/InnerLink';
+
+// 匹配views里面所有的.vue文件
+const modules = import.meta.glob('./../../views/**/*.vue');
+
+const permission = {
+ state: {
+ routes: [],
+ addRoutes: [],
+ defaultRoutes: [],
+ topbarRouters: [],
+ sidebarRouters: [],
+ },
+ mutations: {
+ SET_ROUTES: (state, routes) => {
+ state.addRoutes = routes;
+ state.routes = constantRoutes.concat(routes);
+ },
+ SET_DEFAULT_ROUTES: (state, routes) => {
+ state.defaultRoutes = constantRoutes.concat(routes);
+ },
+ SET_TOPBAR_ROUTES: (state, routes) => {
+ state.topbarRouters = routes;
+ },
+ SET_SIDEBAR_ROUTERS: (state, routes) => {
+ state.sidebarRouters = routes;
+ },
+ },
+ actions: {
+ // 生成路由
+ GenerateRoutes({ commit }) {
+ return new Promise((resolve) => {
+ // 向后端请求路由数据
+ getRouters().then((res) => {
+ const sdata = JSON.parse(JSON.stringify(res));
+ const rdata = JSON.parse(JSON.stringify(res));
+ const defaultData = JSON.parse(JSON.stringify(res));
+ const sidebarRoutes = filterAsyncRouter(sdata);
+ const rewriteRoutes = filterAsyncRouter(rdata, false, true);
+ const defaultRoutes = filterAsyncRouter(defaultData);
+ commit('SET_ROUTES', rewriteRoutes);
+ commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes));
+ commit('SET_DEFAULT_ROUTES', sidebarRoutes);
+ commit('SET_TOPBAR_ROUTES', defaultRoutes);
+ resolve(rewriteRoutes);
+ });
+ });
+ },
+ },
+};
+
+// 遍历后台传来的路由字符串,转换为组件对象
+function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
+ return asyncRouterMap.filter((route) => {
+ if (type && route.children) {
+ route.children = filterChildren(route.children);
+ }
+ if (route.component) {
+ // Layout ParentView 组件特殊处理
+ if (route.component === 'Layout') {
+ route.component = Layout;
+ } else if (route.component === 'ParentView') {
+ route.component = ParentView;
+ } else if (route.component === 'InnerLink') {
+ route.component = InnerLink;
+ } else {
+ route.component = loadView(route.component);
+ }
+ }
+ if (route.children != null && route.children && route.children.length) {
+ route.children = filterAsyncRouter(route.children, route, type);
+ } else {
+ delete route.children;
+ delete route.redirect;
+ }
+ return true;
+ });
+}
+
+function filterChildren(childrenMap, lastRouter = false) {
+ let children = [];
+ childrenMap.forEach((el, index) => {
+ if (el.children && el.children.length) {
+ if (el.component === 'ParentView' && !lastRouter) {
+ el.children.forEach((c) => {
+ c.path = `${el.path}/${c.path}`;
+ if (c.children && c.children.length) {
+ children = children.concat(filterChildren(c.children, c));
+ return;
+ }
+ children.push(c);
+ });
+ return;
+ }
+ }
+ if (lastRouter) {
+ el.path = `${lastRouter.path}/${el.path}`;
+ }
+ children = children.concat(el);
+ });
+ return children;
+}
+
+export const loadView = (view) => {
+ let res;
+ for (const path in modules) {
+ const dir = path.split('views/')[1].split('.vue')[0];
+ if (dir === view) {
+ res = () => modules[path]();
+ }
+ }
+ return res;
+};
+
+export default permission;
diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js
new file mode 100644
index 0000000..bc5af29
--- /dev/null
+++ b/src/store/modules/settings.js
@@ -0,0 +1,45 @@
+import defaultSettings from '@/settings';
+import { useDynamicTitle } from '@/utils/dynamicTitle';
+
+const {
+ sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle,
+} = defaultSettings;
+
+const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '';
+const state = {
+ title: '',
+ theme: storageSetting.theme || '#409EFF',
+ sideTheme: storageSetting.sideTheme || sideTheme,
+ showSettings,
+ topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
+ tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
+ fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
+ sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
+ dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
+};
+const mutations = {
+ CHANGE_SETTING: (state, { key, value }) => {
+ if (state.hasOwnProperty(key)) {
+ state[key] = value;
+ }
+ },
+};
+
+const actions = {
+ // 修改布局设置
+ changeSetting({ commit }, data) {
+ commit('CHANGE_SETTING', data);
+ },
+ // 设置网页标题
+ setTitle({ commit }, title) {
+ state.title = title;
+ useDynamicTitle();
+ },
+};
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions,
+};
diff --git a/src/store/modules/tagsView.js b/src/store/modules/tagsView.js
new file mode 100644
index 0000000..e265b79
--- /dev/null
+++ b/src/store/modules/tagsView.js
@@ -0,0 +1,203 @@
+const state = {
+ visitedViews: [],
+ cachedViews: [],
+};
+
+const mutations = {
+ ADD_VISITED_VIEW: (state, view) => {
+ if (state.visitedViews.some((v) => v.path === view.path)) return;
+ state.visitedViews.push(
+ { ...view, title: view.meta.title || 'no-name' },
+ );
+ },
+ ADD_CACHED_VIEW: (state, view) => {
+ if (state.cachedViews.includes(view.name)) return;
+ if (!view.meta.noCache) {
+ state.cachedViews.push(view.name);
+ }
+ },
+
+ DEL_VISITED_VIEW: (state, view) => {
+ for (const [i, v] of state.visitedViews.entries()) {
+ if (v.path === view.path) {
+ state.visitedViews.splice(i, 1);
+ break;
+ }
+ }
+ },
+ DEL_CACHED_VIEW: (state, view) => {
+ const index = state.cachedViews.indexOf(view.name);
+ index > -1 && state.cachedViews.splice(index, 1);
+ },
+
+ DEL_OTHERS_VISITED_VIEWS: (state, view) => {
+ state.visitedViews = state.visitedViews.filter((v) => v.meta.affix || v.path === view.path);
+ },
+ DEL_OTHERS_CACHED_VIEWS: (state, view) => {
+ const index = state.cachedViews.indexOf(view.name);
+ if (index > -1) {
+ state.cachedViews = state.cachedViews.slice(index, index + 1);
+ } else {
+ state.cachedViews = [];
+ }
+ },
+
+ DEL_ALL_VISITED_VIEWS: (state) => {
+ // keep affix tags
+ const affixTags = state.visitedViews.filter((tag) => tag.meta.affix);
+ state.visitedViews = affixTags;
+ },
+ DEL_ALL_CACHED_VIEWS: (state) => {
+ state.cachedViews = [];
+ },
+
+ UPDATE_VISITED_VIEW: (state, view) => {
+ for (let v of state.visitedViews) {
+ if (v.path === view.path) {
+ v = Object.assign(v, view);
+ break;
+ }
+ }
+ },
+
+ DEL_RIGHT_VIEWS: (state, view) => {
+ const index = state.visitedViews.findIndex((v) => v.path === view.path);
+ if (index === -1) {
+ return;
+ }
+ state.visitedViews = state.visitedViews.filter((item, idx) => {
+ if (idx <= index || (item.meta && item.meta.affix)) {
+ return true;
+ }
+ const i = state.cachedViews.indexOf(item.name);
+ if (i > -1) {
+ state.cachedViews.splice(i, 1);
+ }
+ return false;
+ });
+ },
+
+ DEL_LEFT_VIEWS: (state, view) => {
+ const index = state.visitedViews.findIndex((v) => v.path === view.path);
+ if (index === -1) {
+ return;
+ }
+ state.visitedViews = state.visitedViews.filter((item, idx) => {
+ if (idx >= index || (item.meta && item.meta.affix)) {
+ return true;
+ }
+ const i = state.cachedViews.indexOf(item.name);
+ if (i > -1) {
+ state.cachedViews.splice(i, 1);
+ }
+ return false;
+ });
+ },
+};
+
+const actions = {
+ addView({ dispatch }, view) {
+ dispatch('addVisitedView', view);
+ dispatch('addCachedView', view);
+ },
+ addVisitedView({ commit }, view) {
+ commit('ADD_VISITED_VIEW', view);
+ },
+ addCachedView({ commit }, view) {
+ commit('ADD_CACHED_VIEW', view);
+ },
+
+ delView({ dispatch, state }, view) {
+ return new Promise((resolve) => {
+ dispatch('delVisitedView', view);
+ dispatch('delCachedView', view);
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews],
+ });
+ });
+ },
+ delVisitedView({ commit, state }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_VISITED_VIEW', view);
+ resolve([...state.visitedViews]);
+ });
+ },
+ delCachedView({ commit, state }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_CACHED_VIEW', view);
+ resolve([...state.cachedViews]);
+ });
+ },
+
+ delOthersViews({ dispatch, state }, view) {
+ return new Promise((resolve) => {
+ dispatch('delOthersVisitedViews', view);
+ dispatch('delOthersCachedViews', view);
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews],
+ });
+ });
+ },
+ delOthersVisitedViews({ commit, state }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_OTHERS_VISITED_VIEWS', view);
+ resolve([...state.visitedViews]);
+ });
+ },
+ delOthersCachedViews({ commit, state }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_OTHERS_CACHED_VIEWS', view);
+ resolve([...state.cachedViews]);
+ });
+ },
+
+ delAllViews({ dispatch, state }, view) {
+ return new Promise((resolve) => {
+ dispatch('delAllVisitedViews', view);
+ dispatch('delAllCachedViews', view);
+ resolve({
+ visitedViews: [...state.visitedViews],
+ cachedViews: [...state.cachedViews],
+ });
+ });
+ },
+ delAllVisitedViews({ commit, state }) {
+ return new Promise((resolve) => {
+ commit('DEL_ALL_VISITED_VIEWS');
+ resolve([...state.visitedViews]);
+ });
+ },
+ delAllCachedViews({ commit, state }) {
+ return new Promise((resolve) => {
+ commit('DEL_ALL_CACHED_VIEWS');
+ resolve([...state.cachedViews]);
+ });
+ },
+
+ updateVisitedView({ commit }, view) {
+ commit('UPDATE_VISITED_VIEW', view);
+ },
+
+ delRightTags({ commit }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_RIGHT_VIEWS', view);
+ resolve([...state.visitedViews]);
+ });
+ },
+
+ delLeftTags({ commit }, view) {
+ return new Promise((resolve) => {
+ commit('DEL_LEFT_VIEWS', view);
+ resolve([...state.visitedViews]);
+ });
+ },
+};
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions,
+};
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
new file mode 100644
index 0000000..be5213a
--- /dev/null
+++ b/src/store/modules/user.js
@@ -0,0 +1,106 @@
+import { login, logout, getLoginUserInfo } from '@/api/login';
+import { getToken, setToken, removeToken } from '@/utils/auth';
+import defAva from '@/assets/images/profile.jpg';
+
+const user = {
+ state: {
+ token: getToken(),
+ name: '',
+ avatar: '',
+ role: '',
+ permissions: [],
+ dictTypes: {},
+ },
+
+ mutations: {
+ SET_TOKEN: (state, token) => {
+ state.token = token;
+ },
+ SET_NAME: (state, name) => {
+ state.name = name;
+ },
+ SET_AVATAR: (state, avatar) => {
+ state.avatar = avatar;
+ },
+ SET_ROLE: (state, role) => {
+ state.role = role;
+ },
+ SET_PERMISSIONS: (state, permissions) => {
+ state.permissions = permissions;
+ },
+ SET_DICT_TYPES: (state, dictTypes) => {
+ state.dictTypes = dictTypes;
+ },
+ },
+
+ actions: {
+ // 登录
+ Login({ commit }, userInfo) {
+ const username = userInfo.username.trim();
+ const { password } = userInfo;
+ const { code } = userInfo;
+ const { uuid } = userInfo;
+ return new Promise((resolve, reject) => {
+ login(username, password, code, uuid).then((res) => {
+ setToken(res.token);
+ commit('SET_TOKEN', res.token);
+ resolve();
+ }).catch((error) => {
+ reject(error);
+ });
+ });
+ },
+
+ // 获取用户信息
+ GetInfo({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ getLoginUserInfo().then((res) => {
+ const { user } = res;
+ // console.log(user);
+ const avatar = (user.avatar == '' || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
+ // console.log("获取的key"+ res.roleKey)
+ // console.log(res.permissions)
+ if (res.roleKey) {
+ // 验证返回的roles是否是一个非空数组
+ commit('SET_ROLE', res.roleKey);
+ commit('SET_PERMISSIONS', res.permissions);
+ commit('SET_DICT_TYPES', res.dictTypes);
+ } else {
+ commit('SET_ROLE', 'ROLE_DEFAULT');
+ }
+ commit('SET_NAME', user.username);
+ commit('SET_AVATAR', avatar);
+ resolve(res);
+ }).catch((error) => {
+ reject(error);
+ });
+ });
+ },
+
+ // 退出系统
+ LogOut({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ logout(state.token).then(() => {
+ commit('SET_TOKEN', '');
+ commit('SET_ROLE', null);
+ commit('SET_PERMISSIONS', []);
+ removeToken();
+ resolve();
+ }).catch((error) => {
+ reject(error);
+ });
+ });
+ },
+
+ // 前端 登出
+ FedLogOut({ commit }) {
+ return new Promise((resolve) => {
+ commit('SET_TOKEN', '');
+ removeToken();
+ resolve();
+ });
+ },
+ },
+};
+
+export default user;
diff --git a/src/utils/auth.js b/src/utils/auth.js
new file mode 100644
index 0000000..4968eaf
--- /dev/null
+++ b/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie';
+
+const TokenKey = 'Admin-Token';
+
+export function getToken() {
+ return Cookies.get(TokenKey);
+}
+
+export function setToken(token) {
+ return Cookies.set(TokenKey, token);
+}
+
+export function removeToken() {
+ return Cookies.remove(TokenKey);
+}
diff --git a/src/utils/dict.js b/src/utils/dict.js
new file mode 100644
index 0000000..23ac871
--- /dev/null
+++ b/src/utils/dict.js
@@ -0,0 +1,24 @@
+import { getDictType } from '@/api/system/config';
+import store from '../store';
+/**
+ * 获取字典数据
+ */
+export function useDict(...args) {
+ const res = ref({});
+ return (() => {
+ args.forEach((d, index) => {
+ res.value[d] = [];
+ const dictTypes = store.getters && store.getters.dictTypes;
+
+ if(dictTypes[d]) {
+ res.value[d] = dictTypes[d].map((p) => ({ label: p.label, value: p.value, elTagType: p.cssTag }));
+ } else {
+ getDictType(d).then((resp) => {
+ res.value[d] = resp.map((p) => ({ label: p.label, value: p.value, elTagType: p.cssTag }));
+ });
+ }
+
+ });
+ return toRefs(res.value);
+ })();
+}
diff --git a/src/utils/dynamicTitle.js b/src/utils/dynamicTitle.js
new file mode 100644
index 0000000..33d2136
--- /dev/null
+++ b/src/utils/dynamicTitle.js
@@ -0,0 +1,13 @@
+import store from '@/store';
+import defaultSettings from '@/settings';
+
+/**
+ * 动态修改标题
+ */
+export function useDynamicTitle() {
+ if (store.state.settings.dynamicTitle) {
+ document.title = `${store.state.settings.title} - ${defaultSettings.title}`;
+ } else {
+ document.title = defaultSettings.title;
+ }
+}
diff --git a/src/utils/errorCode.js b/src/utils/errorCode.js
new file mode 100644
index 0000000..5b962a0
--- /dev/null
+++ b/src/utils/errorCode.js
@@ -0,0 +1,6 @@
+export default {
+ 401: '认证失败,无法访问系统资源',
+ 403: '当前操作没有权限',
+ 404: '访问资源不存在',
+ default: '系统未知错误,请反馈给管理员',
+};
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..270d0b0
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,387 @@
+import { parseTime } from '@/ruoyi';
+
+/**
+ * 表格时间格式化
+ */
+export function formatDate(cellValue) {
+ if (cellValue == null || cellValue == '') return '';
+ const date = new Date(cellValue);
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1;
+ const day = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate();
+ const hours = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours();
+ const minutes = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes();
+ const seconds = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds();
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+ if ((`${time}`).length === 10) {
+ time = parseInt(time) * 1000;
+ } else {
+ time = +time;
+ }
+ const d = new Date(time);
+ const now = Date.now();
+
+ const diff = (now - d) / 1000;
+
+ if (diff < 30) {
+ return '刚刚';
+ } if (diff < 3600) {
+ // less 1 hour
+ return `${Math.ceil(diff / 60)}分钟前`;
+ } if (diff < 3600 * 24) {
+ return `${Math.ceil(diff / 3600)}小时前`;
+ } if (diff < 3600 * 24 * 2) {
+ return '1天前';
+ }
+ if (option) {
+ return parseTime(time, option);
+ }
+ return (
+ `${d.getMonth()
+ + 1
+ }月${
+ d.getDate()
+ }日${
+ d.getHours()
+ }时${
+ d.getMinutes()
+ }分`
+ );
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function getQueryObject(url) {
+ url = url == null ? window.location.href : url;
+ const search = url.substring(url.lastIndexOf('?') + 1);
+ const obj = {};
+ const reg = /([^?&=]+)=([^?&=]*)/g;
+ search.replace(reg, (rs, $1, $2) => {
+ const name = decodeURIComponent($1);
+ let val = decodeURIComponent($2);
+ val = String(val);
+ obj[name] = val;
+ return rs;
+ });
+ return obj;
+}
+
+/**
+ * @param {string} input value
+ * @returns {number} output value
+ */
+export function byteLength(str) {
+ // returns the byte length of an utf8 string
+ let s = str.length;
+ for (let i = str.length - 1; i >= 0; i--) {
+ const code = str.charCodeAt(i);
+ if (code > 0x7f && code <= 0x7ff) s++;
+ else if (code > 0x7ff && code <= 0xffff) s += 2;
+ if (code >= 0xDC00 && code <= 0xDFFF) i--;
+ }
+ return s;
+}
+
+/**
+ * @param {Array} actual
+ * @returns {Array}
+ */
+export function cleanArray(actual) {
+ const newArray = [];
+ for (let i = 0; i < actual.length; i++) {
+ if (actual[i]) {
+ newArray.push(actual[i]);
+ }
+ }
+ return newArray;
+}
+
+/**
+ * @param {Object} json
+ * @returns {Array}
+ */
+export function param(json) {
+ if (!json) return '';
+ return cleanArray(
+ Object.keys(json).map((key) => {
+ if (json[key] === undefined) return '';
+ return `${encodeURIComponent(key)}=${encodeURIComponent(json[key])}`;
+ }),
+ ).join('&');
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+ const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
+ if (!search) {
+ return {};
+ }
+ const obj = {};
+ const searchArr = search.split('&');
+ searchArr.forEach((v) => {
+ const index = v.indexOf('=');
+ if (index !== -1) {
+ const name = v.substring(0, index);
+ const val = v.substring(index + 1, v.length);
+ obj[name] = val;
+ }
+ });
+ return obj;
+}
+
+/**
+ * @param {string} val
+ * @returns {string}
+ */
+export function html2Text(val) {
+ const div = document.createElement('div');
+ div.innerHTML = val;
+ return div.textContent || div.innerText;
+}
+
+/**
+ * Merges two objects, giving the last one precedence
+ * @param {Object} target
+ * @param {(Object|Array)} source
+ * @returns {Object}
+ */
+export function objectMerge(target, source) {
+ if (typeof target !== 'object') {
+ target = {};
+ }
+ if (Array.isArray(source)) {
+ return source.slice();
+ }
+ Object.keys(source).forEach((property) => {
+ const sourceProperty = source[property];
+ if (typeof sourceProperty === 'object') {
+ target[property] = objectMerge(target[property], sourceProperty);
+ } else {
+ target[property] = sourceProperty;
+ }
+ });
+ return target;
+}
+
+/**
+ * @param {HTMLElement} element
+ * @param {string} className
+ */
+export function toggleClass(element, className) {
+ if (!element || !className) {
+ return;
+ }
+ let classString = element.className;
+ const nameIndex = classString.indexOf(className);
+ if (nameIndex === -1) {
+ classString += `${className}`;
+ } else {
+ classString = classString.substr(0, nameIndex)
+ + classString.substr(nameIndex + className.length);
+ }
+ element.className = classString;
+}
+
+/**
+ * @param {string} type
+ * @returns {Date}
+ */
+export function getTime(type) {
+ if (type === 'start') {
+ return new Date().getTime() - 3600 * 1000 * 24 * 90;
+ }
+ return new Date(new Date().toDateString());
+}
+
+/**
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {*}
+ */
+export function debounce(func, wait, immediate) {
+ let timeout; let args; let context; let timestamp; let
+ result;
+
+ const later = function () {
+ // 据上一次触发时间间隔
+ const last = +new Date() - timestamp;
+
+ // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
+ if (last < wait && last > 0) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
+ if (!immediate) {
+ result = func.apply(context, args);
+ if (!timeout) context = args = null;
+ }
+ }
+ };
+
+ return function (...args) {
+ context = this;
+ timestamp = +new Date();
+ const callNow = immediate && !timeout;
+ // 如果延时不存在,重新设定延时
+ if (!timeout) timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ context = args = null;
+ }
+
+ return result;
+ };
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+export function deepClone(source) {
+ if (!source && typeof source !== 'object') {
+ throw new Error('error arguments', 'deepClone');
+ }
+ const targetObj = source.constructor === Array ? [] : {};
+ Object.keys(source).forEach((keys) => {
+ if (source[keys] && typeof source[keys] === 'object') {
+ targetObj[keys] = deepClone(source[keys]);
+ } else {
+ targetObj[keys] = source[keys];
+ }
+ });
+ return targetObj;
+}
+
+/**
+ * @param {Array} arr
+ * @returns {Array}
+ */
+export function uniqueArr(arr) {
+ return Array.from(new Set(arr));
+}
+
+/**
+ * @returns {string}
+ */
+export function createUniqueString() {
+ const timestamp = `${+new Date()}`;
+ const randomNum = `${parseInt((1 + Math.random()) * 65536)}`;
+ return (+(randomNum + timestamp)).toString(32);
+}
+
+/**
+ * Check if an element has a class
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ * @returns {boolean}
+ */
+export function hasClass(ele, cls) {
+ return !!ele.className.match(new RegExp(`(\\s|^)${cls}(\\s|$)`));
+}
+
+/**
+ * Add class to element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function addClass(ele, cls) {
+ if (!hasClass(ele, cls)) ele.className += ` ${cls}`;
+}
+
+/**
+ * Remove class from element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function removeClass(ele, cls) {
+ if (hasClass(ele, cls)) {
+ const reg = new RegExp(`(\\s|^)${cls}(\\s|$)`);
+ ele.className = ele.className.replace(reg, ' ');
+ }
+}
+
+export function makeMap(str, expectsLowerCase) {
+ const map = Object.create(null);
+ const list = str.split(',');
+ for (let i = 0; i < list.length; i++) {
+ map[list[i]] = true;
+ }
+ return expectsLowerCase
+ ? (val) => map[val.toLowerCase()]
+ : (val) => map[val];
+}
+
+export const exportDefault = 'export default ';
+
+export const beautifierConf = {
+ html: {
+ indent_size: '2',
+ indent_char: ' ',
+ max_preserve_newlines: '-1',
+ preserve_newlines: false,
+ keep_array_indentation: false,
+ break_chained_methods: false,
+ indent_scripts: 'separate',
+ brace_style: 'end-expand',
+ space_before_conditional: true,
+ unescape_strings: false,
+ jslint_happy: false,
+ end_with_newline: true,
+ wrap_line_length: '110',
+ indent_inner_html: true,
+ comma_first: false,
+ e4x: true,
+ indent_empty_lines: true,
+ },
+ js: {
+ indent_size: '2',
+ indent_char: ' ',
+ max_preserve_newlines: '-1',
+ preserve_newlines: false,
+ keep_array_indentation: false,
+ break_chained_methods: false,
+ indent_scripts: 'normal',
+ brace_style: 'end-expand',
+ space_before_conditional: true,
+ unescape_strings: false,
+ jslint_happy: true,
+ end_with_newline: true,
+ wrap_line_length: '110',
+ indent_inner_html: true,
+ comma_first: false,
+ e4x: true,
+ indent_empty_lines: true,
+ },
+};
+
+// 首字母大小
+export function titleCase(str) {
+ return str.replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
+}
+
+// 下划转驼峰
+export function camelCase(str) {
+ return str.replace(/_[a-z]/g, (str1) => str1.substr(-1).toUpperCase());
+}
+
+export function isNumberStr(str) {
+ return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str);
+}
diff --git a/src/utils/jsencrypt.js b/src/utils/jsencrypt.js
new file mode 100644
index 0000000..1eccf9f
--- /dev/null
+++ b/src/utils/jsencrypt.js
@@ -0,0 +1,20 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min';
+
+// 密钥对生成 http://web.chacuo.net/netrsakeypair
+
+const publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh6HkK+rCM37FAzCHVythTc6pxvr551K07CRhdX/NjCddHAuQMOd/57R5fiIwgVNEfCsD1cIyS6A8IWj4DtJLR2t29JehPpqiFSJ4hNtDcLNxNJiYRcCQvyMQeyQIPE5Ljc35c72YwDtQAsIJChsauyLrc+E6HC3gn1JDm18HNXwIDAQAB';
+const privateKey = 'MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKHoeQr6sIzfsUDMIdXK2FNzqnG+vnnUrTsJGF1f82MJ10cC5Aw53/ntHl+IjCBU0R8KwPVwjJLoDwhaPgO0ktHa3b0l6E+mqIVIniE20Nws3E0mJhFwJC/IxB7JAg8TkuNzflzvZjAO1ACwgkKGxq7Iutz4TocLeCfUkObXwc1fAgMBAAECgYAWwCzqDwnp8bDdkxGaEhPNvi4QJ6ZqRilFZ2TGEiqIGyTl9JEI6sT/QIOJFw3hqSltfDxbAMKwDe221b9rE9+hZhE2rrpwcTKuehob9Z8CObYeUHR9HG7Qb2tYRElvSCWo74iz2zajXAvJLjIE4MPuPYqXC5zOabH+EJ/eaOzVwQJBANmRkMlb+qzp1GWuqFMHP+5MeYhFwUHVX7fxKNA24oHldX8zjPIZ6d3vaRfliTvxOaz1T80acvJkkb7zHBmaW38CQQC+gfF8Lg+nvBY/S3wfOPL8FcntP16jdFhNNZmbOxq72ZmCfl5Zk5cYNBc4rDSrd9Sj4TkLLug+wrK6Wr117P4hAkBOVxnZR2NVy8SM8HzvmJauiZ7hMKzLtbcHlrBpeLnKqALM0JUZv7b0EPa4ghAOI2fvHU2kvrdRDGFmbkdZ+LilAkBnX8eT5MKl+A/yZJmDr7laRNB/poVKGNXZf55Md3P4Pwlnn/6+iLHSdmGrZPZnnOyLyKjVgqyPccLeEGMCXIlBAkAt2OMwss16OH2x79OcfBrabU5iCVbDHg56JYGbWP8KcPfvspxtL/4TdACRsa+yCMcI6L29Q4wn791SEEnE834a';
+
+// 加密
+export function encrypt(txt) {
+ const encryptor = new JSEncrypt();
+ encryptor.setPublicKey(publicKey); // 设置公钥
+ return encryptor.encrypt(txt); // 对数据进行加密
+}
+
+// 解密
+export function decrypt(txt) {
+ const encryptor = new JSEncrypt();
+ encryptor.setPrivateKey(privateKey); // 设置私钥
+ return encryptor.decrypt(txt); // 对数据进行解密
+}
diff --git a/src/utils/permission.js b/src/utils/permission.js
new file mode 100644
index 0000000..45db339
--- /dev/null
+++ b/src/utils/permission.js
@@ -0,0 +1,45 @@
+import store from '@/store';
+
+/**
+ * 字符权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkPermi(value) {
+ if (value && value instanceof Array && value.length > 0) {
+ const permissions = store.getters && store.getters.permissions;
+ const permissionDatas = value;
+ const all_permission = '*:*:*';
+
+ const hasPermission = permissions.some((permission) => all_permission === permission || permissionDatas.includes(permission));
+
+ if (!hasPermission) {
+ return false;
+ }
+ return true;
+ }
+ console.error('need roles! Like checkPermi="[\'system:user:add\',\'system:user:edit\']"');
+ return false;
+}
+
+/**
+ * 角色权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkRole(value) {
+ if (value && value instanceof Array && value.length > 0) {
+ const currentRole = store.getters && store.getters.role;
+ const permissionRoles = value;
+ const super_admin = 'admin';
+
+ const hasRole = super_admin === currentRole || permissionRoles.includes(currentRole);
+
+ if (!hasRole) {
+ return false;
+ }
+ return true;
+ }
+ console.error('need roles! Like checkRole="[\'admin\',\'editor\']"');
+ return false;
+}
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..4752550
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,156 @@
+import axios from 'axios';
+import {
+ ElNotification, ElMessageBox, ElMessage, ElLoading,
+} from 'element-plus';
+import { saveAs } from 'file-saver';
+import store from '@/store';
+import { getToken } from '@/utils/auth';
+import errorCode from '@/utils/errorCode';
+import { tansParams, blobValidate } from '@/utils/ruoyi';
+import cache from '@/plugins/cache';
+
+let downloadLoadingInstance;
+// 是否显示重新登录
+export const isRelogin = { show: false };
+
+axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
+// 创建axios实例
+const service = axios.create({
+ // axios中请求配置有baseURL选项,表示请求URL公共部分
+ baseURL: import.meta.env.VITE_APP_BASE_API,
+ // 超时
+ timeout: 10000,
+});
+
+// request拦截器
+service.interceptors.request.use((config) => {
+ // 是否需要设置 token
+ const isToken = (config.headers || {}).isToken === false;
+ // 是否需要防止数据重复提交
+ const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
+ if (getToken() && !isToken) {
+ config.headers.Authorization = `Bearer ${getToken()}`; // 让每个请求携带自定义token 请根据实际情况自行修改
+ }
+ // get请求映射params参数
+ if (config.method === 'get' && config.params) {
+ let url = `${config.url}?${tansParams(config.params)}`;
+ url = url.slice(0, -1);
+ config.params = {};
+ config.url = url;
+ }
+ if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
+ const requestObj = {
+ url: config.url,
+ data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
+ time: new Date().getTime(),
+ };
+ const sessionObj = cache.session.getJSON('sessionObj');
+ if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
+ cache.session.setJSON('sessionObj', requestObj);
+ } else {
+ const s_url = sessionObj.url; // 请求地址
+ const s_data = sessionObj.data; // 请求数据
+ const s_time = sessionObj.time; // 请求时间
+ const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
+ if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
+ const message = '数据正在处理,请勿重复提交';
+ console.warn(`[${s_url}]: ${message}`);
+ return Promise.reject(new Error(message));
+ }
+ cache.session.setJSON('sessionObj', requestObj);
+ }
+ }
+ return config;
+}, (error) => {
+ console.log(error);
+ Promise.reject(error);
+});
+
+// 响应拦截器
+service.interceptors.response.use(
+ (res) => {
+ // 未设置状态码则默认成功状态
+ const code = res.data.code || 200;
+ // 获取错误信息
+ const msg = errorCode[code] || res.data.msg || errorCode.default;
+ // 二进制数据则直接返回
+ if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
+ return res.data;
+ }
+ if (code === 401) {
+ if (!isRelogin.show) {
+ isRelogin.show = true;
+ ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
+ confirmButtonText: '重新登录',
+ cancelButtonText: '取消',
+ type: 'warning',
+ }).then(() => {
+ isRelogin.show = false;
+ store.dispatch('LogOut').then(() => {
+ location.href = '/index';
+ });
+ }).catch(() => {
+ isRelogin.show = false;
+ });
+ }
+ return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
+ } if (code === 500) {
+ ElMessage({
+ message: msg,
+ type: 'error',
+ });
+ return Promise.reject(new Error(msg));
+ } if (code !== 200) {
+ ElNotification.error({
+ title: msg,
+ });
+ return Promise.reject('error');
+ }
+ return Promise.resolve(res.data.data);
+ },
+ (error) => {
+ console.log(`err${error}`);
+ let { message } = error;
+ if (message == 'Network Error') {
+ message = '后端接口连接异常';
+ } else if (message.includes('timeout')) {
+ message = '系统接口请求超时';
+ } else if (message.includes('Request failed with status code')) {
+ message = `系统接口${message.substr(message.length - 3)}异常`;
+ }
+ ElMessage({
+ message,
+ type: 'error',
+ duration: 5 * 1000,
+ });
+ return Promise.reject(error);
+ },
+);
+
+// 通用下载方法
+export function download(url, params, filename) {
+ downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
+ return service.post(url, params, {
+ transformRequest: [(params) => tansParams(params)],
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ responseType: 'blob',
+ }).then(async (data) => {
+ const isLogin = await blobValidate(data);
+ if (isLogin) {
+ const blob = new Blob([data]);
+ saveAs(blob, filename);
+ } else {
+ const resText = await data.text();
+ const rspObj = JSON.parse(resText);
+ const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode.default;
+ ElMessage.error(errMsg);
+ }
+ downloadLoadingInstance.close();
+ }).catch((r) => {
+ console.error(r);
+ ElMessage.error('下载文件出现错误,请联系管理员!');
+ downloadLoadingInstance.close();
+ });
+}
+
+export default service;
diff --git a/src/utils/ruoyi.js b/src/utils/ruoyi.js
new file mode 100644
index 0000000..be109e0
--- /dev/null
+++ b/src/utils/ruoyi.js
@@ -0,0 +1,263 @@
+/**
+ * 通用js方法封装处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+// 日期格式化
+export function parseTime(time, pattern) {
+ if (arguments.length === 0 || !time) {
+ return null;
+ }
+ const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
+ let date;
+ if (typeof time === 'object') {
+ date = time;
+ } else {
+ if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
+ time = parseInt(time);
+ } else if (typeof time === 'string') {
+ time = time
+ .replace(new RegExp(/-/gm), '/')
+ .replace('T', ' ')
+ .replace(new RegExp(/\.[\d]{3}/gm), '');
+ }
+ if (typeof time === 'number' && time.toString().length === 10) {
+ time *= 1000;
+ }
+ date = new Date(time);
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay(),
+ };
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key];
+ // Note: getDay() returns 0 on Sunday
+ if (key === 'a') {
+ return ['日', '一', '二', '三', '四', '五', '六'][value];
+ }
+ if (result.length > 0 && value < 10) {
+ value = `0${value}`;
+ }
+ return value || 0;
+ });
+ return time_str;
+}
+
+// 表单重置
+export function resetForm(refName) {
+ if (this.$refs[refName]) {
+ this.$refs[refName].resetFields();
+ }
+}
+
+// TODO 弃用 添加日期范围
+export function addDateRange(params, dateRange, propName) {
+ const search = params;
+ search.params = typeof search.params === 'object'
+ && search.params !== null
+ && !Array.isArray(search.params)
+ ? search.params
+ : {};
+ dateRange = Array.isArray(dateRange) ? dateRange : [];
+ if (typeof propName === 'undefined') {
+ search.params.beginTime = dateRange[0];
+ search.params.endTime = dateRange[1];
+ } else {
+ search.params[`begin${propName}`] = dateRange[0];
+ search.params[`end${propName}`] = dateRange[1];
+ }
+ return search;
+}
+
+export function addTimeRange(params, dateRange) {
+ params.beginTime = dateRange[0];
+ params.endTime = dateRange[1];
+ return params;
+}
+
+// 回显数据字典
+export function selectDictLabel(datas, value) {
+ if (value === undefined) {
+ return '';
+ }
+ const actions = [];
+ Object.keys(datas).some((key) => {
+ if (datas[key].value == `${value}`) {
+ actions.push(datas[key].label);
+ return true;
+ }
+ });
+ if (actions.length === 0) {
+ actions.push(value);
+ }
+ return actions.join('');
+}
+
+// 回显数据字典(字符串数组)
+export function selectDictLabels(datas, value, separator) {
+ if (value === undefined) {
+ return '';
+ }
+ const actions = [];
+ const currentSeparator = undefined === separator ? ',' : separator;
+ const temp = value.split(currentSeparator);
+ Object.keys(value.split(currentSeparator)).some((val) => {
+ let match = false;
+ Object.keys(datas).some((key) => {
+ if (datas[key].value == `${temp[val]}`) {
+ actions.push(datas[key].label + currentSeparator);
+ match = true;
+ }
+ });
+ if (!match) {
+ actions.push(temp[val] + currentSeparator);
+ }
+ });
+ return actions.join('').substring(0, actions.join('').length - 1);
+}
+
+// 字符串格式化(%s )
+export function sprintf(str) {
+ const args = arguments;
+ let flag = true;
+ let i = 1;
+ str = str.replace(/%s/g, () => {
+ const arg = args[i++];
+ if (typeof arg === 'undefined') {
+ flag = false;
+ return '';
+ }
+ return arg;
+ });
+ return flag ? str : '';
+}
+
+// 转换字符串,undefined,null等转化为""
+export function parseStrEmpty(str) {
+ if (!str || str == 'undefined' || str == 'null') {
+ return '';
+ }
+ return str;
+}
+
+// 数据合并
+export function mergeRecursive(source, target) {
+ for (const p in target) {
+ try {
+ if (target[p].constructor == Object) {
+ source[p] = mergeRecursive(source[p], target[p]);
+ } else {
+ source[p] = target[p];
+ }
+ } catch (e) {
+ source[p] = target[p];
+ }
+ }
+ return source;
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function handleTree(data, id, parentId, children) {
+ const config = {
+ id: id || 'id',
+ parentId: parentId || 'parentId',
+ childrenList: children || 'children',
+ };
+
+ const childrenListMap = {};
+ const nodeIds = {};
+ const tree = [];
+
+ for (const d of data) {
+ const parentId = d[config.parentId];
+ if (childrenListMap[parentId] == null) {
+ childrenListMap[parentId] = [];
+ }
+ nodeIds[d[config.id]] = d;
+ childrenListMap[parentId].push(d);
+ }
+
+ for (const d of data) {
+ const parentId = d[config.parentId];
+ if (nodeIds[parentId] == null) {
+ tree.push(d);
+ }
+ }
+
+ for (const t of tree) {
+ adaptToChildrenList(t);
+ }
+
+ function adaptToChildrenList(o) {
+ if (childrenListMap[o[config.id]] !== null) {
+ o[config.childrenList] = childrenListMap[o[config.id]];
+ }
+ if (o[config.childrenList]) {
+ for (const c of o[config.childrenList]) {
+ adaptToChildrenList(c);
+ }
+ }
+ }
+ return tree;
+}
+
+/**
+ * 参数处理
+ * @param {*} params 参数
+ */
+export function tansParams(params) {
+ let result = '';
+ for (const propName of Object.keys(params)) {
+ const value = params[propName];
+ const part = `${encodeURIComponent(propName)}=`;
+ if (value !== null && typeof value !== 'undefined') {
+ if (typeof value === 'object') {
+ for (const key of Object.keys(value)) {
+ if (value[key] !== null && typeof value[key] !== 'undefined') {
+ const params = `${propName}[${key}]`;
+ const subPart = `${encodeURIComponent(params)}=`;
+ result += `${subPart + encodeURIComponent(value[key])}&`;
+ }
+ }
+ } else {
+ result += `${part + encodeURIComponent(value)}&`;
+ }
+ }
+ }
+ return result;
+}
+
+// 返回项目路径
+export function getNormalPath(p) {
+ if (p.length === 0 || !p || p == 'undefined') {
+ return p;
+ }
+ const res = p.replace('//', '/');
+ if (res[res.length - 1] === '/') {
+ return res.slice(0, res.length - 1);
+ }
+ return res;
+}
+
+// 验证是否为blob格式
+export async function blobValidate(data) {
+ try {
+ const text = await data.text();
+ JSON.parse(text);
+ return false;
+ } catch (error) {
+ return true;
+ }
+}
diff --git a/src/utils/scroll-to.js b/src/utils/scroll-to.js
new file mode 100644
index 0000000..6500f93
--- /dev/null
+++ b/src/utils/scroll-to.js
@@ -0,0 +1,56 @@
+Math.easeInOutQuad = function (t, b, c, d) {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * t * t + b;
+ }
+ t--;
+ return -c / 2 * (t * (t - 2) - 1) + b;
+};
+
+// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
+const requestAnimFrame = (function () {
+ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); };
+}());
+
+/**
+ * Because it's so fucking difficult to detect the scrolling element, just move them all
+ * @param {number} amount
+ */
+function move(amount) {
+ document.documentElement.scrollTop = amount;
+ document.body.parentNode.scrollTop = amount;
+ document.body.scrollTop = amount;
+}
+
+function position() {
+ return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop;
+}
+
+/**
+ * @param {number} to
+ * @param {number} duration
+ * @param {Function} callback
+ */
+export function scrollTo(to, duration, callback) {
+ const start = position();
+ const change = to - start;
+ const increment = 20;
+ let currentTime = 0;
+ duration = (typeof (duration) === 'undefined') ? 500 : duration;
+ var animateScroll = function () {
+ // increment the time
+ currentTime += increment;
+ // find the value with the quadratic in-out easing function
+ const val = Math.easeInOutQuad(currentTime, start, change, duration);
+ // move the document.body
+ move(val);
+ // do the animation unless its over
+ if (currentTime < duration) {
+ requestAnimFrame(animateScroll);
+ } else if (callback && typeof (callback) === 'function') {
+ // the animation is done so lets callback
+ callback();
+ }
+ };
+ animateScroll();
+}
diff --git a/src/utils/validate.js b/src/utils/validate.js
new file mode 100644
index 0000000..89ed227
--- /dev/null
+++ b/src/utils/validate.js
@@ -0,0 +1,93 @@
+/**
+ * 判断url是否是http或https
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isHttp(url) {
+ return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
+}
+
+/**
+ * 判断path是否为外链
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+ return /^(https?:|mailto:|tel:)/.test(path);
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+ const valid_map = ['admin', 'editor'];
+ return valid_map.indexOf(str.trim()) >= 0;
+}
+
+/**
+ * @param {string} url
+ * @returns {Boolean}
+ */
+export function validURL(url) {
+ const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
+ return reg.test(url);
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validLowerCase(str) {
+ const reg = /^[a-z]+$/;
+ return reg.test(str);
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUpperCase(str) {
+ const reg = /^[A-Z]+$/;
+ return reg.test(str);
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validAlphabets(str) {
+ const reg = /^[A-Za-z]+$/;
+ return reg.test(str);
+}
+
+/**
+ * @param {string} email
+ * @returns {Boolean}
+ */
+export function validEmail(email) {
+ const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return reg.test(email);
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function isString(str) {
+ if (typeof str === 'string' || str instanceof String) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * @param {Array} arg
+ * @returns {Boolean}
+ */
+export function isArray(arg) {
+ if (typeof Array.isArray === 'undefined') {
+ return Object.prototype.toString.call(arg) === '[object Array]';
+ }
+ return Array.isArray(arg);
+}
diff --git a/src/views/error/401.vue b/src/views/error/401.vue
new file mode 100644
index 0000000..9bbc099
--- /dev/null
+++ b/src/views/error/401.vue
@@ -0,0 +1,82 @@
+
+
+
+ 返回
+
+
+
+
+ 401错误!
+
+ 您没有访问权限!
+ 对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error/404.vue b/src/views/error/404.vue
new file mode 100644
index 0000000..0775c44
--- /dev/null
+++ b/src/views/error/404.vue
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+ 404错误!
+
+
+ {{ message }}
+
+
+ 对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
+
+
+ 返回首页
+
+
+
+
+
+
+
+
+
diff --git a/src/views/index.vue b/src/views/index.vue
new file mode 100644
index 0000000..9c18313
--- /dev/null
+++ b/src/views/index.vue
@@ -0,0 +1,869 @@
+
+
+
+
+
+
+
diff --git a/src/views/login.vue b/src/views/login.vue
new file mode 100644
index 0000000..462ef6d
--- /dev/null
+++ b/src/views/login.vue
@@ -0,0 +1,214 @@
+
+
+
+ AgileBoot后台管理系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+ 记住密码
+
+
+ 登 录
+ 登 录 中...
+
+
+ 立即注册
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/cache/index.vue b/src/views/monitor/cache/index.vue
new file mode 100644
index 0000000..7c7a9bf
--- /dev/null
+++ b/src/views/monitor/cache/index.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+ 基本信息
+
+
+
+
+ Redis版本 |
+ {{ cache.info.redis_version }} |
+ 运行模式 |
+ {{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }} |
+ 端口 |
+ {{ cache.info.tcp_port }} |
+ 客户端数 |
+ {{ cache.info.connected_clients }} |
+
+
+ 运行时间(天) |
+ {{ cache.info.uptime_in_days }} |
+ 使用内存 |
+ {{ cache.info.used_memory_human }} |
+ 使用CPU |
+ {{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }} |
+ 内存配置 |
+ {{ cache.info.maxmemory_human }} |
+
+
+ AOF是否开启 |
+ {{ cache.info.aof_enabled == "0" ? "否" : "是" }} |
+ RDB是否成功 |
+ {{ cache.info.rdb_last_bgsave_status }} |
+ Key数量 |
+ {{ cache.dbSize }} |
+ 网络入口/出口 |
+ {{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps |
+
+
+
+
+
+
+
+
+
+ 命令统计
+
+
+
+
+
+
+
+ 内存信息
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/druid/index.vue b/src/views/monitor/druid/index.vue
new file mode 100644
index 0000000..970b2ef
--- /dev/null
+++ b/src/views/monitor/druid/index.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue
new file mode 100644
index 0000000..9682d41
--- /dev/null
+++ b/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 删除
+
+
+ 清空
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.loginTime) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue
new file mode 100644
index 0000000..19083b5
--- /dev/null
+++ b/src/views/monitor/online/index.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ {{ (pageNum - 1) * pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.loginTime) }}
+
+
+
+
+ 强退
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue
new file mode 100644
index 0000000..4cd7a40
--- /dev/null
+++ b/src/views/monitor/operlog/index.vue
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 删除
+
+
+ 清空
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.operationTime) }}
+
+
+
+
+ 详细
+
+
+
+
+
+
+
+
+
+
+
+ {{ form.requestModule }} / {{ typeFormat(form) }}
+ {{ form.username }} / {{ form.operatorIp }} / {{ form.operatorLocation }}
+
+
+ {{ form.requestUrl }}
+ {{ form.requestMethod }}
+
+
+ {{ form.calledMethod }}
+
+
+ {{ form.operationParam }}
+
+
+ {{ form.operationResult }}
+
+
+
+ {{ form.statusStr }}
+
+
+
+ {{ parseTime(form.operationTime) }}
+
+
+ {{ form.errorStack }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/monitor/server/index.vue b/src/views/monitor/server/index.vue
new file mode 100644
index 0000000..5fad8c6
--- /dev/null
+++ b/src/views/monitor/server/index.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+ CPU
+
+
+
+
+ 属性 |
+ 值 |
+
+
+
+
+ 核心数 |
+ {{ server.cpuInfo.cpuNum }} |
+
+
+ 用户使用率 |
+ {{ server.cpuInfo.used }}% |
+
+
+ 系统使用率 |
+ {{ server.cpuInfo.sys }}% |
+
+
+ 当前空闲率 |
+ {{ server.cpuInfo.free }}% |
+
+
+
+
+
+
+
+
+
+ 内存
+
+
+
+
+ 属性 |
+ 内存 |
+ JVM |
+
+
+
+
+ 总内存 |
+ {{ server.memoryInfo.total }}G |
+ {{ server.jvmInfo.total }}M |
+
+
+ 已用内存 |
+ {{ server.memoryInfo.used}}G |
+ {{ server.jvmInfo.used}}M |
+
+
+ 剩余内存 |
+ {{ server.memoryInfo.free }}G |
+ {{ server.jvmInfo.free }}M |
+
+
+ 使用率 |
+ {{ server.memoryInfo.usage }}% |
+ {{ server.jvmInfo.usage }}% |
+
+
+
+
+
+
+
+
+
+ 服务器信息
+
+
+
+
+ 服务器名称 |
+ {{ server.systemInfo.computerName }} |
+ 操作系统 |
+ {{ server.systemInfo.osName }} |
+
+
+ 服务器IP |
+ {{ server.systemInfo.computerIp }} |
+ 系统架构 |
+ {{ server.systemInfo.osArch }} |
+
+
+
+
+
+
+
+
+
+ Java虚拟机信息
+
+
+
+
+ Java名称 |
+ {{ server.jvmInfo.name }} |
+ Java版本 |
+ {{ server.jvmInfo.version }} |
+
+
+ 启动时间 |
+ {{ server.jvmInfo.startTime }} |
+ 运行时长 |
+ {{ server.jvmInfo.runTime }} |
+
+
+ 安装路径 |
+ {{ server.jvmInfo.home }} |
+
+
+ 项目路径 |
+ {{ server.systemInfo.userDir }} |
+
+
+ 运行参数 |
+ {{ server.jvmInfo.inputArgs }} |
+
+
+
+
+
+
+
+
+
+ 磁盘状态
+
+
+
+
+ 盘符路径 |
+ 文件系统 |
+ 盘符类型 |
+ 总大小 |
+ 可用大小 |
+ 已用大小 |
+ 已用百分比 |
+
+
+
+
+ {{ sysFile.dirName }} |
+ {{ sysFile.sysTypeName }} |
+ {{ sysFile.typeName }} |
+ {{ sysFile.total }} |
+ {{ sysFile.free }} |
+ {{ sysFile.used }} |
+ {{ sysFile.usage }}% |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue
new file mode 100644
index 0000000..e3747a7
--- /dev/null
+++ b/src/views/redirect/index.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/src/views/register.vue b/src/views/register.vue
new file mode 100644
index 0000000..fdb92b1
--- /dev/null
+++ b/src/views/register.vue
@@ -0,0 +1,222 @@
+
+
+
+ AgileBoot后台管理系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+ 注 册
+ 注 册 中...
+
+
+ 使用已有账户登录
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue
new file mode 100644
index 0000000..1b59be9
--- /dev/null
+++ b/src/views/system/config/index.vue
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 修改
+
+
+ 刷新缓存
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
new file mode 100644
index 0000000..9b7424c
--- /dev/null
+++ b/src/views/system/dept/index.vue
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 新增
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
new file mode 100644
index 0000000..8e23a72
--- /dev/null
+++ b/src/views/system/menu/index.vue
@@ -0,0 +1,450 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 新增
+
+
+ 展开/折叠
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 新增
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 目录
+ 菜单
+ 按钮
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 是否外链
+
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+ 路由地址
+
+
+
+
+
+
+
+
+
+
+
+
+ 组件路径
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限字符
+
+
+
+
+
+
+
+
+
+
+
+
+ 路由参数
+
+
+
+
+
+
+
+
+
+
+
+ 是否缓存
+
+
+
+ 缓存
+ 不缓存
+
+
+
+
+
+
+
+
+
+
+ 显示状态
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+ 菜单状态
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue
new file mode 100644
index 0000000..99f0580
--- /dev/null
+++ b/src/views/system/notice/index.vue
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue
new file mode 100644
index 0000000..ae87140
--- /dev/null
+++ b/src/views/system/post/index.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 修改
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/authUser.vue b/src/views/system/role/authUser.vue
new file mode 100644
index 0000000..fe2e2d0
--- /dev/null
+++ b/src/views/system/role/authUser.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 添加用户
+
+
+ 批量取消授权
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+ 取消授权
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
new file mode 100644
index 0000000..6b3dbdf
--- /dev/null
+++ b/src/views/system/role/index.vue
@@ -0,0 +1,576 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 权限字符
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 展开/折叠
+ 全选/全不选
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/role/selectUser.vue b/src/views/system/role/selectUser.vue
new file mode 100644
index 0000000..db2078b
--- /dev/null
+++ b/src/views/system/role/selectUser.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue
new file mode 100644
index 0000000..5c1a47a
--- /dev/null
+++ b/src/views/system/user/authRole.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ (pageNum - 1) * pageSize + scope.$index + 1 }}
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+ 提交
+ 返回
+
+
+
+
+
+
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
new file mode 100644
index 0000000..e1296d8
--- /dev/null
+++ b/src/views/system/user/index.vue
@@ -0,0 +1,637 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+ 新增
+
+
+ 修改
+
+
+ 删除
+
+
+ 导入
+
+
+ 导出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ parseTime(scope.row.createTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 将文件拖到此处,或点击上传
+
+
+
+ 是否更新已经存在的用户数据
+
+
仅允许导入xls、xlsx格式文件。
+
下载模板
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/index.vue b/src/views/system/user/profile/index.vue
new file mode 100644
index 0000000..ab7f116
--- /dev/null
+++ b/src/views/system/user/profile/index.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+ 个人信息
+
+
+
+
+
+
+
+ -
+ 用户名称
+
{{ state.user.username }}
+
+ -
+ 手机号码
+
{{ state.user.phoneNumber }}
+
+ -
+ 用户邮箱
+
{{ state.user.email }}
+
+ -
+ 所属部门
+
{{ state.user.deptName }} / {{ state.postName }}
+
+ -
+ 所属角色
+
{{ state.roleName }}
+
+ -
+ 创建日期
+
{{ parseTime(state.user.createTime) }}
+
+
+
+
+
+
+
+
+
+ 基本资料
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue
new file mode 100644
index 0000000..34fd521
--- /dev/null
+++ b/src/views/system/user/profile/resetPwd.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue
new file mode 100644
index 0000000..0c00da5
--- /dev/null
+++ b/src/views/system/user/profile/userAvatar.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+ 选择
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提 交
+
+
+
+
+
+
+
+
diff --git a/src/views/system/user/profile/userInfo.vue b/src/views/system/user/profile/userInfo.vue
new file mode 100644
index 0000000..ca93a3d
--- /dev/null
+++ b/src/views/system/user/profile/userInfo.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 男
+ 女
+
+
+
+ 保存
+ 关闭
+
+
+
+
+
diff --git a/src/views/tool/build/index.vue b/src/views/tool/build/index.vue
new file mode 100644
index 0000000..5fd1a4b
--- /dev/null
+++ b/src/views/tool/build/index.vue
@@ -0,0 +1,3 @@
+
+ 表单构建
+
diff --git a/src/views/tool/swagger/index.vue b/src/views/tool/swagger/index.vue
new file mode 100644
index 0000000..8420a79
--- /dev/null
+++ b/src/views/tool/swagger/index.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..2519f4a
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,58 @@
+import { defineConfig, loadEnv } from 'vite';
+import path from 'path';
+import createVitePlugins from './vite/plugins';
+
+// https://vitejs.dev/config/
+export default defineConfig(({ mode, command }) => {
+ const env = loadEnv(mode, process.cwd());
+ const { VITE_APP_ENV } = env;
+ return {
+ // 部署生产环境和开发环境下的URL。
+ // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
+ // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
+ base: VITE_APP_ENV === 'production' ? '/' : '/',
+ plugins: createVitePlugins(env, command === 'build'),
+ resolve: {
+ // https://cn.vitejs.dev/config/#resolve-alias
+ alias: {
+ // 设置路径
+ '~': path.resolve(__dirname, './'),
+ // 设置别名
+ '@': path.resolve(__dirname, './src'),
+ },
+ // https://cn.vitejs.dev/config/#resolve-extensions
+ extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
+ },
+ // vite 相关配置
+ server: {
+ port: 80,
+ host: '127.0.0.1',
+ open: true,
+ proxy: {
+ // https://cn.vitejs.dev/config/#server-proxy
+ '/dev-api': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ rewrite: (p) => p.replace(/^\/dev-api/, ''),
+ },
+ },
+ },
+ // fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
+ css: {
+ postcss: {
+ plugins: [
+ {
+ postcssPlugin: 'internal:charset-removal',
+ AtRule: {
+ charset: (atRule) => {
+ if (atRule.name === 'charset') {
+ atRule.remove();
+ }
+ },
+ },
+ },
+ ],
+ },
+ },
+ };
+});
diff --git a/vite/plugins/auto-import.js b/vite/plugins/auto-import.js
new file mode 100644
index 0000000..ca8e0bc
--- /dev/null
+++ b/vite/plugins/auto-import.js
@@ -0,0 +1,14 @@
+import autoImport from 'unplugin-auto-import/vite'
+
+export default function createAutoImport() {
+ return autoImport({
+ imports: [
+ 'vue',
+ 'vue-router',
+ {
+ 'vuex': ['useStore']
+ }
+ ],
+ dts: false
+ })
+}
diff --git a/vite/plugins/compression.js b/vite/plugins/compression.js
new file mode 100644
index 0000000..e90aaec
--- /dev/null
+++ b/vite/plugins/compression.js
@@ -0,0 +1,28 @@
+import compression from 'vite-plugin-compression'
+
+export default function createCompression(env) {
+ const { VITE_BUILD_COMPRESS } = env
+ const plugin = []
+ if (VITE_BUILD_COMPRESS) {
+ const compressList = VITE_BUILD_COMPRESS.split(',')
+ if (compressList.includes('gzip')) {
+ // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
+ plugin.push(
+ compression({
+ ext: '.gz',
+ deleteOriginFile: false
+ })
+ )
+ }
+ if (compressList.includes('brotli')) {
+ plugin.push(
+ compression({
+ ext: '.br',
+ algorithm: 'brotliCompress',
+ deleteOriginFile: false
+ })
+ )
+ }
+ }
+ return plugin
+}
diff --git a/vite/plugins/index.js b/vite/plugins/index.js
new file mode 100644
index 0000000..10e17c3
--- /dev/null
+++ b/vite/plugins/index.js
@@ -0,0 +1,15 @@
+import vue from '@vitejs/plugin-vue'
+
+import createAutoImport from './auto-import'
+import createSvgIcon from './svg-icon'
+import createCompression from './compression'
+import createSetupExtend from './setup-extend'
+
+export default function createVitePlugins(viteEnv, isBuild = false) {
+ const vitePlugins = [vue()]
+ vitePlugins.push(createAutoImport())
+ vitePlugins.push(createSetupExtend())
+ vitePlugins.push(createSvgIcon(isBuild))
+ isBuild && vitePlugins.push(...createCompression(viteEnv))
+ return vitePlugins
+}
diff --git a/vite/plugins/setup-extend.js b/vite/plugins/setup-extend.js
new file mode 100644
index 0000000..a4980f3
--- /dev/null
+++ b/vite/plugins/setup-extend.js
@@ -0,0 +1,5 @@
+import setupExtend from 'vite-plugin-vue-setup-extend'
+
+export default function createSetupExtend() {
+ return setupExtend()
+}
diff --git a/vite/plugins/svg-icon.js b/vite/plugins/svg-icon.js
new file mode 100644
index 0000000..1a0b2ef
--- /dev/null
+++ b/vite/plugins/svg-icon.js
@@ -0,0 +1,10 @@
+import svgIcon from 'vite-plugin-svg-icons'
+import path from 'path'
+
+export default function createSvgIcon(isBuild) {
+ return svgIcon({
+ iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],
+ symbolId: 'icon-[dir]-[name]',
+ svgoOptions: isBuild
+ })
+}