feat: 用户没有任何权限进入项目默认展示页面

This commit is contained in:
RubyLiu 2024-01-30 18:58:50 +08:00 committed by 刘瑞斌
parent c978deefc0
commit e15e0611f7
17 changed files with 159 additions and 27 deletions

View File

@ -164,3 +164,17 @@ export function postUpdateEnableFake(data: FakeTableOperationParams) {
export function getDeleteFake(data: FakeTableOperationParams) { export function getDeleteFake(data: FakeTableOperationParams) {
return MSR.post<FakeTableListItem[]>({ url: Url.getFakeTableDeleteUrl, data }); return MSR.post<FakeTableListItem[]>({ url: Url.getFakeTableDeleteUrl, data });
} }
// JIRA插件key校验
export function validateJIRAKey(data: object, pluginId: string) {
return MSR.post<FakeTableListItem[]>({ url: `${Url.postValidateJiraKeyUrl}${pluginId}`, data });
}
// 缺陷管理-获取同步信息
export function getBugSyncInfo(projectId: string) {
return MSR.get<FakeTableListItem[]>({ url: `${Url.getBugSyncInfoUrl}${projectId}` });
}
// 用例管理-获取关联需求信息
export function getCaseRelatedInfo(projectId: string) {
return MSR.get<FakeTableListItem[]>({ url: `${Url.getCaseRelatedInfoUrl}${projectId}` });
}

View File

@ -25,3 +25,9 @@ export const postFakeTableAddUrl = '/fake/error/add';
export const postFakeTableUpdateUrl = '/fake/error/update'; export const postFakeTableUpdateUrl = '/fake/error/update';
// 误报规则列表启用或禁用 // 误报规则列表启用或禁用
export const postFakeTableEnableUrl = '/fake/error/update/enable'; export const postFakeTableEnableUrl = '/fake/error/update/enable';
// JIRAKEY 校验
export const postValidateJiraKeyUrl = '/project/application/validate/';
// 缺陷管理-获取同步信息
export const getBugSyncInfoUrl = '/project/application/bug/sync/info/';
// 用例管理-获取关联需求信息
export const getCaseRelatedInfoUrl = '/project/application/case/related/info/';

View File

@ -4,6 +4,7 @@
:placeholder="t('project.menu.pleaseInputJiraKey')" :placeholder="t('project.menu.pleaseInputJiraKey')"
v-bind="attrs" v-bind="attrs"
@change="(v: string) => emit('update:modelValue', v)" @change="(v: string) => emit('update:modelValue', v)"
@blur="handleBlur"
/> />
<div class="flex flex-row items-center gap-[10px] text-[12px] leading-[16px]"> <div class="flex flex-row items-center gap-[10px] text-[12px] leading-[16px]">
<span class="text-[var(--color-text-4)]">{{ t('project.menu.howGetJiraKey') }}</span> <span class="text-[var(--color-text-4)]">{{ t('project.menu.howGetJiraKey') }}</span>
@ -20,6 +21,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { validateJIRAKey } from '@/api/modules/project-management/menuManagement';
import { getLogo } from '@/api/modules/setting/serviceIntegration'; import { getLogo } from '@/api/modules/setting/serviceIntegration';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -44,6 +48,17 @@
previewIcon.value = URL.createObjectURL(new Blob([data])); previewIcon.value = URL.createObjectURL(new Blob([data]));
}); });
}); });
const handleBlur = async () => {
const pluginId = sessionStorage.getItem('platformKey') || '';
if (pluginId) {
try {
await validateJIRAKey({ name: '1231' }, pluginId);
Message.success('common.validateSuccess');
} catch {
Message.error('common.validateFaild');
}
}
};
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -34,7 +34,7 @@
</template> </template>
<TopMenu /> <TopMenu />
</div> </div>
<ul v-if="!props.isPreview" class="right-side"> <ul v-if="!props.isPreview && !props.hideRight" class="right-side">
<li> <li>
<a-tooltip :content="t('settings.navbar.search')"> <a-tooltip :content="t('settings.navbar.search')">
<a-button type="secondary"> <a-button type="secondary">
@ -142,6 +142,7 @@
isPreview?: boolean; isPreview?: boolean;
logo?: string; logo?: string;
name?: string; name?: string;
hideRight?: boolean;
}>(); }>();
const appStore = useAppStore(); const appStore = useAppStore();

View File

@ -1,7 +1,12 @@
<template> <template>
<a-layout class="layout" :class="{ mobile: appStore.hideMenu }"> <a-layout class="layout" :class="{ mobile: appStore.hideMenu }">
<div v-if="navbar" class="layout-navbar z-[100]"> <div v-if="navbar" class="layout-navbar z-[100]">
<NavBar :is-preview="innerProps.isPreview" :logo="innerLogo" :name="innerName" /> <NavBar
:is-preview="innerProps.isPreview"
:hide-right="innerProps.hideRight"
:logo="innerLogo"
:name="innerName"
/>
</div> </div>
<slot name="body"> <slot name="body">
<a-layout> <a-layout>
@ -44,7 +49,9 @@
> >
<MsBreadCrumb /> <MsBreadCrumb />
<a-layout-content> <a-layout-content>
<PageLayout v-if="!props.isPreview" /> <slot name="page">
<PageLayout v-if="!props.isPreview" />
</slot>
<slot></slot> <slot></slot>
</a-layout-content> </a-layout-content>
<Footer v-if="footer" /> <Footer v-if="footer" />
@ -76,6 +83,7 @@
logo?: string; logo?: string;
name?: string; name?: string;
singleLogo?: boolean; singleLogo?: boolean;
hideRight?: boolean;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();

View File

