refactor: 用户组组织项目的用户选择&登录加密

This commit is contained in:
RubyLiu 2023-08-29 17:52:26 +08:00 committed by rubylliu
parent 7767502504
commit 9c34f9d3ad
28 changed files with 321 additions and 126 deletions

View File

@ -47,6 +47,7 @@
"dayjs": "^1.11.8", "dayjs": "^1.11.8",
"echarts": "^5.4.2", "echarts": "^5.4.2",
"hotbox-minder": "1.0.15", "hotbox-minder": "1.0.15",
"jsencrypt": "^3.3.2",
"jsonpath-picker-vanilla": "^1.2.4", "jsonpath-picker-vanilla": "^1.2.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",

View File

@ -12,7 +12,11 @@ export default function checkStatus(status: number, msg: string, errorMessageMod
errMessage = `${msg}`; errMessage = `${msg}`;
break; break;
case 401: { case 401: {
if (msg.length > 100) {
setSalt(msg); setSalt(msg);
} else {
errMessage = msg || t('api.errMsg401');
}
if (!isLoginPage()) { if (!isLoginPage()) {
// 不是登录页再调用logout // 不是登录页再调用logout
logout(); logout();

View File

@ -75,6 +75,11 @@ export function addUserToOrgOrProject(data: AddUserToOrgOrProjectParams) {
export function getUserByOrganizationOrProject(sourceId: string) { export function getUserByOrganizationOrProject(sourceId: string) {
return MSR.get({ url: `${orgUrl.getUserByOrgOrProjectUrl}${sourceId}` }); return MSR.get({ url: `${orgUrl.getUserByOrgOrProjectUrl}${sourceId}` });
} }
// 系统-获取管理员下拉选项
export function getAdminByOrganizationOrProject() {
return MSR.get({ url: `${orgUrl.getAdminByOrgOrProjectUrl}` });
}
// 删除组织或项目成员 // 删除组织或项目成员
export function deleteUserFromOrgOrProject(sourceId: string, userId: string, isOrg = true) { export function deleteUserFromOrgOrProject(sourceId: string, userId: string, isOrg = true) {
return MSR.get({ return MSR.get({
@ -147,3 +152,13 @@ export function deleteProjectMemberByOrg(projectId: string, userId: string) {
export function addProjectMemberByOrg(data: AddUserToOrgOrProjectParams) { export function addProjectMemberByOrg(data: AddUserToOrgOrProjectParams) {
return MSR.post({ url: orgUrl.postAddProjectMemberByOrgUrl, data }); return MSR.post({ url: orgUrl.postAddProjectMemberByOrgUrl, data });
} }
// 组织-获取项目下的管理员选项
export function getAdminByProjectByOrg(organizationId: string) {
return MSR.get({ url: `${orgUrl.getAdminByOrganizationOrProjectUrl}${organizationId}` });
}
// 组织-获取成员下的成员选项
export function getUserByProjectByOrg(organizationId: string, projectId: string) {
return MSR.get({ url: `${orgUrl.getUserByOrganizationOrProjectUrl}${organizationId}/${projectId}` });
}

View File

@ -67,6 +67,16 @@ export function saveGlobalUSetting(data: SaveGlobalUSettingData) {
return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editGlobalUSettingUrl, data }); return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editGlobalUSettingUrl, data });
} }
// 系统-获取需要关联的用户选项
export function getSystemUserGroupOption(id: string) {
return MSR.get<UserTableItem[]>({ url: `${ugUrl.getSystemUserGroupOptionUrl}${id}` });
}
// 组织-获取需要关联的用户选项
export function getOrgUserGroupOption(organizationId: string, roleId: string) {
return MSR.get<UserTableItem[]>({ url: `${ugUrl.getOrgUserGroupOptionUrl}${organizationId}/${roleId}` });
}
// 组织-编辑用户组对应的权限配置 // 组织-编辑用户组对应的权限配置
export function saveOrgUSetting(data: SaveGlobalUSettingData) { export function saveOrgUSetting(data: SaveGlobalUSettingData) {
return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editOrgUSettingUrl, data }); return MSR.post<UserGroupAuthSetting[]>({ url: ugUrl.editOrgUSettingUrl, data });
@ -86,8 +96,8 @@ export function deleteUserFromUserGroup(id: string) {
return MSR.get<string>({ url: `${ugUrl.deleteUserFromUserGroupUrl}${id}` }); return MSR.get<string>({ url: `${ugUrl.deleteUserFromUserGroupUrl}${id}` });
} }
// 组织-删除用户组对应的用户 // 组织-删除用户组对应的用户
export function deleteOrgUserFromUserGroup(id: string) { export function deleteOrgUserFromUserGroup(data: { userRoleId: string; userIds: string[]; organizationId: string }) {
return MSR.get<string>({ url: `${ugUrl.deleteOrgUserFromUserGroupUrl}${id}` }); return MSR.post<CommonList<UserTableItem[]>>({ url: ugUrl.deleteOrgUserFromUserGroupUrl, data });
} }
// 系统-添加用户到用户组 // 系统-添加用户到用户组
export function addUserToUserGroup(data: { roleId: string; userIds: string[] }) { export function addUserToUserGroup(data: { roleId: string; userIds: string[] }) {

View File

@ -48,7 +48,7 @@ export const getDeleteProjectUrl = '/system/project/delete/';
// 系统-组织及项目,获取用户下拉选项 // 系统-组织及项目,获取用户下拉选项
export const getUserByOrgOrProjectUrl = '/system/organization/get-option/'; export const getUserByOrgOrProjectUrl = '/system/organization/get-option/';
// 系统-组织及项目,获取管理员下拉选项 // 系统-组织及项目,获取管理员下拉选项
export const getAdminByOrgOrProjectUrl = '/system/organization/get-admin-option/'; export const getAdminByOrgOrProjectUrl = '/system/project/user-list';
// 启用项目 // 启用项目
export const getEnableProjectUrl = '/system/project/enable/'; export const getEnableProjectUrl = '/system/project/enable/';
// 禁用项目 // 禁用项目
@ -82,3 +82,7 @@ export const getEnableProjectByOrgUrl = '/organization/project/enable/';
export const getDisableProjectByOrgUrl = '/organization/project/disable/'; export const getDisableProjectByOrgUrl = '/organization/project/disable/';
// 删除项目 // 删除项目
export const getDeleteProjectByOrgUrl = '/organization/project/delete/'; export const getDeleteProjectByOrgUrl = '/organization/project/delete/';
// 获取成员下拉选项
export const getUserByOrganizationOrProjectUrl = '/organization/project/user-member-list/';
// 获取管理员下拉选项
export const getAdminByOrganizationOrProjectUrl = '/organization/project/user-admin-list/';

View File

@ -20,6 +20,8 @@ export const postUserByUserGroupUrl = `/user/role/relation/global/list`;
export const addUserToUserGroupUrl = `/user/role/relation/global/add`; export const addUserToUserGroupUrl = `/user/role/relation/global/add`;
/** 删除用户组用户 */ /** 删除用户组用户 */
export const deleteUserFromUserGroupUrl = `/user/role/relation/global/delete/`; export const deleteUserFromUserGroupUrl = `/user/role/relation/global/delete/`;
/** 获取需要关联的用户选项 */
export const getSystemUserGroupOptionUrl = `/user/role/relation//global/user/option/`;
/** 组织 */ /** 组织 */
// 组织-修改用户组 // 组织-修改用户组
@ -41,4 +43,6 @@ export const postOrgUserByUserGroupUrl = `/user/role/organization/list-member`;
/** 组织-用户组添加用户 */ /** 组织-用户组添加用户 */
export const addOrgUserToUserGroupUrl = `/user/role/organization/add-member`; export const addOrgUserToUserGroupUrl = `/user/role/organization/add-member`;
/** 组织-删除用户组用户 */ /** 组织-删除用户组用户 */
export const deleteOrgUserFromUserGroupUrl = `/user/role/organization/delete/`; export const deleteOrgUserFromUserGroupUrl = `/user/role/organization/remove-member`;
/** 组织-给用户组添加用户下拉选项 */
export const getOrgUserGroupOptionUrl = `/user/role/organization/get-member/option/`;

View File

@ -1,100 +1,130 @@
<template> <template>
<a-select <a-select
v-model="currentValue" :model-value="currentValue"
:disabled="props.disabled" :placeholder="t(props.placeholder || 'common.pleaseSelectMember')"
multiple multiple
:placeholder="props.placeholder ? t(props.placeholder) : t('common.pleaseSelectMember')" :value-key="props.valueKey"
value-key="id" :disabled="props.disabled"
@search="handleSearch" allow-clear
@change="handleChange" @change="change"
@search="search"
> >
<template #label="{ data }"> <template #label="{ data }">
<span class="option-name"> {{ data.value.name }} </span> <span class="text-[var(--color-text-1)]"> {{ data.value.name }} </span>
</template> </template>
<a-option v-for="data in userOptions" :key="data.id" :disabled="(data.disabled as boolean)" :value="data"> <a-option v-for="data in currentOptions" :key="data.id" :disabled="data.disabled" :value="data">
<span class="option-name"> {{ data.name }} </span> <span :class="data.disabled ? 'text-[var(--color-text-4)]' : 'text-[var(--color-text-1)]'">
<span class="option-email"> {{ `(${data.email})` }} </span> {{ data.name }}
</span>
<span v-if="data.email" class="text-[var(--color-text-4)]"> {{ `(${data.email})` }} </span>
</a-option> </a-option>
</a-select> </a-select>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { ref, onMounted, watch } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { getUserByOrganizationOrProject, getAllUser } from '@/api/modules/setting/organizationAndProject'; import initOptionsFunc, { UserRequesetTypeEnum } from './utils';
export interface MsUserSelectorProps { export interface MsUserSelectorOption {
value: string[];
disabled?: boolean;
placeholder?: string;
type?: 'organization' | 'usergroup';
sourceId?: string;
disabledKey?: string;
}
export interface UserItem {
id: string; id: string;
name: string; name: string;
email: string; email: string;
[key: string]: boolean | string; disabled?: boolean;
[key: string]: string | number | boolean | undefined;
} }
const { t } = useI18n(); const props = withDefaults(
const props = withDefaults(defineProps<MsUserSelectorProps>(), { defineProps<{
value: string[] | string; //
disabled?: boolean; //
disabledKey?: string; // key
valueKey?: string; // valuekey
placeholder?: string;
firstLabelKey?: string; // key
secondLabelKey?: string; // key
loadOptionParams?: Record<string, any>; //
type?: UserRequesetTypeEnum; //
}>(),
{
disabled: false, disabled: false,
type: 'usergroup',
disabledKey: 'disabled', disabledKey: 'disabled',
}); valueKey: 'id',
firstLabelKey: 'name',
secondLabelKey: 'email',
type: UserRequesetTypeEnum.SYSTEM_USER_GROUP,
}
);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:value', value: string[]): void; (e: 'update:value', value: string[]): void;
}>(); }>();
const currentValue = ref(props.value); const { t } = useI18n();
const allOption = ref<UserItem[]>([]); const currentOptions = ref<MsUserSelectorOption[]>([]);
const userOptions = ref<UserItem[]>([]); const oldOptions = ref<MsUserSelectorOption[]>([]);
const initUserList = async () => { const currentValue = computed(() => {
let res: UserItem[] = []; return currentOptions.value.filter((item) => props.value.includes(item.id)) || [];
if (props.type === 'organization') {
if (!props.sourceId) {
return;
}
res = await getUserByOrganizationOrProject(props.sourceId);
} else {
res = await getAllUser();
}
res.forEach((item) => {
item.disabled = item[props.disabledKey as string];
}); });
allOption.value = [...res];
userOptions.value = [...res]; const change = (value: string | number | Record<string, any> | (string | number | Record<string, any>)[]) => {
const tmpArr = Array.isArray(value) ? value : [value];
const { valueKey } = props;
emit(
'update:value',
tmpArr.map((item) => item[valueKey])
);
};
const loadList = async () => {
try {
const list = (await initOptionsFunc(props.type, props.loadOptionParams || {})) || [];
const { firstLabelKey, secondLabelKey, disabledKey, valueKey } = props;
list.forEach((item: MsUserSelectorOption) => {
if (firstLabelKey) {
item.name = (item[firstLabelKey] as string) || '';
}
if (secondLabelKey) {
item.email = (item[secondLabelKey] as string) || '';
}
if (disabledKey) {
item.disabled = item[disabledKey] as boolean;
}
if (valueKey) {
item.id = item[valueKey] as string;
}
});
currentOptions.value = [...list];
oldOptions.value = [...list];
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
currentOptions.value = [];
oldOptions.value = [];
}
}; };
const handleSearch = (value: string) => { const idInSelected = (id: string) => {
let timmer = null; return props.value.includes(id);
};
const search = async (value: string) => {
let timeout = null;
if (value) { if (value) {
timmer = window.setTimeout(() => { timeout = window.setTimeout(() => {
userOptions.value = userOptions.value.filter( currentOptions.value = currentOptions.value.filter(
(item) => item.name.includes(value) || currentValue.value.includes(item.id) (item) => item.name.includes(value) || item.email.includes(value) || idInSelected(item.id)
); );
}, 60); }, 60);
} else { } else {
if (timmer) window.clearTimeout(timmer); if (timeout) {
userOptions.value = allOption.value; window.clearTimeout(timeout);
}
currentOptions.value = [...oldOptions.value];
} }
}; };
const handleChange = (value: string | number | Record<string, any> | (string | number | Record<string, any>)[]) => { onMounted(async () => {
emit('update:value', value as string[]); await loadList();
};
onMounted(() => {
initUserList();
}); });
watch(
() => props.value,
(value) => {
currentValue.value = value;
}
);
</script> </script>

View File

@ -0,0 +1,46 @@
import {
getAdminByOrganizationOrProject,
getAdminByProjectByOrg,
getUserByOrganizationOrProject,
getUserByProjectByOrg,
} from '@/api/modules/setting/organizationAndProject';
import { getOrgUserGroupOption, getSystemUserGroupOption } from '@/api/modules/setting/usergroup';
// eslint-disable-next-line no-shadow
export enum UserRequesetTypeEnum {
SYSTEM_USER_GROUP = 'SYSTEM_USER_GROUP',
SYSTEM_ORGANIZATION = 'SYSTEM_ORGANIZATION',
SYSTEM_ORGANIZATION_ADMIN = 'SYSTEM_ORGANIZATION_ADMIN',
SYSTEM_PROJECT = 'SYSTEM_PROJECT',
SYSTEM_PROJECT_ADMIN = 'SYSTEM_PROJECT_ADMIN',
ORGANIZATION_USER_GROUP = 'ORGANIZATION_USER_GROUP',
ORGANIZATION_USER_GROUP_ADMIN = 'ORGANIZATION_USER_GROUP_ADMIN',
ORGANIZATION_PROJECT = 'ORGANIZATION_PROJECT',
ORGANIZATION_PROJECT_ADMIN = 'ORGANIZATION_PROJECT_ADMIN',
}
export default function initOptionsFunc(type: string, params: Record<string, any>) {
if (type === UserRequesetTypeEnum.SYSTEM_USER_GROUP) {
// 系统 - 用户组-添加成员-下拉选项
return getSystemUserGroupOption(params.roleId);
}
if (type === UserRequesetTypeEnum.ORGANIZATION_USER_GROUP) {
// 组织 - 用户组-添加成员-下拉选项
return getOrgUserGroupOption(params.organizationId, params.roleId);
}
if (type === UserRequesetTypeEnum.SYSTEM_ORGANIZATION_ADMIN || type === UserRequesetTypeEnum.SYSTEM_PROJECT_ADMIN) {
// 系统 - 【组织 或 项目】-添加管理员-下拉选项
return getAdminByOrganizationOrProject();
}
if (type === UserRequesetTypeEnum.SYSTEM_ORGANIZATION || type === UserRequesetTypeEnum.SYSTEM_PROJECT) {
// 系统 -【组织 或 项目】-添加成员-下拉选项
return getUserByOrganizationOrProject(params.sourceId);
}
if (type === UserRequesetTypeEnum.ORGANIZATION_PROJECT) {
// 组织 - 项目-添加成员-下拉选项
return getUserByProjectByOrg(params.organizationId, params.projectId);
}
if (type === UserRequesetTypeEnum.ORGANIZATION_PROJECT_ADMIN) {
// 组织 - 项目-添加管理员-下拉选项
return getAdminByProjectByOrg(params.organizationId);
}
}

View File

@ -14,6 +14,7 @@ export default {
'common.enable': 'Enable', 'common.enable': 'Enable',
'common.close': 'Close', 'common.close': 'Close',
'common.create': 'Create', 'common.create': 'Create',
'common.update': 'Update',
'common.confirmEnable': 'Confirm enable', 'common.confirmEnable': 'Confirm enable',
'common.confirmClose': 'Confirm close', 'common.confirmClose': 'Confirm close',
'common.enableSuccess': 'Enable success', 'common.enableSuccess': 'Enable success',

View File

@ -14,6 +14,7 @@ export default {
'common.enable': '启用', 'common.enable': '启用',
'common.close': '关闭', 'common.close': '关闭',
'common.create': '创建', 'common.create': '创建',
'common.update': '更新',
'common.confirmEnable': '确认启用', 'common.confirmEnable': '确认启用',
'common.confirmClose': '确认关闭', 'common.confirmClose': '确认关闭',
'common.enableSuccess': '启用成功', 'common.enableSuccess': '启用成功',

View File

@ -120,4 +120,5 @@ export interface UserTableItem {
createUser: string; createUser: string;
updateUser: string; updateUser: string;
deleted: boolean; deleted: boolean;
[key: string]: string | boolean | number;
} }

View File

@ -64,6 +64,7 @@
import { GetLoginLogoUrl } from '@/api/requrls/setting/config'; import { GetLoginLogoUrl } from '@/api/requrls/setting/config';
import type { LoginData } from '@/models/user'; import type { LoginData } from '@/models/user';
import { WorkbenchRouteEnum } from '@/enums/routeEnum'; import { WorkbenchRouteEnum } from '@/enums/routeEnum';
import JSEncrypt from 'jsencrypt';
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -99,6 +100,13 @@
password: 'metersphere', password: 'metersphere',
}); });
const encrypted = (input: string, publicKey: string) => {
const encrypt = new JSEncrypt({ default_key_size: '1024' });
debugger;
encrypt.setPublicKey(publicKey);
return encrypt.encrypt(input);
};
const handleSubmit = async ({ const handleSubmit = async ({
errors, errors,
values, values,
@ -110,12 +118,15 @@
if (!errors) { if (!errors) {
setLoading(true); setLoading(true);
try { try {
await userStore.login(values as LoginData); const publicKey = userStore.salt;
await userStore.login({
username: encrypted(values.username, publicKey),
password: encrypted(values.password, publicKey),
authenticate: values.authenticate,
} as LoginData);
Message.success(t('login.form.login.success')); Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value; const { rememberPassword } = loginConfig.value;
const { username, password } = values; const { username, password } = values;
//
// The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : ''; loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : ''; loginConfig.value.password = rememberPassword ? password : '';
const { redirect, ...othersQuery } = router.currentRoute.value.query; const { redirect, ...othersQuery } = router.currentRoute.value.query;

View File

@ -5,7 +5,7 @@
class="ms-modal-form ms-modal-medium" class="ms-modal-form ms-modal-medium"
:ok-text="isEdit ? t('common.update') : t('common.create')" :ok-text="isEdit ? t('common.update') : t('common.create')"
unmount-on-close unmount-on-close
@cancel="handleCancel" @cancel="handleCancel(false)"
> >
<template #title> <template #title>
<span v-if="isEdit"> <span v-if="isEdit">
@ -39,7 +39,14 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item field="userIds" :label="t('system.project.projectAdmin')"> <a-form-item field="userIds" :label="t('system.project.projectAdmin')">
<MsUserSelector v-model:value="form.userIds" placeholder="system.project.projectAdminPlaceholder" /> <MsUserSelector
v-model:value="form.userIds"
:type="UserRequesetTypeEnum.ORGANIZATION_PROJECT_ADMIN"
placeholder="system.project.projectAdminPlaceholder"
:load-option-params="{
organizationId: currentOrgId,
}"
/>
</a-form-item> </a-form-item>
<a-form-item field="description" :label="t('system.organization.description')"> <a-form-item field="description" :label="t('system.organization.description')">
<a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" /> <a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" />
@ -63,7 +70,7 @@
</a-tooltip> </a-tooltip>
</div> </div>
<div class="flex flex-row gap-[14px]"> <div class="flex flex-row gap-[14px]">
<a-button type="secondary" :loading="loading" @click="handleCancel"> <a-button type="secondary" :loading="loading" @click="handleCancel(false)">
{{ t('common.cancel') }} {{ t('common.cancel') }}
</a-button> </a-button>
<a-button type="primary" :loading="loading" @click="handleBeforeOk"> <a-button type="primary" :loading="loading" @click="handleBeforeOk">
@ -86,6 +93,7 @@
import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -112,7 +120,7 @@
]; ];
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel', shouldSearch: boolean): void;
}>(); }>();
const form = reactive<CreateOrUpdateSystemProjectParams>({ const form = reactive<CreateOrUpdateSystemProjectParams>({
@ -133,9 +141,9 @@
watchEffect(() => { watchEffect(() => {
currentVisible.value = props.visible; currentVisible.value = props.visible;
}); });
const handleCancel = () => { const handleCancel = (shouldSearch: boolean) => {
formRef.value?.resetFields(); formRef.value?.resetFields();
emit('cancel'); emit('cancel', shouldSearch);
}; };
const handleBeforeOk = async () => { const handleBeforeOk = async () => {
@ -151,7 +159,7 @@
? t('system.organization.updateOrganizationSuccess') ? t('system.organization.updateOrganizationSuccess')
: t('system.organization.createOrganizationSuccess') : t('system.organization.createOrganizationSuccess')
); );
handleCancel(); handleCancel(true);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -5,7 +5,7 @@
class="ms-modal-form ms-modal-medium" class="ms-modal-form ms-modal-medium"
:ok-text="t('system.organization.addMember')" :ok-text="t('system.organization.addMember')"
unmount-on-close unmount-on-close
@cancel="handleCancel" @cancel="handleCancel(false)"
> >
<template #title> {{ t('system.organization.addMember') }} </template> <template #title> {{ t('system.organization.addMember') }} </template>
<div class="form"> <div class="form">
@ -15,12 +15,17 @@
:label="t('system.organization.member')" :label="t('system.organization.member')"
:rules="[{ required: true, message: t('system.organization.addMemberRequired') }]" :rules="[{ required: true, message: t('system.organization.addMemberRequired') }]"
> >
<MsUserSelector v-model:value="form.name" type="organization" :source-id="projectId" /> <MsUserSelector
v-model:value="form.name"
:type="UserRequesetTypeEnum.ORGANIZATION_PROJECT"
:load-option-params="{ organizationId: currentOrgId, projectId: props.projectId }"
disabled-key="memberFlag"
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>
<template #footer> <template #footer>
<a-button type="secondary" :loading="loading" @click="handleCancel"> <a-button type="secondary" :loading="loading" @click="handleCancel(false)">
{{ t('common.cancel') }} {{ t('common.cancel') }}
</a-button> </a-button>
<a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleAddMember"> <a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleAddMember">
@ -32,20 +37,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect, onUnmounted } from 'vue'; import { reactive, ref, watchEffect, onUnmounted, computed } from 'vue';
import { addProjectMemberByOrg } from '@/api/modules/setting/organizationAndProject'; import { addProjectMemberByOrg } from '@/api/modules/setting/organizationAndProject';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue'; import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue'; import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
import { useAppStore } from '@/store';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
projectId?: string; projectId?: string;
}>(); }>();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel', shouldSearch: boolean): void;
(e: 'submit', value: string[]): void;
}>(); }>();
const currentVisible = ref(props.visible); const currentVisible = ref(props.visible);
@ -61,9 +70,9 @@
currentVisible.value = props.visible; currentVisible.value = props.visible;
}); });
const handleCancel = () => { const handleCancel = (shouldSearch: boolean) => {
form.name = []; form.name = [];
emit('cancel'); emit('cancel', shouldSearch);
}; };
const handleAddMember = () => { const handleAddMember = () => {
@ -76,7 +85,7 @@
loading.value = true; loading.value = true;
await addProjectMemberByOrg({ userIds: form.name, projectId }); await addProjectMemberByOrg({ userIds: form.name, projectId });
Message.success(t('system.organization.addSuccess')); Message.success(t('system.organization.addSuccess'));
handleCancel(); handleCancel(true);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -119,8 +119,11 @@
userVisible.value = true; userVisible.value = true;
}; };
const handleHideUserModal = () => { const handleHideUserModal = (shouldSearch: boolean) => {
userVisible.value = false; userVisible.value = false;
if (shouldSearch) {
fetchData();
}
}; };
const handleRemove = async (record: TableData) => { const handleRemove = async (record: TableData) => {

View File

@ -80,8 +80,8 @@
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject';
import AddProjectModal from './components/addProjectModal.vue'; 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'; import MsCard from '@/components/pure/ms-card/index.vue';
import { UserItem } from '@/models/setting/log';
const { t } = useI18n(); const { t } = useI18n();
const tableStore = useTableStore(); const tableStore = useTableStore();
@ -255,13 +255,17 @@
fetchData(); fetchData();
}; };
const handleAddUserModalCancel = () => { const handleAddUserModalCancel = (shouldSearch: boolean) => {
userVisible.value = false; userVisible.value = false;
if (shouldSearch) {
fetchData(); fetchData();
}
}; };
const handleAddProjectModalCancel = () => { const handleAddProjectModalCancel = (shouldSearch: boolean) => {
addProjectVisible.value = false; addProjectVisible.value = false;
if (shouldSearch) {
fetchData(); fetchData();
}
}; };
const handleRevokeDelete = async (record: TableData) => { const handleRevokeDelete = async (record: TableData) => {

View File

@ -6,7 +6,7 @@
text-align="start" text-align="start"
:ok-text="t('system.userGroup.add')" :ok-text="t('system.userGroup.add')"
unmount-on-close unmount-on-close
@cancel="handleCancel" @cancel="handleCancel(false)"
> >
<template #title> {{ t('system.userGroup.addUser') }} </template> <template #title> {{ t('system.userGroup.addUser') }} </template>
<div class="form"> <div class="form">
@ -16,12 +16,20 @@
:label="t('system.userGroup.user')" :label="t('system.userGroup.user')"
:rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]" :rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]"
> >
<MsUserSelector v-model:value="form.name" /> <MsUserSelector
v-model:value="form.name"
:type="UserRequesetTypeEnum.ORGANIZATION_USER_GROUP"
:load-option-params="{
roleId: store.currentId,
organizationId: currentOrgId,
}"
disabled-key="checkRoleFlag"
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>
<template #footer> <template #footer>
<a-button type="secondary" :loading="loading" @click="handleCancel"> <a-button type="secondary" :loading="loading" @click="handleCancel(false)">
{{ t('common.cancel') }} {{ t('common.cancel') }}
</a-button> </a-button>
<a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleBeforeOk"> <a-button type="primary" :loading="loading" :disabled="form.name.length === 0" @click="handleBeforeOk">
@ -39,6 +47,7 @@
import { addOrgUserToUserGroup } from '@/api/modules/setting/usergroup'; import { addOrgUserToUserGroup } from '@/api/modules/setting/usergroup';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue'; import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue'; import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -50,8 +59,7 @@
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel', shouldSearch: boolean): void;
(e: 'submit', value: string[]): void;
}>(); }>();
const currentVisible = ref(props.visible); const currentVisible = ref(props.visible);
@ -69,10 +77,10 @@
currentVisible.value = props.visible; currentVisible.value = props.visible;
}); });
const handleCancel = () => { const handleCancel = (shouldSearch = false) => {
labelCache.clear(); labelCache.clear();
form.name = []; form.name = [];
emit('cancel'); emit('cancel', shouldSearch);
}; };
const handleBeforeOk = () => { const handleBeforeOk = () => {
@ -87,7 +95,7 @@
userIds: form.name, userIds: form.name,
organizationId: currentOrgId.value, organizationId: currentOrgId.value,
}); });
handleCancel(); handleCancel(true);
Message.success(t('common.addSuccess')); Message.success(t('common.addSuccess'));
} catch (e) { } catch (e) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -70,7 +70,11 @@
}; };
const handleRemove = async (record: UserTableItem) => { const handleRemove = async (record: UserTableItem) => {
try { try {
await deleteOrgUserFromUserGroup(record.id); await deleteOrgUserFromUserGroup({
organizationId: currentOrgId.value,
userRoleId: store.currentId,
userIds: [record.id],
});
await fetchData(); await fetchData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -80,8 +84,10 @@
const handleAddUser = () => { const handleAddUser = () => {
userVisible.value = true; userVisible.value = true;
}; };
const handleAddUserModalCancel = () => { const handleAddUserModalCancel = (shouldSearch: boolean) => {
if (shouldSearch) {
fetchData(); fetchData();
}
userVisible.value = false; userVisible.value = false;
}; };
watchEffect(() => { watchEffect(() => {

View File

@ -30,6 +30,7 @@
<MsUserSelector <MsUserSelector
v-model:value="form.memberIds" v-model:value="form.memberIds"
placeholder="system.organization.organizationAdminPlaceholder" placeholder="system.organization.organizationAdminPlaceholder"
:type="UserRequesetTypeEnum.SYSTEM_ORGANIZATION_ADMIN"
/> />
</a-form-item> </a-form-item>
<a-form-item field="description" :label="t('system.organization.description')"> <a-form-item field="description" :label="t('system.organization.description')">
@ -56,6 +57,7 @@
import { createOrUpdateOrg } from '@/api/modules/setting/organizationAndProject'; import { createOrUpdateOrg } from '@/api/modules/setting/organizationAndProject';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { CreateOrUpdateSystemOrgParams } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemOrgParams } from '@/models/setting/system/orgAndProject';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -69,6 +71,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'submit'): void;
}>(); }>();
const form = reactive<{ name: string; memberIds: string[]; description: string }>({ const form = reactive<{ name: string; memberIds: string[]; description: string }>({
@ -86,6 +89,11 @@
emit('cancel'); emit('cancel');
}; };
const handleSubmit = () => {
handleCancel();
emit('submit');
};
const handleBeforeOk = () => { const handleBeforeOk = () => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => { formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) { if (errors) {
@ -99,7 +107,7 @@
? t('system.organization.updateOrganizationSuccess') ? t('system.organization.updateOrganizationSuccess')
: t('system.organization.createOrganizationSuccess') : t('system.organization.createOrganizationSuccess')
); );
handleCancel(); handleSubmit();
return true; return true;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -119,4 +127,3 @@
}); });
const isEdit = computed(() => !!props.currentOrganization?.id); const isEdit = computed(() => !!props.currentOrganization?.id);
</script> </script>
@/api/modules/setting/organizationAndProject

View File

@ -44,7 +44,11 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item field="userIds" :label="t('system.project.projectAdmin')"> <a-form-item field="userIds" :label="t('system.project.projectAdmin')">
<MsUserSelector v-model:value="form.userIds" placeholder="system.project.projectAdminPlaceholder" /> <MsUserSelector
v-model:value="form.userIds"
:type="UserRequesetTypeEnum.SYSTEM_PROJECT_ADMIN"
placeholder="system.project.projectAdminPlaceholder"
/>
</a-form-item> </a-form-item>
<a-form-item field="description" :label="t('system.organization.description')"> <a-form-item field="description" :label="t('system.organization.description')">
<a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" /> <a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" />
@ -90,6 +94,7 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams, SystemOrgOption } from '@/models/setting/system/orgAndProject';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -115,6 +120,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'submit'): void;
}>(); }>();
const form = reactive<CreateOrUpdateSystemProjectParams>({ const form = reactive<CreateOrUpdateSystemProjectParams>({
@ -153,7 +159,9 @@
? t('system.organization.updateOrganizationSuccess') ? t('system.organization.updateOrganizationSuccess')
: t('system.organization.createOrganizationSuccess') : t('system.organization.createOrganizationSuccess')
); );
handleCancel(); handleCancel();
emit('submit');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -15,7 +15,11 @@
:label="t('system.organization.member')" :label="t('system.organization.member')"
:rules="[{ required: true, message: t('system.organization.addMemberRequired') }]" :rules="[{ required: true, message: t('system.organization.addMemberRequired') }]"
> >
<MsUserSelector v-model:value="form.name" type="organization" :source-id="organizationId || projectId" /> <MsUserSelector
v-model:value="form.name"
:type="UserRequesetTypeEnum.SYSTEM_ORGANIZATION"
:load-option-params="{ sourceId: props.organizationId || props.projectId }"
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>
@ -36,6 +40,7 @@
import { addUserToOrgOrProject } from '@/api/modules/setting/organizationAndProject'; import { addUserToOrgOrProject } from '@/api/modules/setting/organizationAndProject';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue'; import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue'; import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { UserRequesetTypeEnum } from '@/components/business/ms-user-selector/utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -46,7 +51,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'submit', value: string[]): void; (e: 'submit'): void;
}>(); }>();
const currentVisible = ref(props.visible); const currentVisible = ref(props.visible);
@ -78,6 +83,7 @@
await addUserToOrgOrProject({ userIds: form.name, organizationId, projectId }); await addUserToOrgOrProject({ userIds: form.name, organizationId, projectId });
Message.success(t('system.organization.addSuccess')); Message.success(t('system.organization.addSuccess'));
handleCancel(); handleCancel();
emit('submit');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(error);

