fix(all): 修复场景部分 bug

This commit is contained in:
baiqi 2024-04-07 20:54:55 +08:00 committed by Craftsman
parent 090e4d5bf9
commit 0db63d9db3
9 changed files with 552 additions and 372 deletions

View File

@ -13,7 +13,7 @@ export interface ApiDefinitionCustomField {
// 创建定义参数
export interface ApiDefinitionCreateParams extends ExecuteRequestParams {
tags: string[];
response: ResponseDefinition;
response: ResponseDefinition[];
description: string;
status: RequestDefinitionStatus;
customFields: ApiDefinitionCustomField[];

View File

@ -97,7 +97,6 @@
/**
* @description 接口测试-接口调试
*/
import { onBeforeRouteLeave } from 'vue-router';
import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue';
@ -123,7 +122,6 @@
} from '@/api/modules/api-test/debug';
import { useI18n } from '@/hooks/useI18n';
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
import useModal from '@/hooks/useModal';
import { parseCurlScript } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
@ -141,7 +139,6 @@
import { parseRequestBodyFiles } from '../components/utils';
const { t } = useI18n();
const { openModal } = useModal();
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
const folderTree = ref<ModuleTreeNode[]>([]);

View File

@ -56,7 +56,6 @@
</template>
<script setup lang="ts">
import { onBeforeRouteLeave } from 'vue-router';
import { cloneDeep } from 'lodash-es';
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
@ -70,7 +69,6 @@
import { getProtocolList } from '@/api/modules/api-test/common';
import { useI18n } from '@/hooks/useI18n';
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app';
import { hasAnyPermission } from '@/utils/permission';
@ -98,7 +96,6 @@
}>();
const appStore = useAppStore();
const { t } = useI18n();
const { openModal } = useModal();
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');

View File

@ -7,7 +7,12 @@
position="br"
@popup-visible-change="handleActionTriggerChange"
>
<MsButton :id="step.uniqueId" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
<MsButton
:id="`trigger-${step.uniqueId}`"
type="icon"
class="ms-tree-node-extra__btn !mr-[4px]"
@click="emit('click')"
>
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
<template #content>
@ -137,7 +142,7 @@
function handleActionsClose() {
activeCreateAction.value = undefined;
innerStep.value.createActionsVisible = false;
document.getElementById(innerStep.value.uniqueId.toString())?.click();
document.getElementById(`trigger-${innerStep.value.uniqueId}`)?.click();
}
</script>

View File

