Explorar el Código

layout+request

zhuangyunsheng hace 1 día
padre
commit
22208aa5f7

+ 1 - 1
src/config/index.js

@@ -23,7 +23,7 @@ const DEFAULT_CONFIG = {
 	//请求是否开启缓存
 	REQUEST_CACHE: false,
 
-    //布局 mingcute:layout-11-line:default | icon-park-outline:grid-three:header | mingcute:layout-line:menu
+    //布局 mingcute:layout-11-line:default | mingcute:layout-line:header
 	LAYOUT: "header",
 
 	//菜单是否折叠

+ 1 - 1
src/layout/components/password.vue

@@ -79,5 +79,5 @@ defineExpose({
 </script>
 
 <style lang="scss" scoped>
-.el-form {padding-right: 16px;}
+.el-form {padding-right: 34px;}
 </style>

+ 3 - 6
src/layout/components/setting.vue

@@ -9,14 +9,11 @@
 		<el-divider></el-divider>
 		<el-form-item class="radio-item" label="框架布局">
             <el-radio-group v-model="layout">
-                <el-radio value="default">
-                    <sc-iconify icon="mingcute:layout-11-line" size="70"></sc-iconify>
-                </el-radio>
                 <el-radio value="header">
-                    <sc-iconify icon="icon-park-outline:grid-three" size="65"></sc-iconify>
+                    <sc-iconify title="header" icon="mingcute:layout-line" size="70"></sc-iconify>
                 </el-radio>
-                <el-radio value="menu">
-                    <sc-iconify icon="mingcute:layout-line" size="70"></sc-iconify>
+                <el-radio value="default">
+                    <sc-iconify title="default" icon="mingcute:layout-11-line" size="70"></sc-iconify>
                 </el-radio>
             </el-radio-group>
         </el-form-item>

+ 7 - 1
src/layout/components/topbar.vue

@@ -1,7 +1,7 @@
 <template>
 	<div class="aminui-topbar">
 		<div class="left-panel">
-            <div class="panel-item hidden-sm-and-down" @click="$store.commit('TOGGLE_menuIsCollapse')">
+            <div v-if="layout == 'default'" class="panel-item hidden-sm-and-down" @click="$store.commit('TOGGLE_menuIsCollapse')">
                 <sc-iconify :icon="menuIsCollapse ? 'ep:expand': 'ep:fold'" size="20"></sc-iconify>
             </div>
 
@@ -29,6 +29,12 @@ export default {
             breadList: []
         }
     },
+
+    computed: {
+        layout() {
+            return this.$store.state.global.layout;
+        }
+    },
     
     watch: {
         $route() {

+ 8 - 6
src/layout/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<!-- 通栏布局 -->
+	<!-- mingcute:layout-line -->
 	<template v-if="layout == 'header'">
         <header class="aminui-header">
             <div class="aminui-header-left">
@@ -32,7 +32,6 @@
                         </el-menu>
                     </el-scrollbar>
                 </div>
-
                 <div class="aminui-side-collapse" @click="$store.commit('TOGGLE_menuIsCollapse')">
                     <sc-iconify :icon="menuIsCollapse ? 'ep:arrow-right': 'ep:arrow-left'" size="14"></sc-iconify>
                 </div>
@@ -52,8 +51,8 @@
         </section>
 	</template>
 
-	<!-- 经典布局 -->
-	<template v-else-if="layout == 'menu'">
+	<!-- mingcute:layout-line 顶部无菜单 -->
+	<!-- <template v-else-if="layout == 'header'">
         <header class="aminui-header">
 			<div class="aminui-header-left">
 				<div class="logo-bar">
@@ -74,6 +73,9 @@
 						</el-menu>
 					</el-scrollbar>
 				</div>
+                <div class="aminui-side-collapse" @click="$store.commit('TOGGLE_menuIsCollapse')">
+                    <sc-iconify :icon="menuIsCollapse ? 'ep:arrow-right': 'ep:arrow-left'" size="14"></sc-iconify>
+                </div>
 			</div>
 			<Side-m v-if="ismobile"></Side-m>
 			<div class="aminui-body el-container">
@@ -89,9 +91,9 @@
 				</div>
 			</div>
 		</section>
-	</template>
+	</template> -->
 
-	<!-- 默认布局 -->
+	<!-- mingcute:layout-11-line default -->
 	<template v-else>
         <section class="aminui-wrapper">
             <div v-if="!ismobile" :class="['aminui-side hasMenu', menuIsCollapse && 'isCollapse']">

+ 92 - 67
src/router/index.js

@@ -15,33 +15,36 @@ const routes = systemRouter
 
 // 系统特殊路由
 const routes_404 = {
-	path: "/:pathMatch(.*)*",
 	hidden: true,
-	component: () => import(/* webpackChunkName: "404" */ "@/layout/other/404"),
+	path: "/:pathMatch(.*)*",
+	component: () => import("@/layout/other/404")
 }
 
 const routes_empty = {
-	path: "/:pathMatch(.*)*",
 	hidden: true,
-	component: () => import(/* webpackChunkName: "404" */ "@/layout/other/empty"),
+	path: "/:pathMatch(.*)*",
+	component: () => import("@/layout/other/empty")
 }
 
-let routes_404_r = () => {}
-
 const router = createRouter({
 	history: createWebHashHistory(),
 	routes: routes
 })
 
 // 判断是否已加载过动态/静态路由
-let isGetRouter = false;
+let isGetRouter = false
 // FIX 多个API同时401时疯狂弹窗BUG
 let MessageBox_401_show = false
+// 存储删除404路由的方法
+let removeNotFoundRoute = () => {}
+// 标记是否正在加载路由
+let isLoadingRouter = false
+let index = 0
 
 router.beforeEach(async (to, from, next) => {
 	NProgress.start()
 	// 动态标题
-	document.title = to.meta.title ? `${to.meta.title} - ${config.APP_NAME}` : `${config.APP_NAME}`
+	document.title = to.meta.title ? `${to.meta.title} - ${config.APP_NAME}` : config.APP_NAME
 
 	let token = tool.cookie.get("MES_TOKEN")
     
@@ -49,7 +52,7 @@ router.beforeEach(async (to, from, next) => {
 		// 删除路由(替换当前layout路由)
 		router.addRoute(routes[0])
 		// 删除路由(404)
-		routes_404_r()
+		removeNotFoundRoute()
 		isGetRouter = false
 		if (token) next(from.fullPath)
         else {
@@ -69,77 +72,102 @@ router.beforeEach(async (to, from, next) => {
     
     // 加载动态/静态路由
 	if (!isGetRouter) {
-        const apiMenu = await api.system.menu.build()
-        if (!apiMenu.length) {
-            if (!MessageBox_401_show) {
-                MessageBox_401_show = true;
-                ElMessageBox.confirm("当前用户无任何菜单权限,请联系系统管理员", "无权限访问", {
-                    type: "error",
-                    showClose: false,
-                    closeOnPressEscape: false,
-                    closeOnClickModal: false,
-                    center: true,
-                    showCancelButton: false,
-                    beforeClose: (action, instance, done) => {
-                        MessageBox_401_show = false
-                        done()
-                    }
-                }).then(() => {
-                    tool.cookie.remove("MES_TOKEN")
-                    location.reload() // 为了重新实例化vue-router对象 避免bug
-                }).catch(() => {})
+        const response = await loadDynamicRoutes()
+        
+        if (response !== "success") {
+            if (response === "NoPermission") {
+                removeNotFoundRoute = router.addRoute(routes_empty)
+                await handleNoPermission()
             }
-
-            routes_404_r = router.addRoute(routes_empty);
-        } else {
-            const zeroMenu = XEUtils.filter(mapAsyncMenu(apiMenu), item => item.type == 0)
-            tool.data.set("MENU", [...userRoutes, ...zeroMenu])
-
-            const menuRouter = XEUtils.mapTree([...userRoutes, ...zeroMenu], item => {
-                return {
-                    ...XEUtils.omit(item, "component"),
-                    [item.component ? "component" : "redirect"]: item.component ? loadComponent(item.component) : XEUtils.get(XEUtils.first(item.children), "path", "")
-                }
-            })
-            XEUtils.arrayEach(XEUtils.toTreeArray(menuRouter), item => router.addRoute("layout", item))
-            routes_404_r = router.addRoute(routes_404)
-            !to.matched.length && router.push(to.fullPath);
+		    next()
+            return
         }
 
-		isGetRouter = true;
+		isGetRouter = true
+        if (!to.matched.length) router.push(to.fullPath)
+        else next()
 	}
 
 	beforeEach(to, from)
-	next();
-});
+	next()
+})
 
 router.afterEach((to, from) => {
-	afterEach(to, from);
-	NProgress.done();
-});
+	afterEach(to, from)
+	NProgress.done()
+})
 
 router.onError(error => {
-    NProgress.done();
-	ElNotification.error({
-		title: "路由错误",
-		message: error.message
-	});
-
-    // const pattern = /Loading chunk (\d)+ failed/g;
-    // const isChunkLoadFailed = error.message.match(pattern);
-    // const targetPath = router.history.pending.fullPath;
+    NProgress.done()
+	ElNotification.error({ title: "路由错误", message: error.message })
+
+    // const pattern = /Loading chunk (\d)+ failed/g
+    // const isChunkLoadFailed = error.message.match(pattern)
+    // const targetPath = router.history.pending.fullPath
     // if (isChunkLoadFailed) {
-    //     router.replace(targetPath);
+    //     router.replace(targetPath)
     // }
-});
+})
+
+// 封装加载动态路由
+async function loadDynamicRoutes() {
+    if (isLoadingRouter) return "loading"
+    isLoadingRouter = true
+  
+    try {
+        const apiMenu = await api.system.menu.build()
+        if (!XEUtils.isArray(apiMenu) || apiMenu.length === 0) return "NoPermission"
+        
+        const zeroMenu = XEUtils.filter(mapAsyncMenu(apiMenu), item => item.type == 0)
+        tool.data.set("MENU", [...userRoutes, ...zeroMenu])
+
+        const menuRouter = XEUtils.mapTree([...userRoutes, ...zeroMenu], item => {
+            return {
+                ...XEUtils.omit(item, "component"),
+                [item.component ? "component" : "redirect"]: item.component ? loadComponent(item.component) : XEUtils.get(XEUtils.first(item.children), "path", "")
+            }
+        })
+        
+        XEUtils.arrayEach(XEUtils.toTreeArray(menuRouter), item => router.addRoute("layout", item))
+        removeNotFoundRoute = router.addRoute(routes_404)
+        return "success"
+    } catch (error) {
+        return "error"
+    } finally {
+        isLoadingRouter = false
+    }
+}
+
+// 处理401无权限
+async function handleNoPermission() {
+    if (MessageBox_401_show) return
+    MessageBox_401_show = true
+  
+    try {
+        await ElMessageBox.confirm("当前用户无任何菜单权限,请联系系统管理员", "无权限访问", {
+            type: "error",
+            center: true,
+            showClose: false,
+            closeOnPressEscape: false,
+            closeOnClickModal: false,
+            showCancelButton: false
+        })
+        
+        tool.cookie.remove("MES_TOKEN")
+        await router.push({ path: "/login", replace: true })
+    } catch (error) {
+    } finally {
+        MessageBox_401_show = false
+    }
+}
 
 function mapPath(path) {
-    return XEUtils.startsWith(path, "/") ? path : `/${path}`;
+    return XEUtils.startsWith(path, "/") ? path : `/${path}`
 }
 
 // 转换
 function mapAsyncMenu(menus) {
-    XEUtils.arrayEach(menus, item => item.path = mapPath(item.path));
+    XEUtils.arrayEach(menus, item => item.path = mapPath(item.path))
     return XEUtils.mapTree(XEUtils.toArrayTree(menus, { parentKey: "pid", sortKey: "menuSort" }), item => {
         return {
             name: item.title,
@@ -153,11 +181,8 @@ function mapAsyncMenu(menus) {
 }
 
 function loadComponent(component) {
-	if (component) {
-		return () => import(/* webpackChunkName: "[request]" */ `@/views/${component}`)
-	} else {
-		return () => import(`@/layout/other/empty`)
-	}
+    if (component) return () => import(`@/views/${component}`)
+    else return () => import(`@/layout/other/empty`)
 }
 
 export default router

+ 4 - 4
src/style/app.scss

@@ -59,13 +59,13 @@ a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: bo
 /* 智慧工地layout */
 .aminui-side.hasMenu {background: #304156;color: #fff;}
 .aminui-side.hasMenu .aminui-side-top {border-bottom: none;}
-.aminui-side.hasMenu .logo-bar {display: flex;align-items: center;width: 100%;height: 100%;margin-left: 10px;font-size: 19px;font-weight: bold;}
-.aminui-side.hasMenu .logo-bar .logo {width: 30px;vertical-align: bottom;}
-.aminui-side.hasMenu .logo-bar .logo + span {margin-left: 10px;}
+.aminui-side.hasMenu .logo-bar {display: flex;align-items: center;width: 100%;height: 100%;padding: 0 10px;font-size: 19px;font-weight: bold;}
+.aminui-side.hasMenu .logo-bar .logo {width: 30px;margin-right: 10px;vertical-align: bottom;}
 .aminui-side.hasMenu .el-menu, .side-menu-popper .el-menu {--el-menu-text-color: #fff;--el-menu-hover-text-color: #fff;--el-menu-bg-color: transparent;--el-menu-hover-bg-color: rgba(255, 255, 255, 0.1);--el-menu-active-color: #fff;}
 .aminui-side.hasMenu .el-menu .el-menu-item.is-active, .side-menu-popper .el-menu .el-menu-item.is-active {background-color: var(--el-color-primary);}
 .aminui-side.hasMenu .el-menu--collapse .el-sub-menu.is-active {background-color: var(--el-color-primary);color: #fff;}
-.aminui-side.hasMenu.isCollapse .logo-bar {justify-content: center;margin-left: 0;}
+.aminui-side.hasMenu.isCollapse .logo-bar {justify-content: center;}
+.aminui-side.hasMenu.isCollapse .logo-bar .logo {margin-right: 0;}
 
 /* 移动端菜单 */
 .mobile-nav {background: #222b45;}

+ 9 - 101
src/utils/request.js

@@ -32,16 +32,7 @@ let MessageBox_401_show = false
 
 // HTTP response 拦截器
 axios.interceptors.response.use(
-	(response) => {
-		if (response.data.code == 400) {
-			ElNotification.error({
-				title: "请求错误",
-				message: response.data.message || "Code:400,未知错误!"
-			});
-			return Promise.reject(response.data);
-		}
-		return response;
-	},
+	(response) => response,
 	(error) => {
 		if (error.response) {
 			if (error.response.status == 404) {
@@ -54,15 +45,16 @@ axios.interceptors.response.use(
 					title: "请求错误",
 					message: error.response.data.message || "Status:500,服务器发生错误!"
 				});
-			} else if (error.response.status == 401 || error.response.data.message == "找不到当前登录的信息") {
+			} else if (error.response.status == 401) {
 				if (!MessageBox_401_show) {
 					MessageBox_401_show = true
 					ElMessageBox.confirm("当前用户已被登出或无权限访问当前资源,请尝试重新登录后再操作。", "无权限访问", {
+                        customStyle: { "--el-messagebox-width": "480px" },
 						type: "error",
+						center: true,
 						showClose: false,
 						closeOnPressEscape: false,
 						closeOnClickModal: false,
-						center: true,
 						confirmButtonText: "重新登录",
 						showCancelButton: false,
 						beforeClose: (action, instance, done) => {
@@ -104,9 +96,9 @@ var http = {
 	get: function (url, params = {}, config = {}) {
 		return new Promise((resolve, reject) => {
 			axios({
+				url,
 				method: "get",
-				url: url,
-				params: params,
+				params,
 				...config
 			}).then(response => {
 				resolve(response.data);
@@ -128,101 +120,17 @@ var http = {
             url.endsWith("/update") && XEUtils.set(data, "emptyField", XEUtils.keys(XEUtils.pick(data, val => XEUtils.isEmpty(val) && !XEUtils.isNumber(val) && !XEUtils.isBoolean(val))))
 			
             axios({
+				url,
 				method: "post",
-				url: url,
-				data: data,
+				data,
 				...config,
-				params: params
+				params
 			}).then(response => {
 				resolve(response.data)
 			}).catch(error => {
 				reject(error);
 			})
 		})
-	},
-
-	/** put 请求
-	 * @param  {string} url 接口地址
-	 * @param  {object} data 请求参数
-	 * @param  {object} config 参数
-	 */
-	put: function (url, data = {}, config = {}) {
-		return new Promise((resolve, reject) => {
-			axios({
-				method: "put",
-				url: url,
-				data: data,
-				...config
-			}).then((response) => {
-				resolve(response.data);
-			}).catch((error) => {
-				reject(error);
-			})
-		})
-	},
-
-	/** patch 请求
-	 * @param  {string} url 接口地址
-	 * @param  {object} data 请求参数
-	 * @param  {object} config 参数
-	 */
-	patch: function (url, data = {}, config = {}) {
-		return new Promise((resolve, reject) => {
-			axios({
-				method: "patch",
-				url: url,
-				data: data,
-				...config
-			}).then((response) => {
-				resolve(response.data);
-			}).catch((error) => {
-				reject(error);
-			})
-		})
-	},
-
-	/** delete 请求
-	 * @param  {string} url 接口地址
-	 * @param  {object} data 请求参数
-	 * @param  {object} config 参数
-	 */
-	delete: function (url, data = {}, config = {}) {
-		return new Promise((resolve, reject) => {
-			axios({
-				method: "delete",
-				url: url,
-				data: data,
-				...config
-			}).then((response) => {
-				resolve(response.data);
-			}).catch((error) => {
-				reject(error);
-			})
-		})
-	},
-
-	/** jsonp 请求
-	 * @param  {string} url 接口地址
-	 * @param  {string} name JSONP回调函数名称
-	 */
-	jsonp: function (url, name = "jsonp") {
-		return new Promise((resolve) => {
-			var script = document.createElement("script")
-			var _id = `jsonp${Math.ceil(Math.random() * 1000000)}`
-			script.id = _id
-			script.type = "text/javascript"
-			script.src = url
-			window[name] = (response) => {
-				resolve(response)
-				document.getElementsByTagName("head")[0].removeChild(script)
-				try {
-					delete window[name];
-				} catch (e) {
-					window[name] = undefined;
-				}
-			}
-			document.getElementsByTagName("head")[0].appendChild(script)
-		})
 	}
 }
 

+ 2 - 1
src/views/login/index.vue

@@ -115,8 +115,9 @@ export default {
                     this.$API.auth.token(this.form).then(res => {
                         this.islogin = false;
                         this.$TOOL.cookie.set("MES_TOKEN", res.token, { expires: this.form.rememberMe ? 24 * 60 * 60 : 2 * 60 * 60 });
-                        this.$TOOL.data.set("USER_INFO", res.user.user);
+                        this.$TOOL.data.set("USER_INFO", res.user.user || res.user);
                         this.$router.replace({ path: XEUtils.get(this.$route, "redirectedFrom.fullPath", "/") });
+                        this.getCode();
                     }).catch(() => {
                         this.islogin = false;
                         this.getCode();

+ 1 - 1
src/views/system/dept/detail.vue

@@ -103,7 +103,7 @@ defineExpose({
 </script>
 
 <style scoped>
-.el-form {padding-right: 16px;}
+.el-form {padding-right: 34px;}
 .el-form .el-input-number {width: 100%;}
 .el-form .el-input-number :deep(.el-input__inner) {text-align: unset;}
 </style>

+ 2 - 2
src/views/system/menu/detail.vue

@@ -117,8 +117,8 @@ const setData = (data = {}, model = "add") => {
 const submit = () => {
     formRef.value.validate(valid => {
         if (valid) {
-            const data = XEUtils.omit(form.value, "emptyField");
-            if (data.type == 0) XEUtils.set(data, "component", null);
+            const data = XEUtils.clone(form.value);
+            data.type == 0 && XEUtils.set(data, "component", null);
 
             isSaving.value = true;
             API.system.menu[mode.value](data).then(res => {

+ 5 - 0
src/views/system/menu/index.vue

@@ -6,6 +6,9 @@
             <el-aside width="310px">
                 <el-header>
                     <el-input v-model="filterText" clearable placeholder="输入关键字进行过滤"></el-input>
+                    <el-button title="刷新菜单" circle @click="fetchMenu">
+                        <template #icon><sc-iconify icon="tabler:refresh"></sc-iconify></template>
+                    </el-button>
                 </el-header>
 
                 <el-main class="nopadding">
@@ -103,6 +106,8 @@ fetchMenu();
 <style lang="scss" scoped>
 .menu-container {border-top: 1px solid var(--el-border-color-light);}
 .menu-container .el-aside {display: flex;flex-direction: column;}
+.menu-container .el-aside .el-header {padding-right: 10px;}
+.menu-container .el-aside .el-header .el-button {margin-left: 10px;}
 .menu-container .el-aside .el-main {padding-top: 15px;padding-left: 15px;}
 .menu-container .el-tree .custom-tree-node .do {display: none;}
 .menu-container .el-tree :deep(.el-tree-node__content):hover .do {display: flex;}

+ 1 - 1
src/views/system/role/detail.vue

@@ -75,7 +75,7 @@ defineExpose({
 </script>
 
 <style scoped>
-.el-form {padding-right: 16px;}
+.el-form {padding-right: 34px;}
 .el-form .el-input-number {width: 100%;}
 .el-form .el-input-number :deep(.el-input__inner) {text-align: unset;}
 </style>

+ 1 - 1
src/views/system/user/detail.vue

@@ -133,6 +133,6 @@ defineExpose({
 </script>
 
 <style scoped>
-.el-form {padding-right: 16px;}
+.el-form {padding-right: 34px;}
 .el-form-item .el-select :deep(.el-select__selection) .el-select__selected-item .el-tag{max-width: 204px !important;}
 </style>

+ 4 - 10
src/views/system/user/index.vue

@@ -6,9 +6,6 @@
             <el-aside width="310px">
                 <el-header>
                     <el-input v-model="filterText" clearable placeholder="输入关键字进行过滤"></el-input>
-                    <el-button title="重置部门" circle @click="dept_unset">
-                        <template #icon><sc-iconify icon="iconoir:remove-link"></sc-iconify></template>
-                    </el-button>
                 </el-header>
 
                 <el-main class="nopadding">
@@ -59,13 +56,11 @@ const filterNode = (value, data) => {
 }
 
 const nodeClick = data => {
-    XEUtils.set(formConfig.data, "deptId", data.id);
-    refreshTable();
-}
+    if (XEUtils.get(formConfig.data, "deptId", null) === data.id) {
+        treeRef.value.setCurrentKey();
+        XEUtils.set(formConfig.data, "deptId", null);
+    } else XEUtils.set(formConfig.data, "deptId", data.id);
 
-const dept_unset = () => {
-    treeRef.value.setCurrentKey();
-    XEUtils.set(formConfig.data, "deptId", null);
     refreshTable();
 }
 
@@ -151,7 +146,6 @@ fetchDept();
 <style lang="scss" scoped>
 .user-container {border-top: 1px solid var(--el-border-color-light);}
 .user-container .el-aside {display: flex;flex-direction: column;}
-.user-container .el-aside .el-header .el-button {margin-left: 10px;}
 .user-container .el-aside .el-main {padding-top: 15px;padding-left: 15px;}
 .user-container .el-aside + .el-main {padding-top: 15px;}