View File

@ -105,4 +105,3 @@
} }
); );
</script> </script>
@/api/modules/setting/organizationAndProject

View File

@ -44,7 +44,12 @@
:visible="orgVisible" :visible="orgVisible"
@cancel="handleAddOrgModalCancel" @cancel="handleAddOrgModalCancel"
/> />
<AddUserModal :organization-id="currentOrganizationId" :visible="userVisible" @cancel="handleAddUserModalCancel" /> <AddUserModal
:organization-id="currentOrganizationId"
:visible="userVisible"
@cancel="handleAddUserModalCancel"
@submit="fetchData"
/>
<ProjectDrawer v-bind="currentProjectDrawer" @cancel="handleProjectDrawerCancel" /> <ProjectDrawer v-bind="currentProjectDrawer" @cancel="handleProjectDrawerCancel" />
<UserDrawer v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" /> <UserDrawer v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" />
</template> </template>
@ -254,7 +259,6 @@
const handleAddUserModalCancel = () => { const handleAddUserModalCancel = () => {
userVisible.value = false; userVisible.value = false;
fetchData();
}; };
const handleAddOrgModalCancel = () => { const handleAddOrgModalCancel = () => {
orgVisible.value = false; orgVisible.value = false;

View File

@ -39,8 +39,14 @@
:current-project="currentUpdateProject" :current-project="currentUpdateProject"
:visible="addProjectVisible" :visible="addProjectVisible"
@cancel="handleAddProjectModalCancel" @cancel="handleAddProjectModalCancel"
@submit="fetchData"
/>
<AddUserModal
:project-id="currentProjectId"
:visible="userVisible"
@submit="fetchData"
@cancel="handleAddUserModalCancel"
/> />
<AddUserModal :project-id="currentProjectId" :visible="userVisible" @cancel="handleAddUserModalCancel" />
<UserDrawer :project-id="currentProjectId" v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" /> <UserDrawer :project-id="currentProjectId" v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" />
</template> </template>
@ -67,7 +73,7 @@
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject';
import AddProjectModal from './addProjectModal.vue'; import AddProjectModal from './addProjectModal.vue';
import { UserItem } from '@/components/business/ms-user-selector/index.vue'; import { UserItem } from '@/models/setting/log';
export interface SystemOrganizationProps { export interface SystemOrganizationProps {
keyword: string; keyword: string;
@ -235,16 +241,13 @@
const handleUserDrawerCancel = () => { const handleUserDrawerCancel = () => {
currentUserDrawer.visible = false; currentUserDrawer.visible = false;
fetchData();
}; };
const handleAddUserModalCancel = () => { const handleAddUserModalCancel = () => {
userVisible.value = false; userVisible.value = false;
fetchData();
}; };
const handleAddProjectModalCancel = () => { const handleAddProjectModalCancel = () => {
addProjectVisible.value = false; addProjectVisible.value = false;
fetchData();
}; };
const handleRevokeDelete = async (record: TableData) => { const handleRevokeDelete = async (record: TableData) => {
@ -278,4 +281,3 @@
cursor: pointer; cursor: pointer;
} }
</style> </style>
@/api/modules/setting/organizationAndProject

View File

@ -44,6 +44,7 @@
:organization-id="props.organizationId" :organization-id="props.organizationId"
:visible="userVisible" :visible="userVisible"
@cancel="handleHideUserModal" @cancel="handleHideUserModal"
@submit="fetchData"
/> />
</template> </template>

View File

@ -29,8 +29,8 @@
<SystemProject v-if="currentTable === 'project'" ref="projectTabeRef" :keyword="currentKeyword" /> <SystemProject v-if="currentTable === 'project'" ref="projectTabeRef" :keyword="currentKeyword" />
</div> </div>
</MsCard> </MsCard>
<AddOrganizationModal :visible="organizationVisible" @cancel="handleAddOrganizationCancel" /> <AddOrganizationModal :visible="organizationVisible" @submit="tableSearch" @cancel="handleAddOrganizationCancel" />
<AddProjectModal :visible="projectVisible" @cancel="handleAddProjectCancel" /> <AddProjectModal :visible="projectVisible" @submit="tableSearch" @cancel="handleAddProjectCancel" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -89,11 +89,9 @@
}; };
const handleAddProjectCancel = () => { const handleAddProjectCancel = () => {
tableSearch();
projectVisible.value = false; projectVisible.value = false;
}; };
const handleAddOrganizationCancel = () => { const handleAddOrganizationCancel = () => {
tableSearch();
organizationVisible.value = false; organizationVisible.value = false;
}; };

View File

@ -15,8 +15,13 @@
field="name" field="name"
:label="t('system.userGroup.user')" :label="t('system.userGroup.user')"
:rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]" :rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]"
asterisk-position="end"
> >
<MsUserSelector v-model:value="form.name" /> <MsUserSelector
v-model:value="form.name"
disabled-key="exclude"
:load-option-params="{ roleId: store.currentId }"
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>

View File

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