@ -128,8 +128,6 @@
</template>
<script setup lang="ts">
// import dayjs from 'dayjs';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
@ -145,7 +143,6 @@
import { ApiScenarioDebugRequest, Scenario, ScenarioStepItem } from '@/models/apiTest/scenario';
import { ScenarioExecuteStatus, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
const props = defineProps<{
isNew?: boolean; //

View File

@ -387,12 +387,64 @@
</div>
</template>
</a-modal>
<a-modal
v-model:visible="saveNewApiModalVisible"
:title="t('common.save')"
:ok-loading="saveLoading"
class="ms-modal-form"
title-align="start"
body-class="!p-0"
@before-ok="handleSave"
@cancel="handleCancel"
>
<a-form ref="saveModalFormRef" :model="saveModalForm" layout="vertical">
<a-form-item
field="name"
:label="t('apiTestDebug.requestName')"
:rules="[{ required: true, message: t('apiTestDebug.requestNameRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.name"
:max-length="255"
:placeholder="t('apiTestDebug.requestNamePlaceholder')"
/>
</a-form-item>
<a-form-item
v-if="activeStep?.config.protocol === 'HTTP'"
field="path"
:label="t('apiTestDebug.requestUrl')"
:rules="[{ required: true, message: t('apiTestDebug.requestUrlRequired') }]"
asterisk-position="end"
>
<a-input
v-model:model-value="saveModalForm.path"
:max-length="255"
:placeholder="t('apiTestDebug.commonPlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.requestModule')" class="mb-0">
<a-tree-select
v-model:modelValue="saveModalForm.moduleId"
:data="moduleTree || []"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:tree-props="{
virtualListProps: {
height: 200,
threshold: 200,
},
}"
allow-search
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { useEventListener } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { FormInstance, Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
@ -412,6 +464,7 @@
import { RequestParam as CaseRequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
import { addDefinition } from '@/api/modules/api-test/management';
import { debugScenario, getScenarioStep } from '@/api/modules/api-test/scenario';
import { getSocket } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n';
@ -437,8 +490,10 @@
ScenarioStepFileParams,
ScenarioStepItem,
} from '@/models/apiTest/scenario';
import { ModuleTreeNode } from '@/models/common';
import { EnvConfig } from '@/models/projectManagement/environmental';
import {
RequestDefinitionStatus,
ScenarioAddStepActionType,
ScenarioExecuteStatus,
ScenarioStepLoopTypeEnum,
@ -449,6 +504,7 @@
import type { RequestParam } from '../common/customApiDrawer.vue';
import updateStepStatus from '../utils';
import useCreateActions from './createAction/useCreateActions';
import { defaultResponseItem } from '@/views/api-test/components/config';
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
import { defaultStepItemCommon } from '@/views/api-test/scenario/components/config';
@ -492,6 +548,8 @@
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const moduleTree = inject<Ref<ModuleTreeNode[]>>('moduleTree');
const activeModule = inject<Ref<string>>('activeModule');
const permissionMap = {
execute: 'PROJECT_API_SCENARIO:READ+EXECUTE',
@ -773,6 +831,67 @@
}
}
const saveNewApiModalVisible = ref(false);
const saveModalForm = ref({
name: '',
path: '',
moduleId: activeModule?.value || 'root',
});
const saveModalFormRef = ref<FormInstance>();
const saveLoading = ref(false);
/**
* 保存请求
* @param fullParams 保存时传入的参数
* @param silence 是否静默保存接口定义另存为用例时要先静默保存接口
*/
async function realSave() {
try {
saveLoading.value = true;
if (activeStep.value) {
const detail = stepDetails.value[activeStep.value.id] as RequestParam;
const fileParams = scenario.value.stepFileParam[activeStep.value.id];
await addDefinition({
...saveModalForm.value,
projectId: appStore.currentProjectId,
tags: [],
description: '',
status: RequestDefinitionStatus.PROCESSING,
customFields: [],
versionId: '',
environmentId: currentEnvConfig?.value.id || '',
request: detail,
uploadFileIds: fileParams?.uploadFileIds || [],
linkFileIds: fileParams?.linkFileIds || [],
response: [defaultResponseItem],
method: detail?.method,
protocol: detail?.protocol,
});
Message.success(t('common.saveSuccess'));
saveNewApiModalVisible.value = false;
saveLoading.value = false;
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
saveLoading.value = false;
}
}
function handleSave(done: (closed: boolean) => void) {
saveModalFormRef.value?.validate(async (errors) => {
if (!errors) {
await realSave();
done(true);
}
});
done(false);
}
function handleCancel() {
saveModalFormRef.value?.resetFields();
}
function handleStepMoreActionSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) {
case 'copy':
@ -851,6 +970,11 @@
deleteNode(steps.value, node.uniqueId, 'uniqueId');
scenario.value.unSaved = true;
break;
case 'saveAsApi':
activeStep.value = node as ScenarioStepItem;
saveModalForm.value.path = (stepDetails.value[node.id] as RequestParam)?.url;
saveNewApiModalVisible.value = true;
break;
default:
break;
}

View File

@ -80,7 +80,7 @@
<create
ref="createRef"
v-model:scenario="activeScenarioTab"
:module-tree="folderTree"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></create>
</div>
@ -88,7 +88,7 @@
<detail
ref="detailRef"
v-model:scenario="activeScenarioTab"
:module-tree="folderTree"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></detail>
</div>
@ -320,7 +320,7 @@
setStepExecuteStatus(activeScenarioTab.value);
}
const folderTree = ref<ModuleTreeNode[]>([]);
const moduleTree = ref<ModuleTreeNode[]>([]);
const folderTreePathMap = ref<Record<string, any>>({});
const activeModule = ref<string>('all');
const activeFolder = ref<string>('all');
@ -415,7 +415,7 @@
const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>();
function handleModuleInit(tree: any, _protocol: string, pathMap: Record<string, any>) {
folderTree.value = tree;
moduleTree.value = tree;
folderTreePathMap.value = pathMap;
}
@ -528,6 +528,18 @@
}
async function openScenarioTab(record: ApiScenarioTableItem | string, action?: 'copy' | 'execute') {
const isLoadedTabIndex = scenarioTabs.value.findIndex(
(e) => e.id === (typeof record === 'string' ? record : record.id)
);
if (isLoadedTabIndex > -1 && action !== 'copy') {
// tabtab
activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex];
// requestCompositionRefid,id
if (action === 'execute') {
handleExecute(executeButtonRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec');
}
return;
}
try {
appStore.showLoading();
const res = await getScenarioDetail(typeof record === 'string' ? record : record.id);
@ -568,6 +580,8 @@
provide('currentEnvConfig', readonly(currentEnvConfig));
provide('scenarioId', scenarioId);
provide('scenarioExecuteLoading', scenarioExecuteLoading);
provide('moduleTree', readonly(moduleTree));
provide('activeModule', readonly(activeModule));
</script>
<style scoped lang="less">

View File

@ -0,0 +1,357 @@
<template>
<MsBaseTable
v-bind="propsRes"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #userRole="{ record }">
<MsTagGroup
v-if="!record.showUserSelect"
:tag-list="record.userRoles || []"
type="primary"
theme="outline"
@click="changeUser(record)"
/>
<a-select
v-else
v-model="record.selectUserList"
:popup-visible="record.showUserSelect"
multiple
class="w-[260px]"
:max-tag-count="2"
@popup-visible-change="(value) => userGroupChange(value, record)"
>
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</template>
<template #enable="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('organization.member.statusEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<MsIcon type="icon-icon_disable" class="mr-[2px]" />
{{ t('organization.member.statusDisable') }}
</div>
</template>
<template #operation="{ record }">
<MsRemoveButton
v-permission="['PROJECT_USER:READ+DELETE']"
position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('project.member.subTitle')"
:loading="deleteLoading"
@ok="removeMember(record)"
/>
</template>
</MsBaseTable>
<AddMemberModal
ref="projectMemberRef"
v-model:visible="addMemberVisible"
:user-group-options="userGroupOptions"
@success="initData()"
/>
<MsBatchModal
ref="batchModalRef"
v-model:visible="batchVisible"
:action="batchAction"
:select-data="selectData"
@add-user-group="addUserGroup"
/>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import { isEqual } from 'lodash-es';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsBatchModal from '@/components/business/ms-batch-modal/index.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import AddMemberModal from './addMemberModal.vue';
import {
addOrUpdateProjectMember,
addProjectUserGroup,
batchRemoveMember,
getProjectMemberList,
getProjectUserGroup,
removeProjectMember,
} from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit, formatPhoneNumber } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import {
ActionProjectMember,
ProjectMemberItem,
ProjectUserOption,
} from '@/models/projectManagement/projectAndPermission';
import { TableKeyEnum } from '@/enums/tableEnum';
const props = defineProps<{
roleIds: string;
userGroupOptions: ProjectUserOption[];
keyword: string;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const { openModal } = useModal();
const tableStore = useTableStore();
const lastProjectId = computed(() => appStore.currentProjectId);
const hasOperationPermission = computed(() => hasAnyPermission(['PROJECT_USER:READ+DELETE']));
const columns: MsTableColumn = [
{
title: 'project.member.tableColumnName',
slotName: 'name',
dataIndex: 'name',
showTooltip: true,
showDrag: false,
},
{
title: 'project.member.tableColumnEmail',
slotName: 'email',
dataIndex: 'email',
showTooltip: true,
showDrag: true,
},
{
title: 'project.member.tableColumnPhone',
slotName: 'phone',
dataIndex: 'phone',
showDrag: true,
width: 150,
},
{
title: 'project.member.tableColumnUserGroup',
slotName: 'userRole',
dataIndex: 'userRoleIdNameMap',
showDrag: true,
width: 300,
},
{
title: 'project.member.tableColumnStatus',
slotName: 'enable',
dataIndex: 'enable',
width: 150,
},
{
title: hasOperationPermission.value ? 'project.member.tableColumnActions' : '',
slotName: 'operation',
fixed: 'right',
dataIndex: 'operation',
width: hasOperationPermission.value ? 120 : 50,
showDrag: false,
},
];
const tableBatchActions = {
baseAction: [
{
label: 'project.member.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup',
permission: ['PROJECT_USER:READ+UPDATE'],
},
{
label: 'project.member.batchActionRemove',
eventTag: 'batchActionRemove',
permission: ['PROJECT_USER:READ+DELETE'],
},
],
};
const tableSelected = ref<(string | number)[]>([]);
const handleTableSelect = (selectArr: (string | number)[]) => {
tableSelected.value = selectArr;
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getProjectMemberList,
{
tableKey: TableKeyEnum.PROJECT_MEMBER,
selectable: hasAnyPermission(['ORGANIZATION_MEMBER:READ+UPDATE']),
showSetting: true,
heightUsed: 288,
scroll: {
x: 1200,
},
},
(record) => {
return {
...record,
phone: formatPhoneNumber(record.phone || ''),
};
}
);
const initData = async () => {
setLoadListParams({
filter: {
roleIds: props.roleIds ? [props.roleIds] : [],
},
projectId: lastProjectId.value,
keyword: props.keyword,
});
await loadList();
};
//
const selectData = ref<string[] | undefined>([]);
//
const batchRemoveHandler = () => {
openModal({
type: 'error',
title: t('project.member.batchRemoveTip', { number: (selectData.value || []).length }),
content: t('project.member.batchRemoveContent'),
okText: t('project.member.deleteMemberConfirm'),
cancelText: t('project.member.Cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const params: ActionProjectMember = {
projectId: lastProjectId.value,
userIds: selectData.value,
};
await batchRemoveMember(params);
Message.success(t('project.member.deleteMemberSuccess'));
loadList();
resetSelector();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
//
const deleteLoading = ref<boolean>(false);
const removeMember = async (record: ProjectMemberItem) => {
deleteLoading.value = true;
try {
if (lastProjectId.value && record.id) {
await removeProjectMember(lastProjectId.value, record.id);
Message.success(t('project.member.deleteMemberSuccess'));
initData();
resetSelector();
}
} catch (error) {
console.log(error);
} finally {
deleteLoading.value = false;
}
};
const batchVisible = ref<boolean>(false);
const batchAction = ref('');
const batchModalRef = ref();
//
const addUserGroup = async (target: string[]) => {
const params = {
projectId: lastProjectId.value,
userIds: selectData.value,
roleIds: target,
};
try {
await batchModalRef.value.batchRequestFun(addProjectUserGroup, params);
initData();
resetSelector();
} catch (error) {
console.log(error);
}
};
//
const handleTableBatch = (event: BatchActionParams, params: BatchActionQueryParams) => {
selectData.value = params.selectedIds;
if (event.eventTag === 'batchActionRemove') {
batchRemoveHandler();
}
if (event.eventTag === 'batchAddUserGroup') {
batchVisible.value = true;
batchAction.value = event.eventTag;
batchModalRef.value.getTreeList(getProjectUserGroup, lastProjectId.value);
}
};
//
const addMemberVisible = ref<boolean>(false);
const projectMemberRef = ref();
const addMember = () => {
addMemberVisible.value = true;
projectMemberRef.value.initProjectMemberOptions();
};
//
const editProjectMember = async (record: ProjectMemberItem) => {
const params: ActionProjectMember = {
projectId: lastProjectId.value,
userId: record.id,
roleIds: record.selectUserList,
};
try {
await addOrUpdateProjectMember(params);
Message.success(t('project.member.batchUpdateSuccess'));
record.showUserSelect = false;
loadList();
} catch (error) {
console.log(error);
}
};
//
const changeUser = (record: ProjectMemberItem) => {
if (!hasAnyPermission(['PROJECT_USER:READ+UPDATE'])) {
return;
}
if (record.enable) {
record.showUserSelect = true;
record.selectUserList = (record.userRoles || []).map((item) => item.id);
}
};
//
const userGroupChange = (visible: boolean, record: ProjectMemberItem) => {
if (visible) {
return;
}
if ((record.selectUserList || []).length < 1) {
Message.warning(t('project.member.selectUserEmptyTip'));
return;
}
const userGroupIds = (record.userRoles || []).map((item: any) => item.id);
if (isEqual(userGroupIds, record.selectUserList)) {
record.showUserSelect = false;
return;
}
editProjectMember(record);
};
onBeforeMount(() => {
initData();
});
defineExpose({
initData,
addMember,
});
await tableStore.initColumn(TableKeyEnum.PROJECT_MEMBER, columns, 'drawer');
</script>
<style lang="less" scoped></style>

View File

@ -1,10 +1,10 @@
<template>
<div class="mb-4 grid grid-cols-4 gap-2">
<div class="col-span-2"
><a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">{{
t('project.member.addMember')
}}</a-button></div
>
<div class="col-span-2">
<a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">
{{ t('project.member.addMember') }}
</a-button>
</div>
<div>
<a-select v-model="roleIds" @change="changeSelect">
<a-option v-for="item of userGroupAll" :key="item.id" :value="item.id">{{ t(item.name) }}</a-option>
@ -15,374 +15,45 @@
>
<div>
<a-input-search
v-model="searchParams.keyword"
v-model="keyword"
:max-length="255"
:placeholder="t('project.member.searchMember')"
allow-clear
@search="searchHandler"
@press-enter="searchHandler"
@clear="searchHandler"
></a-input-search
></div>
</div>
<MsBaseTable
v-bind="propsRes"
<memberTable
v-if="isMounted"
ref="memberTableRef"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #userRole="{ record }">
<MsTagGroup
v-if="!record.showUserSelect"
:tag-list="record.userRoles || []"
type="primary"
theme="outline"
@click="changeUser(record)"
/>
<a-select
v-else
v-model="record.selectUserList"
:popup-visible="record.showUserSelect"
multiple
class="w-[260px]"
:max-tag-count="2"
@popup-visible-change="(value) => userGroupChange(value, record)"
>
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</template>
<template #enable="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('organization.member.statusEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<MsIcon type="icon-icon_disable" class="mr-[2px]" />
{{ t('organization.member.statusDisable') }}
</div>
</template>
<template #operation="{ record }">
<MsRemoveButton
v-permission="['PROJECT_USER:READ+DELETE']"
position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('project.member.subTitle')"
:loading="deleteLoading"
@ok="removeMember(record)"
/>
</template>
</MsBaseTable>
<AddMemberModal
ref="projectMemberRef"
v-model:visible="addMemberVisible"
:keyword="keyword"
:role-ids="roleIds"
:user-group-options="userGroupOptions"
@success="initData()"
/>
<MsBatchModal
ref="batchModalRef"
v-model:visible="batchVisible"
:action="batchAction"
:select-data="selectData"
@add-user-group="addUserGroup"
/>
</template>
<script setup lang="ts">
import { getProjectUserGroup } from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ProjectUserOption } from '@/models/projectManagement/projectAndPermission';
const memberTable = defineAsyncComponent(() => import('./components/memberTable.vue'));
/**
* @description 项目管理-项目与权限-成员
*/
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { isEqual } from 'lodash-es';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsBatchModal from '@/components/business/ms-batch-modal/index.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import AddMemberModal from './components/addMemberModal.vue';
import {
addOrUpdateProjectMember,
addProjectUserGroup,
batchRemoveMember,
getProjectMemberList,
getProjectUserGroup,
removeProjectMember,
} from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit, formatPhoneNumber } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import type {
ActionProjectMember,
ProjectMemberItem,
ProjectUserOption,
SearchParams,
} from '@/models/projectManagement/projectAndPermission';
import { TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const tableStore = useTableStore();
const lastProjectId = computed(() => appStore.currentProjectId);
const hasOperationPermission = computed(() => hasAnyPermission(['PROJECT_USER:READ+DELETE']));
const columns: MsTableColumn = [
{
title: 'project.member.tableColumnName',
slotName: 'name',
dataIndex: 'name',
showTooltip: true,
showDrag: false,
},
{
title: 'project.member.tableColumnEmail',
slotName: 'email',
dataIndex: 'email',
showTooltip: true,
showDrag: true,
},
{
title: 'project.member.tableColumnPhone',
slotName: 'phone',
dataIndex: 'phone',
showDrag: true,
width: 150,
},
{
title: 'project.member.tableColumnUserGroup',
slotName: 'userRole',
dataIndex: 'userRoleIdNameMap',
showDrag: true,
width: 300,
},
{
title: 'project.member.tableColumnStatus',
slotName: 'enable',
dataIndex: 'enable',
width: 150,
},
{
title: hasOperationPermission.value ? 'project.member.tableColumnActions' : '',
slotName: 'operation',
fixed: 'right',
dataIndex: 'operation',
width: hasOperationPermission.value ? 120 : 50,
showDrag: false,
},
];
const tableBatchActions = {
baseAction: [
{
label: 'project.member.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup',
permission: ['PROJECT_USER:READ+UPDATE'],
},
{
label: 'project.member.batchActionRemove',
eventTag: 'batchActionRemove',
permission: ['PROJECT_USER:READ+DELETE'],
},
],
};
const tableSelected = ref<(string | number)[]>([]);
const handleTableSelect = (selectArr: (string | number)[]) => {
tableSelected.value = selectArr;
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getProjectMemberList,
{
tableKey: TableKeyEnum.PROJECT_MEMBER,
selectable: hasAnyPermission(['ORGANIZATION_MEMBER:READ+UPDATE']),
showSetting: true,
heightUsed: 288,
scroll: {
x: 1200,
},
},
(record) => {
return {
...record,
phone: formatPhoneNumber(record.phone || ''),
};
}
);
const searchParams = ref<SearchParams>({
filter: {
roleIds: [],
},
projectId: lastProjectId.value,
keyword: '',
});
const roleIds = ref<string>('');
const initData = async () => {
setLoadListParams({ ...searchParams.value });
await loadList();
};
const searchHandler = () => {
initData();
};
const changeSelect = () => {
searchParams.value.filter.roleIds = roleIds.value ? [roleIds.value] : [];
initData();
};
//
const selectData = ref<string[] | undefined>([]);
//
const batchRemoveHandler = () => {
openModal({
type: 'error',
title: t('project.member.batchRemoveTip', { number: (selectData.value || []).length }),
content: t('project.member.batchRemoveContent'),
okText: t('project.member.deleteMemberConfirm'),
cancelText: t('project.member.Cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const params: ActionProjectMember = {
projectId: lastProjectId.value,
userIds: selectData.value,
};
await batchRemoveMember(params);
Message.success(t('project.member.deleteMemberSuccess'));
loadList();
resetSelector();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
//
const deleteLoading = ref<boolean>(false);
const removeMember = async (record: ProjectMemberItem) => {
deleteLoading.value = true;
try {
if (lastProjectId.value && record.id) {
await removeProjectMember(lastProjectId.value, record.id);
Message.success(t('project.member.deleteMemberSuccess'));
initData();
resetSelector();
}
} catch (error) {
console.log(error);
} finally {
deleteLoading.value = false;
}
};
const batchVisible = ref<boolean>(false);
const batchAction = ref('');
const userGroupOptions = ref<ProjectUserOption[]>([]);
const batchModalRef = ref();
//
const addUserGroup = async (target: string[]) => {
const params = {
projectId: lastProjectId.value,
userIds: selectData.value,
roleIds: target,
};
try {
await batchModalRef.value.batchRequestFun(addProjectUserGroup, params);
initData();
resetSelector();
} catch (error) {
console.log(error);
}
};
//
const handleTableBatch = (event: BatchActionParams, params: BatchActionQueryParams) => {
selectData.value = params.selectedIds;
if (event.eventTag === 'batchActionRemove') {
batchRemoveHandler();
}
if (event.eventTag === 'batchAddUserGroup') {
batchVisible.value = true;
batchAction.value = event.eventTag;
batchModalRef.value.getTreeList(getProjectUserGroup, lastProjectId.value);
}
};
//
const addMemberVisible = ref<boolean>(false);
const projectMemberRef = ref();
const addMember = () => {
addMemberVisible.value = true;
projectMemberRef.value.initProjectMemberOptions();
};
//
const editProjectMember = async (record: ProjectMemberItem) => {
const params: ActionProjectMember = {
projectId: lastProjectId.value,
userId: record.id,
roleIds: record.selectUserList,
};
try {
await addOrUpdateProjectMember(params);
Message.success(t('project.member.batchUpdateSuccess'));
record.showUserSelect = false;
loadList();
} catch (error) {
console.log(error);
}
};
//
const changeUser = (record: ProjectMemberItem) => {
if (!hasAnyPermission(['PROJECT_USER:READ+UPDATE'])) {
return;
}
if (record.enable) {
record.showUserSelect = true;
record.selectUserList = (record.userRoles || []).map((item) => item.id);
}
};
//
const userGroupChange = (visible: boolean, record: ProjectMemberItem) => {
if (visible) {
return;
}
if ((record.selectUserList || []).length < 1) {
Message.warning(t('project.member.selectUserEmptyTip'));
return;
}
const userGroupIds = (record.userRoles || []).map((item: any) => item.id);
if (isEqual(userGroupIds, record.selectUserList)) {
record.showUserSelect = false;
return;
}
editProjectMember(record);
};
const { t } = useI18n();
const userGroupAll = ref<ProjectUserOption[]>([]);
const userGroupOptions = ref<ProjectUserOption[]>([]);
const initOptions = async () => {
userGroupOptions.value = await getProjectUserGroup(lastProjectId.value);
userGroupOptions.value = await getProjectUserGroup(appStore.currentProjectId);
userGroupAll.value = [
{
id: '',
@ -391,14 +62,32 @@
...userGroupOptions.value,
];
};
const memberTableRef = ref();
initOptions();
const memberTableRef = ref<InstanceType<typeof memberTable> | null>(null);
onBeforeMount(() => {
const roleIds = ref<string>('');
const keyword = ref<string>('');
const initData = async () => {
memberTableRef.value?.initData();
};
const searchHandler = () => {
initData();
});
};
await tableStore.initColumn(TableKeyEnum.PROJECT_MEMBER, columns, 'drawer');
const changeSelect = () => {
initData();
};
function addMember() {
memberTableRef.value?.addMember();
}
const isMounted = ref(false);
onMounted(() => {
isMounted.value = true; // memberTable
});
</script>
<style scoped></style>