动态路由调整
This commit is contained in:
parent
5052ab0e56
commit
df639d040a
|
|
@ -1,87 +1,60 @@
|
|||
// 模拟静态数据
|
||||
const mockMenus = [
|
||||
{ menuId: 1, menuName: '系统管理', parentId: 0, orderNum: 1, path: 'system', component: 'Layout', status: '0' },
|
||||
{ menuId: 2, menuName: '用户管理', parentId: 1, orderNum: 1, path: 'user', component: 'system/user/index', status: '0' },
|
||||
{ menuId: 3, menuName: '角色管理', parentId: 1, orderNum: 2, path: 'role', component: 'system/role/index', status: '0' },
|
||||
{ menuId: 4, menuName: '菜单管理', parentId: 1, orderNum: 3, path: 'menu', component: 'system/menu/index', status: '0' }
|
||||
];
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询菜单列表
|
||||
export function listMenu(query) {
|
||||
const { menuName, status } = query;
|
||||
let filteredMenus = [...mockMenus];
|
||||
|
||||
if (menuName) {
|
||||
filteredMenus = filteredMenus.filter(menu => menu.menuName.includes(menuName));
|
||||
}
|
||||
if (status) {
|
||||
filteredMenus = filteredMenus.filter(menu => menu.status === status);
|
||||
}
|
||||
|
||||
return Promise.resolve(filteredMenus);
|
||||
return request({
|
||||
url: '/system/menu/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询菜单详细
|
||||
export function getMenu(menuId) {
|
||||
const menu = mockMenus.find(m => m.menuId === parseInt(menuId));
|
||||
return Promise.resolve(menu || {});
|
||||
return request({
|
||||
url: '/system/menu/' + menuId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询菜单下拉树结构
|
||||
export function treeselect() {
|
||||
const buildTree = (parentId) => {
|
||||
const children = mockMenus.filter(m => m.parentId === parentId);
|
||||
return children.map(child => ({
|
||||
id: child.menuId,
|
||||
label: child.menuName,
|
||||
children: buildTree(child.menuId)
|
||||
}));
|
||||
};
|
||||
|
||||
return Promise.resolve(buildTree(0));
|
||||
return request({
|
||||
url: '/system/menu/treeselect',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据角色ID查询菜单下拉树结构
|
||||
export function roleMenuTreeselect(roleId) {
|
||||
const buildTree = (parentId) => {
|
||||
const children = mockMenus.filter(m => m.parentId === parentId);
|
||||
return children.map(child => ({
|
||||
id: child.menuId,
|
||||
label: child.menuName,
|
||||
children: buildTree(child.menuId)
|
||||
}));
|
||||
};
|
||||
|
||||
return Promise.resolve({
|
||||
menus: buildTree(0),
|
||||
checkedKeys: [1, 2, 3, 4] // 模拟已选中的菜单
|
||||
});
|
||||
return request({
|
||||
url: '/system/menu/roleMenuTreeselect/' + roleId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增菜单
|
||||
export function addMenu(data) {
|
||||
const newMenu = {
|
||||
menuId: mockMenus.length + 1,
|
||||
...data
|
||||
};
|
||||
mockMenus.push(newMenu);
|
||||
return Promise.resolve({ code: 200, msg: '新增成功' });
|
||||
return request({
|
||||
url: '/system/menu',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改菜单
|
||||
export function updateMenu(data) {
|
||||
const index = mockMenus.findIndex(m => m.menuId === data.menuId);
|
||||
if (index !== -1) {
|
||||
mockMenus[index] = { ...mockMenus[index], ...data };
|
||||
}
|
||||
return Promise.resolve({ code: 200, msg: '修改成功' });
|
||||
return request({
|
||||
url: '/system/menu',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
export function delMenu(menuId) {
|
||||
const index = mockMenus.findIndex(m => m.menuId === parseInt(menuId));
|
||||
if (index !== -1) {
|
||||
mockMenus.splice(index, 1);
|
||||
}
|
||||
return Promise.resolve({ code: 200, msg: '删除成功' });
|
||||
return request({
|
||||
url: '/system/menu/' + menuId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const url = ref('http://doc.ruoyi.vip/ruoyi-vue');
|
||||
const url = ref("http://doc.ruoyi.vip/ruoyi-vue");
|
||||
|
||||
function goto() {
|
||||
window.open(url.value)
|
||||
window.open(url.value);
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,121 +1,128 @@
|
|||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar @setLayout="setLayout" />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<settings ref="settingRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === 'mobile'
|
||||
}))
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
watch(() => device.value, () => {
|
||||
if (device.value === 'mobile' && sidebar.value.opened) {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (width.value - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice('mobile')
|
||||
useAppStore().closeSideBar({ withoutAnimation: true })
|
||||
} else {
|
||||
useAppStore().toggleDevice('desktop')
|
||||
}
|
||||
})
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
|
||||
const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
permissionStore.generateRoutes()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.sidebarHide .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar @setLayout="setLayout" />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<settings ref="settingRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === 'mobile'
|
||||
}))
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
watch(() => device.value, () => {
|
||||
if (device.value === 'mobile' && sidebar.value.opened) {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (width.value - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice('mobile')
|
||||
useAppStore().closeSideBar({ withoutAnimation: true })
|
||||
} else {
|
||||
useAppStore().toggleDevice('desktop')
|
||||
}
|
||||
})
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
|
||||
const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 如果路由未加载,则加载路由(避免与路由守卫中的调用冲突)
|
||||
if (permissionStore.addRoutes.length === 0) {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.roles.length > 0) {
|
||||
permissionStore.generateRoutes(userStore.roles)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.sidebarHide .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,61 +1,83 @@
|
|||
import router from './router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { isHttp, isPathMatch } from '@/utils/validate'
|
||||
import { isRelogin } from '@/utils/request'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
const whiteList = ['/login', '/register']
|
||||
|
||||
const isWhiteList = (path) => {
|
||||
return whiteList.some(pattern => isPathMatch(pattern, path))
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
if (getToken()) {
|
||||
to.meta.title && useSettingsStore().setTitle(to.meta.title)
|
||||
/* has token*/
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else if (isWhiteList(to.path)) {
|
||||
next()
|
||||
} else {
|
||||
if (useUserStore().roles.length === 0) {
|
||||
isRelogin.show = true
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
useUserStore().getInfo().then(() => {
|
||||
isRelogin.show = false
|
||||
// 不再动态加载路由,直接使用静态路由
|
||||
next({ ...to, replace: true })
|
||||
}).catch(err => {
|
||||
useUserStore().logOut().then(() => {
|
||||
ElMessage.error(err)
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有token
|
||||
if (isWhiteList(to.path)) {
|
||||
// 在免登录白名单,直接进入
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||
import router from './router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { isHttp, isPathMatch } from '@/utils/validate'
|
||||
import { isRelogin } from '@/utils/request'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
const whiteList = ['/login', '/register']
|
||||
|
||||
const isWhiteList = (path) => {
|
||||
return whiteList.some(pattern => isPathMatch(pattern, path))
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
if (getToken()) {
|
||||
to.meta.title && useSettingsStore().setTitle(to.meta.title)
|
||||
/* has token*/
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else if (isWhiteList(to.path)) {
|
||||
next()
|
||||
} else {
|
||||
if (useUserStore().roles.length === 0) {
|
||||
isRelogin.show = true
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
useUserStore().getInfo().then(() => {
|
||||
isRelogin.show = false
|
||||
// 动态加载路由
|
||||
const permissionStore = usePermissionStore()
|
||||
permissionStore.generateRoutes(useUserStore().roles).then(() => {
|
||||
// 动态路由加载完成后,重新跳转到目标路由
|
||||
next({ ...to, replace: true })
|
||||
}).catch((error) => {
|
||||
console.error('路由加载失败:', error)
|
||||
ElMessage.error(error?.message || '路由加载失败,请刷新页面重试')
|
||||
next({ path: '/' })
|
||||
})
|
||||
}).catch(err => {
|
||||
useUserStore().logOut().then(() => {
|
||||
ElMessage.error(err)
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 已获取用户信息,检查路由是否已加载
|
||||
const permissionStore = usePermissionStore()
|
||||
if (permissionStore.addRoutes.length === 0) {
|
||||
// 路由未加载,重新加载
|
||||
permissionStore.generateRoutes(useUserStore().roles).then(() => {
|
||||
next({ ...to, replace: true })
|
||||
}).catch((error) => {
|
||||
console.error('路由加载失败:', error)
|
||||
ElMessage.error(error?.message || '路由加载失败,请刷新页面重试')
|
||||
next({ path: '/' })
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有token
|
||||
if (isWhiteList(to.path)) {
|
||||
// 在免登录白名单,直接进入
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,203 +1,105 @@
|
|||
import { createWebHistory, createRouter } from 'vue-router'
|
||||
/* Layout */
|
||||
import Layout from '@/layout/index.vue'
|
||||
|
||||
/**
|
||||
* Note: 路由配置项
|
||||
*
|
||||
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
|
||||
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
|
||||
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
|
||||
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
|
||||
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
|
||||
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
|
||||
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
||||
* meta : {
|
||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
|
||||
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
|
||||
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
|
||||
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
|
||||
}
|
||||
*/
|
||||
|
||||
// 公共路由
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: () => import('@/views/redirect/index.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: () => import('@/views/register.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
component: () => import('@/views/error/404.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/error/401.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: '/order/intention',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
component: () => import('@/views/index.vue'),
|
||||
name: 'Index',
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'UserCenter',
|
||||
meta: { title: '个人中心', icon: 'user' },
|
||||
children: [
|
||||
{
|
||||
path: 'profile',
|
||||
component: () => import('@/views/user/profile/index.vue'),
|
||||
name: 'Profile',
|
||||
meta: { title: '个人中心', icon: 'user' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/order',
|
||||
component: Layout,
|
||||
hidden: false,
|
||||
name: 'Order',
|
||||
meta: { title: '订单管理', icon: 'shopping' },
|
||||
children: [
|
||||
{
|
||||
path: 'intention',
|
||||
component: () => import('@/views/order/intention/index.vue'),
|
||||
name: 'Intention',
|
||||
meta: { title: '订单/意向单', icon: 'form' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/order/intention/create.vue'),
|
||||
name: 'IntentionCreate',
|
||||
meta: { title: '创建订单/意向单', icon: 'edit', hidden: true }
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: () => import('@/views/order/intention/search.vue'),
|
||||
name: 'IntentionSearch',
|
||||
meta: { title: '品号查询', icon: 'search', hidden: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 以下为原动态路由,现在直接合并到静态路由中
|
||||
{
|
||||
path: '/system/user-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:user:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'role/:userId(\\d+)',
|
||||
component: () => import('@/views/system/user/authRole.vue'),
|
||||
name: 'AuthRole',
|
||||
meta: { title: '分配角色', activeMenu: '/system/user' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/role-auth',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:role:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'user/:roleId(\\d+)',
|
||||
component: () => import('@/views/system/role/authUser.vue'),
|
||||
name: 'AuthUser',
|
||||
meta: { title: '分配用户', activeMenu: '/system/role' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system/dict-data',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['system:dict:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:dictId(\\d+)',
|
||||
component: () => import('@/views/system/dict/data.vue'),
|
||||
name: 'Data',
|
||||
meta: { title: '字典数据', activeMenu: '/system/dict' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/monitor/job-log',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['monitor:job:list'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:jobId(\\d+)',
|
||||
component: () => import('@/views/monitor/job/log.vue'),
|
||||
name: 'JobLog',
|
||||
meta: { title: '调度日志', activeMenu: '/monitor/job' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/tool/gen-edit',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
permissions: ['tool:gen:edit'],
|
||||
children: [
|
||||
{
|
||||
path: 'index/:tableId(\\d+)',
|
||||
component: () => import('@/views/tool/gen/editTable.vue'),
|
||||
name: 'GenEdit',
|
||||
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 动态路由已合并到静态路由中,不再需要
|
||||
// export const dynamicRoutes = [ ... ];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: constantRoutes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
}
|
||||
return { top: 0 }
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
import { createWebHistory, createRouter } from 'vue-router'
|
||||
/* Layout */
|
||||
import Layout from '@/layout/index.vue'
|
||||
|
||||
/**
|
||||
* Note: 路由配置项
|
||||
*
|
||||
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
|
||||
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
|
||||
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
|
||||
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
|
||||
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
|
||||
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
|
||||
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
||||
* meta : {
|
||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
|
||||
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
|
||||
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
|
||||
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
|
||||
}
|
||||
*/
|
||||
|
||||
// 公共路由
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: () => import('@/views/redirect/index.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: () => import('@/views/register.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
component: () => import('@/views/error/404.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/error/401.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: 'index',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
component: () => import('@/views/index.vue'),
|
||||
name: 'Index',
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'UserCenter',
|
||||
meta: { title: '个人中心', icon: 'user' },
|
||||
children: [
|
||||
{
|
||||
path: 'profile',
|
||||
component: () => import('@/views/user/profile/index.vue'),
|
||||
name: 'Profile',
|
||||
meta: { title: '个人中心', icon: 'user' }
|
||||
}
|
||||
]
|
||||
}
|
||||
// 业务路由(订单管理、系统管理等)将从后端动态获取
|
||||
];
|
||||
|
||||
// 动态路由将从后端接口获取,不再在此处定义
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: constantRoutes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
}
|
||||
return { top: 0 }
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,119 +1,231 @@
|
|||
import auth from '@/plugins/auth'
|
||||
import router, { constantRoutes } from '@/router'
|
||||
// 不再需要动态路由,移除 dynamicRoutes 和 getRouters
|
||||
// import { getRouters } from '@/api/menu'
|
||||
import Layout from '@/layout/index'
|
||||
import ParentView from '@/components/ParentView'
|
||||
import InnerLink from '@/layout/components/InnerLink'
|
||||
|
||||
// 匹配views里面所有的.vue文件
|
||||
const modules = import.meta.glob('./../../views/**/*.vue')
|
||||
|
||||
const usePermissionStore = defineStore(
|
||||
'permission',
|
||||
{
|
||||
state: () => ({
|
||||
routes: [],
|
||||
addRoutes: [],
|
||||
defaultRoutes: [],
|
||||
topbarRouters: [],
|
||||
sidebarRouters: []
|
||||
}),
|
||||
actions: {
|
||||
setRoutes(routes) {
|
||||
this.addRoutes = routes
|
||||
this.routes = constantRoutes.concat(routes)
|
||||
},
|
||||
setDefaultRoutes(routes) {
|
||||
this.defaultRoutes = constantRoutes.concat(routes)
|
||||
},
|
||||
setTopbarRoutes(routes) {
|
||||
this.topbarRouters = routes
|
||||
},
|
||||
setSidebarRouters(routes) {
|
||||
this.sidebarRouters = routes
|
||||
},
|
||||
generateRoutes(roles) {
|
||||
return new Promise(resolve => {
|
||||
// 不再调用后端接口,直接使用静态路由
|
||||
const staticRoutes = constantRoutes
|
||||
this.setRoutes(staticRoutes)
|
||||
this.setSidebarRouters(staticRoutes)
|
||||
this.setDefaultRoutes(staticRoutes)
|
||||
this.setTopbarRoutes(staticRoutes)
|
||||
resolve(staticRoutes)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 遍历后台传来的路由字符串,转换为组件对象
|
||||
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
return asyncRouterMap.filter(route => {
|
||||
if (type && route.children) {
|
||||
route.children = filterChildren(route.children)
|
||||
}
|
||||
if (route.component) {
|
||||
// Layout ParentView 组件特殊处理
|
||||
if (route.component === 'Layout') {
|
||||
route.component = Layout
|
||||
} else if (route.component === 'ParentView') {
|
||||
route.component = ParentView
|
||||
} else if (route.component === 'InnerLink') {
|
||||
route.component = InnerLink
|
||||
} else {
|
||||
route.component = loadView(route.component)
|
||||
}
|
||||
}
|
||||
if (route.children != null && route.children && route.children.length) {
|
||||
route.children = filterAsyncRouter(route.children, route, type)
|
||||
} else {
|
||||
delete route['children']
|
||||
delete route['redirect']
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function filterChildren(childrenMap, lastRouter = false) {
|
||||
var children = []
|
||||
childrenMap.forEach(el => {
|
||||
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path
|
||||
if (el.children && el.children.length && el.component === 'ParentView') {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
} else {
|
||||
children.push(el)
|
||||
}
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
// 动态路由遍历,验证是否具备权限
|
||||
export function filterDynamicRoutes(routes) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
if (route.permissions) {
|
||||
if (auth.hasPermiOr(route.permissions)) {
|
||||
res.push(route)
|
||||
}
|
||||
} else if (route.roles) {
|
||||
if (auth.hasRoleOr(route.roles)) {
|
||||
res.push(route)
|
||||
}
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
let res;
|
||||
for (const path in modules) {
|
||||
const dir = path.split('views/')[1].split('.vue')[0];
|
||||
if (dir === view) {
|
||||
res = () => modules[path]();
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export default usePermissionStore
|
||||
import auth from '@/plugins/auth'
|
||||
import router, { constantRoutes } from '@/router'
|
||||
import { getRouters } from '@/api/menu'
|
||||
import Layout from '@/layout/index'
|
||||
import ParentView from '@/components/ParentView'
|
||||
import InnerLink from '@/layout/components/InnerLink'
|
||||
import { isHttp } from '@/utils/validate'
|
||||
|
||||
// 匹配views里面所有的.vue文件
|
||||
const modules = import.meta.glob('./../../views/**/*.vue')
|
||||
|
||||
const usePermissionStore = defineStore(
|
||||
'permission',
|
||||
{
|
||||
state: () => ({
|
||||
routes: [],
|
||||
addRoutes: [],
|
||||
defaultRoutes: [],
|
||||
topbarRouters: [],
|
||||
sidebarRouters: []
|
||||
}),
|
||||
actions: {
|
||||
setRoutes(routes) {
|
||||
this.addRoutes = routes
|
||||
this.routes = constantRoutes.concat(routes)
|
||||
},
|
||||
setDefaultRoutes(routes) {
|
||||
this.defaultRoutes = constantRoutes.concat(routes)
|
||||
},
|
||||
setTopbarRoutes(routes) {
|
||||
this.topbarRouters = routes
|
||||
},
|
||||
setSidebarRouters(routes) {
|
||||
this.sidebarRouters = routes
|
||||
},
|
||||
generateRoutes(roles) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 调用后端接口获取动态路由
|
||||
getRouters().then(res => {
|
||||
// 确保 res.data 是数组
|
||||
if (!res.data || !Array.isArray(res.data)) {
|
||||
console.error('路由数据格式错误:', res.data)
|
||||
reject(new Error('路由数据格式错误,期望数组'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const sdata = JSON.parse(JSON.stringify(res.data))
|
||||
const rdata = JSON.parse(JSON.stringify(res.data))
|
||||
const defaultData = JSON.parse(JSON.stringify(res.data))
|
||||
const sidebarRoutes = filterAsyncRouter(sdata)
|
||||
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
|
||||
const defaultRoutes = filterAsyncRouter(defaultData)
|
||||
|
||||
// 过滤权限路由
|
||||
const accessedRoutes = filterDynamicRoutes(rewriteRoutes)
|
||||
|
||||
// 调试:打印路由信息
|
||||
console.log('动态路由加载成功,路由数量:', accessedRoutes.length)
|
||||
if (accessedRoutes.length > 0) {
|
||||
console.log('第一个路由示例:', accessedRoutes[0])
|
||||
}
|
||||
|
||||
this.setRoutes(accessedRoutes)
|
||||
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
|
||||
this.setDefaultRoutes(constantRoutes.concat(defaultRoutes))
|
||||
this.setTopbarRoutes(constantRoutes.concat(sidebarRoutes))
|
||||
|
||||
// 动态添加路由到router
|
||||
accessedRoutes.forEach(route => {
|
||||
router.addRoute(route)
|
||||
})
|
||||
|
||||
resolve(accessedRoutes)
|
||||
} catch (error) {
|
||||
console.error('路由处理错误:', error)
|
||||
reject(error)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取路由失败:', error)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 遍历后台传来的路由字符串,转换为组件对象
|
||||
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
|
||||
// 确保 asyncRouterMap 是数组
|
||||
if (!Array.isArray(asyncRouterMap)) {
|
||||
console.error('filterAsyncRouter: 期望数组,但收到:', asyncRouterMap)
|
||||
return []
|
||||
}
|
||||
|
||||
return asyncRouterMap.filter(route => {
|
||||
// 处理外部链接路径:如果 path 是外部链接,需要转换为有效的内部路径
|
||||
if (route.path && isHttp(route.path)) {
|
||||
// 确保 meta.link 保存实际的外部链接
|
||||
if (!route.meta) {
|
||||
route.meta = {}
|
||||
}
|
||||
// 保存原始的外部链接到 meta.link
|
||||
const originalPath = route.path
|
||||
route.meta.link = originalPath
|
||||
|
||||
// 将外部链接路径转换为内部路径
|
||||
// 使用简单的路径格式:/iframe/域名,避免路径冲突
|
||||
const domain = originalPath.replace(/^https?:\/\//, '').replace(/\/.*$/, '')
|
||||
const timestamp = Date.now()
|
||||
const encodedPath = '/iframe/' + encodeURIComponent(domain) + '-' + timestamp
|
||||
|
||||
// 将 path 转换为有效的内部路径
|
||||
route.path = encodedPath
|
||||
} else if (!lastRouter && route.path && !route.path.startsWith('/')) {
|
||||
// 只对顶级路由(lastRouter 为 false)确保路径以 "/" 开头
|
||||
// 子路由的路径会在 filterChildren 中处理
|
||||
route.path = '/' + route.path
|
||||
}
|
||||
|
||||
if (route.component) {
|
||||
// 检查是否有外部链接
|
||||
if (route.meta && route.meta.link && (route.meta.link.startsWith('http://') || route.meta.link.startsWith('https://'))) {
|
||||
route.component = InnerLink
|
||||
}
|
||||
// Layout ParentView 组件特殊处理
|
||||
else if (route.component === 'Layout') {
|
||||
route.component = Layout
|
||||
} else if (route.component === 'ParentView') {
|
||||
route.component = ParentView
|
||||
} else if (route.component === 'InnerLink') {
|
||||
route.component = InnerLink
|
||||
} else {
|
||||
const component = loadView(route.component)
|
||||
if (!component) {
|
||||
console.warn('找不到组件:', route.component)
|
||||
}
|
||||
route.component = component || (() => import('@/views/error/404.vue'))
|
||||
}
|
||||
}
|
||||
if (route.children != null && route.children && route.children.length) {
|
||||
// 如果 type 为 true,先使用 filterChildren 处理子路由路径
|
||||
if (type) {
|
||||
route.children = filterChildren(route.children, route)
|
||||
}
|
||||
// 然后递归处理子路由
|
||||
route.children = filterAsyncRouter(route.children, route, type)
|
||||
} else {
|
||||
delete route['children']
|
||||
delete route['redirect']
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
function filterChildren(childrenMap, lastRouter = false) {
|
||||
var children = []
|
||||
childrenMap.forEach(el => {
|
||||
if (lastRouter) {
|
||||
// 处理子路由路径:如果子路由路径以 / 开头,需要去掉,因为要拼接父路由路径
|
||||
let childPath = el.path || ''
|
||||
if (childPath.startsWith('/')) {
|
||||
childPath = childPath.substring(1)
|
||||
}
|
||||
// 拼接父路由路径和子路由路径
|
||||
const parentPath = lastRouter.path || ''
|
||||
// 确保父路径以 / 结尾(如果父路径不是根路径)
|
||||
const separator = parentPath.endsWith('/') ? '' : '/'
|
||||
el.path = parentPath + separator + childPath
|
||||
}
|
||||
// 确保最终路径以 / 开头(如果不是空路径)
|
||||
if (el.path && !el.path.startsWith('/')) {
|
||||
el.path = '/' + el.path
|
||||
}
|
||||
if (el.children && el.children.length && el.component === 'ParentView') {
|
||||
children = children.concat(filterChildren(el.children, el))
|
||||
} else {
|
||||
children.push(el)
|
||||
}
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
// 动态路由遍历,验证是否具备权限
|
||||
export function filterDynamicRoutes(routes) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
if (route.permissions) {
|
||||
if (auth.hasPermiOr(route.permissions)) {
|
||||
res.push(route)
|
||||
}
|
||||
} else if (route.roles) {
|
||||
if (auth.hasRoleOr(route.roles)) {
|
||||
res.push(route)
|
||||
}
|
||||
} else {
|
||||
// 没有权限控制的路由,直接通过
|
||||
res.push(route)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const loadView = (view) => {
|
||||
if (!view) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 处理外部链接
|
||||
if (view.startsWith('http://') || view.startsWith('https://')) {
|
||||
return null
|
||||
}
|
||||
|
||||
let res = null;
|
||||
for (const path in modules) {
|
||||
const dir = path.split('views/')[1]?.split('.vue')[0];
|
||||
if (dir && dir === view) {
|
||||
res = () => modules[path]();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
console.warn(`loadView: 找不到组件 "${view}",请检查路径是否正确`)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export default usePermissionStore
|
||||
|
||||
|
|
|
|||
|
|
@ -1,228 +1,234 @@
|
|||
/**
|
||||
* 通用js方法封装处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
// 日期格式化
|
||||
export function parseTime(time, pattern) {
|
||||
if (arguments.length === 0 || !time) {
|
||||
return null
|
||||
}
|
||||
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
let date
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||
time = parseInt(time)
|
||||
} else if (typeof time === 'string') {
|
||||
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
|
||||
}
|
||||
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||
time = time * 1000
|
||||
}
|
||||
date = new Date(time)
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
}
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key]
|
||||
// Note: getDay() returns 0 on Sunday
|
||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value
|
||||
}
|
||||
return value || 0
|
||||
})
|
||||
return time_str
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
export function resetForm(refName) {
|
||||
if (this.$refs[refName]) {
|
||||
this.$refs[refName].resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
// 添加日期范围
|
||||
export function addDateRange(params, dateRange, propName) {
|
||||
let search = params;
|
||||
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
|
||||
dateRange = Array.isArray(dateRange) ? dateRange : [];
|
||||
if (typeof (propName) === 'undefined') {
|
||||
search.params['beginTime'] = dateRange[0];
|
||||
search.params['endTime'] = dateRange[1];
|
||||
} else {
|
||||
search.params['begin' + propName] = dateRange[0];
|
||||
search.params['end' + propName] = dateRange[1];
|
||||
}
|
||||
return search;
|
||||
}
|
||||
|
||||
// 回显数据字典
|
||||
export function selectDictLabel(datas, value) {
|
||||
if (value === undefined) {
|
||||
return "";
|
||||
}
|
||||
var actions = [];
|
||||
Object.keys(datas).some((key) => {
|
||||
if (datas[key].value == ('' + value)) {
|
||||
actions.push(datas[key].label);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
if (actions.length === 0) {
|
||||
actions.push(value);
|
||||
}
|
||||
return actions.join('');
|
||||
}
|
||||
|
||||
// 回显数据字典(字符串、数组)
|
||||
export function selectDictLabels(datas, value, separator) {
|
||||
if (value === undefined || value.length ===0) {
|
||||
return "";
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value = value.join(",");
|
||||
}
|
||||
var actions = [];
|
||||
var currentSeparator = undefined === separator ? "," : separator;
|
||||
var temp = value.split(currentSeparator);
|
||||
Object.keys(value.split(currentSeparator)).some((val) => {
|
||||
var match = false;
|
||||
Object.keys(datas).some((key) => {
|
||||
if (datas[key].value == ('' + temp[val])) {
|
||||
actions.push(datas[key].label + currentSeparator);
|
||||
match = true;
|
||||
}
|
||||
})
|
||||
if (!match) {
|
||||
actions.push(temp[val] + currentSeparator);
|
||||
}
|
||||
})
|
||||
return actions.join('').substring(0, actions.join('').length - 1);
|
||||
}
|
||||
|
||||
// 字符串格式化(%s )
|
||||
export function sprintf(str) {
|
||||
var args = arguments, flag = true, i = 1;
|
||||
str = str.replace(/%s/g, function () {
|
||||
var arg = args[i++];
|
||||
if (typeof arg === 'undefined') {
|
||||
flag = false;
|
||||
return '';
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
return flag ? str : '';
|
||||
}
|
||||
|
||||
// 转换字符串,undefined,null等转化为""
|
||||
export function parseStrEmpty(str) {
|
||||
if (!str || str == "undefined" || str == "null") {
|
||||
return "";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// 数据合并
|
||||
export function mergeRecursive(source, target) {
|
||||
for (var p in target) {
|
||||
try {
|
||||
if (target[p].constructor == Object) {
|
||||
source[p] = mergeRecursive(source[p], target[p]);
|
||||
} else {
|
||||
source[p] = target[p];
|
||||
}
|
||||
} catch (e) {
|
||||
source[p] = target[p];
|
||||
}
|
||||
}
|
||||
return source;
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造树型结构数据
|
||||
* @param {*} data 数据源
|
||||
* @param {*} id id字段 默认 'id'
|
||||
* @param {*} parentId 父节点字段 默认 'parentId'
|
||||
* @param {*} children 孩子节点字段 默认 'children'
|
||||
*/
|
||||
export function handleTree(data, id, parentId, children) {
|
||||
let config = {
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
childrenList: children || 'children'
|
||||
};
|
||||
|
||||
var childrenListMap = {};
|
||||
var tree = [];
|
||||
for (let d of data) {
|
||||
let id = d[config.id];
|
||||
childrenListMap[id] = d;
|
||||
if (!d[config.childrenList]) {
|
||||
d[config.childrenList] = [];
|
||||
}
|
||||
}
|
||||
|
||||
for (let d of data) {
|
||||
let parentId = d[config.parentId]
|
||||
let parentObj = childrenListMap[parentId]
|
||||
if (!parentObj) {
|
||||
tree.push(d);
|
||||
} else {
|
||||
parentObj[config.childrenList].push(d)
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
* @param {*} params 参数
|
||||
*/
|
||||
export function tansParams(params) {
|
||||
let result = ''
|
||||
for (const propName of Object.keys(params)) {
|
||||
const value = params[propName];
|
||||
var part = encodeURIComponent(propName) + "=";
|
||||
if (value !== null && value !== "" && typeof (value) !== "undefined") {
|
||||
if (typeof value === 'object') {
|
||||
for (const key of Object.keys(value)) {
|
||||
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
|
||||
let params = propName + '[' + key + ']';
|
||||
var subPart = encodeURIComponent(params) + "=";
|
||||
result += subPart + encodeURIComponent(value[key]) + "&";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += part + encodeURIComponent(value) + "&";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 返回项目路径
|
||||
export function getNormalPath(p) {
|
||||
if (p.length === 0 || !p || p == 'undefined') {
|
||||
return p
|
||||
};
|
||||
let res = p.replace('//', '/')
|
||||
if (res[res.length - 1] === '/') {
|
||||
return res.slice(0, res.length - 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 验证是否为blob格式
|
||||
export function blobValidate(data) {
|
||||
return data.type !== 'application/json'
|
||||
}
|
||||
/**
|
||||
* 通用js方法封装处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
// 日期格式化
|
||||
export function parseTime(time, pattern) {
|
||||
if (arguments.length === 0 || !time) {
|
||||
return null
|
||||
}
|
||||
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||
let date
|
||||
if (typeof time === 'object') {
|
||||
date = time
|
||||
} else {
|
||||
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||
time = parseInt(time)
|
||||
} else if (typeof time === 'string') {
|
||||
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
|
||||
}
|
||||
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||
time = time * 1000
|
||||
}
|
||||
date = new Date(time)
|
||||
}
|
||||
const formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
}
|
||||
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||
let value = formatObj[key]
|
||||
// Note: getDay() returns 0 on Sunday
|
||||
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value
|
||||
}
|
||||
return value || 0
|
||||
})
|
||||
return time_str
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
export function resetForm(refName) {
|
||||
if (this.$refs[refName]) {
|
||||
this.$refs[refName].resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
// 添加日期范围
|
||||
export function addDateRange(params, dateRange, propName) {
|
||||
let search = params;
|
||||
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
|
||||
dateRange = Array.isArray(dateRange) ? dateRange : [];
|
||||
if (typeof (propName) === 'undefined') {
|
||||
search.params['beginTime'] = dateRange[0];
|
||||
search.params['endTime'] = dateRange[1];
|
||||
} else {
|
||||
search.params['begin' + propName] = dateRange[0];
|
||||
search.params['end' + propName] = dateRange[1];
|
||||
}
|
||||
return search;
|
||||
}
|
||||
|
||||
// 回显数据字典
|
||||
export function selectDictLabel(datas, value) {
|
||||
if (value === undefined) {
|
||||
return "";
|
||||
}
|
||||
var actions = [];
|
||||
Object.keys(datas).some((key) => {
|
||||
if (datas[key].value == ('' + value)) {
|
||||
actions.push(datas[key].label);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
if (actions.length === 0) {
|
||||
actions.push(value);
|
||||
}
|
||||
return actions.join('');
|
||||
}
|
||||
|
||||
// 回显数据字典(字符串、数组)
|
||||
export function selectDictLabels(datas, value, separator) {
|
||||
if (value === undefined || value.length ===0) {
|
||||
return "";
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value = value.join(",");
|
||||
}
|
||||
var actions = [];
|
||||
var currentSeparator = undefined === separator ? "," : separator;
|
||||
var temp = value.split(currentSeparator);
|
||||
Object.keys(value.split(currentSeparator)).some((val) => {
|
||||
var match = false;
|
||||
Object.keys(datas).some((key) => {
|
||||
if (datas[key].value == ('' + temp[val])) {
|
||||
actions.push(datas[key].label + currentSeparator);
|
||||
match = true;
|
||||
}
|
||||
})
|
||||
if (!match) {
|
||||
actions.push(temp[val] + currentSeparator);
|
||||
}
|
||||
})
|
||||
return actions.join('').substring(0, actions.join('').length - 1);
|
||||
}
|
||||
|
||||
// 字符串格式化(%s )
|
||||
export function sprintf(str) {
|
||||
var args = arguments, flag = true, i = 1;
|
||||
str = str.replace(/%s/g, function () {
|
||||
var arg = args[i++];
|
||||
if (typeof arg === 'undefined') {
|
||||
flag = false;
|
||||
return '';
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
return flag ? str : '';
|
||||
}
|
||||
|
||||
// 转换字符串,undefined,null等转化为""
|
||||
export function parseStrEmpty(str) {
|
||||
if (!str || str == "undefined" || str == "null") {
|
||||
return "";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// 数据合并
|
||||
export function mergeRecursive(source, target) {
|
||||
for (var p in target) {
|
||||
try {
|
||||
if (target[p].constructor == Object) {
|
||||
source[p] = mergeRecursive(source[p], target[p]);
|
||||
} else {
|
||||
source[p] = target[p];
|
||||
}
|
||||
} catch (e) {
|
||||
source[p] = target[p];
|
||||
}
|
||||
}
|
||||
return source;
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造树型结构数据
|
||||
* @param {*} data 数据源
|
||||
* @param {*} id id字段 默认 'id'
|
||||
* @param {*} parentId 父节点字段 默认 'parentId'
|
||||
* @param {*} children 孩子节点字段 默认 'children'
|
||||
*/
|
||||
export function handleTree(data, id, parentId, children) {
|
||||
// 检查 data 是否为数组
|
||||
if (!data || !Array.isArray(data)) {
|
||||
console.warn('handleTree: 期望数组,但收到:', data)
|
||||
return []
|
||||
}
|
||||
|
||||
let config = {
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
childrenList: children || 'children'
|
||||
};
|
||||
|
||||
var childrenListMap = {};
|
||||
var tree = [];
|
||||
for (let d of data) {
|
||||
let id = d[config.id];
|
||||
childrenListMap[id] = d;
|
||||
if (!d[config.childrenList]) {
|
||||
d[config.childrenList] = [];
|
||||
}
|
||||
}
|
||||
|
||||
for (let d of data) {
|
||||
let parentId = d[config.parentId]
|
||||
let parentObj = childrenListMap[parentId]
|
||||
if (!parentObj) {
|
||||
tree.push(d);
|
||||
} else {
|
||||
parentObj[config.childrenList].push(d)
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
* @param {*} params 参数
|
||||
*/
|
||||
export function tansParams(params) {
|
||||
let result = ''
|
||||
for (const propName of Object.keys(params)) {
|
||||
const value = params[propName];
|
||||
var part = encodeURIComponent(propName) + "=";
|
||||
if (value !== null && value !== "" && typeof (value) !== "undefined") {
|
||||
if (typeof value === 'object') {
|
||||
for (const key of Object.keys(value)) {
|
||||
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
|
||||
let params = propName + '[' + key + ']';
|
||||
var subPart = encodeURIComponent(params) + "=";
|
||||
result += subPart + encodeURIComponent(value[key]) + "&";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += part + encodeURIComponent(value) + "&";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 返回项目路径
|
||||
export function getNormalPath(p) {
|
||||
if (p.length === 0 || !p || p == 'undefined') {
|
||||
return p
|
||||
};
|
||||
let res = p.replace('//', '/')
|
||||
if (res[res.length - 1] === '/') {
|
||||
return res.slice(0, res.length - 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 验证是否为blob格式
|
||||
export function blobValidate(data) {
|
||||
return data.type !== 'application/json'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@
|
|||
</template>
|
||||
|
||||
<script setup name="Menu">
|
||||
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu";
|
||||
import { addMenu, delMenu, getMenu, listMenu, treeselect, updateMenu } from "@/api/system/menu";
|
||||
import SvgIcon from "@/components/SvgIcon";
|
||||
import IconSelect from "@/components/IconSelect";
|
||||
|
||||
|
|
@ -333,7 +333,7 @@ function getList() {
|
|||
/** 查询菜单下拉树结构 */
|
||||
function getTreeselect() {
|
||||
menuOptions.value = [];
|
||||
listMenu().then(response => {
|
||||
treeselect().then(response => {
|
||||
const menu = { menuId: 0, menuName: "主类目", children: [] };
|
||||
menu.children = proxy.handleTree(response.data, "menuId");
|
||||
menuOptions.value.push(menu);
|
||||
|
|
|
|||
Loading…
Reference in New Issue