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

This commit is contained in:
RubyLiu 2023-09-11 16:28:31 +08:00 committed by 刘瑞斌
parent 3d55d8c456
commit 6ee22eaddf
19 changed files with 224 additions and 1776 deletions

View File

@ -19,10 +19,7 @@
<MsUserSelector
v-model:value="form.name"
:type="UserRequesetTypeEnum.ORGANIZATION_USER_GROUP"
:load-option-params="{
roleId: store.currentId,
organizationId: currentOrgId,
}"
:load-option-params="loadOptionParmas"
disabled-key="checkRoleFlag"
/>
</a-form-item>
@ -41,20 +38,21 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect, computed } from 'vue';
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
import { reactive, ref, watchEffect, computed, inject } from 'vue';
import { useAppStore } from '@/store';
import { addOrgUserToUserGroup } from '@/api/modules/setting/usergroup';
import { addOrgUserToUserGroup, addUserToUserGroup } from '@/api/modules/setting/usergroup';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
import { AuthScopeEnum } from '@/enums/commonEnum';
const { t } = useI18n();
const systemType = inject<AuthScopeEnum>('systemType');
const props = defineProps<{
visible: boolean;
currentId: string;
}>();
const store = useUserGroupStore();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
@ -64,6 +62,18 @@
const currentVisible = ref(props.visible);
const loading = ref(false);
const loadOptionParmas = computed(() => {
if (systemType === AuthScopeEnum.SYSTEM) {
return {
roleId: props.currentId,
};
}
return {
roleId: props.currentId,
organizationId: currentOrgId.value,
};
// TODO -
});
const form = reactive({
name: [],
@ -90,11 +100,16 @@
}
try {
loading.value = true;
await addOrgUserToUserGroup({
userRoleId: store.currentId,
userIds: form.name,
organizationId: currentOrgId.value,
});
if (systemType === AuthScopeEnum.SYSTEM) {
await addUserToUserGroup({ roleId: props.currentId, userIds: form.name });
}
if (systemType === AuthScopeEnum.ORGANIZATION) {
await addOrgUserToUserGroup({
userRoleId: props.currentId,
userIds: form.name,
organizationId: currentOrgId.value,
});
}
handleCancel(true);
Message.success(t('common.addSuccess'));
} catch (e) {

View File

@ -51,11 +51,22 @@
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { RenderFunction, VNodeChild, ref, watchEffect, computed } from 'vue';
import { RenderFunction, VNodeChild, ref, watchEffect, computed, inject } from 'vue';
import { Message, type TableColumnData, type TableData } from '@arco-design/web-vue';
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
import { getGlobalUSetting, getOrgUSetting, saveOrgUSetting } from '@/api/modules/setting/usergroup';
import { UserGroupAuthSetting, AuthTableItem, type AuthScopeType, SavePermissions } from '@/models/setting/usergroup';
import {
getGlobalUSetting,
getOrgUSetting,
saveGlobalUSetting,
saveOrgUSetting,
} from '@/api/modules/setting/usergroup';
import {
UserGroupAuthSetting,
AuthTableItem,
type AuthScopeType,
SavePermissions,
CurrentUserGroupItem,
} from '@/models/setting/usergroup';
import { AuthScopeEnum } from '@/enums/commonEnum';
export declare type OperationName = 'selection-checkbox' | 'selection-radio' | 'expand' | 'drag-handle';
@ -68,8 +79,13 @@
isLastLeftFixed?: boolean;
}
const props = defineProps<{
current: CurrentUserGroupItem;
}>();
const systemType = inject<AuthScopeEnum>('systemType');
const loading = ref(false);
const store = useUserGroupStore();
const systemSpan = ref(1);
const projectSpan = ref(1);
@ -83,7 +99,7 @@
const canSave = ref(false);
//
const currentInternal = computed(() => {
return store.userGroupInfo.currentInternal;
return props.current.internal;
});
const dataSpanMethod = (data: {
@ -241,11 +257,19 @@
try {
let res: UserGroupAuthSetting[] = [];
loading.value = true;
if (internal) {
if (systemType === AuthScopeEnum.SYSTEM) {
res = await getGlobalUSetting(id);
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
if (internal) {
res = await getGlobalUSetting(id);
} else {
res = await getOrgUSetting(id);
}
} else {
// TODO
res = await getOrgUSetting(id);
}
tableData.value = transformData(res);
handleAllChange(true);
} catch (error) {
@ -271,12 +295,21 @@
});
});
try {
await saveOrgUSetting({
userRoleId: store.currentId,
permissions,
});
if (systemType === AuthScopeEnum.SYSTEM) {
await saveGlobalUSetting({
userRoleId: props.current.id,
permissions,
});
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
await saveOrgUSetting({
userRoleId: props.current.id,
permissions,
});
} else {
// TODO
}
Message.success(t('common.saveSuccess'));
initData(store.currentId, store.currentInternal);
initData(props.current.id, props.current.internal);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -285,14 +318,14 @@
//
const handleReset = () => {
if (store.currentId) {
initData(store.currentId, store.currentInternal);
if (props.current.id) {
initData(props.current.id, props.current.internal);
}
};
watchEffect(() => {
if (store.currentId) {
initData(store.currentId, store.currentInternal);
if (props.current.id) {
initData(props.current.id, props.current.internal);
}
});
defineExpose({
@ -323,4 +356,3 @@
}
}
</style>
@/store/modules/setting/system/usergroup

View File

@ -114,6 +114,7 @@
if (systemType === AuthScopeEnum.SYSTEM) {
res = await updateOrAddUserGroup({ id: props.id, name: form.name, type: props.authScope });
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
debugger;
//
res = await updateOrAddOrgUserGroup({
id: props.id,

View File

@ -63,7 +63,7 @@
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<MsMoreAction v-if="element.type === systemType" :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
@ -141,7 +141,7 @@
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<MsMoreAction v-if="element.type === systemType" :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
@ -158,11 +158,11 @@
</div>
</CreateUserGroupPopup>
</div>
<a-divider class="my-[0px] mt-[6px]" />
<a-divider v-if="showSystem" class="my-[0px] mt-[6px]" />
</div>
</Transition>
</div>
<div class="mt-2">
<div v-if="showProject" class="mt-2">
<CreateUserGroupPopup
:list="projectUserGroupList"
:visible="projectUserGroupVisible"
@ -219,7 +219,7 @@
>
</a-tooltip>
<div v-if="element.id === currentId && !element.internal" class="flex flex-row items-center gap-[8px]">
<MsMoreAction :list="addMemberActionItem" @select="handleAddMember">
<MsMoreAction v-if="element.type === systemType" :list="addMemberActionItem" @select="handleAddMember">
<div class="icon-button">
<MsIcon type="icon-icon_add_outlined" size="16" />
</div>
@ -240,7 +240,7 @@
</Transition>
</div>
<AddUserModal :visible="userModalVisible" @cancel="userModalVisible = false" />
<AddUserModal :visible="userModalVisible" :current-id="currentItem.id" @cancel="userModalVisible = false" />
</template>
<script setup lang="ts">
@ -248,17 +248,17 @@
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 } from '@/models/setting/usergroup';
import { UserGroupItem, PopVisible, PopVisibleItem, CurrentUserGroupItem } from '@/models/setting/usergroup';
import {
getUserGroupList,
deleteUserGroup,
getOrgUserGroupList,
getProjectUserGroupList,
deleteOrgUserGroup,
} from '@/api/modules/setting/usergroup';
import { computed, onMounted, ref, inject } 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';
@ -267,7 +267,9 @@
const { t } = useI18n();
const store = useUserGroupStore();
const emit = defineEmits<{
(e: 'onSelect', element: UserGroupItem): void;
}>();
const appStore = useAppStore();
const { openModal } = useModal();
@ -275,11 +277,14 @@
const showSystem = computed(() => systemType === AuthScopeEnum.SYSTEM);
const showOrg = computed(() => systemType === AuthScopeEnum.SYSTEM || systemType === AuthScopeEnum.ORGANIZATION);
const showProject = computed(() => systemType === AuthScopeEnum.SYSTEM || systemType === AuthScopeEnum.PROJECT);
//
const userGroupList = ref<UserGroupItem[]>([]);
const currentId = ref('');
const currentItem = ref<CurrentUserGroupItem>({ id: '', name: '', internal: false, type: AuthScopeEnum.SYSTEM });
const currentId = computed(() => currentItem.value.id);
const currentName = computed(() => currentItem.value.name);
const userModalVisible = ref(false);
@ -343,14 +348,8 @@
//
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,
});
currentItem.value = { id, name, type, internal };
emit('onSelect', element);
};
//
@ -394,7 +393,7 @@
if (item.eventTag === 'delete') {
openModal({
type: 'error',
title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(store.currentName) }),
title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(currentName.value) }),
content: t('system.userGroup.beforeDeleteUserGroup'),
okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'),
@ -403,7 +402,13 @@
},
onBeforeOk: async () => {
try {
await deleteUserGroup(id);
if (systemType === AuthScopeEnum.SYSTEM) {
await deleteUserGroup(id);
}
if (systemType === AuthScopeEnum.ORGANIZATION) {
await deleteOrgUserGroup(id);
}
// TODO
Message.success(t('system.user.deleteUserSuccess'));
initData();
} catch (error) {

View File

@ -9,7 +9,7 @@
/>
</template>
</MsBaseTable>
<AddUserModal :visible="userVisible" @cancel="handleAddUserModalCancel" />
<AddUserModal :current-id="props.current.id" :visible="userVisible" @cancel="handleAddUserModalCancel" />
</template>
<script lang="ts" setup>
@ -17,21 +17,28 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { useAppStore } from '@/store';
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
import { watchEffect, ref, computed } from 'vue';
import { postOrgUserByUserGroup, deleteOrgUserFromUserGroup } from '@/api/modules/setting/usergroup';
import { UserTableItem } from '@/models/setting/usergroup';
import { watchEffect, ref, computed, inject } from 'vue';
import {
postOrgUserByUserGroup,
deleteOrgUserFromUserGroup,
postUserByUserGroup,
deleteUserFromUserGroup,
} from '@/api/modules/setting/usergroup';
import { CurrentUserGroupItem, UserTableItem } from '@/models/setting/usergroup';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import AddUserModal from './addUserModal.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import { AuthScopeEnum } from '@/enums/commonEnum';
const systemType = inject<AuthScopeEnum>('systemType');
const { t } = useI18n();
const store = useUserGroupStore();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const userVisible = ref(false);
const props = defineProps<{
keyword: string;
current: CurrentUserGroupItem;
}>();
const userGroupUsercolumns: MsTableColumn = [
@ -55,7 +62,16 @@
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postOrgUserByUserGroup, {
const getRequestBySystemType = () => {
if (systemType === AuthScopeEnum.SYSTEM) {
return postUserByUserGroup;
}
return postOrgUserByUserGroup;
// TODO:
};
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(getRequestBySystemType(), {
columns: userGroupUsercolumns,
scroll: { y: 'auto', x: '600px' },
selectable: false,
@ -70,11 +86,16 @@
};
const handleRemove = async (record: UserTableItem) => {
try {
await deleteOrgUserFromUserGroup({
organizationId: currentOrgId.value,
userRoleId: store.currentId,
userIds: [record.id],
});
if (systemType === AuthScopeEnum.SYSTEM) {
await deleteUserFromUserGroup(record.id);
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
await deleteOrgUserFromUserGroup({
organizationId: currentOrgId.value,
userRoleId: props.current.id,
userIds: [record.id],
});
}
// TODO -
await fetchData();
} catch (error) {
// eslint-disable-next-line no-console
@ -91,8 +112,13 @@
userVisible.value = false;
};
watchEffect(() => {
if (store.currentId && currentOrgId.value) {
setLoadListParams({ userRoleId: store.currentId, organizationId: currentOrgId.value });
if (props.current.id && currentOrgId.value) {
if (systemType === AuthScopeEnum.SYSTEM) {
setLoadListParams({ roleId: props.current.id });
} else if (systemType === AuthScopeEnum.ORGANIZATION) {
setLoadListParams({ userRoleId: props.current.id, organizationId: currentOrgId.value });
}
// TODO -
fetchData();
}
});

View File

@ -1,106 +0,0 @@
<template>
<a-modal
v-model:visible="currentVisible"
class="ms-modal-form ms-modal-medium"
width="680px"
text-align="start"
:ok-text="t('system.userGroup.add')"
unmount-on-close
@cancel="handleCancel"
>
<template #title> {{ t('system.userGroup.addUser') }} </template>
<div class="form">
<a-form ref="formRef" :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
:label="t('system.userGroup.user')"
:rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]"
asterisk-position="end"
>
<MsUserSelector
v-model:value="form.name"
disabled-key="exclude"
:load-option-params="{ roleId: store.currentId }"
/>
</a-form-item>
</a-form>
</div>
<template #footer>
<a-button type="secondary" :loading="loading" @click="handleCancel">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleBeforeOk">
{{ 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 useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { addUserToUserGroup } from '@/api/modules/setting/usergroup';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const store = useUserGroupStore();
const emit = defineEmits<{
(e: 'cancel'): void;
(e: 'submit', value: string[]): void;
}>();
const currentVisible = ref(props.visible);
const loading = ref(false);
const form = reactive({
name: [],
});
const labelCache = new Map();
const formRef = ref<FormInstance>();
watchEffect(() => {
currentVisible.value = props.visible;
});
const handleCancel = () => {
labelCache.clear();
form.name = [];
emit('cancel');
};
const handleBeforeOk = () => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return;
}
try {
loading.value = true;
await addUserToUserGroup({ roleId: store.currentId, userIds: form.name });
handleCancel();
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
} finally {
loading.value = false;
}
});
};
</script>
<style lang="less" scoped>
.option-name {
color: var(--color-text-1);
}
.option-email {
color: var(--color-text-4);
}
</style>

View File

@ -39,6 +39,16 @@ export interface UserGroupItem {
// 自定义排序
pos: number;
}
export interface CurrentUserGroupItem {
// 组ID
id: string;
// 组名称
name: string;
// 所属类型
type: AuthScopeEnum;
// 是否是内置用户组
internal: boolean;
}
export interface SystemUserGroupParams {
id?: string; // 组ID

View File

@ -1,30 +0,0 @@
import { defineStore } from 'pinia';
import { UserGroupState } from '../types';
const useOrgUserGroupStore = defineStore('orgUserGroup', {
state: (): UserGroupState => ({
currentName: '',
currentTitle: '',
currentId: '',
currentType: '',
currentInternal: false,
collapse: true,
}),
getters: {
userGroupInfo(state: UserGroupState): UserGroupState {
return state;
},
},
actions: {
// 设置当前用户组信息
setInfo(partial: Partial<UserGroupState>) {
this.$patch(partial);
},
// 设置用户组菜单开启关闭
setCollapse(collapse: boolean) {
this.collapse = collapse;
},
},
});
export default useOrgUserGroupStore;

View File

@ -1,30 +0,0 @@
import { defineStore } from 'pinia';
import { UserGroupState } from '../types';
const useProjectUserGroupStore = defineStore('projectUserGroup', {
state: (): UserGroupState => ({
currentName: '',
currentTitle: '',
currentId: '',
currentType: '',
currentInternal: false,
collapse: true,
}),
getters: {
userGroupInfo(state: UserGroupState): UserGroupState {
return state;
},
},
actions: {
// 设置当前用户组信息
setInfo(partial: Partial<UserGroupState>) {
this.$patch(partial);
},
// 设置用户组菜单开启关闭
setCollapse(collapse: boolean) {
this.collapse = collapse;
},
},
});
export default useProjectUserGroupStore;

View File

@ -1,30 +0,0 @@
import { defineStore } from 'pinia';
import { UserGroupState } from '../types';
const useSystemUserGroupStore = defineStore('systemUserGroup', {
state: (): UserGroupState => ({
currentName: '',
currentTitle: '',
currentId: '',
currentType: '',
currentInternal: false,
collapse: true,
}),
getters: {
userGroupInfo(state: UserGroupState): UserGroupState {
return state;
},
},
actions: {
// 设置当前用户组信息
setInfo(partial: Partial<UserGroupState>) {
this.$patch(partial);
},
// 设置用户组菜单开启关闭
setCollapse(collapse: boolean) {
this.collapse = collapse;
},
},
});
export default useSystemUserGroupStore;

View File

@ -1,127 +0,0 @@
<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')"
/>
</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, computed, watchEffect } from 'vue';
import { UserGroupItem } from '@/models/setting/usergroup';
import { Message } from '@arco-design/web-vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import { updateOrAddOrgUserGroup } from '@/api/modules/setting/usergroup';
import { useAppStore } from '@/store';
const { t } = useI18n();
const props = defineProps<{
id?: string;
list: UserGroupItem[];
visible: boolean;
defaultName?: string;
}>();
const emit = defineEmits<{
(e: 'cancel', value: boolean): void;
(e: 'search'): void;
}>();
const formRef = ref<FormInstance>();
const currentVisible = ref(props.visible);
const form = reactive({
name: '',
});
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
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 updateOrAddOrgUserGroup({ id: props.id, name: form.name, scopeId: currentOrgId.value });
if (res) {
Message.success(
props.id ? t('system.userGroup.updateUserGroupSuccess') : t('system.userGroup.addUserGroupSuccess')
);
emit('search');
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,244 +0,0 @@
<template>
<a-input-search
class="w-[252px]"
:placeholder="t('system.userGroup.searchHolder')"
allow-clear
@press-enter="enterData"
@search="searchData"
/>
<div class="mt-2 flex flex-col">
<div class="flex h-[38px] items-center px-[8px] leading-[24px]">
<div class="text-[var(--color-text-input-border)]"> {{ t('system.userGroup.global') }}</div>
</div>
<div>
<div
v-for="element in globalUserGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center px-[8px]"
:class="{
'bg-[rgb(var(--primary-1))]': element.id === currentId,
}"
@click="handleListItemClick(element)"
>
<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>
</div>
</div>
</div>
<a-divider class="mt-2" />
<div class="mt-2 flex flex-col">
<AddOrUpdateUserGroupPopup
:visible="addUserGroupVisible"
:list="customUserGroupList"
@cancel="handleAddUserGroupCancel"
@search="initData"
>
<div class="flex h-[38px] items-center justify-between px-[8px] leading-[24px]">
<div class="text-[var(--color-text-input-border)]"> {{ t('system.userGroup.custom') }}</div>
<div class="cursor-pointer text-[rgb(var(--primary-5))]"
><icon-plus-circle-fill style="font-size: 20px" @click="addUserGroup"
/></div>
</div>
</AddOrUpdateUserGroupPopup>
<div>
<div
v-for="element in customUserGroupList"
:key="element.id"
class="flex h-[38px] cursor-pointer items-center"
:class="{ 'bg-[rgb(var(--primary-1))]': element.id === currentId }"
@click="handleListItemClick(element)"
>
<AddOrUpdateUserGroupPopup
:id="element.id"
:visible="popVisible[element.id]"
:default-name="popDefaultName"
:list="customUserGroupList"
@cancel="() => handlePopConfirmCancel(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>
</AddOrUpdateUserGroupPopup>
</div>
</div>
</div>
<AddUserModal :visible="addUserVisible" @cancel="addUserVisible = false" />
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { 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 AddOrUpdateUserGroupPopup from './addOrUpdateUserGroupPopup.vue';
import useModal from '@/hooks/useModal';
import { Message } from '@arco-design/web-vue';
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
import { useAppStore } from '@/store';
import { getOrgUserGroupList, deleteOrgUserGroup } from '@/api/modules/setting/usergroup';
import { characterLimit } from '@/utils';
interface PopVisibleItem {
[key: string]: boolean;
}
const { t } = useI18n();
const store = useUserGroupStore();
const appStore = useAppStore();
const { openModal } = useModal();
// loading
const currentId = ref('');
const addUserVisible = ref(false);
const addUserGroupVisible = ref(false);
//
const popVisible = ref<PopVisibleItem>({});
//
const popType = ref<RenameType>('rename');
const popDefaultName = ref('');
//
const userGroupList = ref<UserGroupItem[]>([]);
const currentOrgId = computed(() => appStore.currentOrgId);
const globalUserGroupList = computed(() => {
return userGroupList.value.filter((ele) => ele.internal);
});
const customUserGroupList = computed(() => {
return userGroupList.value.filter((ele) => !ele.internal);
});
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 getOrgUserGroupList(currentOrgId.value);
if (res.length > 0) {
userGroupList.value = res;
handleListItemClick(res[0]);
//
const tmpObj: PopVisibleItem = {};
res.forEach((element) => {
tmpObj[element.id] = false;
});
popVisible.value = tmpObj;
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
//
const addUserGroup = () => {
addUserGroupVisible.value = true;
};
//
const handleAddUserGroupCancel = () => {
addUserGroupVisible.value = false;
};
//
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 {
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 deleteOrgUserGroup(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 };
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;
}
onMounted(() => {
initData();
});
</script>

View File

@ -1,17 +1,17 @@
<template>
<MsCard simple>
<div class="flex flex-row">
<div class="user-group-left" :style="{ padding: collapse ? '24px 24px 24px 0' : 0 }">
<user-group-left v-if="collapse" />
<div class="user-group-left" :style="{ padding: leftCollapse ? '24px 24px 24px 0' : 0 }">
<UserGroupLeft v-if="leftCollapse" @on-select="handleSelect" />
<div class="usergroup-collapse" @click="handleCollapse">
<MsIcon v-if="collapse" type="icon-icon_up-left_outlined" class="icon" />
<MsIcon v-if="leftCollapse" type="icon-icon_up-left_outlined" class="icon" />
<MsIcon v-else type="icon-icon_down-right_outlined" class="icon" />
</div>
</div>
<div class="relative w-[100%] overflow-x-scroll p-[24px]">
<div class="flex flex-row items-center justify-between">
<a-tooltip :content="store.userGroupInfo.currentName">
<div class="one-line-text max-w-[300px]">{{ store.userGroupInfo.currentName }}</div>
<a-tooltip :content="currentUserGroupItem.name">
<div class="one-line-text max-w-[300px]">{{ currentUserGroupItem.name }}</div>
</a-tooltip>
<div class="flex items-center">
<a-input-search
@ -29,8 +29,13 @@
</div>
</div>
<div class="mt-[16px]">
<user-table v-if="currentTable === 'user' && couldShowUser" ref="userRef" :keyword="currentKeyword" />
<auth-table v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" />
<UserTable
v-if="currentTable === 'user' && couldShowUser"
ref="userRef"
:keyword="currentKeyword"
:current="currentUserGroupItem"
/>
<AuthTable v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" :current="currentUserGroupItem" />
</div>
</div>
</div>
@ -48,21 +53,31 @@
</template>
<script lang="ts" setup>
import { ref, computed, watchEffect, nextTick } from 'vue';
import { ref, computed, watchEffect, nextTick, provide } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue';
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
import UserGroupLeft from './components/index.vue';
import UserTable from './components/userTable.vue';
import AuthTable from './components/authTable.vue';
import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue';
import UserTable from '@/components/business/ms-user-group-comp//userTable.vue';
import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { useAppStore } from '@/store';
import { AuthScopeEnum } from '@/enums/commonEnum';
import { CurrentUserGroupItem } from '@/models/setting/usergroup';
//
provide('systemType', AuthScopeEnum.ORGANIZATION);
const currentTable = ref('user');
const leftCollapse = ref(true);
const { t } = useI18n();
const currentKeyword = ref('');
const currentUserGroupItem = ref<CurrentUserGroupItem>({
id: '',
name: '',
type: AuthScopeEnum.ORGANIZATION,
internal: true,
});
const authRef = ref<{
handleReset: () => void;
handleSave: () => void;
@ -90,16 +105,19 @@
tableSearch();
};
const store = useUserGroupStore();
const couldShowUser = computed(() => store.userGroupInfo.currentType === 'ORGANIZATION');
const couldShowAuth = computed(() => store.userGroupInfo.currentId !== 'admin');
const handleCollapse = () => {
store.setCollapse(!store.collapse);
const couldShowUser = computed(() => currentUserGroupItem.value.type === AuthScopeEnum.ORGANIZATION);
const couldShowAuth = computed(() => currentUserGroupItem.value.id !== 'admin');
const handleSelect = (item: CurrentUserGroupItem) => {
currentUserGroupItem.value = item;
};
const handleCollapse = () => {
leftCollapse.value = !leftCollapse.value;
};
const collapse = computed(() => store.collapse);
const menuWidth = computed(() => {
const width = appStore.menuCollapse ? 86 : appStore.menuWidth;
if (store.collapse) {
if (leftCollapse.value) {
return width + 300;
}
return width + 24;

View File

@ -1,106 +0,0 @@
<template>
<a-modal
v-model:visible="currentVisible"
class="ms-modal-form ms-modal-medium"
width="680px"
text-align="start"
:ok-text="t('system.userGroup.add')"
unmount-on-close
@cancel="handleCancel"
>
<template #title> {{ t('system.userGroup.addUser') }} </template>
<div class="form">
<a-form ref="formRef" :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
:label="t('system.userGroup.user')"
:rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]"
asterisk-position="end"
>
<MsUserSelector
v-model:value="form.name"
disabled-key="exclude"
:load-option-params="{ roleId: store.currentId }"
/>
</a-form-item>
</a-form>
</div>
<template #footer>
<a-button type="secondary" :loading="loading" @click="handleCancel">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleBeforeOk">
{{ 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 useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { addUserToUserGroup } from '@/api/modules/setting/usergroup';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const store = useUserGroupStore();
const emit = defineEmits<{
(e: 'cancel'): void;
(e: 'submit', value: string[]): void;
}>();
const currentVisible = ref(props.visible);
const loading = ref(false);
const form = reactive({
name: [],
});
const labelCache = new Map();
const formRef = ref<FormInstance>();
watchEffect(() => {
currentVisible.value = props.visible;
});
const handleCancel = () => {
labelCache.clear();
form.name = [];
emit('cancel');
};
const handleBeforeOk = () => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return;
}
try {
loading.value = true;
await addUserToUserGroup({ roleId: store.currentId, userIds: form.name });
handleCancel();
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
} finally {
loading.value = false;
}
});
};
</script>
<style lang="less" scoped>
.option-name {
color: var(--color-text-1);
}
.option-email {
color: var(--color-text-4);
}
</style>

View File

@ -1,318 +0,0 @@
<template>
<div class="usergroup-auth-table">
<a-table
:span-method="dataSpanMethod"
:scroll="{ y: '500px', x: '800px' }"
:data="tableData"
:loading="loading"
:bordered="{ wrapper: true, cell: true }"
size="small"
:pagination="false"
>
<template #columns>
<a-table-column :width="100" :title="t('system.userGroup.function')" data-index="ability" />
<a-table-column :width="150" :title="t('system.userGroup.operationObject')" data-index="operationObject" />
<a-table-column :title="t('system.userGroup.auth')">
<template #cell="{ record, rowIndex }">
<a-checkbox-group v-model="record.perChecked" @change="(v) => handleCellAuthChange(v, rowIndex)">
<a-checkbox
v-for="item in record.permissions"
:key="item.id"
:disabled="item.license || currentInternal"
:value="item.id"
>{{ t(item.name) }}</a-checkbox
>
</a-checkbox-group>
</template>
</a-table-column>
<a-table-column :width="50" fixed="right" align="center" :bordered="false">
<template #title>
<a-checkbox
v-if="tableData && tableData?.length > 0"
:model-value="allChecked"
:indeterminate="allIndeterminate"
:disabled="currentInternal"
@change="handleAllAuthChangeByCheckbox"
></a-checkbox>
</template>
<template #cell="{ record, rowIndex }">
<a-checkbox
:model-value="record.enable"
:indeterminate="record.indeterminate"
:disabled="currentInternal"
@change="(value) => handleRowAuthChange(value, rowIndex)"
/>
</template>
</a-table-column>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { RenderFunction, VNodeChild, ref, watchEffect, computed } from 'vue';
import { type TableColumnData, type TableData } from '@arco-design/web-vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { getGlobalUSetting, saveGlobalUSetting } from '@/api/modules/setting/usergroup';
import { UserGroupAuthSetting, AuthTableItem, type AuthScopeType, SavePermissions } from '@/models/setting/usergroup';
export declare type OperationName = 'selection-checkbox' | 'selection-radio' | 'expand' | 'drag-handle';
export interface TableOperationColumn {
name: OperationName | string;
title?: string | RenderFunction;
width?: number;
fixed?: boolean;
render?: (record: TableData) => VNodeChild;
isLastLeftFixed?: boolean;
}
const loading = ref(false);
const store = useUserGroupStore();
const systemSpan = ref(1);
const projectSpan = ref(1);
const organizationSpan = ref(1);
//
const allChecked = ref(false);
const allIndeterminate = ref(false);
const tableData = ref<AuthTableItem[]>();
//
const canSave = ref(false);
//
const currentInternal = computed(() => {
return store.userGroupInfo.currentInternal;
});
const dataSpanMethod = (data: {
record: TableData;
column: TableColumnData | TableOperationColumn;
rowIndex: number;
columnIndex: number;
}) => {
const { record, column } = data;
if ((column as TableColumnData).dataIndex === 'ability') {
if (record.isSystem) {
return {
rowspan: systemSpan.value,
};
}
if (record.isOrganization) {
return {
rowspan: organizationSpan.value,
};
}
if (record.isProject) {
return {
rowspan: projectSpan.value,
};
}
}
};
const { t } = useI18n();
/**
* 生成数据
* @param item
* @param type
*/
const makeData = (item: UserGroupAuthSetting, type: AuthScopeType) => {
const result: AuthTableItem[] = [];
item.children?.forEach((child, index) => {
const perChecked =
child?.permissions?.reduce((acc: string[], cur) => {
if (cur.enable) {
acc.push(cur.id);
}
return acc;
}, []) || [];
const perCheckedLength = perChecked.length;
let indeterminate = false;
if (child?.permissions) {
indeterminate = perCheckedLength > 0 && perCheckedLength < child?.permissions?.length;
}
result.push({
id: child?.id,
license: child?.license,
enable: child?.enable,
permissions: child?.permissions,
indeterminate,
perChecked,
ability: index === 0 ? t(`system.userGroup.${type}`) : undefined,
operationObject: t(child.name),
isSystem: index === 0 && type === 'SYSTEM',
isOrganization: index === 0 && type === 'ORGANIZATION',
isProject: index === 0 && type === 'PROJECT',
});
});
return result;
};
//
const transformData = (data: UserGroupAuthSetting[]) => {
const result: AuthTableItem[] = [];
data.forEach((item) => {
if (item.type === 'SYSTEM') {
systemSpan.value = item.children?.length || 0;
}
if (item.type === 'PROJECT') {
projectSpan.value = item.children?.length || 0;
}
if (item.type === 'ORGANIZATION') {
organizationSpan.value = item.children?.length || 0;
}
result.push(...makeData(item, item.id));
});
return result;
};
// change
const handleAllAuthChangeByCheckbox = () => {
if (!tableData.value) return;
allChecked.value = !allChecked.value;
allIndeterminate.value = false;
const tmpArr = tableData.value;
tmpArr.forEach((item) => {
item.enable = allChecked.value;
item.indeterminate = false;
item.perChecked = allChecked.value ? item.permissions?.map((ele) => ele.id) : [];
});
if (!canSave.value) canSave.value = true;
};
//
const handleAllChange = (isInit = false) => {
if (!tableData.value) return;
const tmpArr = tableData.value;
const { length: allLength } = tmpArr;
const { length } = tmpArr.filter((item) => item.enable);
if (length === allLength) {
allChecked.value = true;
allIndeterminate.value = false;
} else if (length === 0) {
allChecked.value = false;
allIndeterminate.value = false;
} else {
allChecked.value = false;
allIndeterminate.value = true;
}
if (!isInit && !canSave.value) canSave.value = true;
};
// change
const handleRowAuthChange = (value: boolean | (string | number | boolean)[], rowIndex: number) => {
if (!tableData.value) return;
const tmpArr = tableData.value;
tmpArr[rowIndex].indeterminate = false;
if (value) {
tmpArr[rowIndex].enable = true;
tmpArr[rowIndex].perChecked = tmpArr[rowIndex].permissions?.map((item) => item.id);
} else {
tmpArr[rowIndex].enable = false;
tmpArr[rowIndex].perChecked = [];
}
tableData.value = [...tmpArr];
handleAllChange();
if (!canSave.value) canSave.value = true;
};
// change
const handleCellAuthChange = (values: (string | number | boolean)[], rowIndex: number) => {
if (!tableData.value) return;
const tmpArr = tableData.value;
const length = tmpArr[rowIndex].permissions?.length || 0;
if (values.length === length) {
tmpArr[rowIndex].enable = true;
tmpArr[rowIndex].indeterminate = false;
} else if (values.length === 0) {
tmpArr[rowIndex].enable = false;
tmpArr[rowIndex].indeterminate = false;
} else {
tmpArr[rowIndex].enable = false;
tmpArr[rowIndex].indeterminate = true;
}
handleAllChange();
};
const initData = async (id: string) => {
try {
loading.value = true;
const res = await getGlobalUSetting(id);
tableData.value = transformData(res);
handleAllChange(true);
} catch (error) {
tableData.value = [];
} finally {
loading.value = false;
}
};
//
const handleSave = async () => {
if (!tableData.value) return;
const permissions: SavePermissions[] = [];
const tmpArr = tableData.value;
tmpArr.forEach((item) => {
item.permissions?.forEach((ele) => {
ele.enable = item.perChecked?.includes(ele.id) || false;
permissions.push({
id: ele.id,
enable: ele.enable,
});
});
});
try {
await saveGlobalUSetting({
userRoleId: store.currentId,
permissions,
});
initData(store.currentId);
} catch (error) {
// eslint-disable-next-line no-console
console.log('error', error);
}
};
//
const handleReset = () => {
if (store.currentId) {
initData(store.currentId);
}
};
watchEffect(() => {
if (store.currentId) {
initData(store.currentId);
}
});
defineExpose({
handleReset,
handleSave,
canSave,
});
</script>
<style scoped lang="less">
.usergroup-auth-table {
position: relative;
min-height: calc(100vh - 230px);
:deep(.arco-table-container) {
border-top: 1px solid var(--color-text-n8) !important;
border-right: 1px solid var(--color-text-n8) !important;
border-left: 1px solid var(--color-text-n8) !important;
}
.action {
position: absolute;
right: 24px;
bottom: 0;
left: 24px;
display: flex;
justify-content: space-between;
align-items: center;
width: calc(100% - 24px);
}
}
</style>

View File

@ -1,128 +0,0 @@
<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,461 +0,0 @@
<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, AuthScopeEnum.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, AuthScopeEnum.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, AuthScopeEnum.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 } 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';
import { AuthScopeEnum } from '@/enums/commonEnum';
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: AuthScopeEnum) => {
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,95 +0,0 @@
<template>
<a-button type="primary" @click="handleAddUser">{{ t('system.userGroup.quickAddUser') }}</a-button>
<MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
<template #action="{ record }">
<MsRemoveButton
:title="t('system.userGroup.removeName', { name: record.name })"
:sub-title-tip="t('system.userGroup.removeTip')"
:loading="removeLoading"
@ok="handleRemove(record)"
/>
</template>
</MsBaseTable>
<AddUserModal :visible="userVisible" @cancel="handleAddUserModalCancel" />
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import { watchEffect, ref } from 'vue';
import { postUserByUserGroup, deleteUserFromUserGroup } from '@/api/modules/setting/usergroup';
import { UserTableItem } from '@/models/setting/usergroup';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import AddUserModal from './addUserModal.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
const { t } = useI18n();
const store = useUserGroupStore();
const userVisible = ref(false);
const removeLoading = ref(false);
const props = defineProps<{
keyword: string;
}>();
const userGroupUsercolumns: MsTableColumn = [
{
title: 'system.userGroup.name',
dataIndex: 'name',
},
{
title: 'system.userGroup.email',
dataIndex: 'email',
},
{
title: 'system.userGroup.phone',
dataIndex: 'email',
},
{
title: 'system.userGroup.operation',
slotName: 'action',
fixed: 'right',
width: 200,
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postUserByUserGroup, {
columns: userGroupUsercolumns,
scroll: { x: '600px' },
noDisable: true,
});
const fetchData = async () => {
setKeyword(props.keyword);
await loadList();
};
const handleRemove = async (record: UserTableItem) => {
try {
removeLoading.value = true;
await deleteUserFromUserGroup(record.id);
await fetchData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
removeLoading.value = false;
}
};
const handleAddUser = () => {
userVisible.value = true;
};
const handleAddUserModalCancel = () => {
fetchData();
userVisible.value = false;
};
watchEffect(() => {
if (store.currentId) {
setLoadListParams({ roleId: store.currentId });
fetchData();
}
});
defineExpose({
fetchData,
});
</script>

View File

@ -1,17 +1,17 @@
<template>
<MsCard simple>
<div class="flex flex-row">
<div class="user-group-left" :style="{ padding: collapse ? '24px 24px 24px 0' : 0 }">
<UserGroupLeft v-if="collapse" :type="AuthScopeEnum.SYSTEM" />
<div class="user-group-left" :style="{ padding: leftCollapse ? '24px 24px 24px 0' : 0 }">
<UserGroupLeft v-if="leftCollapse" @on-select="handleSelect" />
<div class="usergroup-collapse" @click="handleCollapse">
<MsIcon v-if="collapse" type="icon-icon_up-left_outlined" class="icon" />
<MsIcon v-if="leftCollapse" type="icon-icon_up-left_outlined" class="icon" />
<MsIcon v-else type="icon-icon_down-right_outlined" class="icon" />
</div>
</div>
<div class="relative w-[100%] overflow-x-scroll p-[24px]">
<div class="flex flex-row items-center justify-between">
<a-tooltip :content="store.userGroupInfo.currentName">
<div class="one-line-text max-w-[300px]">{{ store.userGroupInfo.currentName }}</div>
<a-tooltip :content="currentUserGroupItem.name">
<div class="one-line-text max-w-[300px]">{{ currentUserGroupItem.name }}</div>
</a-tooltip>
<div class="flex items-center">
<a-input-search
@ -29,8 +29,13 @@
</div>
</div>
<div class="mt-[16px]">
<user-table v-if="currentTable === 'user' && couldShowUser" ref="userRef" :keyword="currentKeyword" />
<auth-table v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" />
<UserTable
v-if="currentTable === 'user' && couldShowUser"
ref="userRef"
:keyword="currentKeyword"
:current="currentUserGroupItem"
/>
<AuthTable v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" :current="currentUserGroupItem" />
</div>
</div>
</div>
@ -51,20 +56,29 @@
import { ref, computed, watchEffect, nextTick, provide } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue';
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
import UserGroupLeft from '@/components/business/ms-user-group-left/msUserGroupLeft.vue';
import UserTable from './components/userTable.vue';
import AuthTable from './components/authTable.vue';
import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue';
import UserTable from '@/components/business/ms-user-group-comp/userTable.vue';
import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { useAppStore } from '@/store';
import { AuthScopeEnum } from '@/enums/commonEnum';
import { CurrentUserGroupItem } from '@/models/setting/usergroup';
const currentTable = ref('auth');
provide('systemType', AuthScopeEnum.SYSTEM);
const { t } = useI18n();
const currentKeyword = ref('');
const currentUserGroupItem = ref<CurrentUserGroupItem>({
id: '',
name: '',
type: AuthScopeEnum.SYSTEM,
internal: true,
});
const leftCollapse = ref(true);
const authRef = ref<{
handleReset: () => void;
handleSave: () => void;
@ -92,16 +106,18 @@
tableSearch();
};
const store = useUserGroupStore();
const couldShowUser = computed(() => store.userGroupInfo.currentType === 'SYSTEM');
const couldShowAuth = computed(() => store.userGroupInfo.currentId !== 'admin');
const handleCollapse = () => {
store.setCollapse(!store.collapse);
const handleSelect = (item: CurrentUserGroupItem) => {
currentUserGroupItem.value = item;
};
const couldShowUser = computed(() => currentUserGroupItem.value.type === AuthScopeEnum.SYSTEM);
const couldShowAuth = computed(() => currentUserGroupItem.value.id !== 'admin');
const handleCollapse = () => {
leftCollapse.value = !leftCollapse.value;
};
const collapse = computed(() => store.collapse);
const menuWidth = computed(() => {
const width = appStore.menuCollapse ? 86 : appStore.menuWidth;
if (store.collapse) {
if (leftCollapse.value) {
return width + 300;
}
return width + 24;