新功能:
1.菜单新增框架外显示字段
This commit is contained in:
@@ -162,6 +162,7 @@ class Menu(CoreModel):
|
|||||||
(1, "是"),
|
(1, "是"),
|
||||||
)
|
)
|
||||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||||
|
link_url = models.CharField(max_length=255, verbose_name="链接地址", null=True, blank=True, help_text="链接地址")
|
||||||
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
||||||
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||||
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ class WebRouterSerializer(CustomModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Menu
|
model = Menu
|
||||||
fields = (
|
fields = (
|
||||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
|
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link','link_url', 'is_catalog', 'web_path', 'component',
|
||||||
'component_name', 'cache', 'visible', 'status')
|
'component_name', 'cache', 'visible','is_iframe','is_affix', 'status')
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ const menuApi = useMenuApi();
|
|||||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||||
|
|
||||||
// 后端控制路由
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取目录下的 .vue、.tsx 全部文件
|
* 获取目录下的 .vue、.tsx 全部文件
|
||||||
* @method import.meta.glob
|
* @method import.meta.glob
|
||||||
@@ -45,9 +43,12 @@ export async function initBackEndControlRoutes() {
|
|||||||
await useUserInfo().setUserInfos();
|
await useUserInfo().setUserInfos();
|
||||||
// 获取路由菜单数据
|
// 获取路由菜单数据
|
||||||
const res = await getBackEndControlRoutes();
|
const res = await getBackEndControlRoutes();
|
||||||
|
// 无登录权限时,添加判断
|
||||||
|
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
|
||||||
|
if (res.data.length <= 0) return Promise.resolve(true);
|
||||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||||
dynamicRoutes[0].children = await backEndComponent(handleMenu(res.data));
|
const {frameIn,frameOut} = handleMenu(res.data)
|
||||||
|
dynamicRoutes[0].children = await backEndComponent(frameIn);
|
||||||
// 添加动态路由
|
// 添加动态路由
|
||||||
await setAddRoute();
|
await setAddRoute();
|
||||||
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
// 设置路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||||
@@ -80,7 +81,10 @@ export function setCacheTagsViewRoutes() {
|
|||||||
* @returns 返回替换后的路由数组
|
* @returns 返回替换后的路由数组
|
||||||
*/
|
*/
|
||||||
export function setFilterRouteEnd() {
|
export function setFilterRouteEnd() {
|
||||||
|
console.log(dynamicRoutes)
|
||||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||||
|
// notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||||
|
// 关联问题 No match found for location with path 'xxx'
|
||||||
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
||||||
return filterRouteEnd;
|
return filterRouteEnd;
|
||||||
}
|
}
|
||||||
@@ -92,9 +96,11 @@ export function setFilterRouteEnd() {
|
|||||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||||
*/
|
*/
|
||||||
export async function setAddRoute() {
|
export async function setAddRoute() {
|
||||||
|
console.log("默认路由",router.getRoutes())
|
||||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
});
|
});
|
||||||
|
console.log("全部路由",router.getRoutes())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,6 +132,34 @@ export function backEndComponent(routes: any) {
|
|||||||
if (!routes) return;
|
if (!routes) return;
|
||||||
return routes.map((item: any) => {
|
return routes.map((item: any) => {
|
||||||
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
|
if (item.component) 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{
|
||||||
|
console.log(item.is_iframe,item.web_path)
|
||||||
|
if(item.is_iframe){
|
||||||
|
const iframeRoute:RouteRecordRaw = {
|
||||||
|
...item
|
||||||
|
}
|
||||||
|
item.meta.isLink = item.path
|
||||||
|
item.path = `${item.path}Link`
|
||||||
|
item.name = `${item.name}Link`
|
||||||
|
item.component = dynamicImport(dynamicViewsModules, 'layout/routerView/link.vue')
|
||||||
|
item.meta.isIframe = !item.is_iframe
|
||||||
|
item.meta.isKeepAlive = false
|
||||||
|
item.meta.isIframeOpen = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
item.children && backEndComponent(item.children);
|
item.children && backEndComponent(item.children);
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {useKeepALiveNames} from '/@/stores/keepAliveNames';
|
|||||||
import {useRoutesList} from '/@/stores/routesList';
|
import {useRoutesList} from '/@/stores/routesList';
|
||||||
import {useThemeConfig} from '/@/stores/themeConfig';
|
import {useThemeConfig} from '/@/stores/themeConfig';
|
||||||
import {Session} from '/@/utils/storage';
|
import {Session} from '/@/utils/storage';
|
||||||
import {staticRoutes} from '/@/router/route';
|
import {notFoundAndNoPower,staticRoutes} from '/@/router/route';
|
||||||
import {initFrontEndControlRoutes} from '/@/router/frontEnd';
|
import {initFrontEndControlRoutes} from '/@/router/frontEnd';
|
||||||
import {initBackEndControlRoutes} from '/@/router/backEnd';
|
import {initBackEndControlRoutes} from '/@/router/backEnd';
|
||||||
|
|
||||||
@@ -32,7 +32,13 @@ const {isRequestRoutes} = themeConfig.value;
|
|||||||
*/
|
*/
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: staticRoutes,
|
/**
|
||||||
|
* 说明:
|
||||||
|
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
|
||||||
|
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
|
||||||
|
* 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||||
|
*/
|
||||||
|
routes: [...notFoundAndNoPower, ...staticRoutes]
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,14 +69,7 @@ export function formatTwoStageRoutes(arr: any) {
|
|||||||
const cacheList: Array<string> = [];
|
const cacheList: Array<string> = [];
|
||||||
arr.forEach((v: any) => {
|
arr.forEach((v: any) => {
|
||||||
if (v.path === '/') {
|
if (v.path === '/') {
|
||||||
newArr.push({
|
newArr.push({component: v.component,name: v.name,path: v.path,redirect: v.redirect,meta: v.meta,children: []});
|
||||||
component: v.component,
|
|
||||||
name: v.name,
|
|
||||||
path: v.path,
|
|
||||||
redirect: v.redirect,
|
|
||||||
meta: v.meta,
|
|
||||||
children: []
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
|
// 判断是否是动态路由(xx/:id/:name),用于 tagsView 等中使用
|
||||||
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
|
||||||
@@ -114,13 +113,13 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (isRequestRoutes) {
|
if (isRequestRoutes) {
|
||||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||||
await initBackEndControlRoutes();
|
await initBackEndControlRoutes();
|
||||||
// 动态添加路由:防止非首页刷新时跳转回首页的问题
|
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
|
||||||
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
|
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
|
||||||
next({...to, replace: true});
|
next({ path: to.path, query: to.query });
|
||||||
} else {
|
} else {
|
||||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||||
await initFrontEndControlRoutes();
|
await initFrontEndControlRoutes();
|
||||||
next({...to, replace: true});
|
next({ path: to.path, query: to.query });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -88,4 +88,20 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
|||||||
title: '登录',
|
title: '登录',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/operationLog',
|
||||||
|
name: 'operationLog',
|
||||||
|
component: () => import('/@/views/system/personal/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'message.router.personal'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// path: '/demo',
|
||||||
|
// name: 'demo',
|
||||||
|
// component: () => import('/@/views/system/demo/index.vue'),
|
||||||
|
// meta: {
|
||||||
|
// title: 'message.router.personal'
|
||||||
|
// },
|
||||||
|
// }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -22,5 +22,8 @@ export const useRoutesList = defineStore('routesList', {
|
|||||||
async setColumnsNavHover(bool: Boolean) {
|
async setColumnsNavHover(bool: Boolean) {
|
||||||
this.isColumnsNavHover = bool;
|
this.isColumnsNavHover = bool;
|
||||||
},
|
},
|
||||||
|
async addRoutesList(data: Array<string>) {
|
||||||
|
this.routesList.push(data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const handleMenu = (menuData: Array<any>) => {
|
|||||||
const handleMeta = (item: any) => {
|
const handleMeta = (item: any) => {
|
||||||
item.meta = {
|
item.meta = {
|
||||||
title: item.title,
|
title: item.title,
|
||||||
isLink: item.is_link,
|
isLink: item.link_url,
|
||||||
isHide: !item.visible,
|
isHide: !item.visible,
|
||||||
isKeepAlive: item.cache,
|
isKeepAlive: item.cache,
|
||||||
isAffix: item.is_affix,
|
isAffix: item.is_affix,
|
||||||
@@ -29,7 +29,7 @@ export const handleMenu = (menuData: Array<any>) => {
|
|||||||
if (item.is_iframe) {
|
if (item.is_iframe) {
|
||||||
item.meta = {
|
item.meta = {
|
||||||
title: item.title,
|
title: item.title,
|
||||||
isLink: item.is_link,
|
isLink: item.link_url,
|
||||||
isHide: !item.visible,
|
isHide: !item.visible,
|
||||||
isKeepAlive: item.cache,
|
isKeepAlive: item.cache,
|
||||||
isAffix: item.is_affix,
|
isAffix: item.is_affix,
|
||||||
@@ -44,25 +44,23 @@ export const handleMenu = (menuData: Array<any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 框架内路由
|
// 框架内路由
|
||||||
const dynamicRoutes:Array<any> = []
|
const defaultRoutes:Array<any> = []
|
||||||
// 框架外路由
|
// 框架外路由
|
||||||
const staticRoutes:Array<any> = []
|
const iframeRoutes:Array<any> = []
|
||||||
|
|
||||||
menuData.forEach((val) => {
|
menuData.forEach((val) => {
|
||||||
console.log(111,val.is_iframe)
|
// if (val.is_iframe) {
|
||||||
if(val.is_iframe){
|
// // iframeRoutes.push(handleFrame(val))
|
||||||
staticRoutes.push(handleFrame(val))
|
// } else {
|
||||||
console.log(staticRoutes)
|
// defaultRoutes.push(handleMeta(val))
|
||||||
}else{
|
// }
|
||||||
dynamicRoutes.push(handleMeta(val))
|
defaultRoutes.push(handleMeta(val))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
const data = XEUtils.toArrayTree(defaultRoutes, {
|
||||||
const data = XEUtils.toArrayTree(dynamicRoutes, {
|
|
||||||
parentKey: 'parent',
|
parentKey: 'parent',
|
||||||
strict: true,
|
strict: true,
|
||||||
})
|
})
|
||||||
const menu = [
|
const dynamicRoutes = [
|
||||||
{
|
{
|
||||||
path: '/home', name: 'home', component: '/system/home/index', meta: {
|
path: '/home', name: 'home', component: '/system/home/index', meta: {
|
||||||
title: 'message.router.home',
|
title: 'message.router.home',
|
||||||
@@ -77,5 +75,5 @@ export const handleMenu = (menuData: Array<any>) => {
|
|||||||
},
|
},
|
||||||
...data
|
...data
|
||||||
]
|
]
|
||||||
return menu
|
return {frameIn:dynamicRoutes,frameOut:iframeRoutes}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_link" label="路由地址" prop="web_path">
|
<el-form-item label="路由地址" prop="web_path">
|
||||||
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
<el-input v-model="menuFormData.web_path" placeholder="请输入路由地址,请以/开头" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@
|
|||||||
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
<el-input v-model="menuFormData.component_name" placeholder="请输入组件名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="Url" prop="web_path">
|
<el-form-item v-if="!menuFormData.is_catalog && menuFormData.is_link" label="外链接" prop="link_url">
|
||||||
<el-input v-model="menuFormData.web_path" placeholder="请输入Url" />
|
<el-input v-model="menuFormData.link_url" placeholder="请输入外链接地址" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
<el-form-item v-if="!menuFormData.is_catalog" label="缓存">
|
||||||
@@ -139,8 +139,7 @@ const defaultTreeProps: any = {
|
|||||||
};
|
};
|
||||||
const validateWebPath = (rule: any, value: string, callback: Function) => {
|
const validateWebPath = (rule: any, value: string, callback: Function) => {
|
||||||
let pattern = /^\/.*?/;
|
let pattern = /^\/.*?/;
|
||||||
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
const reg = pattern.test(value);
|
||||||
const reg = menuFormData.is_link ? patternUrl.test(value) : pattern.test(value);
|
|
||||||
if (reg) {
|
if (reg) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
@@ -148,6 +147,17 @@ const validateWebPath = (rule: any, value: string, callback: Function) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateLinkUrl = (rule: any, value: string, callback: Function) => {
|
||||||
|
let pattern = /^\/.*?/;
|
||||||
|
let patternUrl = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
|
||||||
|
const reg = pattern.test(value) || patternUrl.test(value)
|
||||||
|
if (reg) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error('请输入正确的地址'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
initFormData: () => null,
|
initFormData: () => null,
|
||||||
treeData: () => [],
|
treeData: () => [],
|
||||||
@@ -162,6 +172,7 @@ const rules = reactive<FormRules>({
|
|||||||
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
name: [{ required: true, message: '菜单名称必填', trigger: 'blur' }],
|
||||||
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
component: [{ required: true, message: '请输入组件地址', trigger: 'blur' }],
|
||||||
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
component_name: [{ required: true, message: '请输入组件名称', trigger: 'blur' }],
|
||||||
|
link_url: [{ required: true, message: '请输入外链接地址',validator:validateLinkUrl, trigger: 'blur' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
let deptDefaultList = ref<MenuTreeItemType[]>([]);
|
||||||
@@ -180,6 +191,7 @@ let menuFormData = reactive<MenuFormDataType>({
|
|||||||
is_link: false,
|
is_link: false,
|
||||||
is_iframe: false,
|
is_iframe: false,
|
||||||
is_affix: false,
|
is_affix: false,
|
||||||
|
link_url:''
|
||||||
});
|
});
|
||||||
let menuBtnLoading = ref(false);
|
let menuBtnLoading = ref(false);
|
||||||
|
|
||||||
@@ -200,6 +212,7 @@ const setMenuFormData = () => {
|
|||||||
menuFormData.is_link = !!props.initFormData.is_link;
|
menuFormData.is_link = !!props.initFormData.is_link;
|
||||||
menuFormData.is_iframe =!!props.initFormData.is_iframe;
|
menuFormData.is_iframe =!!props.initFormData.is_iframe;
|
||||||
menuFormData.is_affix =!!props.initFormData.is_affix;
|
menuFormData.is_affix =!!props.initFormData.is_affix;
|
||||||
|
menuFormData.link_url =props.initFormData.link_url;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export interface MenuTreeItemType {
|
|||||||
parent: number | string;
|
parent: number | string;
|
||||||
is_iframe:boolean;
|
is_iframe:boolean;
|
||||||
is_affix:boolean;
|
is_affix:boolean;
|
||||||
|
link_url:string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuFormDataType {
|
export interface MenuFormDataType {
|
||||||
@@ -62,8 +63,7 @@ export interface MenuFormDataType {
|
|||||||
description: string;
|
description: string;
|
||||||
is_catalog: boolean;
|
is_catalog: boolean;
|
||||||
is_link: boolean;
|
is_link: boolean;
|
||||||
|
|
||||||
is_iframe:boolean;
|
is_iframe:boolean;
|
||||||
|
|
||||||
is_affix:boolean;
|
is_affix:boolean;
|
||||||
|
link_url: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user