feat: 无权限&无资源页面开发
This commit is contained in:
parent
d30f30151f
commit
144b26f213
|
@ -10,6 +10,7 @@
|
|||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useEventListener, useWindowSize } from '@vueuse/core';
|
||||
|
||||
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
|
||||
import { saveBaseUrl } from '@/api/modules/setting/config';
|
||||
import { GetPlatformIconUrl } from '@/api/requrls/setting/config';
|
||||
// import GlobalSetting from '@/components/pure/global-setting/index.vue';
|
||||
|
@ -69,7 +70,19 @@
|
|||
const isLoginPage = route.name === 'login';
|
||||
if (isLogin && appStore.currentProjectId) {
|
||||
// 当前为登陆状态,且已经选择了项目,初始化当前项目配置
|
||||
appStore.setCurrentMenuConfig();
|
||||
try {
|
||||
const res = await getProjectInfo(appStore.currentProjectId);
|
||||
if (res.deleted || !res.enable) {
|
||||
// 如果项目被删除或者被禁用,跳转到无项目页面
|
||||
router.push(WorkbenchRouteEnum.WORKBENCH);
|
||||
return;
|
||||
}
|
||||
appStore.setCurrentMenuConfig(res.moduleIds);
|
||||
} catch (err) {
|
||||
appStore.setCurrentMenuConfig([]);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
if (isLoginPage && isLogin) {
|
||||
// 当前页面为登录页面,且已经登录,跳转到首页
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { MsUserSelectorOption } from '@/components/business/ms-user-selector/types';
|
||||
|
||||
import MSR from '@/api/http/index';
|
||||
import * as orgUrl from '@/api/requrls/setting/organizationAndProject';
|
||||
|
||||
|
@ -172,7 +174,10 @@ export function addProjectMemberByOrg(data: AddUserToOrgOrProjectParams) {
|
|||
|
||||
// 组织-获取项目下的管理员选项
|
||||
export function getAdminByProjectByOrg(organizationId: string, keyword: string) {
|
||||
return MSR.get({ url: `${orgUrl.getAdminByOrganizationOrProjectUrl}${organizationId}`, params: { keyword } });
|
||||
return MSR.get<MsUserSelectorOption[]>({
|
||||
url: `${orgUrl.getAdminByOrganizationOrProjectUrl}${organizationId}`,
|
||||
params: { keyword },
|
||||
});
|
||||
}
|
||||
|
||||
// 组织-获取成员下的成员选项
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="251" height="203" viewBox="0 0 251 203" fill="none">
|
||||
<g filter="url(#filter0_d_16553_74273)">
|
||||
<path d="M9 9C9 6.79086 10.7909 5 13 5H237.088C239.297 5 241.088 6.79086 241.088 9V185.615C241.088 187.824 239.297 189.615 237.088 189.615H13C10.7909 189.615 9 187.824 9 185.615V9Z" fill="#F9F9FE"/>
|
||||
<path d="M9.5 9C9.5 7.06701 11.067 5.5 13 5.5H237.088C239.021 5.5 240.588 7.067 240.588 9V185.615C240.588 187.548 239.021 189.115 237.088 189.115H13C11.067 189.115 9.5 187.548 9.5 185.615V9Z" stroke="white"/>
|
||||
</g>
|
||||
<path d="M10 9C10 7.34315 11.3431 6 13 6H237C238.657 6 240 7.34315 240 9V25H10V9Z" fill="#EDEDF1"/>
|
||||
<circle cx="36.4697" cy="15.1207" r="4.33734" fill="white"/>
|
||||
<circle cx="50.9277" cy="15.1207" r="4.33734" fill="white"/>
|
||||
<circle cx="22.0114" cy="15.1207" r="4.33734" fill="white"/>
|
||||
<path d="M18 36C18 34.8954 18.8954 34 20 34H230C231.105 34 232 34.8954 232 36V177C232 178.105 231.105 179 230 179H20C18.8954 179 18 178.105 18 177V36Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M114.333 81.1667C114.333 74.9995 119.333 70 125.5 70C131.667 70 136.667 74.9995 136.667 81.1667V90.5H114.333V81.1667ZM108.333 90.5V81.1667C108.333 71.6858 116.019 64 125.5 64C134.981 64 142.667 71.6858 142.667 81.1667V90.5H148.167C150.836 90.5 153 92.664 153 95.3333V120.833C153 123.503 150.836 125.667 148.167 125.667H102.833C100.164 125.667 98 123.503 98 120.833V95.3333C98 92.664 100.164 90.5 102.833 90.5H108.333ZM125.5 101.833C126.605 101.833 127.5 102.729 127.5 103.833V112.333C127.5 113.438 126.605 114.333 125.5 114.333C124.395 114.333 123.5 113.438 123.5 112.333V103.833C123.5 102.729 124.395 101.833 125.5 101.833Z" fill="#F2E9F6"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_16553_74273" x="0" y="0" width="250.088" height="202.615" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="1" operator="erode" in="SourceAlpha" result="effect1_dropShadow_16553_74273"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0 0.4 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_16553_74273"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_16553_74273" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,7 @@
|
|||
export interface MsUserSelectorOption {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
disabled?: boolean;
|
||||
[key: string]: string | number | boolean | undefined;
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
<div v-if="navbar" class="layout-navbar z-[100]">
|
||||
<NavBar :is-preview="innerProps.isPreview" :logo="innerLogo" :name="innerName" />
|
||||
</div>
|
||||
<slot name="body">
|
||||
<a-layout>
|
||||
<a-layout>
|
||||
<a-layout-sider
|
||||
|
@ -52,6 +53,7 @@
|
|||
</a-layout>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</slot>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
|
@ -73,6 +75,7 @@
|
|||
isPreview?: boolean;
|
||||
logo?: string;
|
||||
name?: string;
|
||||
singleLogo?: boolean;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div class="single-logo-layout">
|
||||
<DefaultLayout
|
||||
:logo="pageConfig.logoPlatform[0]?.url || defaultPlatformLogo"
|
||||
:name="pageConfig.platformName"
|
||||
class="overflow-hidden"
|
||||
is-preview
|
||||
>
|
||||
<template #body>
|
||||
<div class="body">
|
||||
<div class="content-wrapper">
|
||||
<div class="content">
|
||||
<div class="icon">
|
||||
<div class="icon-svg">
|
||||
<svg-icon width="232px" height="184px" name="no_resource" />
|
||||
</div>
|
||||
<div class="radius-box"></div>
|
||||
</div>
|
||||
<div class="title">
|
||||
<span>{{ props.isProject ? t('common.noProject') : t('common.noResource') }}</span>
|
||||
<span class="user">
|
||||
{{ adminStr }}
|
||||
</span>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DefaultLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from './default-layout.vue';
|
||||
|
||||
import { getAdminByProjectByOrg } from '@/api/modules/setting/organizationAndProject';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
const defaultPlatformLogo = `${import.meta.env.BASE_URL}images/MS-full-logo.svg`;
|
||||
const appStore = useAppStore();
|
||||
const pageConfig = ref({ ...appStore.pageConfig });
|
||||
const adminStr = ref<string>('');
|
||||
|
||||
const props = defineProps<{
|
||||
isProject?: boolean;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const initData = async () => {
|
||||
try {
|
||||
const res = (await getAdminByProjectByOrg(appStore.currentOrgId, '')) || [];
|
||||
adminStr.value = res.map((item) => item.name).join(';');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.single-logo-layout {
|
||||
height: 100vh;
|
||||
background-color: var(--color-text-n9);
|
||||
.body {
|
||||
margin-top: 56px;
|
||||
padding: 17px 14px 15px 18px;
|
||||
height: 100%;
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-color: var(--color-text-fff);
|
||||
.content {
|
||||
.icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
height: 218px;
|
||||
flex-direction: column;
|
||||
.icon-svg {
|
||||
z-index: 100;
|
||||
}
|
||||
.radius-box {
|
||||
position: relative;
|
||||
bottom: 83px;
|
||||
width: 355px;
|
||||
height: 117px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 355px;
|
||||
background: linear-gradient(180deg, #ededf1 0%, rgb(255 255 255 / 0%) 100%);
|
||||
}
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
color: var(--color-text-1);
|
||||
.user {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -98,4 +98,7 @@ export default {
|
|||
'common.tag': 'Tag',
|
||||
'common.success': 'Success',
|
||||
'common.fail': 'Failed',
|
||||
'common.noProject': 'No project, please contact the administrator',
|
||||
'common.noResource': 'No resource, please contact the administrator',
|
||||
'common.noSelectProject': 'No optional items available',
|
||||
};
|
||||
|
|
|
@ -101,4 +101,7 @@ export default {
|
|||
'common.tag': '标签',
|
||||
'common.success': '成功',
|
||||
'common.fail': '失败',
|
||||
'common.noProject': '暂无项目权限,请联系管理员',
|
||||
'common.noResource': '暂无资源权限,请联系管理员',
|
||||
'common.noSelectProject': '无可选项目',
|
||||
};
|
||||
|
|
|
@ -18,4 +18,10 @@ export const REDIRECT_ROUTE_NAME = 'Redirect';
|
|||
// 首页路由
|
||||
export const DEFAULT_ROUTE_NAME = 'workbench';
|
||||
|
||||
// 无资源/权限路由
|
||||
export const NO_RESOURCE_ROUTE_NAME = 'noResource';
|
||||
|
||||
// 无项目路由
|
||||
export const NO_PROJECT_ROUTE_NAME = 'noProject';
|
||||
|
||||
export const WHITE_LIST_NAME = WHITE_LIST.map((el) => el.name);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import usePermission from '@/hooks/usePermission';
|
||||
|
||||
import { NOT_FOUND, WHITE_LIST } from '../constants';
|
||||
import { NO_RESOURCE_ROUTE_NAME, WHITE_LIST } from '../constants';
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export default function setupPermissionGuard(router: Router) {
|
|||
|
||||
if (exist || permissionsAllow) {
|
||||
next();
|
||||
} else next(NOT_FOUND);
|
||||
} else next(NO_RESOURCE_ROUTE_NAME);
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createRouter, createWebHashHistory } from 'vue-router';
|
|||
import 'nprogress/nprogress.css';
|
||||
import createRouteGuard from './guard';
|
||||
import appRoutes from './routes';
|
||||
import { INVITE_ROUTE, NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
|
||||
import { INVITE_ROUTE, NO_PROJECT, NO_RESOURCE, NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
|
||||
NProgress.configure({ showSpinner: false }); // NProgress Configuration
|
||||
|
@ -27,6 +27,8 @@ const router = createRouter({
|
|||
REDIRECT_MAIN,
|
||||
NOT_FOUND_ROUTE,
|
||||
INVITE_ROUTE,
|
||||
NO_PROJECT,
|
||||
NO_RESOURCE,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
|
@ -38,3 +38,21 @@ export const INVITE_ROUTE: RouteRecordRaw = {
|
|||
hideInMenu: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const NO_RESOURCE: RouteRecordRaw = {
|
||||
path: '/no-resource',
|
||||
name: NO_RESOURCE_ROUTE_NAME,
|
||||
component: () => import('@/views/base/no-resource/index.vue'),
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const NO_PROJECT: RouteRecordRaw = {
|
||||
path: '/no-project',
|
||||
name: NO_PROJECT_ROUTE_NAME,
|
||||
component: () => import('@/views/base/no-project/index.vue'),
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ const ProjectManagement: AppRouteRecordRaw = {
|
|||
component: () => import('@/views/project-management/projectAndPermission/menuManagement/menuManagement.vue'),
|
||||
meta: {
|
||||
locale: 'project.permission.menuManagement',
|
||||
roles: ['*'],
|
||||
roles: ['PROJECT_APPLICATION_WORKSTATION:READ'],
|
||||
},
|
||||
},
|
||||
// 项目版本
|
||||
|
@ -74,7 +74,7 @@ const ProjectManagement: AppRouteRecordRaw = {
|
|||
component: () => import('@/views/project-management/projectAndPermission/userGroup/projectUserGroup.vue'),
|
||||
meta: {
|
||||
locale: 'project.permission.userGroup',
|
||||
roles: ['*'],
|
||||
roles: ['PROJECT_GROUP:READ'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -279,13 +279,8 @@ const useAppStore = defineStore('app', {
|
|||
/**
|
||||
* 设置当前项目菜单配置
|
||||
*/
|
||||
async setCurrentMenuConfig() {
|
||||
try {
|
||||
const res = await getProjectInfo(this.currentProjectId);
|
||||
this.currentMenuConfig = res.moduleIds;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
async setCurrentMenuConfig(menuConfig: string[]) {
|
||||
this.currentMenuConfig = menuConfig;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<single-logo-layout is-project>
|
||||
<div class="mt-[24px] flex items-center justify-center">
|
||||
<a-select class="w-[280px]" allow-search @change="selectProject">
|
||||
<template #arrow-icon>
|
||||
<icon-caret-down />
|
||||
</template>
|
||||
<a-tooltip v-for="project of projectList" :key="project.id" :mouse-enter-delay="500" :content="project.name">
|
||||
<a-option
|
||||
:value="project.id"
|
||||
:class="project.id === appStore.currentProjectId ? 'arco-select-option-selected' : ''"
|
||||
>
|
||||
{{ project.name }}
|
||||
</a-option>
|
||||
</a-tooltip>
|
||||
<template #empty>
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{ t('common.noSelectProject') }}
|
||||
</div>
|
||||
</template>
|
||||
</a-select>
|
||||
</div>
|
||||
</single-logo-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, Ref, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import SingleLogoLayout from '@/layout/single-logo-layout.vue';
|
||||
|
||||
import { getProjectList, switchProject } from '@/api/modules/project-management/project';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore, useUserStore } from '@/store';
|
||||
|
||||
import { SelectValue } from '@/models/projectManagement/menuManagement';
|
||||
import type { ProjectListItem } from '@/models/setting/project';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const projectList: Ref<ProjectListItem[]> = ref([]);
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
||||
async function initProjects() {
|
||||
try {
|
||||
const res = await getProjectList(appStore.getCurrentOrgId);
|
||||
projectList.value = res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function selectProject(value: SelectValue) {
|
||||
appStore.setCurrentProjectId(value as string);
|
||||
try {
|
||||
await switchProject({
|
||||
projectId: value as string,
|
||||
userId: userStore.id || '',
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initProjects();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<single-logo-layout is-project />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SingleLogoLayout from '@/layout/single-logo-layout.vue';
|
||||
</script>
|
|
@ -26,11 +26,18 @@
|
|||
</template>
|
||||
<template #operation="{ record }">
|
||||
<div class="flex flex-row flex-nowrap">
|
||||
<span v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']" class="flex flex-row">
|
||||
<MsButton class="!mr-0" @click="showAuthDrawer(record)">{{ t('project.userGroup.viewAuth') }}</MsButton>
|
||||
<a-divider v-if="!record.internal" direction="vertical" />
|
||||
<MsButton v-if="!record.internal" class="!mr-0" status="danger" @click="handleDelete(record)">{{
|
||||
t('common.delete')
|
||||
}}</MsButton>
|
||||
</span>
|
||||
<MsButton
|
||||
v-if="!record.internal"
|
||||
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']"
|
||||
class="!mr-0"
|
||||
status="danger"
|
||||
@click="handleDelete(record)"
|
||||
>{{ t('common.delete') }}</MsButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
|
|
Loading…
Reference in New Issue