feat(设置): 系统&组织-成员抽屉列表增加用户组列

This commit is contained in:
teukkk 2024-09-11 14:53:49 +08:00 committed by Craftsman
parent d80ba71338
commit e2429d0c94
10 changed files with 234 additions and 27 deletions

View File

@ -42,6 +42,7 @@ export interface AddOrUpdateMemberModel {
memberIds?: string[];
userRoleIds?: string[];
projectIds?: string[];
memberId?: string;
}
// 添加组织成员到项目
export interface BatchAddProjectModel {

View File

@ -151,6 +151,7 @@
const protocols = allProtocolList.value.filter((item) => !val.includes(item as string));
setLocalStorage(props.protocolKey, protocols);
emit('selectedProtocolsChange');
if (props.notShowOperation) return;
protocolIsEmptyVisible.value = !val.length;
}
);

View File

@ -1,10 +1,10 @@
<template>
<MsDrawer
:width="680"
:width="800"
:visible="currentVisible"
unmount-on-close
:footer="false"
:title="t('system.organization.addMember')"
:title="t('system.memberList')"
:mask="false"
@cancel="handleCancel"
>
@ -35,6 +35,37 @@
`(${t('common.admin')})`
}}</span>
</template>
<template #userGroup="{ record }">
<MsTagGroup
v-if="!record.selectUserGroupVisible"
:tag-list="record.userRoleList"
type="primary"
theme="outline"
@click="handleTagClick(record)"
/>
<MsSelect
v-else
v-model:model-value="record.userRoleList"
:placeholder="t('system.user.createUserUserGroupPlaceholder')"
:options="userGroupOptions"
:search-keys="['name']"
:loading="record.selectUserGroupLoading"
:disabled="record.selectUserGroupLoading"
:fallback-option="(val) => ({
label: (val as Record<string, any>).name,
value: val,
})"
value-key="id"
label-key="name"
class="w-full max-w-[300px]"
allow-clear
multiple
at-least-one
:object-value="true"
@popup-visible-change="(value) => handleUserGroupChange(value, record)"
>
</MsSelect>
</template>
<template #operation="{ record }">
<MsRemoveButton
v-permission="['ORGANIZATION_PROJECT:READ+DELETE_MEMBER']"
@ -62,14 +93,20 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import MsSelect from '@/components/business/ms-select';
import AddUserModal from './addUserModal.vue';
import { addOrUpdateProjectMember, getProjectUserGroup } from '@/api/modules/project-management/projectMember';
import { deleteProjectMemberByOrg, postProjectMemberByProjectId } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n';
import { formatPhoneNumber } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import type { LinkList } from '@/models/setting/member';
import type { UserListItem } from '@/models/setting/user';
export interface projectDrawerProps {
visible: boolean;
organizationId?: string;
@ -98,11 +135,18 @@
showTooltip: true,
width: 200,
},
{
title: 'system.user.tableColumnUserGroup',
dataIndex: 'userRoleList',
slotName: 'userGroup',
isTag: true,
width: 300,
},
{
title: 'system.organization.email',
dataIndex: 'email',
width: 180,
showTooltip: true,
width: 200,
},
{
title: 'system.organization.phone',
@ -129,6 +173,8 @@
return {
...record,
phone: formatPhoneNumber(record.phone || ''),
selectUserGroupVisible: false,
selectUserGroupLoading: false,
};
}
);
@ -147,6 +193,44 @@
await loadList();
};
const userGroupOptions = ref<LinkList>([]);
const getUserGroupOptions = async () => {
try {
if (props.projectId) {
userGroupOptions.value = await getProjectUserGroup(props.projectId);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};
function handleTagClick(record: UserListItem & Record<string, any>) {
if (hasAnyPermission(['ORGANIZATION_PROJECT:READ+UPDATE_MEMBER'])) {
record.selectUserGroupVisible = true;
}
}
async function handleUserGroupChange(val: boolean, record: UserListItem & Record<string, any>) {
try {
if (!val) {
record.selectUserGroupLoading = true;
if (props.projectId) {
await addOrUpdateProjectMember({
projectId: props.projectId,
userId: record.id,
roleIds: record.userRoleList.map((e) => e.id),
});
}
Message.success(t('system.user.updateUserSuccess'));
fetchData();
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
record.selectUserGroupLoading = false;
}
}
const handleAddMember = () => {
userVisible.value = true;
};
@ -185,6 +269,7 @@
currentVisible.value = visible;
if (visible) {
fetchData();
getUserGroupOptions();
}
}
);
@ -195,4 +280,12 @@
height: 100vh !important;
border: 1px solid red;
}
//
:deep(.arco-select-view) {
height: 32px;
.arco-select-view-inner {
@apply overflow-y-auto overflow-x-hidden;
.ms-scroll-bar();
}
}
</style>

