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[]; memberIds?: string[];
userRoleIds?: string[]; userRoleIds?: string[];
projectIds?: string[]; projectIds?: string[];
memberId?: string;
} }
// 添加组织成员到项目 // 添加组织成员到项目
export interface BatchAddProjectModel { export interface BatchAddProjectModel {

View File

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

View File

@ -1,10 +1,10 @@
<template> <template>
<MsDrawer <MsDrawer
:width="680" :width="800"
:visible="currentVisible" :visible="currentVisible"
unmount-on-close unmount-on-close
:footer="false" :footer="false"
:title="t('system.organization.addMember')" :title="t('system.memberList')"
:mask="false" :mask="false"
@cancel="handleCancel" @cancel="handleCancel"
> >
@ -35,6 +35,37 @@
`(${t('common.admin')})` `(${t('common.admin')})`
}}</span> }}</span>
</template> </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 }"> <template #operation="{ record }">
<MsRemoveButton <MsRemoveButton
v-permission="['ORGANIZATION_PROJECT:READ+DELETE_MEMBER']" v-permission="['ORGANIZATION_PROJECT:READ+DELETE_MEMBER']"
@ -62,14 +93,20 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; 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 MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import MsSelect from '@/components/business/ms-select';
import AddUserModal from './addUserModal.vue'; import AddUserModal from './addUserModal.vue';
import { addOrUpdateProjectMember, getProjectUserGroup } from '@/api/modules/project-management/projectMember';
import { deleteProjectMemberByOrg, postProjectMemberByProjectId } from '@/api/modules/setting/organizationAndProject'; import { deleteProjectMemberByOrg, postProjectMemberByProjectId } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { formatPhoneNumber } from '@/utils'; import { formatPhoneNumber } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { LinkList } from '@/models/setting/member';
import type { UserListItem } from '@/models/setting/user';
export interface projectDrawerProps { export interface projectDrawerProps {
visible: boolean; visible: boolean;
organizationId?: string; organizationId?: string;
@ -98,11 +135,18 @@
showTooltip: true, showTooltip: true,
width: 200, width: 200,
}, },
{
title: 'system.user.tableColumnUserGroup',
dataIndex: 'userRoleList',
slotName: 'userGroup',
isTag: true,
width: 300,
},
{ {
title: 'system.organization.email', title: 'system.organization.email',
dataIndex: 'email', dataIndex: 'email',
width: 180,
showTooltip: true, showTooltip: true,
width: 200,
}, },
{ {
title: 'system.organization.phone', title: 'system.organization.phone',
@ -129,6 +173,8 @@
return { return {
...record, ...record,
phone: formatPhoneNumber(record.phone || ''), phone: formatPhoneNumber(record.phone || ''),
selectUserGroupVisible: false,
selectUserGroupLoading: false,
}; };
} }
); );
@ -147,6 +193,44 @@
await loadList(); 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 = () => { const handleAddMember = () => {
userVisible.value = true; userVisible.value = true;
}; };
@ -185,6 +269,7 @@
currentVisible.value = visible; currentVisible.value = visible;
if (visible) { if (visible) {
fetchData(); fetchData();
getUserGroupOptions();
} }
} }
); );
@ -195,4 +280,12 @@
height: 100vh !important; height: 100vh !important;
border: 1px solid red; 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> </style>

View File

@ -34,7 +34,11 @@
</MsButton> </MsButton>
</div> </div>
</template> </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> </a-tooltip>
</template> </template>
<template #creator="{ record }"> <template #creator="{ record }">

View File

@ -15,7 +15,7 @@
> >
</div> </div>
</template> </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> </a-tooltip>
</template> </template>
<template #creator="{ record }"> <template #creator="{ record }">

View File

@ -15,7 +15,7 @@
> >
</div> </div>
</template> </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> </a-tooltip>
</template> </template>
<template #creator="{ record }"> <template #creator="{ record }">

View File

