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 @@
+
+
+ {{ data }}
+
+
+
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
}
}
}