diff --git a/CHANGELOG.md b/CHANGELOG.md index bbce5923..7e05ff01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# CHANGELOG + +## 1.5.4 (2021-08-10) + +### 🐛 Bug Fixes + +- `暗色模式下多页签背景问题 ` 合并 [#23](https://github.com/jekip/naive-ui-admin/pull/23) 感谢 [@Dishone](https://github.com/Dishone) +- `表格设置列,重复添加action列样式错乱问题` 合并 [#24](https://github.com/jekip/naive-ui-admin/pull/24) 感谢 [@CasbaL](https://github.com/CasbaL) + +- ### ✨ Features +-(破坏性更新) +- 优化 `动态路由配置` 取消`constantRouterComponents.ts`,中组件映射配置,更名为 `router-icons.ts` +- 优化 `admin_info接口结构`,roles 更名为:permissions,roles.roleName,更名为:label +- 优化 多级路由,当没有配置时,`redirect` ,`redirect` 默认为第一个子路由,配置则优先按配置 +- 依赖升级 + # 1.5.3 (2021-08-09) ### 🐛 Bug Fixes - 修复顶部菜单,选中联动 diff --git a/mock/user/menus.ts b/mock/user/menus.ts index 1f8f3556..0ea1343f 100644 --- a/mock/user/menus.ts +++ b/mock/user/menus.ts @@ -4,7 +4,7 @@ const menusList = [ { path: '/dashboard', name: 'Dashboard', - component: 'Layout', + component: 'LAYOUT', redirect: '/dashboard/console', meta: { icon: 'DashboardOutlined', @@ -14,7 +14,7 @@ const menusList = [ { path: 'console', name: 'dashboard_console', - component: 'DashboardConsole', + component: '/dashboard/console/console', meta: { title: '主控台', }, @@ -22,7 +22,7 @@ const menusList = [ { path: 'monitor', name: 'dashboard_monitor', - component: 'DashboardMonitor', + component: '/dashboard/monitor/monitor', meta: { title: '监控页', }, @@ -30,7 +30,7 @@ const menusList = [ { path: 'workplace', name: 'dashboard_workplace', - component: 'DashboardWorkplace', + component: '/dashboard/workplace/workplace', meta: { hidden: true, title: '工作台', diff --git a/mock/user/user.ts b/mock/user/user.ts index 90a1e1b4..636110e3 100644 --- a/mock/user/user.ts +++ b/mock/user/user.ts @@ -13,25 +13,25 @@ const adminInfo = { desc: 'manager', password: Random.string('upper', 4, 16), token, - roles: [ + permissions: [ { - roleName: '主控台', + label: '主控台', value: 'dashboard_console', }, { - roleName: '监控页', + label: '监控页', value: 'dashboard_monitor', }, { - roleName: '工作台', + label: '工作台', value: 'dashboard_workplace', }, { - roleName: '基础列表', + label: '基础列表', value: 'basic_list', }, { - roleName: '基础列表删除', + label: '基础列表删除', value: 'basic_list_delete', }, ], diff --git a/package.json b/package.json index 62335c55..ff89eb5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "naive-ui-admin", - "version": "1.5.3", + "version": "1.5.4", "author": { "name": "Ahjung", "email": "735878602@qq.com", @@ -87,7 +87,7 @@ "stylelint-scss": "^3.19.0", "tailwindcss": "^2.2.7", "typescript": "^4.3.5", - "vite": "2.3.6", + "vite": "2.4.4", "vite-plugin-compression": "^0.3.1", "vite-plugin-html": "^2.0.7", "vite-plugin-mock": "^2.9.3", diff --git a/src/components/Table/src/types/tableAction.ts b/src/components/Table/src/types/tableAction.ts index f12fddad..37c9040e 100644 --- a/src/components/Table/src/types/tableAction.ts +++ b/src/components/Table/src/types/tableAction.ts @@ -1,6 +1,6 @@ // @ts-ignore import { NButton } from 'naive-ui'; -import { RoleEnum } from '@/enums/roleEnum'; +import { PermissionsEnum } from '@/enums/permissionsEnum'; // @ts-ignore export interface ActionItem extends NButton.props { onClick?: Fn; @@ -11,7 +11,7 @@ export interface ActionItem extends NButton.props { disabled?: boolean; divider?: boolean; // 权限编码控制是否显示 - auth?: RoleEnum | RoleEnum[] | string | string[]; + auth?: PermissionsEnum | PermissionsEnum[] | string | string[]; // 业务控制是否显示 ifShow?: boolean | ((action: ActionItem) => boolean); } diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index 965b8d68..b39118de 100644 --- a/src/hooks/web/usePermission.ts +++ b/src/hooks/web/usePermission.ts @@ -7,8 +7,8 @@ export function usePermission() { * 检查权限 * @param accesses */ - function _someRoles(accesses: string[]) { - return userStore.getRoles.some((item) => { + function _somePermissions(accesses: string[]) { + return userStore.getPermissions.some((item) => { const { value }: any = item; return accesses.includes(value); }); @@ -20,7 +20,7 @@ export function usePermission() { * */ function hasPermission(accesses: string[]): boolean { if (!accesses || !accesses.length) return true; - return _someRoles(accesses); + return _somePermissions(accesses); } /** @@ -28,9 +28,9 @@ export function usePermission() { * @param accesses */ function hasEveryPermission(accesses: string[]): boolean { - const rolesList = userStore.getRoles; + const permissionsList = userStore.getPermissions; if (Array.isArray(accesses)) { - return accesses.every((access) => !!rolesList[access]); + return accesses.every((access) => !!permissionsList[access]); } throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); } @@ -41,9 +41,9 @@ export function usePermission() { * @param accessMap */ function hasSomePermission(accesses: string[]): boolean { - const rolesList = userStore.getRoles; + const permissionsList = userStore.getPermissions; if (Array.isArray(accesses)) { - return accesses.some((access) => !!rolesList[access]); + return accesses.some((access) => !!permissionsList[access]); } throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); } diff --git a/src/router/base.ts b/src/router/base.ts index 849161cd..a4683ff4 100644 --- a/src/router/base.ts +++ b/src/router/base.ts @@ -13,7 +13,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = { children: [ { path: '/:path(.*)*', - name: 'ErrorPage', + name: 'ErrorPageSon', component: ErrorPage, meta: { title: 'ErrorPage', diff --git a/src/router/constantRouterComponents.ts b/src/router/constantRouterComponents.ts deleted file mode 100644 index c7f8a889..00000000 --- a/src/router/constantRouterComponents.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { renderIcon } from '@/utils/index'; -import { DashboardOutlined } from '@vicons/antd'; -// import { RouterTransition } from '@/components/transition' - -//前端路由映射表 -export const constantRouterComponents = { - Layout: () => import('@/layout/index.vue'), //布局 - DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台 - DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页 - DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台 -}; - -//前端路由图标映射表 -export const constantRouterIcon = { - DashboardOutlined: renderIcon(DashboardOutlined), -}; diff --git a/src/router/generator-routers.ts b/src/router/generator-routers.ts index 4e7c7acd..db482f03 100644 --- a/src/router/generator-routers.ts +++ b/src/router/generator-routers.ts @@ -1,12 +1,19 @@ import { adminMenus } from '@/api/system/menu'; -import { constantRouterComponents, constantRouterIcon } from './constantRouterComponents'; +import { constantRouterIcon } from './router-icons'; import router from '@/router/index'; import { constantRouter } from '@/router/index'; import { RouteRecordRaw } from 'vue-router'; +import { Layout, ParentLayout } from '@/router/constant'; +import type { AppRouteRecordRaw } from '@/router/types'; + +const Iframe = () => import('@/views/iframe/index.vue'); +const LayoutMap = new Map Promise>(); + +LayoutMap.set('LAYOUT', Layout); +LayoutMap.set('IFRAME', Iframe); /** * 格式化 后端 结构信息并递归生成层级路由表 - * * @param routerMap * @param parent * @returns {*} @@ -19,21 +26,24 @@ export const routerGenerator = (routerMap, parent?): any[] => { // 路由名称,建议唯一 name: item.name || '', // 该路由对应页面的 组件 - component: constantRouterComponents[item.component], + component: item.component, // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) meta: { ...item.meta, label: item.meta.title, icon: constantRouterIcon[item.meta.icon] || null, - permission: item.meta.permission || null, + permissions: item.meta.permissions || null, }, }; + // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠 currentRouter.path = currentRouter.path.replace('//', '/'); // 重定向 item.redirect && (currentRouter.redirect = item.redirect); // 是否有子菜单,并递归处理 if (item.children && item.children.length > 0) { + //如果未定义 redirect 默认第一个子路由为 redirect + !item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`); // Recursion currentRouter.children = routerGenerator(item.children, currentRouter); } @@ -43,7 +53,6 @@ export const routerGenerator = (routerMap, parent?): any[] => { /** * 动态生成菜单 - * @param token * @returns {Promise} */ export const generatorDynamicRouter = (): Promise => { @@ -51,7 +60,8 @@ export const generatorDynamicRouter = (): Promise => { adminMenus() .then((result) => { const routeList = routerGenerator(result); - const asyncRoutesList = [...constantRouter, ...routeList]; + asyncImportRoute(routeList); + const asyncRoutesList = [...routeList, ...constantRouter]; asyncRoutesList.forEach((item) => { router.addRoute(item); }); @@ -62,3 +72,56 @@ export const generatorDynamicRouter = (): Promise => { }); }); }; + +/** + * 查找views中对应的组件文件 + * */ +let viewsModules: Record Promise>; +export const asyncImportRoute = (routes: AppRouteRecordRaw[] | undefined): void => { + viewsModules = viewsModules || import.meta.glob('../views/**/*.{vue,tsx}'); + if (!routes) return; + routes.forEach((item) => { + if (!item.component && item.meta?.frameSrc) { + item.component = 'IFRAME'; + } + const { component, name } = item; + const { children } = item; + if (component) { + const layoutFound = LayoutMap.get(component as string); + if (layoutFound) { + item.component = layoutFound; + } else { + item.component = dynamicImport(viewsModules, component as string); + } + } else if (name) { + item.component = ParentLayout; + } + children && asyncImportRoute(children); + }); +}; + +/** + * 动态导入 + * */ +export const dynamicImport = ( + viewsModules: Record Promise>, + component: string +) => { + const keys = Object.keys(viewsModules); + const matchKeys = keys.filter((key) => { + let k = key.replace('../views', ''); + const lastIndex = k.lastIndexOf('.'); + k = k.substring(0, lastIndex); + return k === component; + }); + if (matchKeys?.length === 1) { + const matchKey = matchKeys[0]; + return viewsModules[matchKey]; + } + if (matchKeys?.length > 1) { + console.warn( + 'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure' + ); + return; + } +}; diff --git a/src/router/index.ts b/src/router/index.ts index 47f3dfec..1569f690 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,6 @@ import { App } from 'vue'; import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; -import { ErrorPageRoute, RedirectRoute } from '@/router/base'; +import { RedirectRoute } from '@/router/base'; import { PageEnum } from '@/enums/pageEnum'; import { createRouterGuards } from './router-guards'; @@ -40,7 +40,7 @@ export const LoginRoute: RouteRecordRaw = { }; //需要验证权限 -export const asyncRoutes = [ErrorPageRoute, ...routeModuleList]; +export const asyncRoutes = [...routeModuleList]; //普通路由 无需验证权限 export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute]; diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts index 1e3f854a..171f9179 100644 --- a/src/router/modules/dashboard.ts +++ b/src/router/modules/dashboard.ts @@ -24,7 +24,7 @@ const routes: Array = [ meta: { title: 'Dashboard', icon: renderIcon(DashboardOutlined), - permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'], + permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'], sort: 0, }, children: [ @@ -33,7 +33,7 @@ const routes: Array = [ name: `${routeName}_console`, meta: { title: '主控台', - permission: ['dashboard_console'], + permissions: ['dashboard_console'], }, component: () => import('@/views/dashboard/console/console.vue'), }, @@ -42,7 +42,7 @@ const routes: Array = [ // name: `${ routeName }_monitor`, // meta: { // title: '监控页', - // permission: ['dashboard_monitor'] + // permissions: ['dashboard_monitor'] // }, // component: () => import('@/views/dashboard/monitor/monitor.vue') // }, @@ -52,7 +52,7 @@ const routes: Array = [ meta: { title: '工作台', keepAlive: true, - permission: ['dashboard_workplace'], + permissions: ['dashboard_workplace'], }, component: () => import('@/views/dashboard/workplace/workplace.vue'), }, diff --git a/src/router/router-guards.ts b/src/router/router-guards.ts index 1ad883ab..3f782306 100644 --- a/src/router/router-guards.ts +++ b/src/router/router-guards.ts @@ -1,9 +1,11 @@ +import type { RouteRecordRaw } from 'vue-router'; import { isNavigationFailure, Router } from 'vue-router'; import { useUserStoreWidthOut } from '@/store/modules/user'; import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute'; import { ACCESS_TOKEN } from '@/store/mutation-types'; import { storage } from '@/utils/Storage'; import { PageEnum } from '@/enums/pageEnum'; +import { ErrorPageRoute } from '@/router/base'; const LOGIN_PATH = PageEnum.BASE_LOGIN; @@ -29,7 +31,7 @@ export function createRouterGuards(router: Router) { const token = storage.get(ACCESS_TOKEN); if (!token) { - // You can access without permission. You need to set the routing meta.ignoreAuth to true + // You can access without permissions. You need to set the routing meta.ignoreAuth to true if (to.meta.ignoreAuth) { next(); return; @@ -60,9 +62,15 @@ export function createRouterGuards(router: Router) { // 动态添加可访问路由表 routes.forEach((item) => { - router.addRoute(item); + router.addRoute(item as unknown as RouteRecordRaw); }); + //添加404 + const isErrorPage = router.getRoutes().findIndex((item) => item.name === ErrorPageRoute.name); + if (isErrorPage === -1) { + router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw); + } + const redirectPath = (from.query.redirect || to.path) as string; const redirect = decodeURIComponent(redirectPath); const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; diff --git a/src/router/router-icons.ts b/src/router/router-icons.ts new file mode 100644 index 00000000..a7369ee1 --- /dev/null +++ b/src/router/router-icons.ts @@ -0,0 +1,7 @@ +import { renderIcon } from '@/utils/index'; +import { DashboardOutlined } from '@vicons/antd'; + +//前端路由图标映射表 +export const constantRouterIcon = { + DashboardOutlined: renderIcon(DashboardOutlined), +}; diff --git a/src/router/types.ts b/src/router/types.ts index c24afa9a..5c0ec5bd 100644 --- a/src/router/types.ts +++ b/src/router/types.ts @@ -1,6 +1,5 @@ import type { RouteRecordRaw, RouteMeta } from 'vue-router'; import { defineComponent } from 'vue'; -import { RoleEnum } from '@/enums/roleEnum'; export type Component = | ReturnType @@ -23,7 +22,7 @@ export interface Meta { title: string; // 是否忽略权限 ignoreAuth?: boolean; - roles?: RoleEnum[]; + permissions?: string[]; // 是否不缓存 noKeepAlive?: boolean; // 是否固定在tab上 diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts index ae270550..707884ff 100644 --- a/src/settings/projectSetting.ts +++ b/src/settings/projectSetting.ts @@ -41,7 +41,7 @@ const setting = { //显示图标 showIcon: false, }, - //菜单权限模式 ROLE 前端固定角色 BACK 动态获取 - permissionMode: 'ROLE', + //菜单权限模式 FIXED 前端固定路由 BACK 动态获取 + permissionMode: 'FIXED', }; export default setting; diff --git a/src/store/modules/asyncRoute.ts b/src/store/modules/asyncRoute.ts index edf350c6..398be2d0 100644 --- a/src/store/modules/asyncRoute.ts +++ b/src/store/modules/asyncRoute.ts @@ -88,12 +88,12 @@ export const useAsyncRouteStore = defineStore({ }, async generateRoutes(data) { let accessedRouters; - const roleList = data.roles || []; + const permissionsList = data.permissions || []; const routeFilter = (route) => { const { meta } = route; - const { permission } = meta || {}; - if (!permission) return true; - return roleList.some((role) => permission.includes(role.value)); + const { permissions } = meta || {}; + if (!permissions) return true; + return permissionsList.some((item) => permissions.includes(item.value)); }; const { getPermissionMode } = useProjectSetting(); const permissionMode = unref(getPermissionMode); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 2c818d39..fbbedf45 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -13,7 +13,7 @@ export interface IUserState { username: string; welcome: string; avatar: string; - roles: any[]; + permissions: any[]; info: any; } @@ -24,7 +24,7 @@ export const useUserStore = defineStore({ username: '', welcome: '', avatar: '', - roles: [], + permissions: [], info: Storage.get(CURRENT_USER, {}), }), getters: { @@ -37,8 +37,8 @@ export const useUserStore = defineStore({ getNickname(): string { return this.username; }, - getRoles(): [any][] { - return this.roles; + getPermissions(): [any][] { + return this.permissions; }, getUserInfo(): object { return this.info; @@ -51,8 +51,8 @@ export const useUserStore = defineStore({ setAvatar(avatar: string) { this.avatar = avatar; }, - setRoles(roles) { - this.roles = roles; + setPermissions(permissions) { + this.permissions = permissions; }, setUserInfo(info) { this.info = info; @@ -83,12 +83,12 @@ export const useUserStore = defineStore({ getUserInfo() .then((res) => { const result = res; - if (result.roles && result.roles.length) { - const roles = result.roles; - that.setRoles(roles); + if (result.permissions && result.permissions.length) { + const permissionsList = result.permissions; + that.setPermissions(permissionsList); that.setUserInfo(result); } else { - reject(new Error('getInfo: roles must be a non-null array !')); + reject(new Error('getInfo: permissionsList must be a non-null array !')); } that.setAvatar(result.avatar); resolve(res); @@ -101,7 +101,7 @@ export const useUserStore = defineStore({ // 登出 async logout() { - this.setRoles([]); + this.setPermissions([]); this.setUserInfo(''); storage.remove(ACCESS_TOKEN); storage.remove(CURRENT_USER); diff --git a/src/views/dashboard/console/console.vue b/src/views/dashboard/console/console.vue index c056d655..2d5bd7e5 100644 --- a/src/views/dashboard/console/console.vue +++ b/src/views/dashboard/console/console.vue @@ -1,7 +1,7 @@