feat: 登录的盐&组织的用户组创建编辑保存权限
This commit is contained in:
parent
14d401342b
commit
383e3fcf78
|
@ -18,10 +18,13 @@
|
|||
import { watchStyle, watchTheme, setFavicon } from '@/utils/theme';
|
||||
import { GetPlatformIconUrl } from '@/api/requrls/setting/config';
|
||||
import { useUserStore } from '@/store';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { WorkbenchRouteEnum } from './enums/routeEnum';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const licenseStore = useLicenseStore();
|
||||
const router = useRouter();
|
||||
|
||||
const { currentLocale } = useLocale();
|
||||
const locale = computed(() => {
|
||||
|
@ -61,7 +64,15 @@
|
|||
console.log(error);
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
userStore.isLogin();
|
||||
const checkIsLogin = async () => {
|
||||
const isLogin = await userStore.isLogin();
|
||||
const isLoginPage = window.location.hash === '#/login';
|
||||
if (isLoginPage && isLogin) {
|
||||
// 当前页面为登录页面,且已经登录,跳转到首页
|
||||
router.push(WorkbenchRouteEnum.WORKBENCH);
|
||||
}
|
||||
};
|
||||
onMounted(async () => {
|
||||
await checkIsLogin();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,15 +5,19 @@ import useUser from '@/hooks/useUser';
|
|||
|
||||
export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
|
||||
const { t } = useI18n();
|
||||
const { logout } = useUser();
|
||||
const { logout, setSalt, isLoginPage } = useUser();
|
||||
let errMessage = '';
|
||||
switch (status) {
|
||||
case 400:
|
||||
errMessage = `${msg}`;
|
||||
break;
|
||||
case 401: {
|
||||
errMessage = msg || t('api.errMsg401');
|
||||
setSalt(msg);
|
||||
if (!isLoginPage()) {
|
||||
// 不是登录页再调用logout
|
||||
logout();
|
||||
errMessage = t('api.errMsg401');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 403:
|
||||
|
|
|
@ -155,7 +155,6 @@ const transform: AxiosTransform = {
|
|||
} catch (e) {
|
||||
throw new Error(e as unknown as string);
|
||||
}
|
||||
|
||||
checkStatus(error?.response?.status, msg, errorMessageMode);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
|
|
|
@ -1,65 +1,85 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import {
|
||||
updateUserGroupU,
|
||||
getUserGroupU,
|
||||
addUserGroupU,
|
||||
deleteUserGroupU,
|
||||
getGlobalUSettingUrl,
|
||||
editGlobalUSettingUrl,
|
||||
postUserByUserGroupUrl,
|
||||
deleteUserFromUserGroupUrl,
|
||||
getUserListUrl,
|
||||
addUserToUserGroupUrl,
|
||||
} from '@/api/requrls/setting/usergroup';
|
||||
import * as ugUrl from '@/api/requrls/setting/usergroup';
|
||||
import { TableQueryParams, CommonList } from '@/models/common';
|
||||
import { UserGroupItem, UserGroupAuthSetting, SaveGlobalUSettingData, UserTableItem } from '@/models/setting/usergroup';
|
||||
import {
|
||||
UserGroupItem,
|
||||
SystemUserGroupParams,
|
||||
OrgUserGroupParams,
|
||||
UserGroupAuthSetting,
|
||||
SaveGlobalUSettingData,
|
||||
UserTableItem,
|
||||
} from '@/models/setting/usergroup';
|
||||
|
||||
export function updateOrAddUserGroup(data: Partial<UserGroupItem>) {
|
||||
// 系统-创建或修改用户组
|
||||
export function updateOrAddUserGroup(data: SystemUserGroupParams) {
|
||||
return MSR.post<UserGroupItem>({
|
||||
url: data.id ? updateUserGroupU : addUserGroupU,
|
||||
url: data.id ? ugUrl.updateUserGroupU : ugUrl.addUserGroupU,
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 组织-创建或修改用户组
|
||||
export function updateOrAddOrgUserGroup(data: OrgUserGroupParams) {
|
||||
return MSR.post<UserGroupItem>({
|
||||
url: data.id ? ugUrl.updateOrgUserGroupU : ugUrl.addOrgUserGroupU,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateSocpe(data: Partial<UserGroupItem>) {
|
||||
export function updateSocpe(data: Partial<SystemUserGroupParams>) {
|
||||
return MSR.post<UserGroupItem>({
|
||||
url: updateUserGroupU,
|
||||
url: ugUrl.updateUserGroupU,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 系统-获取用户组列表
|
||||
export function getUserGroupList() {
|
||||
return MSR.get<UserGroupItem[]>({ url: getUserGroupU });
|
||||
return MSR.get<UserGroupItem[]>({ url: ugUrl.getUserGroupU });
|
||||
}
|
||||
// 组织-获取用户组列表
|
||||
export function getOrgUserGroupList(organizationId: string) {
|
||||
return MSR.get<UserGroupItem[]>({ url: `${ugUrl.getOrgUserGroupU}${organizationId}` });
|
||||
}
|
||||
// 系统-删除用户组
|
||||
export function deleteUserGroup(id: string) {
|
||||
return MSR.get<string>({ url: `${ugUrl.deleteUserGroupU}${id}` });
|
||||
}
|
||||
|
||||
export function deleteUserGroup(id: string) {
|
||||
return MSR.get<string>({ url: `${deleteUserGroupU}${id}` });
|
||||
// 组织-删除用户组
|
||||
export function deleteOrgUserGroup(id: string) {
|
||||
return MSR.get<string>({ url: `${ugUrl.deleteOrgUserGroupU}${id}` });
|
||||
}
|
||||
|
||||
export function getUsergroupInfo(id: string) {
|
||||
return MSR.get<UserGroupItem>({ url: `${getUserGroupU}${id}` });
|
||||
return MSR.get<UserGroupItem>({ url: `${ugUrl.getUserGroupU}${id}` });
|
||||
}
|
||||
|
||||
// 系统-获取用户组对应的权限配置
|
||||
export function getGlobalUSetting(id: string) {
|
||||
return MSR.get<UserGroupAuthSetting[]>({ url: `${getGlobalUSettingUrl}${id}` });
|
||||
return MSR.get<UserGroupAuthSetting[]>({ url: `${ugUrl.getGlobalUSettingUrl}${id}` });
|
||||
}
|
||||
// 组织-获取用户组对应的权限配置
|
||||
export function getOrgUSetting(id: string) {
|
||||
return MSR.get<UserGroupAuthSetting[]>({ url: `${ugUrl.getOrgUSettingUrl}${id}` });
|
||||
}
|
||||
|
||||
// 系统-编辑用户组对应的权限配置
|
||||
export function saveGlobalUSetting(data: SaveGlobalUSettingData) {
|
||||
return MSR.post<UserGroupAuthSetting[]>({ url: editGlobalUSettingUrl, data });
|
||||
return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editGlobalUSettingUrl, data });
|
||||
}
|
||||
|
||||
// 组织-编辑用户组对应的权限配置
|
||||
export function saveOrgUSetting(data: SaveGlobalUSettingData) {
|
||||
return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editOrgUSettingUrl, data });
|
||||
}
|
||||
|
||||
export function postUserByUserGroup(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<UserTableItem[]>>({ url: postUserByUserGroupUrl, data });
|
||||
return MSR.post<CommonList<UserTableItem[]>>({ url: ugUrl.postUserByUserGroupUrl, data });
|
||||
}
|
||||
|
||||
export function deleteUserFromUserGroup(id: string) {
|
||||
return MSR.get<string>({ url: `${deleteUserFromUserGroupUrl}${id}` });
|
||||
}
|
||||
|
||||
export function getUserList() {
|
||||
return MSR.get<UserTableItem[]>({ url: getUserListUrl });
|
||||
return MSR.get<string>({ url: `${ugUrl.deleteUserFromUserGroupUrl}${id}` });
|
||||
}
|
||||
|
||||
export function addUserToUserGroup(data: { roleId: string; userIds: string[] }) {
|
||||
return MSR.post<string>({ url: addUserToUserGroupUrl, data });
|
||||
return MSR.post<string>({ url: ugUrl.addUserToUserGroupUrl, data });
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export const postModifyProjectUrl = '/system/project/update';
|
|||
// 获取项目列表
|
||||
export const postProjectTableUrl = '/system/project/page';
|
||||
// 获取项目成员
|
||||
export const postProjectMemberUrl = '/system/project/member/list';
|
||||
export const postProjectMemberUrl = '/system/project/member-list';
|
||||
// 添加项目
|
||||
export const postAddProjectUrl = '/system/project/add';
|
||||
// 添加项目成员
|
||||
|
@ -45,7 +45,7 @@ export const getProjectInfoUrl = '/system/project/get/';
|
|||
// 删除项目
|
||||
export const getDeleteProjectUrl = '/system/project/delete/';
|
||||
// 系统-组织及项目,获取用户下拉选项
|
||||
export const getUserByOrgOrProjectUrl = '/system/user/get-option/';
|
||||
export const getUserByOrgOrProjectUrl = '/system/organization/get-option/';
|
||||
// 启用项目
|
||||
export const getEnableProjectUrl = '/system/project/enable/';
|
||||
// 禁用项目
|
||||
|
|
|
@ -19,5 +19,26 @@ export const postUserByUserGroupUrl = `/user/role/relation/global/list`;
|
|||
export const addUserToUserGroupUrl = `/user/role/relation/global/add`;
|
||||
/** 删除用户组用户 */
|
||||
export const deleteUserFromUserGroupUrl = `/user/role/relation/global/delete/`;
|
||||
/** 获取所有用户 */
|
||||
export const getUserListUrl = '/system/user/list';
|
||||
|
||||
// 组织下的用户组
|
||||
|
||||
export const updateOrgUserGroupU = `/user/role/organization/update`;
|
||||
/** 编辑用户组对应的权限配置 */
|
||||
export const editOrgUSettingUrl = `/user/role/organization/permission/update`;
|
||||
/** 添加用户组 */
|
||||
export const addOrgUserGroupU = `/user/role/organization/add`;
|
||||
/** 获取用户组对应的权限配置 */
|
||||
export const getOrgUSettingUrl = `/user/role/organization/permission/setting/`;
|
||||
/** 获取用户组 */
|
||||
export const getOrgUserGroupU = `/user/role/organization/list/`;
|
||||
/** 获取单个用户组信息 */
|
||||
export const getOrgUsergroupInfoU = `/user/role/organization/get/`;
|
||||
/** 删除用户组 */
|
||||
export const deleteOrgUserGroupU = `/user/role/organization/delete/`;
|
||||
|
||||
/** 根据用户组获取用户列表 */
|
||||
export const postOrgUserByUserGroupUrl = `/user/role/relation/organization/list`;
|
||||
/** 创建用户组添加用户 */
|
||||
export const addOrgUserToUserGroupUrl = `/user/role/relation/organization/add`;
|
||||
/** 删除用户组用户 */
|
||||
export const deleteOrgUserFromUserGroupUrl = `/user/role/relation/organization/delete/`;
|
||||
|
|
|
@ -100,4 +100,3 @@
|
|||
}
|
||||
);
|
||||
</script>
|
||||
@/api/modules/setting/organizationAndProject
|
||||
|
|
|
@ -128,6 +128,20 @@ export const pathMap = [
|
|||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_USER_ROLE', // 系统设置-组织-用户组
|
||||
locale: 'menu.settings.organization.userGroup',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_USER_GROUP,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_PROJECT', // 系统设置-组织-项目
|
||||
locale: 'menu.settings.organization.project',
|
||||
route: RouteEnum.SETTING_ORGANIZATION_PROJECT,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[1],
|
||||
},
|
||||
{
|
||||
key: 'SETTING_ORGANIZATION_SERVICE', // 系统设置-组织-服务集成
|
||||
locale: 'menu.settings.organization.serviceIntegration',
|
||||
|
|
|
@ -46,6 +46,8 @@ export enum SettingRouteEnum {
|
|||
SETTING_SYSTEM_PLUGIN_MANAGEMENT = 'settingSystemPluginManagement',
|
||||
SETTING_ORGANIZATION = 'settingOrganization',
|
||||
SETTING_ORGANIZATION_MEMBER = 'settingOrganizationMember',
|
||||
SETTING_ORGANIZATION_USER_GROUP = 'settingOrganizationUserGroup',
|
||||
SETTING_ORGANIZATION_PROJECT = 'settingOrganizationProject',
|
||||
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
|
||||
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',
|
||||
}
|
||||
|
|
|
@ -21,7 +21,17 @@ export default function useUser() {
|
|||
});
|
||||
};
|
||||
|
||||
const setSalt = (salt: string) => {
|
||||
userStore.setSalt(salt);
|
||||
};
|
||||
|
||||
const isLoginPage = () => {
|
||||
return router.currentRoute.value.name === 'login';
|
||||
};
|
||||
|
||||
return {
|
||||
logout,
|
||||
setSalt,
|
||||
isLoginPage,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,4 +43,5 @@ export default {
|
|||
'common.unSaveLeaveTitle': 'Leave this page?',
|
||||
'common.unSaveLeaveContent': 'The system may not save your changes',
|
||||
'common.leave': 'Leave',
|
||||
'common.rename': 'Rename',
|
||||
};
|
||||
|
|
|
@ -42,6 +42,8 @@ export default {
|
|||
'menu.settings.system.log': 'System Log',
|
||||
'menu.settings.organization': 'Organization',
|
||||
'menu.settings.organization.member': 'Member',
|
||||
'menu.settings.organization.userGroup': 'User Group',
|
||||
'menu.settings.organization.project': 'Project',
|
||||
'menu.settings.organization.serviceIntegration': 'Service Integration',
|
||||
'menu.settings.organization.log': 'Org Log',
|
||||
'navbar.action.locale': 'Switch to English',
|
||||
|
|
|
@ -43,4 +43,5 @@ export default {
|
|||
'common.unSaveLeaveTitle': '离开此页面?',
|
||||
'common.unSaveLeaveContent': '系统可能不会保存你所做的更改',
|
||||
'common.leave': '离开',
|
||||
'common.rename': '重命名',
|
||||
};
|
||||
|
|
|
@ -41,6 +41,8 @@ export default {
|
|||
'menu.settings.system.log': '系统日志',
|
||||
'menu.settings.organization': '组织',
|
||||
'menu.settings.organization.member': '成员',
|
||||
'menu.settings.organization.userGroup': '用户组',
|
||||
'menu.settings.organization.project': '项目',
|
||||
'menu.settings.organization.serviceIntegration': '服务集成',
|
||||
'menu.settings.organization.log': '组织日志',
|
||||
'navbar.action.locale': '切换为中文',
|
||||
|
|
|
@ -40,6 +40,18 @@ export interface UserGroupItem {
|
|||
pos: number;
|
||||
}
|
||||
|
||||
export interface SystemUserGroupParams {
|
||||
id?: string; // 组ID
|
||||
name?: string; // 名称
|
||||
scopeId?: string; // 组织ID
|
||||
type?: string; // 组类型:SYSTEM | PROJECT | ORGANIZATION
|
||||
}
|
||||
export interface OrgUserGroupParams {
|
||||
id?: string; // 组ID
|
||||
name: string;
|
||||
scopeId: string; // 组织ID
|
||||
}
|
||||
|
||||
export interface UserGroupPermissionItem {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
@ -148,6 +148,26 @@ const Setting: AppRouteRecordRaw = {
|
|||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'usergroup',
|
||||
name: SettingRouteEnum.SETTING_ORGANIZATION_USER_GROUP,
|
||||
component: () => import('@/views/setting/organization/usergroup/orgUserGroup.vue'),
|
||||
meta: {
|
||||
locale: 'menu.settings.organization.userGroup',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'project',
|
||||
name: SettingRouteEnum.SETTING_ORGANIZATION_PROJECT,
|
||||
component: () => import('@/views/setting/organization/project/orgProject.vue'),
|
||||
meta: {
|
||||
locale: 'menu.settings.organization.project',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'serviceIntegration',
|
||||
name: SettingRouteEnum.SETTING_ORGANIZATION_SERVICE,
|
||||
|
|
|
@ -4,10 +4,9 @@ import useAppStore from './modules/app';
|
|||
import useVisitStore from './modules/app/visit';
|
||||
import useUserStore from './modules/user';
|
||||
import { debouncePlugin } from './plugins';
|
||||
import useUserGroupStore from './modules/setting/usergroup';
|
||||
import useTableStore from './modules/ms-table';
|
||||
|
||||
const pinia = createPinia().use(debouncePlugin).use(piniaPluginPersistedstate);
|
||||
|
||||
export { useAppStore, useUserStore, useVisitStore, useUserGroupStore, useTableStore };
|
||||
export { useAppStore, useUserStore, useVisitStore, useTableStore };
|
||||
export default pinia;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { UserGroupState } from './types';
|
||||
import { UserGroupState } from '../types';
|
||||
|
||||
const useUserGroupStore = defineStore('userGroup', {
|
||||
const useOrgUserGroupStore = defineStore('orgUserGroup', {
|
||||
state: (): UserGroupState => ({
|
||||
currentName: '',
|
||||
currentTitle: '',
|
||||
|
@ -27,4 +27,4 @@ const useUserGroupStore = defineStore('userGroup', {
|
|||
},
|
||||
});
|
||||
|
||||
export default useUserGroupStore;
|
||||
export default useOrgUserGroupStore;
|
|
@ -0,0 +1,30 @@
|
|||
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;
|
|
@ -0,0 +1,30 @@
|
|||
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;
|
|
@ -8,7 +8,6 @@ import { useI18n } from '@/hooks/useI18n';
|
|||
|
||||
import type { LoginData } from '@/models/user';
|
||||
import type { UserState } from './types';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
// 开启数据持久化
|
||||
|
@ -30,6 +29,7 @@ const useUserStore = defineStore('user', {
|
|||
accountId: undefined,
|
||||
certification: undefined,
|
||||
role: '',
|
||||
salt: '',
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
@ -100,12 +100,15 @@ const useUserStore = defineStore('user', {
|
|||
if (appStore.currentOrgId === '') {
|
||||
appStore.setCurrentOrgId(res.lastOrganizationId || '');
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
const { t } = useI18n();
|
||||
Message.error(t('message.loginExpired'));
|
||||
this.logoutCallBack();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// 加盐
|
||||
setSalt(salt: string) {
|
||||
this.$patch({ salt });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -17,4 +17,6 @@ export interface UserState {
|
|||
certification?: number;
|
||||
role: RoleType;
|
||||
lastOrganizationId?: string;
|
||||
// 盐
|
||||
salt: string;
|
||||
}
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
import useLoading from '@/hooks/useLoading';
|
||||
import { setLoginExpires } from '@/utils/auth';
|
||||
import { GetLoginLogoUrl } from '@/api/requrls/setting/config';
|
||||
|
||||
import type { LoginData } from '@/models/user';
|
||||
import { WorkbenchRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
@ -121,7 +121,7 @@
|
|||
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
||||
setLoginExpires();
|
||||
router.push({
|
||||
name: (redirect as string) || 'settingSystemUser',
|
||||
name: (redirect as string) || WorkbenchRouteEnum.WORKBENCH,
|
||||
query: {
|
||||
...othersQuery,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="currentVisible"
|
||||
width="680px"
|
||||
class="ms-modal-form ms-modal-medium"
|
||||
:ok-text="isEdit ? t('common.update') : t('common.create')"
|
||||
unmount-on-close
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template #title>
|
||||
<span v-if="isEdit">
|
||||
{{ t('system.project.updateProject') }}
|
||||
<span class="text-[var(--color-text-4)]">({{ props.currentProject?.name }})</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('system.project.createProject') }}
|
||||
</span>
|
||||
</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.project.name')"
|
||||
:rules="[{ required: true, message: t('system.project.projectNameRequired') }]"
|
||||
>
|
||||
<a-input v-model="form.name" :placeholder="t('system.project.projectNamePlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
required
|
||||
field="organizationId"
|
||||
:label="t('system.project.affiliatedOrg')"
|
||||
:rules="[{ required: true, message: t('system.project.affiliatedOrgRequired') }]"
|
||||
>
|
||||
<a-select
|
||||
v-model="form.organizationId"
|
||||
:disabled="!isXpack"
|
||||
allow-search
|
||||
:options="affiliatedOrgOption"
|
||||
:default-value="isXpack ? '' : 'default_organization'"
|
||||
:placeholder="t('system.project.affiliatedOrgPlaceholder')"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="userIds" :label="t('system.project.projectAdmin')">
|
||||
<MsUserSelector v-model:value="form.userIds" placeholder="system.project.projectAdminPlaceholder" />
|
||||
</a-form-item>
|
||||
<a-form-item field="description" :label="t('system.organization.description')">
|
||||
<a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item field="module" :label="t('system.organization.description')">
|
||||
<a-checkbox-group v-model="form.moduleIds" :options="moduleOption">
|
||||
<template #label="{ data }">
|
||||
<span>{{ t(data.label) }}</span>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex flex-row items-center gap-[4px]">
|
||||
<a-switch v-model="form.enable" />
|
||||
<span>{{ t('system.organization.status') }}</span>
|
||||
<a-tooltip :content="t('system.project.createTip')" position="top">
|
||||
<MsIcon type="icon-icon-maybe_outlined" class="text-[var(--color-text-4)]" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="flex flex-row gap-[14px]">
|
||||
<a-button type="secondary" :loading="loading" @click="handleCancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="handleBeforeOk">
|
||||
{{ isEdit ? t('common.confirm') : t('common.create') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { reactive, ref, watchEffect, computed } from 'vue';
|
||||
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
||||
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
|
||||
import { createOrUpdateProject, getSystemOrgOption } from '@/api/modules/setting/organizationAndProject';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject';
|
||||
import useLicenseStore from '@/store/modules/setting/license';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
currentProject?: CreateOrUpdateSystemProjectParams;
|
||||
}>();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const loading = ref(false);
|
||||
const isEdit = computed(() => !!props.currentProject?.id);
|
||||
const affiliatedOrgOption = ref<SystemOrgOption[]>([]);
|
||||
const licenseStore = useLicenseStore();
|
||||
const moduleOption = [
|
||||
{ label: 'menu.workbench', value: 'workstation' },
|
||||
{ label: 'menu.testPlan', value: 'testPlan' },
|
||||
{ label: 'menu.bugManagement', value: 'bugManagement' },
|
||||
{ label: 'menu.featureTest', value: 'caseManagement' },
|
||||
{ label: 'menu.apiTest', value: 'apiTest' },
|
||||
{ label: 'menu.uiTest', value: 'uiTest' },
|
||||
{ label: 'menu.performanceTest', value: 'loadTest' },
|
||||
];
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
const form = reactive<CreateOrUpdateSystemProjectParams>({
|
||||
name: '',
|
||||
userIds: [],
|
||||
organizationId: '',
|
||||
description: '',
|
||||
enable: true,
|
||||
moduleIds: [],
|
||||
});
|
||||
|
||||
const currentVisible = ref(props.visible);
|
||||
|
||||
const isXpack = computed(() => {
|
||||
return licenseStore.hasLicense();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
currentVisible.value = props.visible;
|
||||
});
|
||||
const handleCancel = () => {
|
||||
formRef.value?.resetFields();
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const handleBeforeOk = async () => {
|
||||
await formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loading.value = true;
|
||||
await createOrUpdateProject({ id: props.currentProject?.id, ...form });
|
||||
Message.success(
|
||||
isEdit.value
|
||||
? t('system.organization.updateOrganizationSuccess')
|
||||
: t('system.organization.createOrganizationSuccess')
|
||||
);
|
||||
handleCancel();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const initAffiliatedOrgOption = async () => {
|
||||
try {
|
||||
const res = await getSystemOrgOption();
|
||||
affiliatedOrgOption.value = res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
watchEffect(() => {
|
||||
initAffiliatedOrgOption();
|
||||
if (props.currentProject) {
|
||||
form.id = props.currentProject.id;
|
||||
form.name = props.currentProject.name;
|
||||
form.description = props.currentProject.description;
|
||||
form.enable = props.currentProject.enable;
|
||||
form.userIds = props.currentProject.userIds;
|
||||
form.organizationId = props.currentProject.organizationId;
|
||||
form.moduleIds = props.currentProject.moduleIds;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@/api/modules/setting/organizationAndProject
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="currentVisible"
|
||||
title-align="start"
|
||||
class="ms-modal-form ms-modal-medium"
|
||||
:ok-text="t('system.organization.addMember')"
|
||||
unmount-on-close
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template #title> {{ t('system.organization.addMember') }} </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.organization.member')"
|
||||
:rules="[{ required: true, message: t('system.organization.addMemberRequired') }]"
|
||||
>
|
||||
<MsUserSelector v-model:value="form.name" type="organization" :source-id="organizationId || projectId" />
|
||||
</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="handleAddMember">
|
||||
{{ t('common.add') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { reactive, ref, watchEffect, onUnmounted } from 'vue';
|
||||
import { addUserToOrgOrProject } from '@/api/modules/setting/organizationAndProject';
|
||||
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
|
||||
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}>();
|
||||
|
||||
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 formRef = ref<FormInstance>();
|
||||
|
||||
watchEffect(() => {
|
||||
currentVisible.value = props.visible;
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
form.name = [];
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const handleAddMember = () => {
|
||||
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (errors) {
|
||||
loading.value = false;
|
||||
}
|
||||
const { organizationId, projectId } = props;
|
||||
try {
|
||||
loading.value = true;
|
||||
await addUserToOrgOrProject({ userIds: form.name, organizationId, projectId });
|
||||
Message.success(t('system.organization.addSuccess'));
|
||||
handleCancel();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
form.name = [];
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.option-name {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.option-email {
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
</style>
|
||||
@/api/modules/setting/organizationAndProject
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
:mask="false"
|
||||
:width="680"
|
||||
:visible="currentVisible"
|
||||
unmount-on-close
|
||||
:footer="false"
|
||||
:title="t('system.organization.addMember')"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<a-button type="primary" @click="handleAddMember">
|
||||
{{ t('system.organization.addMember') }}
|
||||
</a-button>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('system.user.searchUser')"
|
||||
class="w-[230px]"
|
||||
@search="searchUser"
|
||||
@press-enter="searchUser"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<ms-base-table class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<span>{{ record.name }}</span>
|
||||
<span v-if="record.adminFlag" class="ml-[4px] text-[var(--color-text-4)]">{{
|
||||
`(${t('common.admin')})`
|
||||
}}</span>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsRemoveButton
|
||||
:title="t('system.organization.removeName', { name: record.name })"
|
||||
:sub-title-tip="t('system.organization.removeTip')"
|
||||
@ok="handleRemove(record)"
|
||||
/>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
<AddUserModal
|
||||
:project-id="props.projectId"
|
||||
:organization-id="props.organizationId"
|
||||
:visible="userVisible"
|
||||
@cancel="handleHideUserModal"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
postUserTableByOrgIdOrProjectId,
|
||||
deleteUserFromOrgOrProject,
|
||||
} from '@/api/modules/setting/organizationAndProject';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { watch, ref } from 'vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import AddUserModal from './addUserModal.vue';
|
||||
import { TableData, Message } from '@arco-design/web-vue';
|
||||
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
|
||||
|
||||
export interface projectDrawerProps {
|
||||
visible: boolean;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<projectDrawerProps>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
const currentVisible = ref(props.visible);
|
||||
|
||||
const userVisible = ref(false);
|
||||
|
||||
const keyword = ref('');
|
||||
|
||||
const projectColumn: MsTableColumn = [
|
||||
{
|
||||
title: 'system.organization.userName',
|
||||
slotName: 'name',
|
||||
},
|
||||
{
|
||||
title: 'system.organization.email',
|
||||
dataIndex: 'email',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.phone',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{ title: 'system.organization.operation', slotName: 'operation' },
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postUserTableByOrgIdOrProjectId, {
|
||||
columns: projectColumn,
|
||||
showSetting: false,
|
||||
scroll: { y: 'auto', x: '600px' },
|
||||
selectable: false,
|
||||
size: 'small',
|
||||
noDisable: false,
|
||||
});
|
||||
|
||||
async function searchUser() {
|
||||
setKeyword(keyword.value);
|
||||
await loadList();
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoadListParams({ organizationId: props.organizationId });
|
||||
await loadList();
|
||||
};
|
||||
|
||||
const handleAddMember = () => {
|
||||
userVisible.value = true;
|
||||
};
|
||||
|
||||
const handleHideUserModal = () => {
|
||||
userVisible.value = false;
|
||||
};
|
||||
|
||||
const handleRemove = async (record: TableData) => {
|
||||
try {
|
||||
if (props.organizationId) {
|
||||
await deleteUserFromOrgOrProject(props.organizationId, record.id);
|
||||
}
|
||||
if (props.projectId) {
|
||||
await deleteUserFromOrgOrProject(props.projectId, record.id, true);
|
||||
}
|
||||
Message.success(t('common.removeSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.organizationId,
|
||||
() => {
|
||||
fetchData();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.projectId,
|
||||
() => {
|
||||
fetchData();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
currentVisible.value = visible;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@/api/modules/setting/organizationAndProject
|
|
@ -0,0 +1,74 @@
|
|||
export default {
|
||||
'system.organization.create': 'Create',
|
||||
'system.organization.cancel': 'Cancel',
|
||||
'system.organization.add': 'Add',
|
||||
'system.organization.delete': 'Delete',
|
||||
'system.organization.edit': 'Edit',
|
||||
'system.organization.save': 'Save',
|
||||
'system.organization.end': 'End',
|
||||
'system.organization.removeName': 'Confirm to remove {name} this user',
|
||||
'system.organization.removeTip': 'After removal, the organization permission will be lost',
|
||||
'system.organization.addMember': 'Add Member',
|
||||
'system.organization.addMemberPlaceholder': 'Please select member',
|
||||
'system.organization.addMemberRequired': 'Please select member',
|
||||
'system.organization.searchPlaceholder': 'Please enter the organization name to query',
|
||||
'system.organization.addOrganization': 'Add organization',
|
||||
'system.organization.createOrganization': 'Create organization',
|
||||
'system.organization.organizationName': 'Organization name',
|
||||
'system.organization.organizationNamePlaceholder':
|
||||
'Please enter the organization name, which cannot be duplicated with other organization names',
|
||||
'system.organization.organizationNameRequired': 'Organization name cannot be empty',
|
||||
'system.organization.organizationNameDuplicate': 'Already have {name} please change',
|
||||
'system.organization.organizationAdmin': 'Organization administrator',
|
||||
'system.organization.organizationAdminPlaceholder':
|
||||
'The organization administrator defaults to the person who created the organization',
|
||||
'system.organization.description': 'Description',
|
||||
'system.organization.descriptionPlaceholder': 'Please describe the organization',
|
||||
'system.organization.ID': 'ID',
|
||||
'system.organization.name': 'Name',
|
||||
'system.organization.member': 'Member',
|
||||
'system.organization.project': 'Project',
|
||||
'system.organization.status': 'Status',
|
||||
'system.organization.creator': 'Creator',
|
||||
'system.organization.createTime': 'Create Time',
|
||||
'system.organization.operation': 'Operation',
|
||||
'system.organization.organizationCount': 'Organization({count})',
|
||||
'system.organization.projectCount': 'Project({count})',
|
||||
'system.organization.projectName': 'Project({name})',
|
||||
'system.project.name': 'Project name',
|
||||
'system.organization.userName': 'Name',
|
||||
'system.organization.email': 'Email',
|
||||
'system.organization.phone': 'Phone',
|
||||
'system.organization.addSuccess': 'Add success',
|
||||
'system.organization.deleteName': 'Are you sure to delete {name}',
|
||||
'system.organization.deleteTip':
|
||||
'Delete the organization and delete the project data under that organization together. Please be cautious!',
|
||||
'system.organization.revokeDeleteToolTip': 'The organization will be deleted automatically after 30 days',
|
||||
'system.organization.createOrganizationSuccess': 'Create organization success',
|
||||
'system.organization.enableTitle': 'Start organization',
|
||||
'system.organization.endTitle': 'Close organization',
|
||||
'system.organization.enableContent': 'The organization after opening is displayed in the organization switching list',
|
||||
'system.organization.endContent':
|
||||
'The organization after closing is not displayed in the organization switching list',
|
||||
'system.organization.updateOrganization': 'Update organization',
|
||||
'system.organization.updateOrganizationSuccess': 'Update organization success',
|
||||
'system.organization.createProject': 'Create project',
|
||||
'system.organization.subordinateOrg': 'Subordinate organization',
|
||||
'system.project.revokeDeleteTitle': 'Confirm revoke {name} ?',
|
||||
'system.project.enableTitle': 'Start project',
|
||||
'system.project.endTitle': 'Close project',
|
||||
'system.project.enableContent': 'The project after opening is displayed in the organization switching list',
|
||||
'system.project.endContent': 'The project after closing is not displayed in the project switching list',
|
||||
'system.project.projectNamePlaceholder':
|
||||
'Please enter the project name, which cannot be duplicated with other project names',
|
||||
'system.project.updateProject': 'Update project',
|
||||
'system.project.createProject': 'Create project',
|
||||
'system.project.affiliatedOrg': 'Affiliated organization',
|
||||
'system.project.affiliatedOrgPlaceholder': 'Please select affiliated organization',
|
||||
'system.project.projectAdmin': 'Project administrator',
|
||||
'system.project.projectAdminPlaceholder': 'The project administrator defaults to the person who created the project',
|
||||
'system.project.moduleSetting': 'Module setting',
|
||||
'system.project.projectNameRequired': 'Project name cannot be empty',
|
||||
'system.project.createTip': 'After the project is enabled, it will be displayed in the project switching list',
|
||||
'system.project.affiliatedOrgRequired': 'Affiliated organization cannot be empty',
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
export default {
|
||||
'system.organization.create': '创建',
|
||||
'system.organization.cancel': '取消',
|
||||
'system.organization.add': '添加',
|
||||
'system.organization.delete': '删除',
|
||||
'system.organization.edit': '编辑',
|
||||
'system.organization.save': '保存',
|
||||
'system.organization.end': '结束',
|
||||
'system.organization.removeName': '确认移除 {name} 这个用户吗',
|
||||
'system.organization.removeTip': '移除后,将失去组织权限',
|
||||
'system.organization.addMember': '添加成员',
|
||||
'system.organization.addMemberPlaceholder': '请选择成员',
|
||||
'system.organization.addMemberRequired': '请选择成员',
|
||||
'system.organization.searchPlaceholder': '请输入组织名称回车查询',
|
||||
'system.organization.addOrganization': '添加组织',
|
||||
'system.organization.createOrganization': '创建组织',
|
||||
'system.organization.organizationName': '组织名称',
|
||||
'system.organization.organizationNamePlaceholder': '请输入组织名称,不可与其他组织名称重复',
|
||||
'system.organization.organizationNameRequired': '组织名称不能为空',
|
||||
'system.organization.organizationNameDuplicate': '已有 {name} 请更改',
|
||||
'system.organization.organizationAdmin': '组织管理员',
|
||||
'system.organization.organizationAdminPlaceholder': '默认选择创建组织人为组织管理员',
|
||||
'system.organization.description': '描述',
|
||||
'system.organization.descriptionPlaceholder': '请对组织进行描述',
|
||||
'system.organization.ID': 'ID',
|
||||
'system.organization.name': '名称',
|
||||
'system.organization.member': '成员',
|
||||
'system.organization.project': '项目',
|
||||
'system.organization.status': '状态',
|
||||
'system.organization.creator': '创建人',
|
||||
'system.organization.createTime': '创建时间',
|
||||
'system.organization.operation': '操作',
|
||||
'system.organization.organizationCount': '组织({count})',
|
||||
'system.organization.projectCount': '项目({count})',
|
||||
'system.organization.projectName': '项目({name})',
|
||||
'system.project.name': '项目名称',
|
||||
'system.organization.userName': '姓名',
|
||||
'system.organization.email': '邮箱',
|
||||
'system.organization.phone': '手机',
|
||||
'system.organization.addSuccess': '添加成功',
|
||||
'system.organization.deleteName': '确认删除 {name} 这个组织吗',
|
||||
'system.organization.deleteTip': '删除组织同时将该组织下的项目数据一起删除,请谨慎操作!',
|
||||
'system.organization.revokeDeleteToolTip': '该组织将与 30 天后自动删除',
|
||||
'system.organization.createOrganizationSuccess': '创建组织成功',
|
||||
'system.organization.enableTitle': '开启组织',
|
||||
'system.organization.endTitle': '关闭组织',
|
||||
'system.organization.enableContent': '开启后的组织展示在组织切换列表',
|
||||
'system.organization.endContent': '关闭后的组织不展示在组织切换列表',
|
||||
'system.organization.updateOrganization': '更新组织',
|
||||
'system.organization.updateOrganizationSuccess': '更新组织成功',
|
||||
'system.organization.createProject': '创建项目',
|
||||
'system.organization.subordinateOrg': '所属组织',
|
||||
'system.project.revokeDeleteTitle': '确认恢复 {name} 这个项目吗?',
|
||||
'system.project.enableTitle': '开启项目',
|
||||
'system.project.endTitle': '关闭项目',
|
||||
'system.project.enableContent': '开启后的项目展示在项目切换列表',
|
||||
'system.project.endContent': '关闭后的项目不展示在项目切换列表',
|
||||
'system.project.projectNamePlaceholder': '请输入项目名称,不可与其他项目名称重复',
|
||||
'system.project.updateProject': '更新项目',
|
||||
'system.project.createProject': '创建项目',
|
||||
'system.project.affiliatedOrg': '所属组织',
|
||||
'system.project.affiliatedOrgPlaceholder': '请选择所属组织',
|
||||
'system.project.projectAdmin': '项目管理员',
|
||||
'system.project.projectAdminPlaceholder': '默认选择创建项目人为项目管理员',
|
||||
'system.project.moduleSetting': '模块设置',
|
||||
'system.project.projectNameRequired': '项目名称不能为空',
|
||||
'system.project.createTip': '项目启用后,将展示在项目切换列表',
|
||||
'system.project.affiliatedOrgRequired': '所属组织不能为空',
|
||||
};
|
|
@ -0,0 +1,290 @@
|
|||
<template>
|
||||
<MsCard simple>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<a-button type="primary" @click="showAddProject">{{ t('system.organization.createProject') }}</a-button>
|
||||
<a-input-search
|
||||
v-model="keyword"
|
||||
:placeholder="t('system.user.searchUser')"
|
||||
class="w-[240px]"
|
||||
@press-enter="fetchData"
|
||||
@search="fetchData"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<span>{{ record.name }}</span>
|
||||
<a-tooltip background-color="#FFFFFF">
|
||||
<template #content>
|
||||
<span class="text-[var(--color-text-1)]">{{ t('system.organization.revokeDeleteToolTip') }}</span>
|
||||
<MsButton class="ml-[8px]" @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton>
|
||||
</template>
|
||||
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #creator="{ record }">
|
||||
<span>{{ record.createUser }}</span>
|
||||
<span v-if="record.projectCreateUserIsAdmin" class="ml-[8px] text-[var(--color-text-4)]">{{
|
||||
`(${t('common.admin')})`
|
||||
}}</span>
|
||||
</template>
|
||||
<template #memberCount="{ record }">
|
||||
<span class="primary-color" @click="showUserDrawer(record)">{{ record.memberCount }}</span>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<template v-if="record.deleted">
|
||||
<MsButton @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton>
|
||||
</template>
|
||||
<template v-else-if="!record.enable">
|
||||
<MsButton @click="handleEnableOrDisableProject(record)">{{ t('common.enable') }}</MsButton>
|
||||
<MsButton @click="handleDelete(record)">{{ t('common.delete') }}</MsButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<MsButton @click="showAddProjectModal(record)">{{ t('common.edit') }}</MsButton>
|
||||
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton>
|
||||
<MsButton @click="handleEnableOrDisableProject(record, false)">{{ t('common.end') }}</MsButton>
|
||||
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
|
||||
</template>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<AddProjectModal
|
||||
:current-project="currentUpdateProject"
|
||||
:visible="addProjectVisible"
|
||||
@cancel="handleAddProjectModalCancel"
|
||||
/>
|
||||
<AddUserModal :project-id="currentProjectId" :visible="userVisible" @cancel="handleAddUserModalCancel" />
|
||||
<UserDrawer :project-id="currentProjectId" v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" />
|
||||
</MsCard>
|
||||
</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 { useTableStore } from '@/store';
|
||||
import { ref, reactive } from 'vue';
|
||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import {
|
||||
postProjectTable,
|
||||
deleteProject,
|
||||
enableOrDisableProject,
|
||||
revokeDeleteProject,
|
||||
} from '@/api/modules/setting/organizationAndProject';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import { Message, TableData } from '@arco-design/web-vue';
|
||||
import UserDrawer from './components/userDrawer.vue';
|
||||
import AddUserModal from './components/addUserModal.vue';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject';
|
||||
import AddProjectModal from './components/addProjectModal.vue';
|
||||
import { UserItem } from '@/components/business/ms-user-selector/index.vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const tableStore = useTableStore();
|
||||
const userVisible = ref(false);
|
||||
const addProjectVisible = ref(false);
|
||||
const currentProjectId = ref('');
|
||||
const currentUpdateProject = ref<CreateOrUpdateSystemProjectParams>();
|
||||
const { openDeleteModal, openModal } = useModal();
|
||||
|
||||
const keyword = ref('');
|
||||
|
||||
const organizationColumns: MsTableColumn = [
|
||||
{
|
||||
title: 'system.organization.ID',
|
||||
dataIndex: 'num',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.name',
|
||||
slotName: 'name',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.member',
|
||||
slotName: 'memberCount',
|
||||
},
|
||||
{
|
||||
title: 'system.organization.status',
|
||||
dataIndex: 'enable',
|
||||
},
|
||||
{
|
||||
title: 'system.organization.description',
|
||||
dataIndex: 'description',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.subordinateOrg',
|
||||
dataIndex: 'organizationName',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'system.organization.creator',
|
||||
slotName: 'creator',
|
||||
dataIndex: 'createUser',
|
||||
},
|
||||
{
|
||||
title: 'system.organization.createTime',
|
||||
dataIndex: 'createTime',
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
title: 'system.organization.operation',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 208,
|
||||
},
|
||||
];
|
||||
|
||||
tableStore.initColumn(TableKeyEnum.SYSTEM_PROJECT, organizationColumns, 'drawer');
|
||||
|
||||
const { propsRes, propsEvent, loadList, setKeyword } = useTable(postProjectTable, {
|
||||
tableKey: TableKeyEnum.SYSTEM_PROJECT,
|
||||
scroll: { y: 'auto', x: '1300px' },
|
||||
selectable: false,
|
||||
noDisable: false,
|
||||
size: 'default',
|
||||
showSetting: true,
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
setKeyword(keyword.value);
|
||||
await loadList();
|
||||
};
|
||||
|
||||
const currentUserDrawer = reactive({
|
||||
visible: false,
|
||||
organizationId: '',
|
||||
});
|
||||
|
||||
const tableActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'system.user.delete',
|
||||
eventTag: 'delete',
|
||||
danger: true,
|
||||
},
|
||||
];
|
||||
|
||||
const showAddProject = () => {
|
||||
addProjectVisible.value = true;
|
||||
};
|
||||
|
||||
const handleDelete = (record: TableData) => {
|
||||
openDeleteModal({
|
||||
title: t('system.organization.deleteName', { name: record.name }),
|
||||
content: t('system.organization.deleteTip'),
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await deleteProject(record.id);
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreAction = (tag: ActionsItem, record: TableData) => {
|
||||
if (tag.eventTag === 'delete') {
|
||||
handleDelete(record);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnableOrDisableProject = async (record: any, isEnable = true) => {
|
||||
const title = isEnable ? t('system.project.enableTitle') : t('system.project.endTitle');
|
||||
const content = isEnable ? t('system.project.enableContent') : t('system.project.endContent');
|
||||
const okText = isEnable ? t('common.confirmEnable') : t('common.confirmClose');
|
||||
openModal({
|
||||
type: 'error',
|
||||
cancelText: t('common.cancel'),
|
||||
title,
|
||||
content,
|
||||
okText,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await enableOrDisableProject(record.id, isEnable);
|
||||
Message.success(isEnable ? t('common.enableSuccess') : t('common.closeSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
};
|
||||
|
||||
const showAddProjectModal = (record: any) => {
|
||||
const { id, name, description, enable, adminList, organizationId, moduleIds } = record;
|
||||
addProjectVisible.value = true;
|
||||
currentUpdateProject.value = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
enable,
|
||||
userIds: adminList.map((item: UserItem) => item.id),
|
||||
organizationId,
|
||||
moduleIds,
|
||||
};
|
||||
};
|
||||
|
||||
const showAddUserModal = (record: any) => {
|
||||
currentProjectId.value = record.id;
|
||||
userVisible.value = true;
|
||||
};
|
||||
|
||||
const showUserDrawer = (record: TableData) => {
|
||||
currentUserDrawer.visible = true;
|
||||
currentUserDrawer.organizationId = record.id;
|
||||
};
|
||||
|
||||
const handleUserDrawerCancel = () => {
|
||||
currentUserDrawer.visible = false;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleAddUserModalCancel = () => {
|
||||
userVisible.value = false;
|
||||
fetchData();
|
||||
};
|
||||
const handleAddProjectModalCancel = () => {
|
||||
addProjectVisible.value = false;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleRevokeDelete = async (record: TableData) => {
|
||||
openModal({
|
||||
type: 'error',
|
||||
cancelText: t('common.cancel'),
|
||||
title: t('system.project.revokeDeleteTitle', { name: record.name }),
|
||||
content: t('system.organization.enableContent'),
|
||||
okText: t('common.revokeDelete'),
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await revokeDeleteProject(record.id);
|
||||
Message.success(t('common.revokeDeleteSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.primary-color {
|
||||
color: rgb(var(--primary-5));
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<a-popconfirm
|
||||
:popup-visible="currentVisible"
|
||||
:ok-text="props.id ? t('common.rename') : t('common.create')"
|
||||
unmount-on-close
|
||||
:on-before-ok="handleBeforeOk"
|
||||
:ok-loading="loading"
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
position="bl"
|
||||
class="w-[276px]"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<template #icon>{{ null }}</template>
|
||||
<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-[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>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</a-popconfirm>
|
||||
</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;
|
||||
}>();
|
||||
|
||||
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 = '';
|
||||
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')
|
||||
);
|
||||
loading.value = false;
|
||||
handleCancel();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
watchEffect(() => {
|
||||
currentVisible.value = props.visible;
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,101 @@
|
|||
<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') }]"
|
||||
>
|
||||
<MsUserSelector v-model:value="form.name" />
|
||||
</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/organization/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>
|
|
@ -0,0 +1,322 @@
|
|||
<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) => handleAuthChange(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="handleAllChangeByCheckbox"
|
||||
></a-checkbox>
|
||||
</template>
|
||||
<template #cell="{ record, rowIndex }">
|
||||
<a-checkbox
|
||||
:model-value="record.enable"
|
||||
:indeterminate="record.indeterminate"
|
||||
:disabled="currentInternal"
|
||||
@change="(value) => handleActionChangeAll(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 { 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';
|
||||
|
||||
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 type
|
||||
* @param idx
|
||||
*/
|
||||
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;
|
||||
}, []) || [];
|
||||
result.push({
|
||||
id: child?.id,
|
||||
license: child?.license,
|
||||
enable: child?.enable,
|
||||
permissions: child?.permissions,
|
||||
indeterminate: perChecked?.length > 0,
|
||||
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;
|
||||
};
|
||||
|
||||
const initData = async (id: string, internal: boolean) => {
|
||||
try {
|
||||
let tmpArr = [];
|
||||
let res: UserGroupAuthSetting[] = [];
|
||||
loading.value = true;
|
||||
if (internal) {
|
||||
res = await getGlobalUSetting(id);
|
||||
} else {
|
||||
res = await getOrgUSetting(id);
|
||||
}
|
||||
tmpArr = transformData(res);
|
||||
tableData.value = tmpArr;
|
||||
} catch (error) {
|
||||
tableData.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
// 表格总全选change事件
|
||||
const handleAllChangeByCheckbox = () => {
|
||||
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 = () => {
|
||||
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 (!canSave.value) canSave.value = true;
|
||||
};
|
||||
|
||||
// 表格最后一列的复选框change事件
|
||||
const handleActionChangeAll = (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 handleAuthChange = (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;
|
||||
handleAllChange();
|
||||
} else if (values.length === 0) {
|
||||
tmpArr[rowIndex].enable = false;
|
||||
tmpArr[rowIndex].indeterminate = false;
|
||||
handleAllChange();
|
||||
} else {
|
||||
tmpArr[rowIndex].enable = false;
|
||||
tmpArr[rowIndex].indeterminate = true;
|
||||
}
|
||||
if (!canSave.value) canSave.value = true;
|
||||
};
|
||||
|
||||
// 保存
|
||||
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 saveOrgUSetting({
|
||||
userRoleId: store.currentId,
|
||||
permissions,
|
||||
});
|
||||
Message.success(t('common.saveSuccess'));
|
||||
initData(store.currentId, store.currentInternal);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复默认值
|
||||
const handleReset = () => {
|
||||
if (store.currentId) {
|
||||
initData(store.currentId, store.currentInternal);
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (store.currentId) {
|
||||
initData(store.currentId, store.currentInternal);
|
||||
}
|
||||
});
|
||||
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>
|
||||
@/store/modules/setting/system/usergroup
|
|
@ -0,0 +1,284 @@
|
|||
<template>
|
||||
<div class="user-group-left">
|
||||
<a-input-search
|
||||
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 px-[8px] leading-[24px]">
|
||||
<div class="second-color"> {{ t('system.userGroup.global') }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
v-for="element in globalUserGroupList"
|
||||
:key="element.id"
|
||||
:class="{
|
||||
'flex': true,
|
||||
'h-[38px]': true,
|
||||
'items-center': true,
|
||||
'px-[8px]': true,
|
||||
'is-active': element.id === currentId,
|
||||
}"
|
||||
@click="handleListItemClick(element)"
|
||||
>
|
||||
<div class="flex grow flex-row">
|
||||
<div class="usergroup-title leading-[24px]">
|
||||
<span class="n1">{{ element.name }}</span>
|
||||
<span v-if="element.type" class="n4">({{ t(`system.userGroup.${element.type}`) }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="mt-2" />
|
||||
<div class="mt-2 flex flex-col">
|
||||
<AddOrUpdateUserGroupPopup
|
||||
:visible="addUserGroupVisible"
|
||||
:list="customUserGroupList"
|
||||
@cancel="handleAddUserGroupCancel"
|
||||
>
|
||||
<div class="flex h-[38px] items-center justify-between px-[8px] leading-[24px]">
|
||||
<div class="second-color"> {{ t('system.userGroup.custom') }}</div>
|
||||
<div class="primary-color"><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] items-center px-[8px]"
|
||||
:class="{ 'is-active': 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="draglist-item flex grow flex-row justify-between">
|
||||
<div class="usergroup-title leading-[24px]">
|
||||
<span class="n1">{{ element.name }}</span>
|
||||
<span v-if="element.type" class="n4">({{ t(`system.userGroup.${element.type}`) }})</span>
|
||||
</div>
|
||||
<div v-if="element.id === currentId && !element.internal">
|
||||
<MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" />
|
||||
</div>
|
||||
</div>
|
||||
</AddOrUpdateUserGroupPopup>
|
||||
</div>
|
||||
</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 { 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 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, updateOrAddOrgUserGroup, deleteOrgUserGroup } from '@/api/modules/setting/usergroup';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
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;
|
||||
initData();
|
||||
};
|
||||
// 点击更多操作
|
||||
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>
|
||||
|
||||
<style scoped lang="less">
|
||||
.primary-color {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
.n1 {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.n4 {
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.second-color {
|
||||
color: var(--color-text-input-border);
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.is-active {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.custom-empty {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
border-radius: 4px;
|
||||
color: #8f959e;
|
||||
background: #f7f9fc;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.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));
|
||||
}
|
||||
.usergroup-title {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<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')"
|
||||
@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 { useTableStore } from '@/store';
|
||||
import useUserGroupStore from '@/store/modules/setting/organization/usergroup';
|
||||
import { watchEffect, ref, watch } from 'vue';
|
||||
import { postUserByUserGroup, deleteUserFromUserGroup } from '@/api/modules/setting/usergroup';
|
||||
import { UserTableItem } from '@/models/setting/usergroup';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
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 tableStore = useTableStore();
|
||||
const userVisible = 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,
|
||||
},
|
||||
];
|
||||
|
||||
tableStore.initColumn(TableKeyEnum.USERGROUPUSER, userGroupUsercolumns, 'drawer');
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postUserByUserGroup, {
|
||||
tableKey: TableKeyEnum.USERGROUPUSER,
|
||||
scroll: { x: '600px' },
|
||||
selectable: true,
|
||||
noDisable: true,
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
setKeyword(props.keyword);
|
||||
await loadList();
|
||||
};
|
||||
const handleRemove = async (record: UserTableItem) => {
|
||||
try {
|
||||
await deleteUserFromUserGroup(record.id);
|
||||
await fetchData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
const handleAddUser = () => {
|
||||
userVisible.value = true;
|
||||
};
|
||||
const handleAddUserModalCancel = () => {
|
||||
fetchData();
|
||||
userVisible.value = false;
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (store.currentId) {
|
||||
setLoadListParams({ roleId: store.currentId });
|
||||
fetchData();
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => props.keyword,
|
||||
() => {
|
||||
fetchData();
|
||||
}
|
||||
);
|
||||
</script>
|
|
@ -0,0 +1,148 @@
|
|||
<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="usergroup-collapse" @click="handleCollapse">
|
||||
<MsIcon v-if="collapse" 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">
|
||||
<div class="title">{{ store.userGroupInfo.currentName }}</div>
|
||||
<div class="flex items-center">
|
||||
<a-input-search
|
||||
v-if="currentTable === 'user'"
|
||||
:placeholder="t('system.user.searchUser')"
|
||||
class="w-[240px]"
|
||||
@press-enter="handleEnter"
|
||||
@search="handleSearch"
|
||||
></a-input-search>
|
||||
<a-radio-group v-if="couldShowUser && couldShowAuth" v-model="currentTable" class="ml-[14px]" type="button">
|
||||
<a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
|
||||
<a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[16px]">
|
||||
<user-table v-if="currentTable === 'user' && couldShowUser" :keyword="currentKeyword" />
|
||||
<auth-table v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MsCard>
|
||||
<div
|
||||
v-if="currentTable === 'auth'"
|
||||
class="fixed bottom-[16px] right-[16px] z-[999] flex justify-between bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
||||
:style="{ width: `calc(100% - ${menuWidth + 16}px)` }"
|
||||
>
|
||||
<ms-button class="btn" :disabled="!canSave" @click="handleReset">{{ t('system.userGroup.reset') }}</ms-button>
|
||||
<a-button class="btn" :disabled="!canSave" type="primary" @click="handleSave">{{
|
||||
t('system.userGroup.save')
|
||||
}}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watchEffect } 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 MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const currentTable = ref('auth');
|
||||
|
||||
const { t } = useI18n();
|
||||
const currentKeyword = ref('');
|
||||
const authRef = ref<{
|
||||
handleReset: () => void;
|
||||
handleSave: () => void;
|
||||
canSave: boolean;
|
||||
}>();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
currentKeyword.value = value;
|
||||
};
|
||||
const handleEnter = (eve: Event) => {
|
||||
currentKeyword.value = (eve.target as HTMLInputElement).value;
|
||||
};
|
||||
|
||||
const store = useUserGroupStore();
|
||||
const couldShowUser = computed(() => store.userGroupInfo.currentType === 'ORGANIZATION');
|
||||
const couldShowAuth = computed(() => store.userGroupInfo.currentId !== 'admin');
|
||||
const handleCollapse = () => {
|
||||
store.setCollapse(!store.collapse);
|
||||
};
|
||||
const collapse = computed(() => store.collapse);
|
||||
const menuWidth = computed(() => {
|
||||
const width = appStore.menuCollapse ? 86 : appStore.menuWidth;
|
||||
if (store.collapse) {
|
||||
return width + 300;
|
||||
}
|
||||
return width + 24;
|
||||
});
|
||||
|
||||
const handleReset = () => {
|
||||
authRef.value?.handleReset();
|
||||
};
|
||||
const handleSave = () => {
|
||||
authRef.value?.handleSave();
|
||||
};
|
||||
const canSave = computed(() => {
|
||||
if (currentTable.value === 'auth') {
|
||||
return authRef.value?.canSave;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
watchEffect(() => {
|
||||
if (!couldShowAuth.value) {
|
||||
currentTable.value = 'user';
|
||||
} else if (!couldShowUser.value) {
|
||||
currentTable.value = 'auth';
|
||||
} else {
|
||||
currentTable.value = 'auth';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.user-group {
|
||||
height: calc(100vh - 72px);
|
||||
}
|
||||
.user-group-left {
|
||||
position: relative;
|
||||
border-right: 1px solid var(--color-border);
|
||||
.usergroup-collapse {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -16px;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 36px;
|
||||
background-color: var(--color-text-n8);
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.icon {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/store/modules/setting/system/usergroup
|
|
@ -4,7 +4,7 @@
|
|||
<span>{{ record.name }}</span>
|
||||
<a-tooltip background-color="#FFFFFF">
|
||||
<template #content>
|
||||
<span class="text-[var(--color-text-1)]">{{ t('system.organization.revokeDeleteToolTip') }}</span>
|
||||
<span class="text-[var(--color-text-1)]">{{ t('system.project.revokeDeleteToolTip') }}</span>
|
||||
<MsButton class="ml-[8px]" @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton>
|
||||
</template>
|
||||
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
|
||||
|
@ -252,7 +252,7 @@
|
|||
type: 'error',
|
||||
cancelText: t('common.cancel'),
|
||||
title: t('system.project.revokeDeleteTitle', { name: record.name }),
|
||||
content: t('system.organization.enableContent'),
|
||||
content: t('system.project.enableContent'),
|
||||
okText: t('common.revokeDelete'),
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
|
|
|
@ -114,7 +114,12 @@
|
|||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
if (props.organizationId) {
|
||||
setLoadListParams({ organizationId: props.organizationId });
|
||||
}
|
||||
if (props.projectId) {
|
||||
setLoadListParams({ projectId: props.projectId });
|
||||
}
|
||||
await loadList();
|
||||
};
|
||||
|
||||
|
@ -161,4 +166,3 @@
|
|||
}
|
||||
);
|
||||
</script>
|
||||
@/api/modules/setting/organizationAndProject
|
||||
|
|
|
@ -71,4 +71,5 @@ export default {
|
|||
'system.project.projectNameRequired': 'Project name cannot be empty',
|
||||
'system.project.createTip': 'After the project is enabled, it will be displayed in the project switching list',
|
||||
'system.project.affiliatedOrgRequired': 'Affiliated organization cannot be empty',
|
||||
'system.project.revokeDeleteToolTip': 'The project will be deleted automatically after 30 days',
|
||||
};
|
||||
|
|
|
@ -66,4 +66,5 @@ export default {
|
|||
'system.project.projectNameRequired': '项目名称不能为空',
|
||||
'system.project.createTip': '项目启用后,将展示在项目切换列表',
|
||||
'system.project.affiliatedOrgRequired': '所属组织不能为空',
|
||||
'system.project.revokeDeleteToolTip': '该项目将与 30 天后自动删除',
|
||||
};
|
||||
|
|
|
@ -33,10 +33,9 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { reactive, ref, watchEffect, onMounted } from 'vue';
|
||||
import { UserTableItem } from '@/models/setting/usergroup';
|
||||
import { useUserGroupStore } from '@/store';
|
||||
import { getUserList, addUserToUserGroup } from '@/api/modules/setting/usergroup';
|
||||
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';
|
||||
|
||||
|
@ -61,18 +60,8 @@
|
|||
|
||||
const labelCache = new Map();
|
||||
|
||||
const allOption = ref<UserTableItem[]>([]);
|
||||
|
||||
const userOptions = ref<UserTableItem[]>([]);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const initUserList = async () => {
|
||||
const res = await getUserList();
|
||||
allOption.value = res;
|
||||
userOptions.value = res;
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
currentVisible.value = props.visible;
|
||||
});
|
||||
|
@ -100,10 +89,6 @@
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
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/usergroup';
|
||||
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';
|
||||
|
||||
|
@ -313,3 +313,4 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
@/store/modules/setting/system/usergroup
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
import useModal from '@/hooks/useModal';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import popconfirm from './popconfirm.vue';
|
||||
import useUserGroupStore from '@/store/modules/setting/usergroup';
|
||||
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
|
||||
import { getUserGroupList, updateOrAddUserGroup, deleteUserGroup } from '@/api/modules/setting/usergroup';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
|
@ -174,6 +174,7 @@
|
|||
Message.success(t('system.user.deleteUserSuccess'));
|
||||
initData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
|
@ -301,4 +302,4 @@
|
|||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
||||
@/models/setting/usergroup @/api/modules/setting/usergroup
|
||||
@/models/setting/usergroup @/api/modules/setting/usergroup @/store/modules/setting/system/usergroup
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
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, useTableStore } from '@/store';
|
||||
import { useTableStore } from '@/store';
|
||||
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
|
||||
import { watchEffect, ref, watch } from 'vue';
|
||||
import { postUserByUserGroup, deleteUserFromUserGroup } from '@/api/modules/setting/usergroup';
|
||||
import { UserTableItem } from '@/models/setting/usergroup';
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
import { ref, computed, watchEffect } from 'vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import useUserGroupStore from '@/store/modules/setting/usergroup';
|
||||
import useUserGroupStore from '@/store/modules/setting/system/usergroup';
|
||||
import UserGroupLeft from './components/index.vue';
|
||||
import UserTable from './components/userTable.vue';
|
||||
import AuthTable from './components/authTable.vue';
|
||||
|
@ -145,3 +145,4 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
@/store/modules/setting/system/usergroup
|
||||
|
|
|
@ -56,6 +56,7 @@ export default {
|
|||
quickAddUser: 'Quick add user',
|
||||
removeName: 'Confirm to remove {name} this user',
|
||||
removeTip: 'After removal, the User Group permission will be lost',
|
||||
custom: 'Custom user group',
|
||||
},
|
||||
},
|
||||
permission: {
|
||||
|
|
|
@ -55,6 +55,7 @@ export default {
|
|||
quickAddUser: '快速添加用户',
|
||||
removeName: '确认移除 {name} 这个用户吗',
|
||||
removeTip: '移除后,将失去用户组权限',
|
||||
custom: '自定义用户组',
|
||||
},
|
||||
},
|
||||
permission: {
|
||||
|
|
Loading…
Reference in New Issue