View File

@ -34,7 +34,11 @@
</MsButton>
</div>
</template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
<MsIcon
v-if="record.deleted"
type="icon-icon_delete_countdown"
class="ml-[4px] text-[rgb(var(--danger-6))]"
/>
</a-tooltip>
</template>
<template #creator="{ record }">

View File

@ -15,7 +15,7 @@
>
</div>
</template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
<MsIcon v-if="record.deleted" type="icon-icon_delete_countdown" class="ml-[4px] text-[rgb(var(--danger-6))]" />
</a-tooltip>
</template>
<template #creator="{ record }">

View File

@ -15,7 +15,7 @@
>
</div>
</template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
<MsIcon v-if="record.deleted" type="icon-icon_delete_countdown" class="ml-[4px] text-[rgb(var(--danger-6))]" />
</a-tooltip>
</template>
<template #creator="{ record }">

View File

@ -1,12 +1,12 @@
<template>
<ms-drawer
:mask="false"
:width="680"
:width="800"
:visible="currentVisible"
unmount-on-close
:footer="false"
class="ms-drawer-no-mask"
:title="t('system.organization.addMemberTitle')"
:title="t('system.memberList')"
@cancel="handleCancel"
>
<div>
@ -36,6 +36,37 @@
`(${t('common.admin')})`
}}</span>
</template>
<template #userGroup="{ record }">
<MsTagGroup
v-if="!record.selectUserGroupVisible"
:tag-list="record.userRoleList"
type="primary"
theme="outline"
@click="handleTagClick(record)"
/>
<MsSelect
v-else
v-model:model-value="record.userRoleList"
:placeholder="t('system.user.createUserUserGroupPlaceholder')"
:options="userGroupOptions"
:search-keys="['name']"
:loading="record.selectUserGroupLoading"
:disabled="record.selectUserGroupLoading"
:fallback-option="(val) => ({
label: (val as Record<string, any>).name,
value: val,
})"
value-key="id"
label-key="name"
class="w-full max-w-[300px]"
allow-clear
multiple
at-least-one
:object-value="true"
@popup-visible-change="(value) => handleUserGroupChange(value, record)"
>
</MsSelect>
</template>
<template #operation="{ record }">
<MsRemoveButton
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+DELETE_MEMBER']"
@ -64,9 +95,13 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import MsSelect from '@/components/business/ms-select';
import AddUserModal from './addUserModal.vue';
import { addOrUpdateProjectMember, getProjectUserGroup } from '@/api/modules/project-management/projectMember';
import { addOrUpdate, getGlobalUserGroup } from '@/api/modules/setting/member';
import {
deleteUserFromOrgOrProject,
postUserTableByOrgIdOrProjectId,
@ -75,6 +110,9 @@
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import type { LinkList } from '@/models/setting/member';
import type { UserListItem } from '@/models/setting/user';
export interface projectDrawerProps {
visible: boolean;
organizationId?: string;
@ -110,10 +148,17 @@
showTooltip: true,
width: 200,
},
{
title: 'system.user.tableColumnUserGroup',
dataIndex: 'userRoleList',
slotName: 'userGroup',
isTag: true,
width: 300,
},
{
title: 'system.organization.email',
dataIndex: 'email',
width: 200,
width: 180,
showTooltip: true,
},
{
@ -136,6 +181,8 @@
(record: any) => ({
...record,
nameTooltip: record.name + (record.adminFlag ? `(${t('common.admin')})` : ''),
selectUserGroupVisible: false,
selectUserGroupLoading: false,
})
);
@ -159,6 +206,55 @@
await loadList();
};
const userGroupOptions = ref<LinkList>([]);
const getUserGroupOptions = async () => {
try {
if (props.organizationId) {
userGroupOptions.value = await getGlobalUserGroup(props.organizationId);
} else if (props.projectId) {
userGroupOptions.value = await getProjectUserGroup(props.projectId);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
};
function handleTagClick(record: UserListItem & Record<string, any>) {
if (hasAnyPermission(['SYSTEM_ORGANIZATION_PROJECT:READ+UPDATE_MEMBER'])) {
record.selectUserGroupVisible = true;
}
}
async function handleUserGroupChange(val: boolean, record: UserListItem & Record<string, any>) {
try {
if (!val) {
record.selectUserGroupLoading = true;
if (props.organizationId) {
await addOrUpdate(
{
organizationId: props.organizationId,
memberId: record.id,
userRoleIds: record.userRoleList.map((e) => e.id),
},
'edit'
);
} else if (props.projectId) {
await addOrUpdateProjectMember({
projectId: props.projectId,
userId: record.id,
roleIds: record.userRoleList.map((e) => e.id),
});
}
Message.success(t('system.user.updateUserSuccess'));
fetchData();
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
record.selectUserGroupLoading = false;
}
}
const handleAddMember = () => {
userVisible.value = true;
};
@ -206,7 +302,19 @@
currentVisible.value = visible;
if (visible) {
fetchData();
getUserGroupOptions();
}
}
);
</script>
<style lang="less" scoped>
//
:deep(.arco-select-view) {
height: 32px;
.arco-select-view-inner {
@apply overflow-y-auto overflow-x-hidden;
.ms-scroll-bar();
}
}
</style>

View File

@ -2,7 +2,13 @@
<MsCard simple>
<MsTrialAlert :tip-content="t('system.authorized.orgAndProTipContent')" />
<div class="mb-4 flex items-center justify-between">
<div>
<div class="flex items-center">
<a-radio-group v-model="currentTable" size="medium" class="mr-[14px]" type="button">
<a-radio value="organization">
{{ t('system.organization.organizationCount', { count: organizationCount }) }}
</a-radio>
<a-radio value="project">{{ t('system.organization.projectCount', { count: projectCount }) }}</a-radio>
</a-radio-group>
<a-button
v-if="currentTable !== 'organization' || licenseStore.hasLicense()"
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+ADD']"
@ -15,23 +21,15 @@
}}</a-button
>
</div>
<div class="flex items-center">
<a-input-search
v-model="keyword"
:placeholder="t('system.organization.searchIndexPlaceholder')"
class="w-[240px]"
allow-clear
@press-enter="handleEnter"
@search="handleSearch"
@clear="handleSearch('')"
></a-input-search>
<a-radio-group v-model="currentTable" class="ml-[14px]" type="button">
<a-radio value="organization">{{
t('system.organization.organizationCount', { count: organizationCount })
}}</a-radio>
<a-radio value="project">{{ t('system.organization.projectCount', { count: projectCount }) }}</a-radio>
</a-radio-group>
</div>
<a-input-search
v-model="keyword"
:placeholder="t('system.organization.searchIndexPlaceholder')"
class="w-[240px]"
allow-clear
@press-enter="handleEnter"
@search="handleSearch"
@clear="handleSearch('')"
></a-input-search>
</div>
<div>
<SystemOrganization v-if="currentTable === 'organization'" ref="orgTableRef" :keyword="currentKeyword" />

View File

@ -83,4 +83,5 @@ export default {
'system.project.pleaseSelectAdmin': 'Please select project administrator',
'system.project.poolIsNotNull': 'Resource pool cannot be empty',
'system.project.enterOrganization': 'Enter the organization',
'system.memberList': 'Member list',
};

View File

@ -79,4 +79,5 @@ export default {
'system.project.pleaseSelectAdmin': '请选择项目管理员',
'system.project.poolIsNotNull': '资源池不能为空',
'system.project.enterOrganization': '进入组织',
'system.memberList': '成员列表',
};