feat(邮箱邀请): 邮箱邀请重复提示&组织/项目成员邮箱邀请

This commit is contained in:
baiqi 2024-07-08 14:20:10 +08:00 committed by 刘瑞斌
parent 6b896dabc4
commit 6f86483461
14 changed files with 158 additions and 37 deletions

View File

@ -171,7 +171,9 @@ const transform: AxiosTransform = {
throw new Error(e as unknown as string);
}
checkStatus(response?.status, msg, response?.data?.code, errorMessageMode);
return Promise.reject(response?.data?.message || error);
return Promise.reject(
response?.config?.requestOptions?.isReturnNativeResponse ? response?.data : response?.data?.message || error
);
},
};

View File

@ -6,6 +6,7 @@ import {
EditProjectMemberUrl,
GetProjectMemberListUrl,
ProjectMemberCommentOptions,
ProjectMemberInviteUrl,
ProjectMemberList,
ProjectMemberOptions,
ProjectUserGroupUrl,
@ -14,7 +15,12 @@ import {
import { ReviewUserItem } from '@/models/caseManagement/caseReview';
import type { CommonList, TableQueryParams } from '@/models/common';
import type { ActionProjectMember, ProjectMemberItem } from '@/models/projectManagement/projectAndPermission';
import type {
ActionProjectMember,
InviteMemberParams,
ProjectMemberItem,
ProjectUserOption,
} from '@/models/projectManagement/projectAndPermission';
// 获取项目成员列表
export function getProjectMemberList(data: TableQueryParams) {
@ -46,7 +52,7 @@ export function removeProjectMember(projectId: string, userId: string) {
// 获取用户组下拉
export function getProjectUserGroup(projectId: string) {
return MSR.get({ url: ProjectUserGroupUrl, params: projectId });
return MSR.get<ProjectUserOption[]>({ url: ProjectUserGroupUrl, params: projectId });
}
// 项目成员下拉选项
@ -65,3 +71,8 @@ export function getProjectMemberCommentOptions(projectId: string, keyword?: stri
params: { keyword },
});
}
// 邀请成员
export function inviteMember(data: InviteMemberParams) {
return MSR.post({ url: ProjectMemberInviteUrl, data }, { isReturnNativeResponse: true });
}

View File

@ -8,11 +8,18 @@ import {
getProjectListUrl,
getUserGroupList,
getUserList,
inviteOrgMemberUrl,
UpdateMemberUrl,
} from '@/api/requrls/setting/member';
import type { CommonList, TableQueryParams } from '@/models/common';
import type { AddOrUpdateMemberModel, BatchAddProjectModel, LinkItem, MemberItem } from '@/models/setting/member';
import type {
AddOrUpdateMemberModel,
BatchAddProjectModel,
InviteOrgMemberParams,
LinkItem,
MemberItem,
} from '@/models/setting/member';
// 获取成员列表
export function getMemberList(data: TableQueryParams) {
return MSR.post<CommonList<MemberItem>>({ url: GetMemberListUrl, data });
@ -48,3 +55,7 @@ export function getUser(organizationId: string, keyword: string) {
export function getProjectList(organizationId: string, keyword?: string) {
return MSR.get<LinkItem[]>({ url: `${getProjectListUrl}/${organizationId}`, params: { keyword } });
}
// 添加到用户组
export function inviteOrgMember(data: InviteOrgMemberParams) {
return MSR.post({ url: inviteOrgMemberUrl, data }, { isReturnNativeResponse: true });
}

View File

@ -112,7 +112,7 @@ export function getSystemProjects() {
// 邀请用户
export function inviteUser(data: InviteUserParams) {
return MSR.post({ url: InviteUserUrl, data });
return MSR.post({ url: InviteUserUrl, data }, { isReturnNativeResponse: true });
}
// 用户注册

View File

@ -8,3 +8,5 @@ export const ProjectUserGroupUrl = '/project/member/get-role/option';
export const ProjectMemberOptions = '/project/member/get-member/option';
export const ProjectMemberList = '/project/get-member/option';
export const ProjectMemberCommentOptions = '/project/member/comment/user-option'; // 项目成员-@成员下拉列表
// 项目成员-邀请成员
export const ProjectMemberInviteUrl = '/project/member/invite';

View File

@ -12,4 +12,7 @@ export const getUserGroupList = '/organization/user/role/list';
export const getUserList = '/organization/not-exist/user/list';
// 获取弹窗里边的穿梭项目列表
export const getProjectListUrl = '/organization/project/list';
export const getSystemProjectListUrl = '/system/project/list'; // 获取系统项目列表
// 获取系统项目列表
export const getSystemProjectListUrl = '/system/project/list';
// 邀请组织成员
export const inviteOrgMemberUrl = '/organization/user/invite';

View File

@ -180,7 +180,7 @@
label: t('personal.switchOrg'),
icon: () => (
<MsIcon
type="icon-icon_switch_outlined1"
type="icon-icon_switch_outlined"
class={isActiveSwitchOrg.value ? 'text-[rgb(var(--primary-5))]' : 'text-[var(--color-text-4)]'}
/>
),
@ -434,7 +434,7 @@
clearTimeout(mouseEnterTimer);
}}
>
<MsIcon type="icon-icon_switch_outlined1" class="text-[var(--color-text-4)]" />
<MsIcon type="icon-icon_switch_outlined" class="text-[var(--color-text-4)]" />
</div>
))
: ''}

View File

@ -61,3 +61,10 @@ export interface AddProjectMember {
userIds: string[] | string;
roleIds: string[] | string;
}
export interface InviteMemberParams {
inviteEmails: string[];
userRoleIds: string[];
organizationId: string;
projectId: string;
}

View File

@ -58,3 +58,9 @@ export interface LinkItem {
disabled?: boolean;
}
export type LinkList = LinkItem[];
export interface InviteOrgMemberParams {
inviteEmails: string[];
userRoleIds: string[];
organizationId: string;
}

View File

@ -5,6 +5,9 @@
<a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">
{{ t('project.member.addMember') }}
</a-button>
<a-button v-permission="['PROJECT_USER:READ+INVITE']" type="outline" class="mr-3" @click="inviteVisible = true">
{{ t('system.user.emailInvite') }}
</a-button>
</div>
<div>
<a-select v-model="roleIds" @change="changeSelect">
@ -90,6 +93,7 @@
:current-select-count="batchParams.currentSelectCount"
@add-user-group="addUserGroup"
/>
<inviteModal v-model:visible="inviteVisible" :user-group-options="userGroupOptions" range="project"></inviteModal>
</template>
<script setup lang="ts">
@ -103,6 +107,7 @@
import MsBatchModal from '@/components/business/ms-batch-modal/index.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import AddMemberModal from './addMemberModal.vue';
import inviteModal from '@/views/setting/system/components/inviteModal.vue';
import {
addOrUpdateProjectMember,
@ -306,6 +311,7 @@
loadList();
resetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
@ -433,6 +439,8 @@
editProjectMember(record);
};
const inviteVisible = ref(false);
onBeforeMount(async () => {
await initOptions();
initData();

View File

@ -7,8 +7,17 @@
class="mr-3"
type="primary"
@click="addOrEditMember('add')"
>{{ t('organization.member.addMember') }}</a-button
>
{{ t('organization.member.addMember') }}
</a-button>
<a-button
v-permission="['ORGANIZATION_MEMBER:READ+INVITE']"
type="outline"
class="mr-3"
@click="inviteVisible = true"
>
{{ t('system.user.emailInvite') }}
</a-button>
</div>
<a-input-search
v-model="keyword"
@ -114,6 +123,11 @@
@add-project="addProjectOrAddUserGroup"
@add-user-group="addProjectOrAddUserGroup"
/>
<inviteModal
v-model:visible="inviteVisible"
:user-group-options="userGroupOptions"
range="organization"
></inviteModal>
</template>
<script setup lang="ts">
@ -133,6 +147,7 @@
import MSBatchModal from '@/components/business/ms-batch-modal/index.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import AddMemberModal from './components/addMemberModal.vue';
import inviteModal from '@/views/setting/system/components/inviteModal.vue';
import {
addOrUpdate,
@ -149,7 +164,7 @@
import { hasAnyPermission } from '@/utils/permission';
import type { TableQueryParams } from '@/models/common';
import type { AddOrUpdateMemberModel, BatchAddProjectModel, LinkList, MemberItem } from '@/models/setting/member';
import type { AddOrUpdateMemberModel, LinkList, MemberItem } from '@/models/setting/member';
import { TableKeyEnum } from '@/enums/tableEnum';
const tableStore = useTableStore();
@ -293,6 +308,7 @@
initData();
resetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
deleteLoading.value = false;
@ -380,6 +396,7 @@
initData();
resetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
record.showUserSelect = false;
@ -448,6 +465,8 @@
}
};
const inviteVisible = ref(false);
onBeforeMount(() => {
initData();
getLinkList();

View File

@ -16,9 +16,10 @@
<template #second>
<div class="p-[16px]">
<div class="flex flex-row items-center justify-between">
<a-tooltip :content="currentUserGroupItem.name">
<div class="one-line-text max-w-[300px] font-medium">{{ currentUserGroupItem.name }}</div>
</a-tooltip>
<a-radio-group v-if="couldShowUser && couldShowAuth" v-model="currentTable" class="ml-[14px]" type="button">
<a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
<a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
</a-radio-group>
<div class="flex items-center">
<a-input-search
v-if="currentTable === 'user'"
@ -29,15 +30,6 @@
@search="handleSearch"
@clear="() => handleSearch('')"
></a-input-search>
<a-radio-group
v-if="couldShowUser && couldShowAuth"
v-model="currentTable"
class="ml-[14px]"
type="button"
>
<a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
<a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
</a-radio-group>
</div>
</div>
<div class="mt-[16px]">

View File

@ -8,6 +8,7 @@
>
<a-form ref="inviteFormRef" class="overflow-hidden rounded-[4px]" :model="emailForm" layout="vertical">
<a-form-item
id="emailInviteInput"
field="emails"
:label="t('system.user.inviteEmail')"
:rules="[{ required: true, message: t('system.user.createUserEmailNotNull') }]"
@ -54,17 +55,28 @@
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { inviteMember } from '@/api/modules/project-management/projectMember';
import { inviteOrgMember } from '@/api/modules/setting/member';
import { inviteUser } from '@/api/modules/setting/user';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ProjectUserOption } from '@/models/projectManagement/projectAndPermission';
import type { SystemRole } from '@/models/setting/user';
const appStore = useAppStore();
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
userGroupOptions: SystemRole[];
}>();
const props = withDefaults(
defineProps<{
visible: boolean;
userGroupOptions: (SystemRole | ProjectUserOption)[];
range?: 'system' | 'organization' | 'project';
}>(),
{
range: 'system',
}
);
const emit = defineEmits(['update:visible']);
@ -95,7 +107,13 @@
() => props.userGroupOptions,
(arr) => {
if (arr.length) {
emailForm.value.userGroup = arr.filter((e: SystemRole) => e.selected === true).map((e: SystemRole) => e.id);
if (props.range === 'system') {
emailForm.value.userGroup = (arr as SystemRole[]).filter((e) => e.selected === true).map((e) => e.id);
} else if (props.range === 'project') {
emailForm.value.userGroup = ['project_member'];
} else if (props.range === 'organization') {
emailForm.value.userGroup = ['org_member'];
}
}
}
);
@ -104,9 +122,34 @@
inviteVisible.value = false;
inviteFormRef.value?.resetFields();
emailForm.value.emails = [];
emailForm.value.userGroup = props.userGroupOptions
.filter((e: SystemRole) => e.selected === true)
.map((e: SystemRole) => e.id);
if (props.range === 'system') {
emailForm.value.userGroup = (props.userGroupOptions as SystemRole[])
.filter((e) => e.selected === true)
.map((e) => e.id);
} else if (props.range === 'project') {
emailForm.value.userGroup = ['project_member'];
} else if (props.range === 'organization') {
emailForm.value.userGroup = ['org_member'];
}
}
function handleInviteError(error: any) {
if (error?.messageDetail) {
try {
const errEmails = JSON.parse(error.messageDetail);
if (Array.isArray(errEmails)) {
const inputEmails = document.getElementById('emailInviteInput')?.querySelectorAll('.arco-input-tag-tag');
inputEmails?.forEach((input) => {
if (errEmails.includes(input.textContent)) {
input.setAttribute('style', 'border-color: rgb(var(--danger-6))');
}
});
}
} catch (_error) {
// eslint-disable-next-line no-console
console.log(_error);
}
}
}
function emailInvite() {
@ -114,14 +157,30 @@
if (!errors) {
try {
inviteLoading.value = true;
await inviteUser({
inviteEmails: emailForm.value.emails,
userRoleIds: emailForm.value.userGroup,
});
if (props.range === 'project') {
await inviteMember({
inviteEmails: emailForm.value.emails,
userRoleIds: emailForm.value.userGroup,
projectId: appStore.currentProjectId,
organizationId: appStore.currentOrgId,
});
} else if (props.range === 'organization') {
await inviteOrgMember({
inviteEmails: emailForm.value.emails,
userRoleIds: emailForm.value.userGroup,
organizationId: appStore.currentOrgId,
});
} else {
await inviteUser({
inviteEmails: emailForm.value.emails,
userRoleIds: emailForm.value.userGroup,
});
}
Message.success(t('system.user.inviteSuccess'));
inviteVisible.value = false;
} catch (error) {
console.log(error);
// eslint-disable-next-line no-console
handleInviteError(error);
} finally {
inviteLoading.value = false;
}

View File

@ -19,7 +19,7 @@
{{ t('system.user.emailInvite') }}
</a-button>
<a-button
v-permission="['SYSTEM_USER:READ+IMPORT', 'SYSTEM_USER_ROLE:READ']"
v-permission.all="['SYSTEM_USER:READ+IMPORT', 'SYSTEM_USER_ROLE:READ']"
class="mr-3"
type="outline"
@click="showImportModal"
@ -309,8 +309,8 @@
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel } from '@/components/business/ms-batch-form/types';
import MsSelect from '@/components/business/ms-select';
import inviteModal from '../components/inviteModal.vue';
import batchModal from './components/batchModal.vue';
import inviteModal from './components/inviteModal.vue';
import {
batchCreateUser,
@ -367,6 +367,7 @@
title: 'system.user.tableColumnPhone',
dataIndex: 'phone',
showDrag: true,
width: 140,
},
{
title: 'system.user.tableColumnOrg',