feat: 用户没有任何权限进入项目默认展示页面
This commit is contained in:
parent
c978deefc0
commit
e15e0611f7
|
@ -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}` });
|
||||||
|
}
|
||||||
|
|
|
@ -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/';
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
<slot name="page">
|
||||||
<PageLayout v-if="!props.isPreview" />
|
<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>();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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: ['*'],
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -799,7 +799,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// 点击展开
|
// 点击展开
|
||||||
const handleRowClick = (record: TableData, ev: Event) => {
|
const handleRowClick = (record: TableData) => {
|
||||||
if (record.module) {
|
if (record.module) {
|
||||||
expandChange(record);
|
expandChange(record);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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': '添加至项目',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.",
|
||||||
};
|
};
|
||||||
|
|
|
@ -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': '移除后,将失去项目权限',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue