refactor(系统设置): 重构系统用户组

This commit is contained in:
RubyLiu 2023-09-08 18:20:38 +08:00 committed by 刘瑞斌
parent 409f1538a3
commit 7c12e2560d
13 changed files with 624 additions and 509 deletions

View File

@ -315,6 +315,13 @@
border: 1px solid var(--color-text-input-border); border: 1px solid var(--color-text-input-border);
} }
} }
.arco-checkbox-indeterminate .arco-checkbox-icon {
border-color: rgba(var(--primary-7));
background-color: rgba(var(--primary-1));
&::after {
background-color: rgb(var(--primary-7));
}
}
.arco-checkbox-disabled, .arco-checkbox-disabled,
.arco-checkbox-disabled:hover { .arco-checkbox-disabled:hover {
.arco-checkbox-icon { .arco-checkbox-icon {

View File

@ -73,11 +73,9 @@
font-size: 12px; font-size: 12px;
border-radius: 50%; border-radius: 50%;
color: var(--color-text-4); color: var(--color-text-4);
background-color: rgb(var(--primary-3));
} }
.dropdown-icon:hover { .dropdown-icon:hover {
color: rgb(var(--primary-5)); color: rgb(var(--primary-5));
background-color: transparent;
} }
} }
</style> </style>

View File

@ -13,11 +13,9 @@ export interface CustomMoreActionItem {
eventKey: string; eventKey: string;
name: string; name: string;
} }
export interface PopVisibleItem {
[key: string]: boolean;
}
export type RenameType = 'rename' | 'auth'; export type RenameType = 'rename' | 'auth';
export type AuthScopeType = 'SYSTEM' | 'PROJECT' | 'ORGANIZATION';
export interface UserGroupItem { export interface UserGroupItem {
// 组ID // 组ID
@ -29,7 +27,7 @@ export interface UserGroupItem {
// 是否是内置用户组 // 是否是内置用户组
internal: true; internal: true;
// 所属类型 // 所属类型
type: string; type: AuthScopeType;
createTime: number; createTime: number;
updateTime: number; updateTime: number;
// 创建人 // 创建人
@ -59,8 +57,6 @@ export interface UserGroupPermissionItem {
license: boolean; license: boolean;
} }
export type AuthScopeType = 'SYSTEM' | 'PROJECT' | 'ORGANIZATION';
// 用户组对应的权限配置 // 用户组对应的权限配置
export interface UserGroupAuthSetting { export interface UserGroupAuthSetting {
// 菜单项ID // 菜单项ID
@ -122,3 +118,15 @@ export interface UserTableItem {
deleted: boolean; deleted: boolean;
[key: string]: string | boolean | number; [key: string]: string | boolean | number;
} }
export type MoreActionType = 'rename' | 'addMember' | 'create';
export interface PopVisibleItem {
id?: string;
visible: boolean;
authScope: AuthScopeType;
defaultName: string;
}
export interface PopVisible {
[key: string]: PopVisibleItem;
}

View File

@ -82,7 +82,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { PopVisibleItem, RenameType, UserGroupItem } from '@/models/setting/usergroup'; import { RenameType, UserGroupItem } from '@/models/setting/usergroup';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import AddUserModal from './addUserModal.vue'; import AddUserModal from './addUserModal.vue';
@ -94,6 +94,10 @@
import { getOrgUserGroupList, deleteOrgUserGroup } from '@/api/modules/setting/usergroup'; import { getOrgUserGroupList, deleteOrgUserGroup } from '@/api/modules/setting/usergroup';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
interface PopVisibleItem {
[key: string]: boolean;
}
const { t } = useI18n(); const { t } = useI18n();
const store = useUserGroupStore(); const store = useUserGroupStore();

View File

@ -163,4 +163,3 @@
} }
} }
</style> </style>
@/store/modules/setting/system/usergroup

View File

@ -1,114 +0,0 @@
<template>
<a-modal
v-model:visible="currentVisible"
width="680px"
:ok-text="t('system.userGroup.create')"
unmount-on-close
@cancel="handleCancel(false)"
>
<template #title> {{ t('system.userGroup.createUserGroup') }} </template>
<div class="form">
<a-form ref="formRef" :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
required
:label="t('system.userGroup.userGroupName')"
:rules="[
{ required: true, message: t('system.userGroup.userGroupNameIsNotNone') },
{ validator: validateName },
]"
>
<a-input v-model="form.name" :placeholder="t('system.userGroup.pleaseInputUserGroupName')" />
</a-form-item>
<a-form-item
field="type"
:label="t('system.userGroup.authScope')"
:rules="[{ required: true, message: t('system.userGroup.authScopeIsNotNone') }]"
>
<a-select v-model="form.type" :placeholder="t('system.userGroup.pleaseSelectAuthScope')">
<a-option value="SYSTEM">{{ t('system.userGroup.SYSTEM') }}</a-option>
<a-option value="ORGANIZATION">{{ t('system.userGroup.ORGANIZATION') }}</a-option>
<a-option value="PROJECT">{{ t('system.userGroup.PROJECT') }}</a-option>
</a-select>
</a-form-item>
</a-form>
</div>
<template #footer>
<a-button type="secondary" :disabled="loading" @click="handleCancel(false)">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleOK">
{{ t('common.add') }}
</a-button>
</template>
</a-modal>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect } from 'vue';
import { UserGroupItem } from '@/models/setting/usergroup';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import { updateOrAddUserGroup } from '@/api/modules/setting/usergroup';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
list: UserGroupItem[];
}>();
const formRef = ref<FormInstance>();
const loading = ref(false);
const emit = defineEmits<{
(e: 'cancel', shouldSearch: boolean): void;
}>();
const form = reactive({
name: '',
type: '',
});
const currentVisible = ref(props.visible);
const validateName = (value: string, callback: (error?: string) => void) => {
if (value !== '') {
const isExist = props.list.some((item) => item.name === value);
if (isExist) {
callback(t('system.userGroup.userGroupNameIsExist', { name: value }));
}
callback();
}
};
watchEffect(() => {
currentVisible.value = props.visible;
});
const handleCancel = (shouldSearch: boolean) => {
form.name = '';
form.type = '';
emit('cancel', shouldSearch);
};
const handleOK = () => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return;
}
try {
loading.value = true;
const res = await updateOrAddUserGroup(form);
if (res) {
Message.success(t('system.userGroup.addUserGroupSuccess'));
loading.value = false;
handleCancel(true);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
loading.value = false;
}
});
};
</script>

View File

@ -0,0 +1,128 @@
<template>
<a-popover :popup-visible="currentVisible" position="bl" trigger="click" class="w-[276px]">
<template #content>
<div class="form">
<a-form
ref="formRef"
:model="form"
size="large"
layout="vertical"
:label-col-props="{ span: 0 }"
:wrapper-col-props="{ span: 24 }"
>
<a-form-item>
<div class="text-[14px] text-[var(--color-text-1)]">{{
props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup')
}}</div>
</a-form-item>
<a-form-item field="name" :rules="[{ validator: validateName }]">
<a-input
v-model="form.name"
class="w-[228px]"
:placeholder="t('system.userGroup.pleaseInputUserGroupName')"
@press-enter="handleBeforeOk"
@keyup.esc="handleCancel"
/>
</a-form-item>
</a-form>
</div>
<div class="flex flex-row flex-nowrap justify-end gap-2">
<a-button type="secondary" size="mini" :disabled="loading" @click="handleCancel">
{{ t('common.cancel') }}
</a-button>
<a-button
type="primary"
size="mini"
:loading="loading"
:disabled="form.name.length === 0"
@click="handleBeforeOk"
>
{{ props.id ? t('common.rename') : t('common.create') }}
</a-button>
</div>
</template>
<slot></slot>
</a-popover>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect } from 'vue';
import { Message } from '@arco-design/web-vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import { updateOrAddUserGroup } from '@/api/modules/setting/usergroup';
import { UserGroupItem, AuthScopeType } from '@/models/setting/usergroup';
const { t } = useI18n();
const props = defineProps<{
id?: string;
list: UserGroupItem[];
visible: boolean;
defaultName?: string;
//
authScope: AuthScopeType;
}>();
const emit = defineEmits<{
(e: 'cancel', value: boolean): void;
(e: 'submit', currentId: string): void;
}>();
const formRef = ref<FormInstance>();
const currentVisible = ref(props.visible);
const form = reactive({
name: '',
});
const loading = ref(false);
const validateName = (value: string | undefined, callback: (error?: string) => void) => {
if (value === undefined || value === '') {
callback(t('system.userGroup.userGroupNameIsNotNone'));
} else {
if (value === props.defaultName) {
callback();
} else {
const isExist = props.list.some((item) => item.name === value);
if (isExist) {
callback(t('system.userGroup.userGroupNameIsExist', { name: value }));
}
}
callback();
}
};
const handleCancel = () => {
form.name = '';
loading.value = false;
emit('cancel', false);
};
const handleBeforeOk = () => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return false;
}
try {
loading.value = true;
const res = await updateOrAddUserGroup({ id: props.id, name: form.name, type: props.authScope });
if (res) {
Message.success(
props.id ? t('system.userGroup.updateUserGroupSuccess') : t('system.userGroup.addUserGroupSuccess')
);
emit('submit', res.id);
handleCancel();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
loading.value = false;
}
});
};
watchEffect(() => {
currentVisible.value = props.visible;
form.name = props.defaultName || '';
});
</script>

View File

@ -1,241 +0,0 @@
<template>
<a-input-search
allow-clear
class="w-[252px]"
:placeholder="t('system.userGroup.searchHolder')"
@press-enter="enterData"
@search="searchData"
/>
<div class="mt-2 flex flex-col">
<div class="flex h-[38px] items-center justify-between px-[8px] leading-[24px]">
<div class="text-[var(--color-text-input-border)]"> {{ t('system.userGroup.global') }}</div>
<div class="cursor-pointer text-[rgb(var(--primary-5))]"
><icon-plus-circle-fill style="font-size: 20px" @click="addUserGroup"
/></div>
</div>
<div>
<div
v-for="element in userGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center"
:class="{ 'bg-[rgb(var(--primary-1))]': element.id === currentId }"
@click="handleListItemClick(element)"
>
<popconfirm
:visible="popVisible[element.id]"
:loading="popLoading[element.id]"
:type="popType"
:default-name="popDefaultName"
:list="userGroupList"
@cancel="() => handlePopConfirmCancel(element.id)"
@submit="(value: CustomMoreActionItem) => handlePopConfirmSubmit(value,element.id)"
>
<div class="flex grow flex-row justify-between px-[8px]">
<a-tooltip :content="element.name">
<div class="flex flex-row flex-nowrap">
<div class="one-line-text max-w-[156px] text-[var(--color-text-1)]">{{ element.name }}</div>
<div v-if="element.type" class="text-[var(--color-text-4)]"
>{{ t(`system.userGroup.${element.type}`) }}</div
>
</div>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal">
<MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" />
</div>
</div>
</popconfirm>
</div>
</div>
</div>
<AddUserModal :visible="addUserVisible" @cancel="addUserVisible = false" />
<AddUserGroupModal :list="userGroupList" :visible="addUserGroupVisible" @cancel="handleAddUserGroupModalCancel" />
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { CustomMoreActionItem, PopVisibleItem, RenameType, UserGroupItem } from '@/models/setting/usergroup';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import AddUserModal from './addUserModal.vue';
import AddUserGroupModal from './addUserGroupModal.vue';
import useModal from '@/hooks/useModal';
import { Message } from '@arco-design/web-vue';
import popconfirm from './popconfirm.vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { getUserGroupList, updateOrAddUserGroup, deleteUserGroup } from '@/api/modules/setting/usergroup';
import { characterLimit } from '@/utils';
const { t } = useI18n();
const store = useUserGroupStore();
const { openModal } = useModal();
// loading
const currentId = ref('');
const addUserVisible = ref(false);
const addUserGroupVisible = ref(false);
//
const popVisible = ref<PopVisibleItem>({});
const popLoading = ref<PopVisibleItem>({});
//
const popType = ref<RenameType>('rename');
const popDefaultName = ref('');
//
const userGroupList = ref<UserGroupItem[]>([]);
const customAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
},
];
//
const handleListItemClick = (element: UserGroupItem) => {
const { id, name, type, internal } = element;
currentId.value = id;
store.setInfo({
currentName: name,
currentTitle: type,
currentId: id,
currentType: type,
currentInternal: internal,
});
};
//
const initData = async () => {
try {
const res = await getUserGroupList();
if (res.length > 0) {
userGroupList.value = res;
handleListItemClick(res[0]);
//
const tmpObj: PopVisibleItem = {};
res.forEach((element) => {
tmpObj[element.id] = false;
});
popVisible.value = { ...tmpObj };
popLoading.value = { ...tmpObj };
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
//
const addUserGroup = () => {
// eslint-disable-next-line no-console
addUserGroupVisible.value = true;
};
//
const handleMoreAction = (item: ActionsItem, id: string) => {
if (item.eventTag !== 'delete') {
popType.value = item.eventTag as RenameType;
const tmpObj = userGroupList.value.filter((ele) => ele.id === id)[0];
popVisible.value = { ...popVisible.value, [id]: true };
if (item.eventTag === 'rename') {
popDefaultName.value = tmpObj.name;
} else {
popDefaultName.value = tmpObj.scopeId;
}
} else {
openModal({
type: 'error',
title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(store.currentName) }),
content: t('system.userGroup.beforeDeleteUserGroup'),
okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await deleteUserGroup(id);
Message.success(t('system.user.deleteUserSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
};
// confirm
const handlePopConfirmCancel = (id: string) => {
popVisible.value = { ...popVisible.value, [id]: false };
};
//
const handlePopConfirmSubmit = async (item: CustomMoreActionItem, id: string) => {
if (item.eventKey === 'rename') {
//
try {
popLoading.value = { ...popLoading.value, [id]: true };
const res = await updateOrAddUserGroup({ id, name: item.name });
if (res) {
initData();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
} finally {
popLoading.value = { ...popLoading.value, [id]: false };
}
} else {
//
try {
const res = await updateOrAddUserGroup({ id, scopeId: item.name });
if (res) {
initData();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
popVisible.value = { ...popVisible.value, [id]: false };
initData();
};
function enterData(eve: Event) {
if (!(eve.target as HTMLInputElement).value) {
initData();
return;
}
const keyword = (eve.target as HTMLInputElement).value;
const tmpArr = userGroupList.value.filter((ele) => ele.name.includes(keyword));
userGroupList.value = tmpArr;
}
function searchData(value: string) {
if (!value) {
initData();
return;
}
const keyword = value;
const tmpArr = userGroupList.value.filter((ele) => ele.name.includes(keyword));
userGroupList.value = tmpArr;
}
const handleAddUserGroupModalCancel = (shouldSearch: boolean) => {
addUserGroupVisible.value = false;
if (shouldSearch) {
initData();
}
};
onMounted(() => {
initData();
});
</script>

View File

@ -0,0 +1,454 @@
<template>
<a-input-search
class="w-[252px]"
:placeholder="t('system.userGroup.searchHolder')"
allow-clear
@press-enter="enterData"
@search="searchData"
/>
<div class="mt-2">
<CreateUserGroupPopup
:list="systemUserGroupList"
:visible="systemUserGroupVisible"
auth-scope="SYSTEM"
@cancel="systemUserGroupVisible = false"
@submit="handleCreateUserGroup"
>
<div class="flex items-center justify-between px-[4px] py-[7px]">
<div class="flex flex-row items-center gap-1 text-[var(--color-text-4)]">
<MsIcon
v-if="systemToggle"
class="cursor-pointer"
type="icon-icon_expand-down_filled"
size="12"
@click="systemToggle = false"
/>
<MsIcon
v-else
class="cursor-pointer"
type="icon-icon_expand-right_filled"
size="12"
@click="systemToggle = true"
/>
<div class="text-[14px]">
{{ t('system.userGroup.systemUserGroup') }}
</div>
</div>
<MsMoreAction :list="createSystemUGActionItem" @select="systemUserGroupVisible = true">
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction>
</div>
</CreateUserGroupPopup>
<Transition>
<div v-if="systemToggle">
<div
v-for="element in systemUserGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center py-[7px] pl-[20px] pr-[4px]"
:class="{ 'bg-[rgb(var(--primary-1))]': element.id === currentId }"
@click="handleListItemClick(element)"
>
<CreateUserGroupPopup
:list="systemUserGroupList"
v-bind="popVisible[element.id]"
@cancel="handleRenameCancel(element)"
@submit="handleRenameCancel(element, element.id)"
>
<div class="flex grow flex-row items-center justify-between">
<a-tooltip :content="element.name">
<div
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
>{{ element.name }}</div
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
</MsMoreAction>
<MsMoreAction :list="moreAction" @select="(value) => handleMoreAction(value, element.id, 'SYSTEM')">
<div class="icon-button">
<MsIcon type="icon-icon_more_outlined" size="16" />
</div>
</MsMoreAction>
</div>
</div>
</CreateUserGroupPopup>
</div>
<a-divider class="my-[0px] mt-[6px]" />
</div>
</Transition>
</div>
<div class="mt-2">
<CreateUserGroupPopup
:list="orgUserGroupList"
:visible="orgUserGroupVisible"
auth-scope="ORGANIZATION"
@cancel="orgUserGroupVisible = false"
@submit="handleCreateUserGroup"
>
<div class="flex items-center justify-between px-[4px] py-[7px]">
<div class="flex flex-row items-center gap-1 text-[var(--color-text-4)]">
<MsIcon
v-if="orgToggle"
class="cursor-pointer"
type="icon-icon_expand-down_filled"
size="12"
@click="orgToggle = false"
/>
<MsIcon
v-else
class="cursor-pointer"
type="icon-icon_expand-right_filled"
size="12"
@click="orgToggle = true"
/>
<div class="text-[14px]">
{{ t('system.userGroup.orgUserGroup') }}
</div>
</div>
<MsMoreAction :list="createOrgUGActionItem" @select="orgUserGroupVisible = true">
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction>
</div>
</CreateUserGroupPopup>
<Transition>
<div v-if="orgToggle">
<div
v-for="element in orgUserGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center py-[7px] pl-[20px] pr-[4px]"
:class="{ 'bg-[rgb(var(--primary-1))]': element.id === currentId }"
@click="handleListItemClick(element)"
>
<CreateUserGroupPopup
:list="orgUserGroupList"
v-bind="popVisible[element.id]"
@cancel="handleRenameCancel(element)"
@submit="handleRenameCancel(element, element.id)"
>
<div class="flex grow flex-row items-center justify-between">
<a-tooltip :content="element.name">
<div
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
>{{ element.name }}</div
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
</MsMoreAction>
<MsMoreAction
:list="moreAction"
@select="(value) => handleMoreAction(value, element.id, 'ORGANIZATION')"
>
<div class="icon-button">
<MsIcon type="icon-icon_more_outlined" size="16" />
</div>
</MsMoreAction>
</div>
</div>
</CreateUserGroupPopup>
</div>
<a-divider class="my-[0px] mt-[6px]" />
</div>
</Transition>
</div>
<div class="mt-2">
<CreateUserGroupPopup
:list="projectUserGroupList"
:visible="projectUserGroupVisible"
auth-scope="PROJECT"
@cancel="projectUserGroupVisible = false"
@submit="handleCreateUserGroup"
>
<div class="flex items-center justify-between px-[4px] py-[7px]">
<div class="flex flex-row items-center gap-1 text-[var(--color-text-4)]">
<MsIcon
v-if="projectToggle"
class="cursor-pointer"
type="icon-icon_expand-down_filled"
size="12"
@click="projectToggle = false"
/>
<MsIcon
v-else
class="cursor-pointer"
type="icon-icon_expand-right_filled"
size="12"
@click="projectToggle = true"
/>
<div class="text-[14px]">
{{ t('system.userGroup.projectUserGroup') }}
</div>
</div>
<MsMoreAction :list="createProjectUGActionItem" @select="projectUserGroupVisible = true">
<icon-plus-circle-fill class="text-[rgb(var(--primary-7))]" size="20" />
</MsMoreAction>
</div>
</CreateUserGroupPopup>
<Transition>
<div v-if="projectToggle">
<div
v-for="element in projectUserGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center py-[7px] pl-[20px] pr-[4px]"
:class="{ 'bg-[rgb(var(--primary-1))]': element.id === currentId }"
@click="handleListItemClick(element)"
>
<CreateUserGroupPopup
:list="projectUserGroupList"
v-bind="popVisible[element.id]"
@cancel="handleRenameCancel(element)"
@submit="handleRenameCancel(element, element.id)"
>
<div class="flex grow flex-row items-center justify-between">
<a-tooltip :content="element.name">
<div
class="one-line-text max-w-[156px] text-[var(--color-text-1)]"
:class="{ 'text-[rgb(var(--primary-7))]': element.id === currentId }"
>{{ element.name }}</div
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
</MsMoreAction>
<MsMoreAction :list="moreAction" @select="(value) => handleMoreAction(value, element.id, 'PROJECT')">
<div class="icon-button">
<MsIcon type="icon-icon_more_outlined" size="16" />
</div>
</MsMoreAction>
</div>
</div>
</CreateUserGroupPopup>
</div>
</div>
</Transition>
</div>
<AddUserModal :visible="userModalVisible" @cancel="userModalVisible = false" />
</template>
<script setup lang="ts">
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { useI18n } from '@/hooks/useI18n';
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { UserGroupItem, PopVisible, PopVisibleItem, AuthScopeType } from '@/models/setting/usergroup';
import { getUserGroupList, deleteUserGroup } from '@/api/modules/setting/usergroup';
import { computed, onMounted, ref } from 'vue';
import CreateUserGroupPopup from './createOrUpdateUserGroup.vue';
import AddUserModal from './addUserModal.vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { Message } from '@arco-design/web-vue';
import useModal from '@/hooks/useModal';
import { characterLimit } from '@/utils';
const { t } = useI18n();
const store = useUserGroupStore();
const { openModal } = useModal();
//
const userGroupList = ref<UserGroupItem[]>([]);
const currentId = ref('');
const userModalVisible = ref(false);
//
const popVisible = ref<PopVisible>({});
// visible
const systemUserGroupVisible = ref(false);
// visible
const orgUserGroupVisible = ref(false);
// visible
const projectUserGroupVisible = ref(false);
// Toggle
const systemToggle = ref(true);
// Toggle
const orgToggle = ref(true);
// Toggle
const projectToggle = ref(true);
//
const systemUserGroupList = computed(() => {
return userGroupList.value.filter((ele) => ele.type === 'SYSTEM');
});
//
const orgUserGroupList = computed(() => {
return userGroupList.value.filter((ele) => ele.type === 'ORGANIZATION');
});
//
const projectUserGroupList = computed(() => {
return userGroupList.value.filter((ele) => ele.type === 'PROJECT');
});
const createSystemUGActionItem: ActionsItem[] = [
{ label: 'system.userGroup.addSysUserGroup', eventTag: 'createUserGroup' },
];
const createOrgUGActionItem: ActionsItem[] = [
{ label: 'system.userGroup.addOrgUserGroup', eventTag: 'createUserGroup' },
];
const createProjectUGActionItem: ActionsItem[] = [
{ label: 'system.userGroup.addProjectUserGroup', eventTag: 'createUserGroup' },
];
const addMemberActionItem: ActionsItem[] = [{ label: 'system.userGroup.addMember', eventTag: 'addMember' }];
const moreAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
},
];
//
const handleListItemClick = (element: UserGroupItem) => {
const { id, name, type, internal } = element;
currentId.value = id;
store.setInfo({
currentName: name,
currentTitle: type,
currentId: id,
currentType: type,
currentInternal: internal,
});
};
//
const initData = async (id?: string) => {
try {
const res = await getUserGroupList();
if (res.length > 0) {
userGroupList.value = res;
let tmpItem = res[0];
if (id) {
tmpItem = res.find((i) => i.id === id) || res[0];
}
handleListItemClick(tmpItem);
//
const tmpObj: PopVisible = {};
res.forEach((element) => {
tmpObj[element.id] = { visible: false, authScope: element.type, defaultName: '', id: element.id };
});
popVisible.value = tmpObj;
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
//
const handleMoreAction = (item: ActionsItem, id: string, authScope: AuthScopeType) => {
if (item.eventTag === 'rename') {
const tmpObj = userGroupList.value.filter((ele) => ele.id === id)[0];
const visibleItem: PopVisibleItem = { visible: true, authScope, defaultName: tmpObj.name, id };
popVisible.value[id] = visibleItem;
}
if (item.eventTag === 'delete') {
openModal({
type: 'error',
title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(store.currentName) }),
content: t('system.userGroup.beforeDeleteUserGroup'),
okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await deleteUserGroup(id);
Message.success(t('system.user.deleteUserSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
};
//
const handleAddMember = () => {
userModalVisible.value = true;
};
function enterData(eve: Event) {
if (!(eve.target as HTMLInputElement).value) {
initData();
return;
}
const keyword = (eve.target as HTMLInputElement).value;
const tmpArr = userGroupList.value.filter((ele) => ele.name.includes(keyword));
userGroupList.value = tmpArr;
}
function searchData(value: string) {
if (!value) {
initData();
return;
}
const keyword = value;
const tmpArr = userGroupList.value.filter((ele) => ele.name.includes(keyword));
userGroupList.value = tmpArr;
}
const handleCreateUserGroup = (id: string) => {
initData(id);
};
const handleRenameCancel = (element: UserGroupItem, id?: string) => {
if (id) {
initData(id);
}
popVisible.value[element.id].visible = false;
};
onMounted(() => {
initData();
});
</script>
<style lang="less" scoped>
.icon-increase {
background-color: rgb(var(--primary-7));
}
.icon-button {
display: flex;
box-sizing: border-box;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
color: rgb(var(--primary-7));
}
.icon-button:hover {
background-color: rgb(var(--primary-9));
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>

View File

@ -1,142 +0,0 @@
<template>
<a-popconfirm
:popup-visible="renameVisible"
:ok-text="t('system.userGroup.confirm')"
:cancel-text="t('system.userGroup.cancel')"
:ok-loading="props.loading"
:cancel-button-props="{ disabled: loading }"
class="w-[276px]"
position="bl"
@before-ok="handleSubmit"
@cancel="handleCancel"
@popup-visible-change="() => (form.name = '')"
>
<template #icon>{{ null }}</template>
<template #content>
<a-form ref="formRef" :model="form" :label-col-props="{ span: 0 }" :wrapper-col-props="{ span: 24 }">
<a-form-item>
<div class="title">{{ message.title }}</div>
</a-form-item>
<a-form-item field="name" :rules="[{ validator: validateName }]">
<a-input v-if="props.type === 'rename'" v-model="form.name" class="w-[234px]" />
<a-select v-else v-model="form.name" class="w-[176px]">
<a-option value="SYSTEM">{{ t('system.userGroup.SYSTEM') }}</a-option>
<a-option value="ORGANIZATION">{{ t('system.userGroup.ORGANIZATION') }}</a-option>
<a-option value="PROJECT">{{ t('system.userGroup.PROJECT') }}</a-option>
</a-select>
</a-form-item>
</a-form>
</template>
<slot></slot>
</a-popconfirm>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { watchEffect, ref, computed, onUnmounted } from 'vue';
import { CustomMoreActionItem, RenameType, UserGroupItem } from '@/models/setting/usergroup';
import { ValidatedError } from '@arco-design/web-vue';
const { t } = useI18n();
const formRef = ref();
const form = ref({
name: '',
});
const props = defineProps<{
visible: boolean;
defaultName: string;
type: RenameType;
list: UserGroupItem[];
loading: boolean;
}>();
const validateName = (value: string | undefined, callback: (error?: string) => void) => {
if (props.type === 'rename') {
if (value === undefined || value === '') {
callback(t('system.userGroup.userGroupNameIsNotNone'));
} else {
if (value === props.defaultName) {
callback();
} else {
const isExist = props.list.some((item) => item.name === value);
if (isExist) {
callback(t('system.userGroup.userGroupNameIsExist', { name: value }));
}
}
callback();
}
} else if (value === '') {
callback(t('system.userGroup.userGroupAuthScopeIsNotNone'));
} else {
callback();
}
};
const message = computed(() => {
if (props.type === 'rename') {
return {
rule: t('system.userGroup.userGroupNameIsNotNone'),
title: t('system.userGroup.rename'),
};
}
return {
rule: t('system.userGroup.userGroupAuthScopeIsNotNone'),
title: t('system.userGroup.changeAuthScope'),
};
});
const emit = defineEmits<{
(e: 'submit', value: CustomMoreActionItem): Promise<void>;
(e: 'cancel'): void;
}>();
const renameVisible = ref(props.visible);
const handleSubmit = async () => {
await formRef.value.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
emit('submit', { eventKey: props.type, name: form.value.name });
return true;
}
});
return false;
};
const handleCancel = () => {
form.value.name = '';
emit('cancel');
};
watchEffect(() => {
renameVisible.value = props.visible;
form.value.name = props.defaultName;
});
onUnmounted(() => {
handleCancel();
});
</script>
<style lang="less" scoped>
.title {
color: var(--color-text-1);
}
.error-6 {
color: rgb(var(--danger-6));
&:hover {
color: rgb(var(--danger-6));
}
}
:deep(.arco-form-item) {
margin-bottom: 8px;
}
.button-icon {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-9));
}
</style>
@/models/setting/usergroup

View File

@ -52,7 +52,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup'; import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import UserGroupLeft from './components/index.vue'; import UserGroupLeft from './components/ms-usergroup-list.vue';
import UserTable from './components/userTable.vue'; import UserTable from './components/userTable.vue';
import AuthTable from './components/authTable.vue'; import AuthTable from './components/authTable.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';

View File

@ -4,11 +4,14 @@ export default {
addUserGroupSuccess: 'Add user group success', addUserGroupSuccess: 'Add user group success',
updateUserGroupSuccess: 'Update user group success', updateUserGroupSuccess: 'Update user group success',
searchHolder: 'Please input user group name', searchHolder: 'Please input user group name',
systemUserGroup: 'System user group',
inSystem: 'In system', inSystem: 'In system',
customUserGroup: 'Custom user group', customUserGroup: 'Custom user group',
addSystemUserGroup: 'Add system user group', addSystemUserGroup: 'Add system user group',
addSystemUser: 'Add system user', addSystemUser: 'Add system user',
addUser: 'Add user', addUser: 'Add user',
addMember: 'Add member',
createUserGroup: 'Create user group',
emptyUserGroup: emptyUserGroup:
'There are currently no custom user groups available. Please click "Create" or "+" above to create a user group', 'There are currently no custom user groups available. Please click "Create" or "+" above to create a user group',
rename: 'Rename', rename: 'Rename',
@ -18,7 +21,6 @@ export default {
add: 'Add', add: 'Add',
cancel: 'Cancel', cancel: 'Cancel',
create: 'Create', create: 'Create',
createUserGroup: 'create user group',
userGroupName: 'User group name', userGroupName: 'User group name',
authScope: 'Auth scope', authScope: 'Auth scope',
auth: 'Auth', auth: 'Auth',
@ -58,6 +60,11 @@ export default {
removeName: 'Confirm to remove {name} this user', removeName: 'Confirm to remove {name} this user',
removeTip: 'After removal, the User Group permission will be lost', removeTip: 'After removal, the User Group permission will be lost',
custom: 'Custom user group', custom: 'Custom user group',
projectUserGroup: 'Project user group',
orgUserGroup: 'Organization user group',
addSysUserGroup: 'Add system user group',
addOrgUserGroup: 'Add organization user group',
addProjectUserGroup: 'Add project user group',
}, },
}, },
permission: { permission: {

View File

@ -3,6 +3,7 @@ export default {
userGroup: { userGroup: {
addUserGroupSuccess: '添加用户组成功', addUserGroupSuccess: '添加用户组成功',
updateUserGroupSuccess: '更新用户组成功', updateUserGroupSuccess: '更新用户组成功',
systemUserGroup: '系统用户组',
global: '全局用户组', global: '全局用户组',
searchHolder: '请输入用户组名称', searchHolder: '请输入用户组名称',
inSystem: '系统内置', inSystem: '系统内置',
@ -10,6 +11,7 @@ export default {
addSystemUserGroup: '添加系统用户组', addSystemUserGroup: '添加系统用户组',
addSystemUser: '添加系统用户', addSystemUser: '添加系统用户',
addUser: '添加成员', addUser: '添加成员',
addMember: '添加成员',
emptyUserGroup: '暂无自定义用户组,请点击上方创建或 “+” 创建用户组', emptyUserGroup: '暂无自定义用户组,请点击上方创建或 “+” 创建用户组',
rename: '重命名', rename: '重命名',
changeAuthScope: '修改权限范围', changeAuthScope: '修改权限范围',
@ -57,6 +59,11 @@ export default {
removeName: '确认移除 {name} 这个用户吗', removeName: '确认移除 {name} 这个用户吗',
removeTip: '移除后,将失去用户组权限', removeTip: '移除后,将失去用户组权限',
custom: '自定义用户组', custom: '自定义用户组',
projectUserGroup: '项目用户组',
orgUserGroup: '组织用户组',
addSysUserGroup: '添加系统用户组',
addOrgUserGroup: '添加组织用户组',
addProjectUserGroup: '添加项目用户组',
}, },
}, },
permission: { permission: {