【更新】大调整,详细如下:1、去掉了单页管理功能,所有单页可以挂载到某个菜单下面,这样打开某个单页左侧高亮它的上级菜单,就不会不知道哪个界面是哪里来的。2、增加了菜单显示隐藏功能。3、优化了路由相关,单独打开URL后可自动对应到对应的模块。4、取消了首页固定,默认设置菜单第一项则是首页,这样能有便于不同的角色锁定不同的首页。

This commit is contained in:
小诺
2023-11-24 02:07:06 +08:00
committed by 俞宝山
parent 5be1048912
commit 865d891a1b
35 changed files with 417 additions and 1430 deletions

View File

@@ -2,21 +2,21 @@
<a-list :grid="grid" :data-source="props.dataSource" :pagination="pagination" :loading="loading">
<template #renderItem="{ item, index }">
<a-list-item :key="index">
<a-badge-ribbon :text="item.badge.text" :color="item.badge.color?item.badge.color:''">
<a-badge-ribbon :text="item.badge.text" :color="item.badge.color ? item.badge.color : ''">
<a-card class="xn-card">
<template #title>
<a-tag color="orange">{{ item.title }}</a-tag>
</template>
<template #actions>
<template v-for="{ key, label, icon, color} in props.actions">
<template v-for="{ key, label, icon, color } in props.actions">
<a-tooltip :title="label">
<component :is="icon" @click="doAction(key, item)" :style="{color:color}"/>
<component :is="icon" @click="doAction(key, item)" :style="{ color: color }" />
</a-tooltip>
</template>
</template>
<a-card-meta class="xn-card-meta">
<template #avatar>
<a-avatar shape="square" :size="64" :src="item.img"/>
<a-avatar shape="square" :size="64" :src="item.img" />
</template>
<template #title>
<span class="xn-card-meta-title">{{ item.subTitle }}</span>
@@ -35,71 +35,71 @@
</template>
<script setup name="xnCardList">
import {message} from 'ant-design-vue'
import { message } from 'ant-design-vue'
const props = defineProps({
// grid布局
grid: {
type: Object,
default: () => {
return {gutter: 20, xs: 1, sm: 1, md: 2, lg: 2, xl: 3, xxl: 3, xxxl: 4}
const props = defineProps({
// grid布局
grid: {
type: Object,
default: () => {
return { gutter: 20, xs: 1, sm: 1, md: 2, lg: 2, xl: 3, xxl: 3, xxxl: 4 }
}
},
// 数据源
dataSource: {
type: Array,
required: true
},
// 分页
page: {
type: Object,
required: true
},
// 动作
actions: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
},
// 数据源
dataSource: {
type: Array,
required: true
},
// 分页
page: {
type: Object,
required: true
},
// 动作
actions: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['action', 'page-change'])
})
const emit = defineEmits(['action', 'page-change'])
// 分页参数
const {current, size, total} = toRefs(props.page)
const pagination = reactive({
onChange: current => {
emit('page-change', current)
},
current: current,
pageSize: size,
total: total
})
// 分页参数
const { current, size, total } = toRefs(props.page)
const pagination = reactive({
onChange: (current) => {
emit('page-change', current)
},
current: current,
pageSize: size,
total: total
})
// 触发 action
const doAction = (key, item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
// 触发 action
const doAction = (key, item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
}
emit('action', { key, record: item.record })
}
emit('action', {key, record: item.record})
}
</script>
<style lang="less" scoped>
.xn-card {
background: linear-gradient(141.6deg, var(--primary-1) 0%, rgba(255, 255, 255, 0) 70%);
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
.xn-card {
background: linear-gradient(141.6deg, var(--primary-1) 0%, rgba(255, 255, 255, 0) 70%);
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
.xn-card-meta {
display: flex;
align-items: center;
.xn-card-meta {
display: flex;
align-items: center;
.xn-card-meta-title {
font-size: 14px;
.xn-card-meta-title {
font-size: 14px;
}
}
}
}
</style>

View File

@@ -47,10 +47,9 @@
</div>
</template>
<template #actions>
<a-button type="link" @click="doAction(key, item)"
v-for="{ key, label, icon, color} in props.actions">
<a-button type="link" @click="doAction(key, item)" v-for="{ key, label, icon, color } in props.actions">
<template #icon>
<component :is="icon" :style="{color:color}"/>
<component :is="icon" :style="{ color: color }" />
</template>
{{ label }}
</a-button>
@@ -61,129 +60,129 @@
</template>
<script setup name="xnDataList">
import {message} from "ant-design-vue";
import { message } from 'ant-design-vue'
const props = defineProps({
// 数据源
dataSource: {
type: Array,
required: true
},
// 分页
page: {
type: Object,
required: true
},
// 动作
actions: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['title', 'action', 'page-change'])
const props = defineProps({
// 数据源
dataSource: {
type: Array,
required: true
},
// 分页
page: {
type: Object,
required: true
},
// 动作
actions: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['title', 'action', 'page-change'])
// 分页参数
const {current, size, total} = toRefs(props.page)
const pagination = reactive({
onChange: current => {
emit('page-change', current)
},
current: current,
pageSize: size,
total: total
})
// 分页参数
const { current, size, total } = toRefs(props.page)
const pagination = reactive({
onChange: (current) => {
emit('page-change', current)
},
current: current,
pageSize: size,
total: total
})
// 出发 点击标题
const clickTitle = (item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
// 出发 点击标题
const clickTitle = (item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
}
emit('title', { record: item.record })
}
emit('title', {record: item.record})
}
// 触发 action
const doAction = (key, item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
// 触发 action
const doAction = (key, item) => {
if (!item.record) {
message.error('记录参数[record]错误')
return
}
emit('action', { key, record: item.record })
}
emit('action', {key, record: item.record})
}
</script>
<style lang="less" scoped>
.xn-data-list-item {
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
padding: 12px 15px;
margin-bottom: 10px;
.xn-data-list-item {
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
padding: 12px 15px;
margin-bottom: 10px;
.xn-data-list-item-meta {
align-items: center;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
.xn-data-list-item-meta {
align-items: center;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
.xn-data-list-item-meta-title {
.xn-data-list-item-meta-title {
display: flex;
align-items: center;
.xn-data-list-item-meta-title-prefix {
padding-left: 5px;
}
a {
color: var(--text-color);
transition: all 0.3s;
padding: 0 5px;
}
.xn-data-list-item-meta-title-suffix {
display: flex;
flex: 1;
justify-content: flex-end;
}
}
}
.xn-content {
//box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
}
.xn-extra {
display: flex;
align-items: center;
.xn-data-list-item-meta-title-prefix {
padding-left: 5px;
}
a {
color: var(--text-color);
transition: all 0.3s;
padding: 0 5px;
}
.xn-data-list-item-meta-title-suffix {
display: flex;
flex: 1;
justify-content: flex-end;
}
height: 100%;
}
}
.xn-content {
//box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
}
.xn-extra {
display: flex;
align-items: center;
height: 100%;
}
}
.xn-statistics {
display: inline-block;
box-sizing: border-box;
margin: 5px 20px;
padding: 10px;
color: var(--text-color);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
&:hover {
box-shadow: var(--card-shadow);
}
.xn-statistic-title {
margin-bottom: 4px;
color: var(--text-color-secondary);
.xn-statistics {
display: inline-block;
box-sizing: border-box;
margin: 5px 20px;
padding: 10px;
color: var(--text-color);
font-size: 14px;
}
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum';
.xn-statistic-content {
color: var(--heading-color);
font-size: 15px;
&:hover {
box-shadow: var(--card-shadow);
}
.xn-statistic-title {
margin-bottom: 4px;
color: var(--text-color-secondary);
font-size: 14px;
}
.xn-statistic-content {
color: var(--heading-color);
font-size: 15px;
}
}
}
</style>

View File

@@ -25,25 +25,12 @@ const routes = {
children: []
}
],
// 默认首页个人中心
// 默认首页个人中心
menu: [
{
id: '001',
name: 'index',
path: '/index',
component: 'index/index',
meta: {
title: '首页',
type: 'menu',
icon: 'bank-outlined',
affix: true
},
children: []
},
{
id: '002',
name: 'userCenter',
path: '/userCenter',
name: 'usercenter',
path: '/usercenter',
component: 'sys/user/userCenter',
meta: {
title: '个人中心',

View File

@@ -3,7 +3,7 @@
<a-alert message="无任何菜单" type="info" :closable="false" />
</div>
<template v-for="navMenu in navMenus" :key="navMenu">
<a-menu-item v-if="!hasChildren(navMenu)" :key="navMenu.path">
<a-menu-item v-if="!hasChildren(navMenu) & !hasHidden(navMenu)" :key="navMenu.path">
<template v-if="navMenu.meta.icon" #icon>
<component :is="navMenu.meta.icon" />
</template>
@@ -12,15 +12,15 @@
:href="navMenu.path"
target="_blank"
@click.stop="() => {}"
>{{ navMenu.meta.title }}</a
>{{ navMenu.meta.title }}</a
>
<a v-else>{{ navMenu.meta.title }}</a>
</a-menu-item>
<a-sub-menu v-else :key="navMenu.path" :title="navMenu.meta.title">
<a-sub-menu v-else-if="!hasHidden(navMenu)" :key="navMenu.path" :title="navMenu.meta.title">
<template v-if="navMenu.meta.icon" #icon>
<component :is="navMenu.meta.icon" />
</template>
<NavMenu :nav-menus="navMenu.children"></NavMenu>
<NavMenu :nav-menus="navMenu.children" />
</a-sub-menu>
</template>
</template>
@@ -32,8 +32,15 @@
default: () => []
}
})
const hasChildren = (item) => {
return item.children && !item.children.every((item) => item.meta.hidden)
}
// 是否隐藏
const hasHidden = (item) => {
if (item.meta.hidden === true) {
return true
}
// 为空跟false都会显示
return false
}
</script>

View File

@@ -63,6 +63,7 @@
import XnContextMenu from '@/components/XnContextMenu/index.vue'
import { globalStore, iframeStore, keepAliveStore, viewTagsStore } from '@/store'
import { mapState, mapActions } from 'pinia'
import routerUtil from '@/utils/routerUtil'
export default {
name: 'Tags',
@@ -95,7 +96,7 @@
},
created() {
const module = this.$router.getMenu()
const indexMenu = tool.data.get('MENU') ? tool.data.get('MENU')[0].children[0].path : this.$CONFIG.DASHBOARD_URL
const indexMenu = routerUtil.getIndexMenu(module).path
// eslint-disable-next-line eqeqeq
const dashboardRoute = this.treeFind(module, (node) => node.path === indexMenu)
if (dashboardRoute) {

View File

@@ -25,6 +25,7 @@
:theme="sideTheme"
mode="inline"
@select="onSelect"
@openChange="onOpenChange"
>
<NavMenu :nav-menus="menu" />
</a-menu>
@@ -79,9 +80,14 @@
</div>
</div>
</header>
<a-menu v-model:selectedKeys="doublerowSelectedKey" :theme="sideTheme" class="snowy-doublerow-layout-menu">
<a-menu
v-model:selectedKeys="doublerowSelectedKey"
:theme="sideTheme"
class="snowy-doublerow-layout-menu"
v-for="item in menu"
:key="item.path"
>
<a-menu-item
v-for="item in menu"
:key="item.path"
style="
text-align: center;
@@ -93,8 +99,9 @@
padding: 12px 0 !important;
"
@click="showMenu(item)"
v-if="!item.meta.hidden"
>
<a v-if="item.meta && item.meta.type === 'link'" :href="item.path" target="_blank" @click.stop="() => {}"></a>
<a v-if="item.meta && item.meta.type === 'link'" :href="item.path" target="_blank" @click.stop="() => {}" />
<template #icon>
<component :is="item.meta.icon" style="padding-left: 10px" />
</template>
@@ -176,7 +183,7 @@
import TopBar from '@/layout/components/topbar.vue'
import { globalStore, keepAliveStore } from '@/store'
import { ThemeModeEnum } from '@/utils/enum'
import { useRouter, useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import tool from '@/utils/tool'
import { message } from 'ant-design-vue'
@@ -194,7 +201,6 @@
const moduleMenuShow = ref(true)
const doublerowSelectedKey = ref([])
const layoutSiderDowbleMenu = ref(true)
const currentRoute = ref()
// computed计算方法 - start
const layout = computed(() => {
return store.layout
@@ -241,42 +247,33 @@
const secondMenuSideTheme = computed(() => {
return theme.value === ThemeModeEnum.REAL_DARK ? ThemeModeEnum.DARK : ThemeModeEnum.LIGHT
})
// 转换外部链接的路由
const filterUrl = (map) => {
const newMap = []
const traverse = (maps) => {
maps &&
maps.forEach((item) => {
item.meta = item.meta ? item.meta : {}
// 处理隐藏
if (item.meta.hidden) {
return false
}
// 处理iframe
if (item.meta.type === 'iframe') {
item.path = `/i/${item.name}`
}
// 递归循环
if (item.children && item.children.length > 0) {
item.children = filterUrl(item.children)
}
newMap.push(item)
})
}
traverse(map)
return newMap
}
// 路由监听高亮
const showThis = () => {
pMenu.value = route.meta.breadcrumb ? route.meta.breadcrumb[0] : {}
// 展开的
nextTick(() => {
// 取得默认路由地址并设置展开
const active = route.meta.active || route.path
let active = route.meta.active || route.path
// 如果是目录,必须往下找
if (route.meta.type === 'catalog') {
active = traverseChild(pMenu.value.children, active).path
}
selectedKeys.value = new Array(active)
const pidKey = getParentKeys(pMenu.value.children, active)
// 判断是隐藏的路由,找其上级
if (route.meta.hidden && pidKey) {
if (pidKey.length > 1) {
selectedKeys.value = new Array(pidKey[1])
}
}
const nextTickMenu = pMenu.value.children
if (pidKey) {
const modelPidKey = getParentKeys(moduleMenu.value, route.path)
moduleMenu.value.forEach((item) => {
if (modelPidKey.includes(item.path)) {
tagSwitchModule(item.id)
}
})
const parentPath = pidKey[pidKey.length - 1]
if (layout.value === 'doublerow') {
// 这一串操作下来只为取到最上面的路由的孩子们,最后成为双排菜单的第二排
@@ -300,19 +297,17 @@
moduleMenu.value = router.getMenu()
// 获取缓存中的菜单模块是哪个
const menuModuleId = tool.data.get('SNOWY_MENU_MODULE_ID')
let initMenu = []
if (menuModuleId) {
// 防止切换一个无此应用的人
const module = router.getMenu().filter((item) => item.id === menuModuleId)
if (module.length > 0) {
initMenu = module[0].children
menu.value = module[0].children
} else {
initMenu = router.getMenu()[0].children
menu.value = router.getMenu()[0].children
}
} else {
initMenu = router.getMenu()[0].children
menu.value = router.getMenu()[0].children
}
menu.value = filterUrl(initMenu)
showThis()
onMounted(() => {
@@ -321,18 +316,9 @@
switchoverTopHeaderThemeColor()
})
watch(route, (newValue) => {
currentRoute.value = route.path
// 清理选中的
selectedKeys.value = []
showThis()
if (layoutTagsOpen.value) {
const pidKey = getParentKeys(moduleMenu.value, route.path)
moduleMenu.value.forEach((item) => {
if (pidKey.includes(item.path)) {
tagSwitchModule(item.id)
}
})
}
})
// 监听是否开启了顶栏颜色
watch(layout, (newValue) => {
@@ -400,6 +386,21 @@
}
})
}
// 菜单展开/关闭的回调
const onOpenChange = (keys) => {
if (sideUniqueOpen.value) {
// 获取最新的
const openKey = keys[keys.length - 1]
if (keys.length > 1) {
// 获取上级
openKeys.value = getParentKeys(menu.value, openKey)
} else {
openKeys.value = Array.of(openKey) // new Array(openKey);
}
} else {
openKeys.value = keys
}
}
// 获取上级keys
const getParentKeys = (data, val) => {
const traverse = (array, val) => {
@@ -422,13 +423,29 @@
const showMenu = (route) => {
pMenu.value = route
if (pMenu.value.children) {
nextMenu.value = filterUrl(pMenu.value.children)
nextMenu.value = pMenu.value.children
}
if (!route.children || route.children.length === 0) {
layoutSiderDowbleMenu.value = false
router.push({ path: route.path })
} else {
layoutSiderDowbleMenu.value = true
if (route.children) {
let hidden = 0
route.children.forEach((item) => {
if (item.meta.hidden && item.meta.hidden === true) {
hidden++
}
})
// 如果全部都隐藏了,就跳转这个,不展开另一排
if (hidden === route.children.length) {
layoutSiderDowbleMenu.value = false
router.push({ path: route.path })
} else {
layoutSiderDowbleMenu.value = true
}
} else {
layoutSiderDowbleMenu.value = false
}
}
if (layout.value === 'doublerow') {
doublerowSelectedKey.value = [route.path]
@@ -454,7 +471,7 @@
const menus = moduleMenu.value.filter((item) => item.id === id)[0].children
if (menus.length > 0) {
// 正儿八百的菜单
menu.value = filterUrl(menus)
menu.value = menus
const firstMenu = traverseChild(menu.value)
const path = firstMenu.path
// 如果是外链
@@ -472,21 +489,22 @@
}
}
// 通过标签切换应用
const tagSwitchModule = (id, path) => {
const tagSwitchModule = (id) => {
// 将此模块的唯一值加入缓存
tool.data.set('SNOWY_MENU_MODULE_ID', id)
store.setModule(id)
const menus = moduleMenu.value.filter((item) => item.id === id)[0].children
// 正儿八百的菜单
menu.value = filterUrl(menus)
menu.value = moduleMenu.value.filter((item) => item.id === id)[0].children
}
// 遍历子集获取一个path
// 遍历获取子集
const traverseChild = (menu) => {
if (menu[0].children !== undefined) {
if (menu[0] && menu[0].children !== undefined) {
if (menu[0].children.length > 0) {
return traverseChild(menu[0].children)
} else {
return menu[0]
if (menu[0].children[0] && menu[0].children[0].meta.hidden && menu[0].children[0].meta.hidden === true) {
return menu[0]
} else {
return traverseChild(menu[0].children)
}
}
} else {
return menu[0]

View File

@@ -86,8 +86,10 @@ router.beforeEach(async (to, from, next) => {
next()
return false
} else {
// 这里需要使用 localStorage 保存登录之前要访问的页面
tool.data.set('LAST_VIEWS_PATH', to.fullPath)
if (token) {
// 有token的时候才保存登录之前要访问的页面
tool.data.set('LAST_VIEWS_PATH', to.fullPath)
}
}
if (!token) {
next({
@@ -149,7 +151,28 @@ router.getMenu = () => {
const childrenApiMenu = apiMenu[0].children
apiMenu[0].children = [...userMenu, ...childrenApiMenu]
}
return apiMenu
return filterUrl(apiMenu)
}
const filterUrl = (map) => {
const newMap = []
const traverse = (maps) => {
maps &&
maps.forEach((item) => {
item.meta = item.meta ? item.meta : {}
// 处理iframe
if (item.meta.type === 'iframe') {
item.path = `/${item.name}`
}
// 递归循环
if (item.children && item.children.length > 0) {
item.children = filterUrl(item.children)
}
newMap.push(item)
})
}
traverse(map)
return newMap
}
// 转换
@@ -160,7 +183,7 @@ const filterAsyncRouter = (routerMap) => {
// 处理外部链接特殊路由
if (item.meta.type === 'iframe') {
item.meta.url = item.path
item.path = `/i/${item.name}`
item.path = `/${item.name}`
}
// MAP转路由对象
const route = {

View File

@@ -10,6 +10,7 @@
*/
import config from '@/config'
import tool from '@/utils/tool'
import routerUtil from '@/utils/routerUtil'
// 系统路由
const routes = [
@@ -17,7 +18,7 @@ const routes = [
name: 'layout',
path: '/',
component: () => import('@/layout/index.vue'),
redirect: tool.data.get('MENU') ? tool.data.get('MENU')[0].children[0].path : config.DASHBOARD_URL,
redirect: tool.data.get('MENU') ? routerUtil.getIndexMenu(tool.data.get('MENU')).path : config.DASHBOARD_URL,
children: []
},
{

View File

@@ -1,3 +1,13 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import pinyin from 'js-pinyin'
// 中文转拼音 传入仅首字母

View File

@@ -8,30 +8,30 @@
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/sys/spa/${url}`, ...arg)
/**
* 单页
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取菜单分页
spaPage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除菜单
spaDelete(data) {
return request('delete', data)
},
// 获取菜单详情
spaDetail(data) {
return request('detail', data, 'get')
// 获取第一个界面
const getIndexMenu = (menu) => {
let indexMenu = menu[0].children[0]
// 如果第一个菜单为目录,接着往下找
if (indexMenu.meta.type === 'catalog') {
indexMenu = traverseChild(menu)
}
return indexMenu
}
// 遍历进行判断,其中处理了被隐藏的
const traverseChild = (menu) => {
if (menu[0] && menu[0].children !== undefined) {
if (menu[0].children.length > 0) {
if (menu[0].children[0] && menu[0].children[0].meta.hidden && menu[0].children[0].meta.hidden === true) {
return menu[0]
} else {
return traverseChild(menu[0].children)
}
}
} else {
return menu[0]
}
}
export default {
getIndexMenu
}

View File

@@ -5,6 +5,7 @@ import router from '@/router'
import tool from '@/utils/tool'
import { message } from 'ant-design-vue'
import { useGlobalStore } from '@/store'
import routerUtil from '@/utils/routerUtil'
export const afterLogin = async (loginToken) => {
tool.data.set('TOKEN', loginToken)
@@ -16,7 +17,7 @@ export const afterLogin = async (loginToken) => {
// 获取用户的菜单
const menu = await userCenterApi.userLoginMenu()
let indexMenu = menu[0].children[0].path
let indexMenu = routerUtil.getIndexMenu(menu).path
tool.data.set('MENU', menu)
// 重置系统默认应用
tool.data.set('SNOWY_MENU_MODULE_ID', menu[0].id)
@@ -36,7 +37,8 @@ export const afterLogin = async (loginToken) => {
}
})
if (routerTag === 0) {
indexMenu = menu[0].children[0].path
// 取首页
indexMenu = routerUtil.getIndexMenu(menu).path
}
}
dictApi.dictTree().then((data) => {

View File

@@ -6,6 +6,7 @@
:destroy-on-close="true"
@close="onClose"
>
<a-alert class="mb-3" message="温馨提示:排序第一为首页!若有多个模块根据授权可见情况而变化。" type="warning" />
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
@@ -105,6 +106,15 @@
<a-button type="primary" @click="iconSelector.showIconModal(formData.icon)">选择</a-button>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否可见:" name="visible">
<a-radio-group
v-model:value="formData.visible"
button-style="solid"
:options="visibleOptions"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sortCode">
<a-input-number style="width: 100%" v-model:value="formData.sortCode" :max="100" />
@@ -121,17 +131,17 @@
</template>
<script setup>
import { required, rules } from '@/utils/formRules'
import { required } from '@/utils/formRules'
import SnowflakeId from 'snowflake-id'
import tool from '@/utils/tool'
import menuApi from '@/api/sys/resource/menuApi'
import IconSelector from '@/components/Selector/iconSelector.vue'
// 默认是关闭状态
let visible = $ref(false)
const visible = ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
const treeData = ref([])
let iconSelector = ref()
const iconSelector = ref()
// 表单数据,也就是默认给一些数据
const formData = ref({})
// 默认展开的节点(顶级)
@@ -142,13 +152,20 @@
// 打开抽屉
const onOpen = (record, module) => {
moduleId.value = module
visible = true
formData.value = {
menuType: 'MENU',
sortCode: 99
}
visible.value = true
if (record) {
formData.value = Object.assign({}, record)
formData.value = record
// 因为版本升级后该字段无参数,所以默认为可见
if (!record.visible) {
formData.value.visible = 'true'
}
} else {
formData.value = {
menuType: 'MENU',
visible: 'true',
sortCode: 99
}
formData.value = Object.assign(formData.value, record)
}
// 获取菜单树并加入顶级
const treeParam = {
@@ -168,7 +185,7 @@
// 关闭抽屉
const onClose = () => {
formRef.value.resetFields()
visible = false
visible.value = false
}
// 选择上级加载模块的选择框
const parentChange = (value) => {
@@ -197,10 +214,21 @@
path: [required('请输入路由地址')],
name: [required('请输入组件中name属性')],
module: [required('请选择模块')],
component: [required('请输入组件地址')]
component: [required('请输入组件地址')],
visible: [required('请选择是否可见')]
}
const categoryOptions = tool.dictList('MENU_TYPE')
const visibleOptions = [
{
label: '显示',
value: 'true'
},
{
label: '隐藏',
value: 'false'
}
]
// 验证并提交数据
const onSubmit = () => {
formRef.value

View File

@@ -1,189 +0,0 @@
<template>
<xn-form-container
:title="formData.id ? '编辑单页' : '增加单页'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-alert style="margin-bottom: 10px" message="温馨提示:排序第一条为首页页面!" type="warning" closable />
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="单页名称:" name="title">
<a-input v-model:value="formData.title" placeholder="请输入单页名称" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="单页类型:" name="menuType">
<a-radio-group
v-model:value="formData.menuType"
button-style="solid"
:options="categoryOptions"
option-type="button"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item name="path">
<template #label>
<a-tooltip>
<template #title>
类型为内外链条时输入https开头的链接即可https://www.xiaonuo.vip,正常路由前面必须有反斜杠!
</template>
<question-circle-outlined />
</a-tooltip>
&nbsp {{ formData.menuType === 'MENU' ? '路由地址' : 'https链接地址' }}
</template>
<a-input v-model:value="formData.path" placeholder="请输入路由地址" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12" v-if="formData.menuType === 'MENU'">
<a-form-item name="component">
<template #label>
<a-tooltip>
<template #title> 按规范可设置为代码组件文件夹名称,首字母无反斜杠哦 </template>
<question-circle-outlined />
</a-tooltip>
&nbsp 组件地址
</template>
<a-input
v-model:value="formData.component"
addon-before="src/views/"
placeholder="请输入组件地址"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12" v-if="formData.menuType === 'MENU'">
<a-form-item name="name">
<template #label>
<a-tooltip>
<template #title> 按规范可设置为代码组件文件夹名称,首字母无反斜杠哦 </template>
<question-circle-outlined />
</a-tooltip>
&nbsp 别名
</template>
<a-input
v-model:value="formData.name"
addon-before="setup name="
placeholder="请输入组件组件中name属性"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="图标:" name="icon">
<a-input
v-model:value="formData.icon"
style="width: calc(100% - 70px)"
placeholder="请选择图标"
allow-clear
/>
<a-button type="primary" @click="iconSelector.showIconModal(formData.icon)">选择</a-button>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sortCode">
<a-input-number style="width: 100%" v-model:value="formData.sortCode" :max="100" />
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
</template>
<Icon-selector ref="iconSelector" @iconCallBack="iconCallBack" />
</xn-form-container>
</template>
<script setup name="spaForm">
import { required } from '@/utils/formRules'
import IconSelector from '@/components/Selector/iconSelector.vue'
import spaApi from '@/api/sys/resource/spaApi'
import tool from '@/utils/tool'
// 默认是关闭状态
let visible = $ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
const formData = ref({})
const submitLoading = ref(false)
const iconSelector = ref()
// 打开抽屉
const onOpen = (record) => {
visible = true
formData.value = {
menuType: 'MENU',
sortCode: 99
}
if (record) {
formData.value = Object.assign({}, record)
}
}
// 关闭抽屉
const onClose = () => {
formRef.value.resetFields()
visible = false
}
// 默认要校验的
const formRules = {
title: [required('请输入菜单名称')],
menuType: [required('请选择菜单类型')],
path: [required('请输入路由地址')],
name: [required('请输入组件中name属性')],
module: [required('请选择模块')],
component: [required('请输入组件地址')]
}
// 图标选择器回调
const iconCallBack = (value) => {
formData.value.icon = value
}
let categoryOptions = tool
.dictList('MENU_TYPE')
.filter((item) => {
// 排除
if (item.value !== 'CATALOG') {
return item
}
})
.map((item) => {
return {
value: item['value'],
label: item['label'] + '页'
}
})
// 验证并提交数据
const onSubmit = () => {
formRef.value.validate().then(() => {
const param = parameterChanges(formData.value)
submitLoading.value = true
spaApi
.submitForm(param, param.id)
.then(() => {
visible = false
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
const parameterChanges = (data) => {
if (!data.component) {
return data
}
// 如果用户输入的组件path路径
if (data.component.slice(0, 1) === '/') {
data.component = data.component.slice(1)
}
return data
}
// 调用这个函数将子组件的一些数据和方法暴露出去
defineExpose({
onOpen
})
</script>

View File

@@ -1,188 +0,0 @@
<template>
<a-card :bordered="false" :body-style="{ 'padding-bottom': '0px' }" class="mb-2">
<a-form ref="formRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="名称关键词" name="searchKey">
<a-input v-model:value="searchFormState.searchKey" placeholder="请输入单页名称关键词" allow-clear />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="类型" name="menuType">
<a-select
v-model:value="searchFormState.menuType"
:options="categoryOptions"
placeholder="请选择类型"
allow-clear
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-button type="primary" html-type="submit" @click="table.refresh()">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false">
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="form.onOpen()">
<template #icon><plus-outlined /></template>
新增单页
</a-button>
<xn-batch-delete :selectedRowKeys="selectedRowKeys" @batchDelete="deleteBatchSpa" />
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'icon'">
<component :is="record.icon" />
</template>
<template v-if="column.dataIndex === 'menuType'">
<a-tag v-if="record.menuType === 'MENU'" color="blue">
{{ $TOOL.dictTypeData('MENU_TYPE', record.menuType) }}页
</a-tag>
<a-tag v-if="record.menuType === 'IFRAME'" color="purple">
{{ $TOOL.dictTypeData('MENU_TYPE', record.menuType) }}页
</a-tag>
<a-tag v-if="record.menuType === 'LINK'" color="orange">
{{ $TOOL.dictTypeData('MENU_TYPE', record.menuType) }}页
</a-tag>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="form.onOpen(record)">编辑</a>
<a-divider type="vertical" />
<a-popconfirm title="确定要删除此单页吗" @confirm="removeSpa(record)">
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="form" @successful="table.refresh(true)" />
</template>
<script setup name="sysSpa">
import spaApi from '@/api/sys/resource/spaApi'
import tool from '@/utils/tool'
import Form from './form.vue'
let searchFormState = reactive({})
const formRef = ref()
const table = ref(null)
let form = ref()
const toolConfig = { refresh: true, height: true, columnSetting: false, striped: false }
const columns = [
{
title: '单页名称',
dataIndex: 'title',
width: 260
},
{
title: '图标',
dataIndex: 'icon'
},
{
title: '类型',
dataIndex: 'menuType'
},
{
title: '路由地址',
dataIndex: 'path',
ellipsis: true,
width: 150
},
{
title: '组件',
dataIndex: 'component',
ellipsis: true,
width: 150
},
{
title: '排序',
dataIndex: 'sortCode',
sorter: true
},
{
title: '创建时间',
dataIndex: 'createTime',
ellipsis: true,
sorter: true
},
{
title: '操作',
dataIndex: 'action',
width: '180px',
align: 'center',
scopedSlots: { customRender: 'action' }
}
]
let selectedRowKeys = ref([])
// 列表选择配置
const options = {
alert: {
show: false,
clear: () => {
selectedRowKeys = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const categoryOptions = tool
.dictList('MENU_TYPE')
.filter((item) => {
// 排除
if (item.value !== 'CATALOG') {
return item
}
})
.map((item) => {
return {
value: item['value'],
label: item['label'] + '页'
}
})
// 列表数据
const loadData = (parameter) => {
return spaApi.spaPage(Object.assign(parameter, searchFormState)).then((res) => {
return res
})
}
// 重置
const reset = () => {
formRef.value.resetFields()
table.value.refresh(true)
}
// 删除
const removeSpa = (record) => {
let params = [
{
id: record.id
}
]
spaApi.spaDelete(params).then(() => {
table.value.refresh(true)
})
}
// 批量删除
const deleteBatchSpa = (params) => {
spaApi.spaDelete(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>

View File

@@ -84,7 +84,7 @@
key: 'title',
title: '菜单',
dataIndex: 'title',
width: 200
width: 240
},
{
key: 'button',

View File

@@ -88,7 +88,7 @@
key: 'title',
title: '菜单',
dataIndex: 'title',
width: 200
width: 240
},
{
key: 'button',

View File

@@ -269,7 +269,7 @@ public class MobileMenuServiceImpl extends ServiceImpl<MobileMenuMapper, MobileM
@Override
public List<Tree<String>> loginMobileMenuTree(List<String> menuIdList) {
// 获取所有的菜单和模块以及单页面列表,并按分类和排序码排序
// 获取所有的菜单和模块列表,并按分类和排序码排序
List<MobileMenu> allModuleAndMenuAndSpaList = this.list(new LambdaQueryWrapper<MobileMenu>()
.in(MobileMenu::getCategory, MobileResourceCategoryEnum.MODULE.getValue(), MobileResourceCategoryEnum.MENU.getValue())
.orderByAsc(CollectionUtil.newArrayList(MobileMenu::getCategory, MobileMenu::getSortCode)));

View File

@@ -30,10 +30,7 @@ public enum SysBuildInEnum {
BUILD_IN_ROLE_CODE("superAdmin", "超管"),
/** 系统内置模块编码 */
BUILD_IN_MODULE_CODE("system", "系统内置"),
/** 系统内置单页面编码 */
BUILD_IN_SPA_CODE("system", "系统内置");
BUILD_IN_MODULE_CODE("system", "系统内置");
private final String value;

View File

@@ -1,125 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import vip.xiaonuo.common.annotation.CommonLog;
import vip.xiaonuo.common.pojo.CommonResult;
import vip.xiaonuo.common.pojo.CommonValidList;
import vip.xiaonuo.sys.modular.resource.entity.SysSpa;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaAddParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaEditParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaIdParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaPageParam;
import vip.xiaonuo.sys.modular.resource.service.SysSpaService;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
/**
* 单页面控制器
*
* @author xuyuxiang
* @date 2022/6/27 14:14
**/
@Api(tags = "单页面控制器")
@ApiSupport(author = "SNOWY_TEAM", order = 7)
@RestController
@Validated
public class SysSpaController {
@Resource
private SysSpaService sysSpaService;
/**
* 获取单页面分页
*
* @author xuyuxiang
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 1)
@ApiOperation("获取单页面分页")
@GetMapping("/sys/spa/page")
public CommonResult<Page<SysSpa>> page(SysSpaPageParam sysSpaPageParam) {
return CommonResult.data(sysSpaService.page(sysSpaPageParam));
}
/**
* 添加单页面
*
* @author xuyuxiang
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 2)
@ApiOperation("添加单页面")
@CommonLog("添加单页面")
@PostMapping("/sys/spa/add")
public CommonResult<String> add(@RequestBody @Valid SysSpaAddParam sysSpaAddParam) {
sysSpaService.add(sysSpaAddParam);
return CommonResult.ok();
}
/**
* 编辑单页面
*
* @author xuyuxiang
* @date 2022/4/24 20:47
*/
@ApiOperationSupport(order = 3)
@ApiOperation("编辑单页面")
@CommonLog("编辑单页面")
@PostMapping("/sys/spa/edit")
public CommonResult<String> edit(@RequestBody @Valid SysSpaEditParam sysSpaEditParam) {
sysSpaService.edit(sysSpaEditParam);
return CommonResult.ok();
}
/**
* 删除单页面
*
* @author xuyuxiang
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 4)
@ApiOperation("删除单页面")
@CommonLog("删除单页面")
@PostMapping("/sys/spa/delete")
public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
CommonValidList<SysSpaIdParam> sysSpaIdParamList) {
sysSpaService.delete(sysSpaIdParamList);
return CommonResult.ok();
}
/**
* 获取单页面详情
*
* @author xuyuxiang
* @date 2022/4/24 20:00
*/
@ApiOperationSupport(order = 5)
@ApiOperation("获取单页面详情")
@GetMapping("/sys/spa/detail")
public CommonResult<SysSpa> detail(@Valid SysSpaIdParam sysSpaIdParam) {
return CommonResult.data(sysSpaService.detail(sysSpaIdParam));
}
}

View File

@@ -82,12 +82,16 @@ public class SysMenu extends CommonEntity {
@ApiModelProperty(value = "颜色", position = 12)
private String color;
/** 是否可见 */
@ApiModelProperty(value = "是否可见", position = 13)
private String visible;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 13)
@ApiModelProperty(value = "排序码", position = 14)
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 14)
@ApiModelProperty(value = "扩展信息", position = 15)
@TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
private String extJson;
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import vip.xiaonuo.common.pojo.CommonEntity;
/**
* 单页面实体
*
* @author xuyuxiang
* @date 2022/7/27 18:27
**/
@Getter
@Setter
@TableName("SYS_RESOURCE")
public class SysSpa extends CommonEntity {
/** id */
@ApiModelProperty(value = "id", position = 1)
private String id;
/** 标题 */
@ApiModelProperty(value = "标题", position = 2)
private String title;
/** 别名 */
@ApiModelProperty(value = "别名", position = 3)
private String name;
/** 编码 */
@ApiModelProperty(value = "编码", position = 4)
private String code;
/** 分类 */
@ApiModelProperty(value = "分类", position = 5)
private String category;
/** 菜单类型 */
@ApiModelProperty(value = "菜单类型", position = 6)
private String menuType;
/** 路径 */
@ApiModelProperty(value = "路径", position = 7)
private String path;
/** 组件 */
@ApiModelProperty(value = "组件", position = 8)
private String component;
/** 图标 */
@ApiModelProperty(value = "图标", position = 9)
private String icon;
/** 排序码 */
@ApiModelProperty(value = "排序码", position = 10)
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 11)
@TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
private String extJson;
}

View File

@@ -27,9 +27,6 @@ public enum SysResourceCategoryEnum {
/** 模块 */
MODULE("MODULE"),
/** 单页面 */
SPA("SPA"),
/** 菜单 */
MENU("MENU"),
@@ -43,7 +40,7 @@ public enum SysResourceCategoryEnum {
}
public static void validate(String value) {
boolean flag = MODULE.getValue().equals(value) || SPA.getValue().equals(value) || MENU.getValue().equals(value)
boolean flag = MODULE.getValue().equals(value) || MENU.getValue().equals(value)
|| BUTTON.getValue().equals(value);
if(!flag) {
throw new CommonException("不支持的资源分类:{}", value);

View File

@@ -1,25 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import vip.xiaonuo.sys.modular.resource.entity.SysSpa;
/**
* 单页面Mapper接口
*
* @author xuyuxiang
* @date 2022/4/21 18:37
**/
public interface SysSpaMapper extends BaseMapper<SysSpa> {
}

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="vip.xiaonuo.sys.modular.resource.mapper.SysSpaMapper">
</mapper>

View File

@@ -71,7 +71,11 @@ public class SysMenuAddParam {
@ApiModelProperty(value = "图标", position = 9)
private String icon;
/** 是否可见 */
@ApiModelProperty(value = "是否可见", position = 10)
private String visible;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 10)
@ApiModelProperty(value = "扩展信息", position = 11)
private String extJson;
}

View File

@@ -76,7 +76,11 @@ public class SysMenuEditParam {
@ApiModelProperty(value = "图标", position = 10)
private String icon;
/** 是否可见 */
@ApiModelProperty(value = "是否可见", position = 11)
private String visible;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 11)
@ApiModelProperty(value = "扩展信息", position = 12)
private String extJson;
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.param.spa;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 单页面添加参数
*
* @author xuyuxiang
* @date 2022/7/27 18:40
**/
@Getter
@Setter
public class SysSpaAddParam {
/** 标题 */
@ApiModelProperty(value = "标题", required = true, position = 1)
@NotBlank(message = "title不能为空")
private String title;
/** 菜单类型 */
@ApiModelProperty(value = "菜单类型", required = true, position = 2)
@NotBlank(message = "menuType不能为空")
private String menuType;
/** 别名 */
@ApiModelProperty(value = "别名", required = true, position = 3)
private String name;
/** 路径 */
@ApiModelProperty(value = "路径", required = true, position = 4)
@NotBlank(message = "path不能为空")
private String path;
/** 组件 */
@ApiModelProperty(value = "组件", required = true, position = 5)
private String component;
/** 图标 */
@ApiModelProperty(value = "图标", required = true, position = 6)
private String icon;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 7)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 8)
private String extJson;
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.param.spa;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 单页面编辑参数
*
* @author xuyuxiang
* @date 2022/7/27 18:40
**/
@Getter
@Setter
public class SysSpaEditParam {
/** id */
@ApiModelProperty(value = "id", required = true, position = 1)
@NotBlank(message = "id不能为空")
private String id;
/** 标题 */
@ApiModelProperty(value = "标题", required = true, position = 2)
@NotBlank(message = "title不能为空")
private String title;
/** 菜单类型 */
@ApiModelProperty(value = "菜单类型", required = true, position = 3)
@NotBlank(message = "menuType不能为空")
private String menuType;
/** 别名 */
@ApiModelProperty(value = "别名", required = true, position = 4)
private String name;
/** 路径 */
@ApiModelProperty(value = "路径", required = true, position = 5)
@NotBlank(message = "path不能为空")
private String path;
/** 组件 */
@ApiModelProperty(value = "组件", required = true, position = 6)
private String component;
/** 图标 */
@ApiModelProperty(value = "图标", required = true, position = 7)
private String icon;
/** 排序码 */
@ApiModelProperty(value = "排序码", required = true, position = 8)
@NotNull(message = "sortCode不能为空")
private Integer sortCode;
/** 扩展信息 */
@ApiModelProperty(value = "扩展信息", position = 9)
private String extJson;
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.param.spa;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* 单页面Id参数
*
* @author xuyuxiang
* @date 2022/7/27 18:40
**/
@Getter
@Setter
public class SysSpaIdParam {
/** id */
@ApiModelProperty(value = "id", required = true)
@NotBlank(message = "id不能为空")
private String id;
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.param.spa;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 单页面查询参数
*
* @author xuyuxiang
* @date 2022/7/27 18:40
**/
@Getter
@Setter
public class SysSpaPageParam {
/** 当前页 */
@ApiModelProperty(value = "当前页码")
private Integer current;
/** 每页条数 */
@ApiModelProperty(value = "每页条数")
private Integer size;
/** 排序字段 */
@ApiModelProperty(value = "排序字段字段驼峰名称userName")
private String sortField;
/** 排序方式 */
@ApiModelProperty(value = "排序方式升序ASCEND降序DESCEND")
private String sortOrder;
/** 名称关键词 */
@ApiModelProperty(value = "名称关键词")
private String searchKey;
}

View File

@@ -1,80 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import vip.xiaonuo.sys.modular.resource.entity.SysSpa;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaAddParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaEditParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaIdParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaPageParam;
import java.util.List;
/**
* 单页面Service接口
*
* @author xuyuxiang
* @date 2022/6/27 14:03
**/
public interface SysSpaService extends IService<SysSpa> {
/**
* 获取单页面分页
*
* @author xuyuxiang
* @date 2022/4/24 20:08
*/
Page<SysSpa> page(SysSpaPageParam sysSpaPageParam);
/**
* 添加单页面
*
* @author xuyuxiang
* @date 2022/4/24 20:48
*/
void add(SysSpaAddParam sysSpaAddParam);
/**
* 编辑单页面
*
* @author xuyuxiang
* @date 2022/4/24 21:13
*/
void edit(SysSpaEditParam sysSpaEditParam);
/**
* 删除单页面
*
* @author xuyuxiang
* @date 2022/4/24 21:18
*/
void delete(List<SysSpaIdParam> sysSpaIdParamList);
/**
* 获取单页面详情
*
* @author xuyuxiang
* @date 2022/4/24 21:18
*/
SysSpa detail(SysSpaIdParam sysSpaIdParam);
/**
* 获取单页面详情
*
* @author xuyuxiang
* @date 2022/4/24 21:18
*/
SysSpa queryEntity(String id);
}

View File

@@ -239,6 +239,11 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
boolean repeatTitle = this.count(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, sysMenu.getParentId())
.eq(SysMenu::getCategory, SysResourceCategoryEnum.MENU.getValue()).eq(SysMenu::getTitle, sysMenu.getTitle())
.ne(SysMenu::getId, sysMenu.getId())) > 0;
// 不管是哪个改为菜单,设置组件为空
if(sysMenuEditParam.getMenuType().equals(SysResourceMenuTypeEnum.MENU.getValue())) {
sysMenuEditParam.setComponent(null);
sysMenuEditParam.setName(null);
}
if(repeatTitle) {
throw new CommonException("存在重复的菜单,名称为:{}", sysMenu.getTitle());
}

View File

@@ -1,178 +0,0 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.sys.modular.resource.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
import vip.xiaonuo.common.page.CommonPageRequest;
import vip.xiaonuo.sys.core.enums.SysBuildInEnum;
import vip.xiaonuo.sys.core.enums.SysDataTypeEnum;
import vip.xiaonuo.sys.modular.resource.entity.SysSpa;
import vip.xiaonuo.sys.modular.resource.enums.SysResourceCategoryEnum;
import vip.xiaonuo.sys.modular.resource.enums.SysResourceMenuTypeEnum;
import vip.xiaonuo.sys.modular.resource.mapper.SysSpaMapper;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaAddParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaEditParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaIdParam;
import vip.xiaonuo.sys.modular.resource.param.spa.SysSpaPageParam;
import vip.xiaonuo.sys.modular.resource.service.SysSpaService;
import java.util.List;
import java.util.stream.Collectors;
/**
* 单页面Service接口实现类
*
* @author xuyuxiang
* @date 2022/6/27 14:25
**/
@Service
public class SysSpaServiceImpl extends ServiceImpl<SysSpaMapper, SysSpa> implements SysSpaService {
@Override
public Page<SysSpa> page(SysSpaPageParam sysSpaPageParam) {
QueryWrapper<SysSpa> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SysSpa::getCategory, SysResourceCategoryEnum.SPA.getValue());
if(ObjectUtil.isNotEmpty(sysSpaPageParam.getSearchKey())) {
queryWrapper.lambda().like(SysSpa::getTitle, sysSpaPageParam.getSearchKey());
}
if(ObjectUtil.isAllNotEmpty(sysSpaPageParam.getSortField(), sysSpaPageParam.getSortOrder())) {
CommonSortOrderEnum.validate(sysSpaPageParam.getSortOrder());
queryWrapper.orderBy(true, sysSpaPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
StrUtil.toUnderlineCase(sysSpaPageParam.getSortField()));
} else {
queryWrapper.lambda().orderByAsc(SysSpa::getSortCode);
}
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void add(SysSpaAddParam sysSpaAddParam) {
checkParam(sysSpaAddParam);
SysSpa sysSpa = BeanUtil.toBean(sysSpaAddParam, SysSpa.class);
boolean repeatTitle = this.count(new LambdaQueryWrapper<SysSpa>().eq(SysSpa::getCategory,
SysResourceCategoryEnum.SPA.getValue()).eq(SysSpa::getTitle, sysSpa.getTitle())) > 0;
if(repeatTitle) {
throw new CommonException("存在重复的单页面,名称为:{}", sysSpa.getTitle());
}
sysSpa.setCode(RandomUtil.randomString(10));
sysSpa.setCategory(SysResourceCategoryEnum.SPA.getValue());
this.save(sysSpa);
// 发布增加事件
CommonDataChangeEventCenter.doAddWithData(SysDataTypeEnum.RESOURCE.getValue(), JSONUtil.createArray().put(sysSpa));
}
@SuppressWarnings("all")
private void checkParam(SysSpaAddParam sysSpaAddParam) {
SysResourceMenuTypeEnum.validate(sysSpaAddParam.getMenuType());
if(SysResourceMenuTypeEnum.MENU.getValue().equals(sysSpaAddParam.getMenuType())) {
if(ObjectUtil.isEmpty(sysSpaAddParam.getName())) {
throw new CommonException("name不能为空");
}
if(ObjectUtil.isEmpty(sysSpaAddParam.getComponent())) {
throw new CommonException("component不能为空");
}
} else if(SysResourceMenuTypeEnum.IFRAME.getValue().equals(sysSpaAddParam.getMenuType()) ||
SysResourceMenuTypeEnum.LINK.getValue().equals(sysSpaAddParam.getMenuType())) {
sysSpaAddParam.setName(RandomUtil.randomNumbers(10));
sysSpaAddParam.setComponent(null);
} else {
sysSpaAddParam.setName(null);
sysSpaAddParam.setComponent(null);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void edit(SysSpaEditParam sysSpaEditParam) {
SysSpa sysSpa = this.queryEntity(sysSpaEditParam.getId());
checkParam(sysSpaEditParam);
BeanUtil.copyProperties(sysSpaEditParam, sysSpa);
boolean repeatTitle = this.count(new LambdaQueryWrapper<SysSpa>().eq(SysSpa::getCategory,
SysResourceCategoryEnum.SPA.getValue()).eq(SysSpa::getTitle, sysSpa.getTitle())
.ne(SysSpa::getId, sysSpa.getId())) > 0;
if(repeatTitle) {
throw new CommonException("存在重复的单页面,名称为:{}", sysSpa.getTitle());
}
this.updateById(sysSpa);
// 发布更新事件
CommonDataChangeEventCenter.doUpdateWithData(SysDataTypeEnum.RESOURCE.getValue(), JSONUtil.createArray().put(sysSpa));
}
@SuppressWarnings("all")
private void checkParam(SysSpaEditParam sysSpaEditParam) {
SysResourceMenuTypeEnum.validate(sysSpaEditParam.getMenuType());
if(SysResourceMenuTypeEnum.MENU.getValue().equals(sysSpaEditParam.getMenuType())) {
if(ObjectUtil.isEmpty(sysSpaEditParam.getName())) {
throw new CommonException("name不能为空");
}
if(ObjectUtil.isEmpty(sysSpaEditParam.getComponent())) {
throw new CommonException("component不能为空");
}
} else if(SysResourceMenuTypeEnum.IFRAME.getValue().equals(sysSpaEditParam.getMenuType()) ||
SysResourceMenuTypeEnum.LINK.getValue().equals(sysSpaEditParam.getMenuType())) {
sysSpaEditParam.setName(RandomUtil.randomNumbers(10));
sysSpaEditParam.setComponent(null);
} else {
sysSpaEditParam.setName(null);
sysSpaEditParam.setComponent(null);
}
}
@Override
public void delete(List<SysSpaIdParam> sysSpaIdParamList) {
List<String> sysSpaIdList = CollStreamUtil.toList(sysSpaIdParamList, SysSpaIdParam::getId);
if(ObjectUtil.isNotEmpty(sysSpaIdList)) {
boolean containsSystemSpa = this.listByIds(sysSpaIdList).stream().map(SysSpa::getCode)
.collect(Collectors.toSet()).contains(SysBuildInEnum.BUILD_IN_SPA_CODE.getValue());
if(containsSystemSpa) {
throw new CommonException("不可删除系统内置单页面");
}
// 删除
this.removeByIds(sysSpaIdList);
// 发布删除事件
CommonDataChangeEventCenter.doDeleteWithDataId(SysDataTypeEnum.RESOURCE.getValue(), sysSpaIdList);
}
}
@Override
public SysSpa detail(SysSpaIdParam sysSpaIdParam) {
return this.queryEntity(sysSpaIdParam.getId());
}
@Override
public SysSpa queryEntity(String id) {
SysSpa sysSpa = this.getById(id);
if(ObjectUtil.isEmpty(sysSpa)) {
throw new CommonException("单页面不存在id值为{}", id);
}
return sysSpa;
}
}

View File

@@ -50,6 +50,7 @@ import vip.xiaonuo.sys.modular.relation.service.SysRelationService;
import vip.xiaonuo.sys.modular.resource.entity.SysMenu;
import vip.xiaonuo.sys.modular.resource.entity.SysModule;
import vip.xiaonuo.sys.modular.resource.enums.SysResourceCategoryEnum;
import vip.xiaonuo.sys.modular.resource.enums.SysResourceMenuTypeEnum;
import vip.xiaonuo.sys.modular.resource.service.SysMenuService;
import vip.xiaonuo.sys.modular.resource.service.SysModuleService;
import vip.xiaonuo.sys.modular.role.entity.SysRole;
@@ -356,8 +357,11 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
new TreeNode<>(sysMenu.getId(), sysMenu.getParentId(),
sysMenu.getTitle(), sysMenu.getSortCode())).collect(Collectors.toList());
List<Tree<String>> treeList = TreeUtil.build(treeNodeList, "-1");
sysMenuList.forEach(sysMenu -> {
boolean isLeafMenu = this.getChildListById(sysMenuList, sysMenu.getId(), false).size() == 0;
// 排除菜单中的目录
boolean isLeafMenu = !"0".equals(sysMenu.getId())
&& !sysMenu.getMenuType().equals(SysResourceMenuTypeEnum.CATALOG.getValue());
if(isLeafMenu) {
SysRoleGrantResourceTreeResult.SysRoleGrantResourceMenuResult sysRoleGrantResourceMenuResult =
new SysRoleGrantResourceTreeResult.SysRoleGrantResourceMenuResult();

View File

@@ -108,6 +108,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.stream.Collectors;
/**
@@ -591,15 +593,13 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
SysRelationCategoryEnum.SYS_ROLE_HAS_RESOURCE.getValue()));
}
// 获取所有的菜单和模块以及单页面列表,并按分类和排序码排序
// 获取所有的菜单和模块列表,并按分类和排序码排序
List<SysMenu> allModuleAndMenuAndSpaList = sysMenuService.list(new LambdaQueryWrapper<SysMenu>()
.in(SysMenu::getCategory, SysResourceCategoryEnum.MODULE.getValue(), SysResourceCategoryEnum.MENU.getValue(),
SysResourceCategoryEnum.SPA.getValue()).orderByAsc(CollectionUtil.newArrayList(SysMenu::getCategory,
SysMenu::getSortCode)));
.in(SysMenu::getCategory, SysResourceCategoryEnum.MODULE.getValue(), SysResourceCategoryEnum.MENU.getValue())
.orderByAsc(CollectionUtil.newArrayList(SysMenu::getCategory,SysMenu::getSortCode)));
// 全部以菜单承载
List<SysMenu> allModuleList = CollectionUtil.newArrayList();
List<SysMenu> allMenuList = CollectionUtil.newArrayList();
List<SysMenu> allSpaList = CollectionUtil.newArrayList();
// 根据类型抽取
allModuleAndMenuAndSpaList.forEach(sysMenu -> {
boolean isModule = sysMenu.getCategory().equals(SysResourceCategoryEnum.MODULE.getValue());
@@ -612,11 +612,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 抽取所有的菜单列表
allMenuList.add(sysMenu);
}
boolean isSpa = sysMenu.getCategory().equals(SysResourceCategoryEnum.SPA.getValue());
if (isSpa) {
// 抽取所有的单页面列表
allSpaList.add(sysMenu);
}
});
// 定义结果
@@ -643,8 +638,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
if (ObjectUtil.isEmpty(moduleList)) {
// 如果系统中无模块(极端情况)
if (ObjectUtil.isEmpty(allModuleList)) {
// 如果系统中无单页面,则返回空列表
if (ObjectUtil.isEmpty(allSpaList)) {
// 如果系统中无单,则返回空列表
if (ObjectUtil.isEmpty(allMenuList)) {
return CollectionUtil.newArrayList();
} else {
// 否则构造一个模块,并添加到拥有模块
@@ -667,17 +662,11 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 获取第一个模块
SysMenu firstModule = moduleList.get(0);
// 第一个模块作为所有单页面的所属模块,并添加
List<SysMenu> spaList = allSpaList.stream().peek(sysMenu -> {
sysMenu.setParentId(firstModule.getId());
sysMenu.setModule(firstModule.getId());
}).collect(Collectors.toList());
// 获取第一个单页面的id
String firstSpaId = spaList.get(0).getId();
// 将单页面放入集合
resultList.addAll(spaList);
// 获取第一个模块下的第一个菜单
Optional<SysMenu> sysMenus = menuList.stream()
.filter(sysMenu -> sysMenu.getModule().equals(firstModule.getId()))
.findFirst()
.filter(sysMenu -> !sysMenu.getMenuType().equals(SysResourceMenuTypeEnum.CATALOG.getValue()));
// 最终处理构造meta
List<JSONObject> resultJsonObjectList = resultList.stream().map(sysMenu -> {
@@ -697,22 +686,20 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
JSONObject metaJsonObject = JSONUtil.createObj();
metaJsonObject.set("icon", sysMenu.getIcon());
metaJsonObject.set("title", sysMenu.getTitle());
metaJsonObject.set("type", sysMenu.getCategory().toLowerCase());
metaJsonObject.set("type", ObjectUtil.isEmpty(sysMenu.getMenuType())
? sysMenu.getCategory().toLowerCase() : sysMenu.getMenuType().toLowerCase());
// 如果是菜单则设置type菜单类型为小写
if (sysMenu.getCategory().equals(SysResourceCategoryEnum.MENU.getValue())) {
if (!sysMenu.getMenuType().equals(SysResourceMenuTypeEnum.CATALOG.getValue())) {
metaJsonObject.set("type", sysMenu.getMenuType().toLowerCase());
// 如果设置了不可见那么设置为false为了兼容已有所以只是false的为不显示
if (ObjectUtil.isNotEmpty(sysMenu.getVisible()) && sysMenu.getVisible().equals("false")) {
metaJsonObject.set("hidden", true);
}
}
}
// 如果是单页面
if (sysMenu.getCategory().equals(SysResourceCategoryEnum.SPA.getValue())) {
metaJsonObject.set("type", SysResourceCategoryEnum.MENU.getValue().toLowerCase());
if (sysMenu.getId().equals(firstSpaId)) {
// 如果是首页第一个单页面则设置affix
if (sysMenu.getId().equals(sysMenus.orElse(null).getId())) {
// 如果是首页则设置affix
metaJsonObject.set("affix", true);
} else {
// 否则隐藏该单页面
metaJsonObject.set("hidden", true);
}
}
menuJsonObject.set("meta", metaJsonObject);