diff --git a/backend/dvadmin/system/views/menu_field.py b/backend/dvadmin/system/views/menu_field.py index 8f6cd1a..26f2a42 100644 --- a/backend/dvadmin/system/views/menu_field.py +++ b/backend/dvadmin/system/views/menu_field.py @@ -26,7 +26,7 @@ class MenuFieldViewSet(CustomModelViewSet): """ 列权限视图集 """ - queryset = MenuField.objects.all() + queryset = MenuField.objects.order_by('-model') serializer_class = MenuFieldSerializer def list(self, request, *args, **kwargs): diff --git a/web/src/components/dept-format/index.vue b/web/src/components/dept-format/index.vue new file mode 100644 index 0000000..1c71fcf --- /dev/null +++ b/web/src/components/dept-format/index.vue @@ -0,0 +1,28 @@ + + diff --git a/web/src/router/backEnd.ts b/web/src/router/backEnd.ts index 159a1c2..bdeecad 100644 --- a/web/src/router/backEnd.ts +++ b/web/src/router/backEnd.ts @@ -13,7 +13,8 @@ import { useMenuApi } from '/@/api/menu/index'; import { handleMenu } from '../utils/menu'; import { BtnPermissionStore } from '/@/plugin/permission/store.permission'; import {SystemConfigStore} from "/@/stores/systemConfig"; - +import {useDeptInfoStore} from "/@/stores/modules/dept"; +import {DictionaryStore} from "/@/stores/dictionary"; const menuApi = useMenuApi(); const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}'); @@ -112,6 +113,10 @@ export function getBackEndControlRoutes() { BtnPermissionStore().getBtnPermissionStore(); // 获取系统配置 SystemConfigStore().getSystemConfigs() + // 获取所有部门信息 + useDeptInfoStore().requestDeptInfo() + // 获取字典信息 + DictionaryStore().getSystemDictionarys() return menuApi.getSystemMenu(); } diff --git a/web/src/router/index.ts b/web/src/router/index.ts index b864473..8276ed1 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -10,6 +10,7 @@ import {Session} from '/@/utils/storage'; import {notFoundAndNoPower,staticRoutes} from '/@/router/route'; import {initFrontEndControlRoutes} from '/@/router/frontEnd'; import {initBackEndControlRoutes} from '/@/router/backEnd'; +import {useFrontendMenuStore} from "/@/stores/frontendMenu"; /** * 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。 @@ -107,6 +108,7 @@ router.beforeEach(async (to, from, next) => { next('/home'); NProgress.done(); } else { + const storesRoutesList = useRoutesList(pinia); const {routesList} = storeToRefs(storesRoutesList); if (routesList.value.length === 0) { @@ -115,6 +117,8 @@ router.beforeEach(async (to, from, next) => { await initBackEndControlRoutes(); // 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx' // to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理 + console.log("缓存的路由",routesList.value) + console.log("所有路由",router.getRoutes()) next({ path: to.path, query: to.query }); } else { // https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP diff --git a/web/src/router/route.ts b/web/src/router/route.ts index 9557d5c..35df9f6 100644 --- a/web/src/router/route.ts +++ b/web/src/router/route.ts @@ -89,19 +89,11 @@ export const staticRoutes: Array = [ }, }, { - path: '/operationLog', - name: 'operationLog', - component: () => import('/@/views/system/personal/index.vue'), + path: '/demo', + name: 'demo', + component: () => import('/@/views/system/demo/index.vue'), meta: { title: 'message.router.personal' }, - }, - // { - // path: '/demo', - // name: 'demo', - // component: () => import('/@/views/system/demo/index.vue'), - // meta: { - // title: 'message.router.personal' - // }, - // } + } ]; diff --git a/web/src/stores/frontendMenu.ts b/web/src/stores/frontendMenu.ts new file mode 100644 index 0000000..3251973 --- /dev/null +++ b/web/src/stores/frontendMenu.ts @@ -0,0 +1,159 @@ +import {defineStore} from 'pinia'; +import {FrontendMenu} from './interface'; +import {Session} from '/@/utils/storage'; +import {request} from '../utils/service'; +import XEUtils from "xe-utils"; +import {RouteRecordRaw} from "vue-router"; + + +const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}'); +const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}'); + + +/** + * 获取目录下的 .vue、.tsx 全部文件 + * @method import.meta.glob + * @link 参考:https://cn.vitejs.dev/guide/features.html#json + */ +const dynamicViewsModules: Record = Object.assign({}, { ...layouModules }, { ...viewsModules }); + +/** + * 后端路由 component 转换函数 + * @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件 + * @param component 当前要处理项 component + * @returns 返回处理成函数后的 component + */ +export function dynamicImport(dynamicViewsModules: Record, component: string) { + const keys = Object.keys(dynamicViewsModules); + const matchKeys = keys.filter((key) => { + const k = key.replace(/..\/views|../, ''); + return k.startsWith(`${component}`) || k.startsWith(`/${component}`); + }); + if (matchKeys?.length === 1) { + const matchKey = matchKeys[0]; + return dynamicViewsModules[matchKey]; + } + if (matchKeys?.length > 1) { + return false; + } +} + +/** + * @description: 处理后端菜单数据格式 + * @param {Array} menuData + * @return {*} + */ +export const handleMenu = (menuData: Array) => { + // 框架内路由 + const frameInRoutes:Array = [] + // 框架外路由 + const frameOutRoutes:Array = [] + // 先处理menu meta数据转换 + const handleMeta = (item: any) => { + item.path = item.web_path + item.meta = { + title: item.title, + isLink: item.link_url, + isHide: !item.visible, + isKeepAlive: item.cache, + isAffix: item.is_affix, + isIframe: item.is_iframe, + roles: ['admin'], + icon: item.icon + } + item.component = dynamicImport(dynamicViewsModules, item.component as string) + if(item.is_catalog){ + // 对目录的处理 + item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/parent') + } + if(item.is_link){ + // 对外链接的处理 + item.meta.isIframe = !item.is_iframe + if(item.is_iframe){ + item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link') + }else { + item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/iframes') + } + }else{ + if(item.is_iframe){ + const route = JSON.parse(JSON.stringify(item)) + route.meta.isLink = '' + route.path = `${item.web_path}` + route.name = `${item.name}` + route.meta.isIframe = true + route.meta.isKeepAlive = false + route.meta.isIframeOpen = true + route.component = item.component + frameOutRoutes.push(route) + item.path = `${item.web_path}FrameOut` + item.name = `frameOut_${item.name}` + item.meta.isLink = item.web_path + item.meta.isIframe = !item.is_iframe + item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue') + } + } + item.children && handleMeta(item.children); + return item + } + menuData.forEach((val) => { + frameInRoutes.push(handleMeta(val)) + }) + const data = XEUtils.toArrayTree(frameInRoutes, { + parentKey: 'parent', + strict: true, + }) + const dynamicRoutes = [ + { + path: '/home', name: 'home', + component: '/system/home/index', + meta: { + title: 'message.router.home', + isLink: '', + isHide: false, + isKeepAlive: true, + isAffix: true, + isIframe: false, + roles: ['admin'], + icon: 'iconfont icon-shouye' + } + }, + ...data + ] + return {frameIn:dynamicRoutes,frameOut:frameOutRoutes} +} + +export const useFrontendMenuStore = defineStore('frontendMenu',{ + state: (): FrontendMenu => ({ + arrayRouter: [], + treeRouter: [], + frameInRoutes:[], + frameOutRoutes:[] + }), + actions:{ + async requestMenu(){ + return request({ + url: '/api/system/menu/web_router/', + method: 'get', + params:{}, + }).then((res:any)=>{ + return res.data + }); + }, + async handleRouter(){ + const menuData = await this.requestMenu(); + this.arrayRouter = menuData + const {frameIn,frameOut} = handleMenu(menuData); + this.treeRouter = [...frameIn,...frameOut] + this.frameInRoutes=frameIn + this.frameOutRoutes=frameOut + }, + async getRouter(){ + await this.handleRouter() + return { + frameInRoutes:this.frameInRoutes, + frameOutRoutes:this.frameOutRoutes, + treeRouter:this.treeRouter + } + } + } +}) diff --git a/web/src/stores/interface/index.ts b/web/src/stores/interface/index.ts index 6d2c775..82dc5c6 100644 --- a/web/src/stores/interface/index.ts +++ b/web/src/stores/interface/index.ts @@ -2,6 +2,7 @@ * 定义接口来定义对象的类型 * `stores` 全部类型定义在这里 */ +import {useFrontendMenuStore} from "/@/stores/frontendMenu"; // 用户信息 export interface UserInfosState { @@ -102,3 +103,12 @@ export interface DictionaryStates { export interface ConfigStates { systemConfig: any; } + +export interface FrontendMenu { + arrayRouter: Array; + treeRouter:Array; + + frameOutRoutes:Array; + + frameInRoutes:Array; +} diff --git a/web/src/stores/modules/dept.ts b/web/src/stores/modules/dept.ts new file mode 100644 index 0000000..c1db3b8 --- /dev/null +++ b/web/src/stores/modules/dept.ts @@ -0,0 +1,30 @@ +import {defineStore} from "pinia"; +import {request} from "/@/utils/service"; +import XEUtils from "xe-utils"; +import {toRaw} from 'vue' +export const useDeptInfoStore = defineStore('deptInfo', { + state:()=>( + { + list:[], + tree:[], + } + ), + actions:{ + async requestDeptInfo() { + // 请求部门信息 + const ret = await request({ + url: '/api/system/dept/all_dept/' + }) + this.list = ret.data + this.tree = XEUtils.toArrayTree(ret.data,{parentKey:'parent',strict:true}) + }, + async getDeptById(id:any){ + + }, + async getParentDeptById(id: any){ + const tree = toRaw(this.tree) + const obj = XEUtils.findTree(tree, item => item.id == id) + return obj + } + } +}) diff --git a/web/src/utils/commonCrud.ts b/web/src/utils/commonCrud.ts index b9719a7..37e55c7 100644 --- a/web/src/utils/commonCrud.ts +++ b/web/src/utils/commonCrud.ts @@ -1,4 +1,6 @@ import { dict } from "@fast-crud/fast-crud"; +import {shallowRef} from 'vue' +import deptFormat from "/@/components/dept-format/index.vue"; export const commonCrudConfig = (options = { create_datetime: { form: false, @@ -32,6 +34,48 @@ export const commonCrudConfig = (options = { }, }) => { return { + dept_belong_id: { + title: '所属部门', + type: 'dict-cascader', + search: { + show: false + }, + dict: dict({ + url: '/api/system/dept/all_dept/', + isTree: true, + value: 'id', + label: 'name', + children: 'children', + }), + column: { + align: 'center', + width: 200, + show: options.dept_belong_id?.table || false, + component:{ + name: shallowRef(deptFormat), + vModel: "modelValue", + } + }, + form: { + show: options.dept_belong_id?.form || false, + component: { + multiple: false, + clearable: true, + props: { + showAllLevels:false, + props: { + // 为什么这里要写两层props + // 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层 + label: "name", + value: "id", + checkStrictly: true, + emitPath:false + } + } + }, + helper: "默认不填则为当前创建用户的部门ID" + } + }, description: { title: '备注', search: { @@ -39,15 +83,19 @@ export const commonCrudConfig = (options = { }, type: 'textarea', column: { + width: 100, show: options.description?.table || false, }, form: { + show: options.description?.form || false, component: { - show: options.description?.form || false, placeholder: '请输入内容', showWordLimit: true, maxlength: '200', } + }, + viewForm: { + show: true } }, modifier_name: { @@ -58,6 +106,28 @@ export const commonCrudConfig = (options = { column: { width: 100, show: options.modifier_name?.table || false, + }, + form: { + show: false, + }, + viewForm: { + show: true + } + }, + creator_name: { + title: '创建人', + search: { + show: options.creator_name?.search || false + }, + column: { + width: 100, + show: options.creator_name?.table || false, + }, + form: { + show: false, + }, + viewForm: { + show: true } }, update_datetime: { @@ -69,16 +139,12 @@ export const commonCrudConfig = (options = { column: { width: 160, show: options.update_datetime?.table || false, - } - }, - creator_name: { - title: '创建人', - search: { - show: options.creator_name?.search || false }, - column: { - width: 100, - show: options.creator_name?.table || false, + form: { + show: false, + }, + viewForm: { + show: true } }, create_datetime: { @@ -90,40 +156,12 @@ export const commonCrudConfig = (options = { column: { width: 160, show: options.create_datetime?.table || false, - } - }, - dept_belong_id: { - title: '所属部门', - type: 'dict-tree', - search: { - show: false - }, - dict: dict({ - url: '/api/system/dept/all_dept/', - isTree: true, - value: 'id', - label: 'name', - children: 'children' // 数据字典中children字段的属性名 - }), - column: { - width: 150, - show: options.dept_belong_id?.table || false, }, form: { - component: { - show: options.dept_belong_id?.form || false, - multiple: false, - clearable: true, - props: { - props: { - // 为什么这里要写两层props - // 因为props属性名与fs的动态渲染的props命名冲突,所以要多写一层 - label: "name", - value: "id", - } - } - }, - helper: "默认不填则为当前创建用户的部门ID" + show: false, + }, + viewForm: { + show: true } } }