@ -1,12 +1,12 @@
<template> <template>
<ms-drawer <ms-drawer
:mask="false" :mask="false"
:width="680" :width="800"
:visible="currentVisible" :visible="currentVisible"
unmount-on-close unmount-on-close
:footer="false" :footer="false"
class="ms-drawer-no-mask" class="ms-drawer-no-mask"
:title="t('system.organization.addMemberTitle')" :title="t('system.memberList')"
@cancel="handleCancel" @cancel="handleCancel"
> >
<div> <div>
@ -36,6 +36,37 @@
`(${t('common.admin')})` `(${t('common.admin')})`
}}</span> }}</span>
</template> </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 }"> <template #operation="{ record }">
<MsRemoveButton <MsRemoveButton
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+DELETE_MEMBER']" v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+DELETE_MEMBER']"
@ -64,9 +95,13 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; 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 MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import MsSelect from '@/components/business/ms-select';
import AddUserModal from './addUserModal.vue'; import AddUserModal from './addUserModal.vue';
import { addOrUpdateProjectMember, getProjectUserGroup } from '@/api/modules/project-management/projectMember';
import { addOrUpdate, getGlobalUserGroup } from '@/api/modules/setting/member';
import { import {
deleteUserFromOrgOrProject, deleteUserFromOrgOrProject,
postUserTableByOrgIdOrProjectId, postUserTableByOrgIdOrProjectId,
@ -75,6 +110,9 @@
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { LinkList } from '@/models/setting/member';
import type { UserListItem } from '@/models/setting/user';
export interface projectDrawerProps { export interface projectDrawerProps {
visible: boolean; visible: boolean;
organizationId?: string; organizationId?: string;
@ -110,10 +148,17 @@
showTooltip: true, showTooltip: true,
width: 200, width: 200,
}, },
{
title: 'system.user.tableColumnUserGroup',
dataIndex: 'userRoleList',
slotName: 'userGroup',
isTag: true,
width: 300,
},
{ {
title: 'system.organization.email', title: 'system.organization.email',
dataIndex: 'email', dataIndex: 'email',
width: 200, width: 180,
showTooltip: true, showTooltip: true,
}, },
{ {
@ -136,6 +181,8 @@
(record: any) => ({ (record: any) => ({
...record, ...record,
nameTooltip: record.name + (record.adminFlag ? `(${t('common.admin')})` : ''), nameTooltip: record.name + (record.adminFlag ? `(${t('common.admin')})` : ''),
selectUserGroupVisible: false,
selectUserGroupLoading: false,
}) })
); );
@ -159,6 +206,55 @@
await loadList(); 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 = () => { const handleAddMember = () => {
userVisible.value = true; userVisible.value = true;
}; };
@ -206,7 +302,19 @@
currentVisible.value = visible; currentVisible.value = visible;
if (visible) { if (visible) {
fetchData(); fetchData();
getUserGroupOptions();
} }
} }
); );
</script> </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> <MsCard simple>
<MsTrialAlert :tip-content="t('system.authorized.orgAndProTipContent')" /> <MsTrialAlert :tip-content="t('system.authorized.orgAndProTipContent')" />
<div class="mb-4 flex items-center justify-between"> <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 <a-button
v-if="currentTable !== 'organization' || licenseStore.hasLicense()" v-if="currentTable !== 'organization' || licenseStore.hasLicense()"
v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+ADD']" v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+ADD']"
@ -15,23 +21,15 @@
}}</a-button }}</a-button
> >
</div> </div>
<div class="flex items-center"> <a-input-search
<a-input-search v-model="keyword"
v-model="keyword" :placeholder="t('system.organization.searchIndexPlaceholder')"
:placeholder="t('system.organization.searchIndexPlaceholder')" class="w-[240px]"
class="w-[240px]" allow-clear
allow-clear @press-enter="handleEnter"
@press-enter="handleEnter" @search="handleSearch"
@search="handleSearch" @clear="handleSearch('')"
@clear="handleSearch('')" ></a-input-search>
></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>
</div> </div>
<div> <div>
<SystemOrganization v-if="currentTable === 'organization'" ref="orgTableRef" :keyword="currentKeyword" /> <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.pleaseSelectAdmin': 'Please select project administrator',
'system.project.poolIsNotNull': 'Resource pool cannot be empty', 'system.project.poolIsNotNull': 'Resource pool cannot be empty',
'system.project.enterOrganization': 'Enter the organization', 'system.project.enterOrganization': 'Enter the organization',
'system.memberList': 'Member list',
}; };

View File

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