@ -0,0 +1,92 @@
<template>
<div class="end-item">
<DefaultLayout
:logo="pageConfig.logoPlatform[0]?.url || defaultPlatformLogo"
:name="pageConfig.platformName"
class="overflow-hidden"
hide-right
>
<template #page>
<div class="page">
<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>{{ t('common.noProject') }}</span>
</div>
<slot></slot>
</div>
</div>
</div>
</template>
</DefaultLayout>
</div>
</template>
<script lang="ts" setup>
import DefaultLayout from './default-layout.vue';
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 { t } = useI18n();
</script>
<style lang="less" scoped>
.end-item {
:deep(.arco-menu-vertical) {
.arco-menu-inner {
justify-content: end;
}
}
}
.page {
height: 100vh;
background-color: var(--color-text-fff);
.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>

View File

@ -18,9 +18,6 @@
</div> </div>
<div class="title"> <div class="title">
<span>{{ props.isProject ? t('common.noProject') : t('common.noResource') }}</span> <span>{{ props.isProject ? t('common.noProject') : t('common.noResource') }}</span>
<span class="user">
{{ adminStr }}
</span>
</div> </div>
<slot></slot> <slot></slot>
</div> </div>
@ -34,32 +31,18 @@
<script setup lang="ts"> <script setup lang="ts">
import DefaultLayout from './default-layout.vue'; import DefaultLayout from './default-layout.vue';
import { getAdminByProjectByOrg } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
const defaultPlatformLogo = `${import.meta.env.BASE_URL}images/MS-full-logo.svg`; const defaultPlatformLogo = `${import.meta.env.BASE_URL}images/MS-full-logo.svg`;
const appStore = useAppStore(); const appStore = useAppStore();
const pageConfig = ref({ ...appStore.pageConfig }); const pageConfig = ref({ ...appStore.pageConfig });
const adminStr = ref<string>('');
const props = defineProps<{ const props = defineProps<{
isProject?: boolean; isProject?: boolean;
}>(); }>();
const { t } = useI18n(); 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> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -4,11 +4,12 @@ import type { RouteRecordRaw } from 'vue-router';
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue'); export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
export const PAGE_LAYOUT = () => import('@/layout/page-layout.vue'); export const PAGE_LAYOUT = () => import('@/layout/page-layout.vue');
export const NO_PERMISSION_LAYOUT = () => import('@/layout/no-permission-layout.vue');
export const INDEX_ROUTE: RouteRecordRaw = { export const INDEX_ROUTE: RouteRecordRaw = {
path: '/index', path: '/index',
name: 'metersphereIndex', name: 'metersphereIndex',
component: DEFAULT_LAYOUT, component: NO_PERMISSION_LAYOUT,
meta: { meta: {
hideInMenu: true, hideInMenu: true,
roles: ['*'], roles: ['*'],

View File

@ -49,7 +49,7 @@ export function hasAnyPermission(permissions: string[], typeList = ['PROJECT', '
} }
function filterProject(role: UserRole, id: string) { function filterProject(role: UserRole, id: string) {
return role && role.type === 'PROJECT' && role.scopeId === id; return role && role.type === 'PROJECT' && (role.scopeId === id || role.scopeId === 'global');
} }
function filterOrganization(role: UserRole, id: string) { function filterOrganization(role: UserRole, id: string) {
return role && role.type === 'ORGANIZATION' && (role.scopeId === id || role.scopeId === 'global'); return role && role.type === 'ORGANIZATION' && (role.scopeId === id || role.scopeId === 'global');

View File

@ -121,6 +121,10 @@
import { PoolOption, SelectValue } from '@/models/projectManagement/menuManagement'; import { PoolOption, SelectValue } from '@/models/projectManagement/menuManagement';
import { MenuEnum } from '@/enums/commonEnum'; import { MenuEnum } from '@/enums/commonEnum';
defineOptions({
name: 'DefectSync',
});
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;

View File

@ -799,7 +799,7 @@
}; };
// //
const handleRowClick = (record: TableData, ev: Event) => { const handleRowClick = (record: TableData) => {
if (record.module) { if (record.module) {
expandChange(record); expandChange(record);
} }

View File

@ -1,7 +1,7 @@
export default { export default {
'organization.member.addMember': '添加成员', 'organization.member.addMember': '添加成员',
'organization.member.updateMember': '更新成员({name}', 'organization.member.updateMember': '更新成员({name}',
'organization.member.searchMember': '通过名称或邮箱搜索搜索', 'organization.member.searchMember': '通过名称或邮箱搜索',
'organization.member.remove': '移除', 'organization.member.remove': '移除',
'organization.member.edit': '编辑', 'organization.member.edit': '编辑',
'organization.member.batchActionAddProject': '添加至项目', 'organization.member.batchActionAddProject': '添加至项目',

View File

@ -142,6 +142,7 @@
} }
Message.success(t('common.removeSuccess')); Message.success(t('common.removeSuccess'));
fetchData(); fetchData();
emit('requestFetchData');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -1,6 +1,9 @@
<template> <template>
<MsCard simple> <MsCard simple>
<div class="mb-[16px] flex items-center justify-between"> <div
class="mb-[16px] flex items-center"
:class="{ 'justify-between': hasAddPermission, 'justify-end': !hasAddPermission }"
>
<a-button v-permission="['ORGANIZATION_PROJECT:READ+ADD']" type="primary" @click="showAddProject">{{ <a-button v-permission="['ORGANIZATION_PROJECT:READ+ADD']" type="primary" @click="showAddProject">{{
t('system.organization.createProject') t('system.organization.createProject')
}}</a-button> }}</a-button>
@ -124,6 +127,7 @@
const { openDeleteModal, openModal } = useModal(); const { openDeleteModal, openModal } = useModal();
const appStore = useAppStore(); const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
const hasAddPermission = computed(() => hasAnyPermission(['ORGANIZATION_PROJECT:READ+ADD']));
const hasOperationPermission = computed(() => const hasOperationPermission = computed(() =>
hasAnyPermission([ hasAnyPermission([
'ORGANIZATION_PROJECT:READ+RECOVER', 'ORGANIZATION_PROJECT:READ+RECOVER',

View File

@ -33,7 +33,7 @@
<template #operation="{ record }"> <template #operation="{ record }">
<MsRemoveButton <MsRemoveButton
:title="t('system.organization.removeName', { name: record.name })" :title="t('system.organization.removeName', { name: record.name })"
:sub-title-tip="t('system.organization.removeTip')" :sub-title-tip="props.organizationId ? t('system.organization.removeTip') : t('system.project.removeTip')"
@ok="handleRemove(record)" @ok="handleRemove(record)"
/> />
</template> </template>
@ -160,10 +160,11 @@
await deleteUserFromOrgOrProject(props.organizationId, record.id); await deleteUserFromOrgOrProject(props.organizationId, record.id);
} }
if (props.projectId) { if (props.projectId) {
await deleteUserFromOrgOrProject(props.projectId, record.id, true); await deleteUserFromOrgOrProject(props.projectId, record.id, false);
} }
Message.success(t('common.removeSuccess')); Message.success(t('common.removeSuccess'));
fetchData(); fetchData();
emit('requestFetchData');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -73,4 +73,5 @@ export default {
'system.project.createTip': 'After the project is enabled, it will be displayed in the project switching list', 'system.project.createTip': 'After the project is enabled, it will be displayed in the project switching list',
'system.project.affiliatedOrgRequired': 'Affiliated organization cannot be empty', 'system.project.affiliatedOrgRequired': 'Affiliated organization cannot be empty',
'system.project.revokeDeleteToolTip': 'The project will be deleted automatically after 30 days', 'system.project.revokeDeleteToolTip': 'The project will be deleted automatically after 30 days',
'system.project.removeTip': "Remove it, and you'll lose access to the project.",
}; };

View File

@ -68,4 +68,5 @@ export default {
'system.project.createTip': '项目启用后,将展示在项目切换列表', 'system.project.createTip': '项目启用后,将展示在项目切换列表',
'system.project.affiliatedOrgRequired': '所属组织不能为空', 'system.project.affiliatedOrgRequired': '所属组织不能为空',
'system.project.revokeDeleteToolTip': '该项目将于30 天后自动删除', 'system.project.revokeDeleteToolTip': '该项目将于30 天后自动删除',
'system.project.removeTip': '移除后,将失去项目权限',
}; };