fix(all): 修复场景部分 bug
This commit is contained in:
parent
090e4d5bf9
commit
0db63d9db3
|
@ -13,7 +13,7 @@ export interface ApiDefinitionCustomField {
|
|||
// 创建定义参数
|
||||
export interface ApiDefinitionCreateParams extends ExecuteRequestParams {
|
||||
tags: string[];
|
||||
response: ResponseDefinition;
|
||||
response: ResponseDefinition[];
|
||||
description: string;
|
||||
status: RequestDefinitionStatus;
|
||||
customFields: ApiDefinitionCustomField[];
|
||||
|
|
|
@ -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[]>([]);
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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; // 是否新建
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||
activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex];
|
||||
// requestCompositionRef里监听的是id,所以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">
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue