feat(项目管理): 环境管理相关问题修复和调整交互

This commit is contained in:
xinxin.wu 2024-03-12 20:07:38 +08:00 committed by Craftsman
parent 44f998dc25
commit fdabab9052
26 changed files with 668 additions and 59 deletions

View File

@ -36,4 +36,9 @@ export function reportBathDelete(moduleType: string, data: TableQueryParams) {
return MSR.post({ url: reportUrl.ApiBatchDeleteUrl, data });
}
// 报告详情
export function reportDetail(reportId: string) {
return MSR.get<Record<string, any>>({ url: `${reportUrl.ScenarioReportDetailUrl}/${reportId}` });
}
export default {};

View File

@ -88,6 +88,10 @@ export function groupDeleteEnv(data: EnvListItem) {
export function groupProjectEnv(organizationId: string) {
return MSR.get<ProjectOptionItem[]>({ url: envURL.groupProjectEnvUrl + organizationId });
}
// 获取项目组的项目
export function groupCategoryEnvList(projectId: string) {
return MSR.get<ProjectOptionItem[]>({ url: `${envURL.getProjectEnvCategoryUrl}/${projectId}` });
}
/** 项目管理-环境-全局参数-更新or新增 */
export function updateOrAddGlobalParam(data: GlobalParams) {

View File

@ -15,3 +15,6 @@ export const ApiReportRenameUrl = '/api/report/case/rename';
export const ApiDeleteUrl = '/api/report/case/delete';
// 批量删除接口用例报告
export const ApiBatchDeleteUrl = '/api/report/case/batch/delete';
// 场景报告拔高详情获取
export const ScenarioReportDetailUrl = '/api/report/scenario/get';

View File

@ -24,3 +24,5 @@ export const addGlobalParamUrl = '/project/global/params/add';
export const importGlobalParamUrl = '/project/global/params/import';
export const detailGlobalParamUrl = '/project/global/params/get/';
export const exportGlobalParamUrl = '/project/global/params/export/';
// 获取环境目录列表
export const getProjectEnvCategoryUrl = '/project/environment/get-options';

View File

@ -386,7 +386,6 @@
getCurrentItemState.value =
assertions.value.find((item: any) => item.id === activeKey.value) || assertions.value[0] || {};
activeKey.value = getCurrentItemState.value.id;
console.log(getCurrentItemState.value, 'getCurrentItemState.value');
});
</script>

View File

@ -372,9 +372,10 @@ export default defineComponent(
}
emit('update:modelValue', value);
emit('change', value);
emit(
'changeObject',
remoteOriginOptions.value.filter((e) => value === e[props.valueKey || 'value'])
remoteOriginOptions.value.find((e) => value === e[props.valueKey || 'value'])
);
}

View File

@ -73,7 +73,7 @@
function selectItem(index: any) {
const item = props.items[index];
if (item) {
props.command({ id: item.id, label: `${item.name}` } as any);
props.command({ id: item.id, label: `${item.name}`, style: 'color:blur' } as any);
}
}

View File

@ -77,7 +77,7 @@
const isError = computed(
() =>
innerInputValue.value.length > props.maxLength ||
innerModelValue.value.some((item) => item.toString().length > props.maxLength)
(innerModelValue.value || []).some((item) => item.toString().length > props.maxLength)
);
watch(
() => props.modelValue,

View File

@ -18,7 +18,7 @@
size="16"
/>
<template #content>
<div>{{ t('apiTestDebug.batchAddParamsTip1') }}</div>
<div>{{ props?.addTypeText || t('apiTestDebug.batchAddParamsTip1') }}</div>
<div v-if="!props.noParamType">{{ t('apiTestDebug.batchAddParamsTip2') }}</div>
<div>{{ t('apiTestDebug.batchAddParamsTip3') }}</div>
</template>
@ -53,6 +53,7 @@
params: Record<string, any>[];
defaultParamItem?: Record<string, any>; //
noParamType?: boolean; //
addTypeText?: string; //
}>(),
{
noParamType: false,

View File

@ -138,7 +138,7 @@
{{ t('common.clear') }}
</a-button>
<a-button
v-if="!props.isBuildIn"
v-if="!props.isBuildIn && !props.showPrePostRequest"
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"

View File

@ -211,7 +211,7 @@
<template #tag="{ record, columnConfig, rowIndex }">
<a-popover
position="tl"
:disabled="record[columnConfig.dataIndex as string].length === 0"
:disabled="(record[columnConfig.dataIndex as string]||[]).length === 0"
class="ms-params-input-popover"
>
<template #content>
@ -307,15 +307,13 @@
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip
v-for="project of appStore.projectList"
:key="project.id"
:mouse-enter-delay="500"
:content="project.name"
>
<a-tooltip v-for="project of disProjectList" :key="project.id" :mouse-enter-delay="500" :content="project.name">
<a-option
:key="project.id"
:value="project.id"
:class="project.id === appStore.currentProjectId ? 'arco-select-option-selected' : ''"
:disabled="project.disabled"
>
{{ project.name }}
</a-option>
@ -343,17 +341,13 @@
<span v-else></span>
</template>
<template #host="{ record }">
<span v-if="!record.domain || record.domain.length === 0"></span>
<span v-else-if="Array.isArray(record.domain) && record.domain.length === 1" class="text-[var(--color-text-4)]">{{
record.domain[0].protocol + '://' + (record.domain[0].hostname || '')
}}</span>
<span
v-if="Array.isArray(record.domain) && record.domain.length > 1"
class="cursor-pointer text-[var(--color-text-4)]"
<MsTagGroup
v-if="Array.isArray(record.domain)"
:tag-list="getDomain(record.domain)"
size="small"
@click="showHostModal(record)"
>
{{ t('common.more') }}
</span>
/>
<div v-else class="text-[var(--color-text-1)]">{{ '-' }}</div>
</template>
<template #operation="{ record, rowIndex, columnConfig }">
<div class="flex flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
@ -450,9 +444,10 @@
:max-length="1000"
></a-textarea>
</a-modal>
<a-modal v-model:visible="hostVisible" :title="t('project.environmental.host')" @close="hostModalClose">
<!-- <a-modal v-model:visible="hostVisible" :title="t('project.environmental.host')" @close="hostModalClose">
<a-table :columns="hostColumn" :data="hostData" />
</a-modal>
</a-modal> -->
<DomainModal v-model:visible="hostVisible" :data="hostData" />
</template>
<script async setup lang="ts">
@ -464,18 +459,20 @@
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsSelect from '@/components/business/ms-select/index';
import paramDescInput from './paramDescInput.vue';
import DomainModal from '@/views/project-management/environmental/components/envParams/popUp/domain.vue';
import { groupProjectEnv, listEnv } from '@/api/modules/project-management/envManagement';
import { groupCategoryEnvList, groupProjectEnv } from '@/api/modules/project-management/envManagement';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
import { ProjectOptionItem } from '@/models/projectManagement/environmental';
import { HttpForm, ProjectOptionItem } from '@/models/projectManagement/environmental';
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -645,15 +642,18 @@
const res = await groupProjectEnv(appStore.currentOrgId);
sourceProjectOptions.value = res;
};
//
const envDomainList = ref<ProjectOptionItem[]>([]);
// options
const initEnvOptions = async (params: Record<string, any>) => {
const { projectId, keyword } = params;
const res = await listEnv({ projectId, keyword });
const { projectId } = params;
const res = await groupCategoryEnvList(projectId);
envDomainList.value = res;
return res;
};
const handleEnvironment = (obj: Record<string, any>, record: Record<string, any>) => {
record.domain = {};
record.domain = obj.domain;
emitChange('handleEnvironment');
};
@ -663,6 +663,8 @@
{
title: t('project.environmental.http.host'),
dataIndex: 'host',
showTooltip: true,
width: 300,
},
{
title: t('project.environmental.http.desc'),
@ -672,10 +674,6 @@
const showHostModal = (record: Record<string, any>) => {
hostVisible.value = true;
record.domain?.forEach((e: any) => {
e.host = `${e.protocol} :// ${e.hostname || ''}`;
});
hostData.value = record.domain || [];
};
@ -899,6 +897,29 @@
addTableLine(rowIndex);
}
const disProjectList = computed(() => {
const selectProjectIds = (props.params || []).map((item) => item.projectId).filter((item) => item);
return appStore.projectList.map((item: any) => {
if (selectProjectIds.includes(item.id)) {
return {
...item,
disabled: true,
};
}
return {
...item,
};
});
});
function getDomain(domain: HttpForm[]) {
return (domain || []).map((item: any) => {
return {
id: item.id,
name: item.hostname,
};
});
}
defineExpose({
addTableLine,
});

View File

@ -0,0 +1,205 @@
<template>
<MsDetailDrawer
ref="detailDrawerRef"
v-model:visible="showDrawer"
:width="960"
:footer="false"
:title="t('project.fileManagement.detail')"
:detail-id="props.reportId"
:detail-index="props.activeReportIndex"
:get-detail-func="reportDetail"
:pagination="props.pagination"
:table-data="props.tableData"
:page-change="props.pageChange"
show-full-screen
:unmount-on-close="true"
@loaded="loadedReport"
>
<template #titleRight="{ loading }">
<div class="rightButtons flex items-center">
<MsButton
type="icon"
status="secondary"
class="mr-4 !rounded-[var(--border-radius-small)]"
:disabled="loading"
:loading="shareLoading"
@click="shareHandler"
>
<MsIcon type="icon-icon_share1" class="mr-2 font-[16px]" />
{{ t('common.share') }}
</MsButton>
<MsButton
type="icon"
status="secondary"
class="mr-4 !rounded-[var(--border-radius-small)]"
:disabled="loading"
:loading="exportLoading"
@click="exportHandler"
>
<MsIcon type="icon-icon_move_outlined" class="mr-2 font-[16px]" />
{{ t('common.export') }}
</MsButton>
</div>
</template>
<template #default>
<div class="report-container h-full">
<!-- 报告参数开始 -->
<div class="report-header flex items-center justify-between">
<!-- TODO 虚拟数据替换接口后边 -->
<span>
dev环境
<a-divider direction="vertical" :margin="4"></a-divider>
66 资源池
<a-divider direction="vertical" :margin="4"></a-divider>
1000ms
<a-divider direction="vertical" :margin="4"></a-divider>
admin
</span>
<span>
<span class="text-[var(--color-text-4)]">执行时间</span>
2023-08-10 17:53:03
<span class="text-[var(--color-text-4)]"></span>
2023-08-10 17:53:03
</span>
</div>
<!-- 报告参数结束 -->
<!-- 报告步骤分析和请求分析开始 -->
<div class="analyze">
<div class="step-analyze min-w-[522px]">
<div class="block-title">步骤分析</div>
<div class="mb-2 flex items-center">
<!-- 总数 -->
<div class="countItem">
<span class="mr-2 text-[var(--color-text-4)]"> {{ t('report.detail.stepTotal') }}</span>
{{ reportStepDetail.stepTotal }}
</div>
<!-- 通过 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.successCount') }}</div>
{{ reportStepDetail.successCount }}
</div>
<!-- 误报 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.fakeErrorCount') }}</div>
{{ reportStepDetail.fakeErrorCount }}
</div>
<!-- 失败 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.errorCount') }}</div>
{{ reportStepDetail.errorCount }}
</div>
<!-- 未执行 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-input-border)]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.pendingCount') }}</div>
{{ reportStepDetail.pendingCount }}
</div>
</div>
<StepProgress :report-detail="reportStepDetail" height="8px" radius="var(--border-radius-mini)" />
</div>
<div class="request-analyze"></div>
</div>
<!-- 报告步骤分析和请求分析结束 -->
<!-- 报告明细开始 -->
<div class="report-info"></div>
<!-- 报告明细结束 -->
</div>
</template>
</MsDetailDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import type { MsPaginationI } from '@/components/pure/ms-table/type';
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
import StepProgress from './stepProgress.vue';
import { reportDetail } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
reportId: string;
activeReportIndex: number;
tableData: any[];
pagination: MsPaginationI;
pageChange: (page: number) => Promise<void>;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
}>();
const showDrawer = computed({
get() {
return props.visible;
},
set(val) {
emit('update:visible', val);
},
});
const innerFileId = ref(props.reportId);
function loadedReport(detail: Record<string, any>) {
innerFileId.value = detail.id;
}
/**
* 分享share
*/
const shareLoading = ref<boolean>(false);
function shareHandler() {}
/**
* 导出
*/
const exportLoading = ref<boolean>(false);
function exportHandler() {}
const reportStepDetail = ref({
stepTotal: 8,
errorCount: 2,
fakeErrorCount: 8,
pendingCount: 9,
successCount: 9,
});
</script>
<style scoped lang="less">
.report-container {
padding: 16px;
height: calc(100vh - 56px);
background: var(--color-text-n9);
.report-header {
padding: 0 16px;
height: 54px;
border-radius: 4px;
background: white;
@apply mb-4 bg-white;
}
.analyze {
padding: 16px;
min-height: 196px;
border-radius: 4px;
@apply flex justify-between bg-white;
.step-analyze {
@apply mb-4 h-full;
.countItem {
@apply mr-6 flex items-center;
}
}
.request-analyze {
@apply h-full;
}
}
}
.block-title {
@apply mb-4 font-medium;
}
</style>

View File

@ -24,6 +24,11 @@
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #name="{ record, rowIndex }">
<a-button type="text" class="flex w-full" @click="showReportDetail(record.id, rowIndex)">{{
record.name
}}</a-button>
</template>
<!-- 报告类型 -->
<template #integrated="{ record }">
<MsTag theme="light" :type="record.integrated ? 'primary' : undefined">
@ -101,6 +106,14 @@
>
</template>
</ms-base-table>
<ReportDetailDrawer
v-model:visible="showDetailDrawer"
:report-id="activeDetailId"
:active-report-index="activeReportIndex"
:table-data="propsRes.data"
:page-change="propsEvent.pageChange"
:pagination="propsRes.msPagination!"
/>
</div>
</template>
@ -114,6 +127,7 @@
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import ReportDetailDrawer from './reportDetailDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import { reportBathDelete, reportDelete, reportList, reportRename } from '@/api/modules/api-test/report';
@ -361,6 +375,18 @@
initData();
}
/**
* 报告详情 showReportDetail
*/
const activeDetailId = ref<string>('');
const activeReportIndex = ref<number>(0);
const showDetailDrawer = ref<boolean>(false);
function showReportDetail(id: string, rowIndex: number) {
showDetailDrawer.value = true;
activeDetailId.value = id;
activeReportIndex.value = rowIndex;
}
watch(
() => props.moduleType,
(val) => {

View File

@ -0,0 +1,116 @@
<template>
<MsColorLine :color-data="colorData" :height="props.height" :radius="props.radius">
<template #popoverContent>
<table class="min-w-[144px]">
<tr>
<td class="popover-label-td">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
<div>{{ t('report.detail.successCount') }}</div>
</td>
<td class="popover-value-td">
{{ props.reportDetail.successCount }}
</td>
</tr>
<tr>
<td class="popover-label-td">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
<div>{{ t('report.detail.fakeErrorCount') }}</div>
</td>
<td class="popover-value-td">
{{ props.reportDetail.fakeErrorCount }}
</td>
</tr>
<tr>
<td class="popover-label-td">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
<div>{{ t('report.detail.errorCount') }}</div>
</td>
<td class="popover-value-td">
{{ props.reportDetail.errorCount }}
</td>
</tr>
<tr>
<td class="popover-label-td">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-input-border)]"></div>
<div>{{ t('report.detail.pendingCount') }}</div>
</td>
<td class="popover-value-td">
{{ props.reportDetail.pendingCount }}
</td>
</tr>
</table>
</template>
</MsColorLine>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsColorLine from '@/components/pure/ms-color-line/index.vue';
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
reportDetail: {
stepTotal: number;
errorCount: number;
fakeErrorCount: number;
pendingCount: number;
successCount: number;
[key: string]: any;
};
height: string;
radius?: string;
}>();
const { t } = useI18n();
const colorData = computed(() => {
if (
props.reportDetail.status === 'ERROR' ||
(props.reportDetail.successCount === 0 &&
props.reportDetail.errorCount === 0 &&
props.reportDetail.fakeErrorCount === 0 &&
props.reportDetail.pendingCount === 0)
) {
return [
{
percentage: 100,
color: 'var(--color-text-n8)',
},
];
}
return [
{
percentage: (props.reportDetail.successCount / props.reportDetail.stepTotal) * 100,
color: 'rgb(var(--success-6))',
},
{
percentage: (props.reportDetail.errorCount / props.reportDetail.stepTotal) * 100,
color: 'rgb(var(--danger-6))',
},
{
percentage: (props.reportDetail.fakeErrorCount / props.reportDetail.stepTotal) * 100,
color: 'rgb(var(--warning-6))',
},
{
percentage: (props.reportDetail.pendingCount / props.reportDetail.stepTotal) * 100,
color: 'var(--color-text-input-border)',
},
];
});
</script>
<style lang="less" scoped>
.popover-label-td {
@apply flex items-center;
padding: 8px 8px 0 0;
color: var(--color-text-4);
}
.popover-value-td {
@apply font-medium;
padding-top: 8px;
color: var(--color-text-1);
}
</style>

View File

@ -23,4 +23,9 @@ export default {
'report.trigger.interface': 'API',
'report.trigger.batch.execution': 'Batch',
'report.delete.tip': 'Are you sure you want to delete {count} selected reports?',
'report.detail.successCount': 'pass',
'report.detail.errorCount': 'Failure',
'report.detail.fakeErrorCount': 'False alarm',
'report.detail.pendingCount': 'Not executed',
'report.detail.stepTotal': 'total',
};

View File

@ -23,4 +23,9 @@ export default {
'report.trigger.interface': 'API 执行',
'report.trigger.batch.execution': '批量执行',
'report.delete.tip': '确认删除已选中的 {count} 个报告吗?',
'report.detail.successCount': '通过',
'report.detail.errorCount': '失败',
'report.detail.fakeErrorCount': '误报',
'report.detail.pendingCount': '未执行',
'report.detail.stepTotal': '总数',
};

View File

@ -91,13 +91,15 @@
title: 'project.environmental.env',
dataIndex: 'environmentId',
slotName: 'environment',
width: 200,
width: 300,
},
{
title: 'project.environmental.host',
dataIndex: 'host',
slotName: 'host',
width: 200,
showTooltip: true,
isTag: true,
width: 456,
},
{
title: 'project.environmental.desc',

View File

@ -94,11 +94,14 @@
import { getEnvPlugin, updateOrAddEnv } from '@/api/modules/project-management/envManagement';
import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import { useAppStore } from '@/store';
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
import { ContentTabItem, EnvPluginListItem } from '@/models/projectManagement/environmental';
const { setState } = useLeaveUnSaveTip();
setState(false);
const emit = defineEmits<{
(e: 'ok'): void;
(e: 'resetEnv'): void;
@ -200,6 +203,7 @@
loading.value = true;
store.currentEnvDetailInfo.mock = true;
await updateOrAddEnv({ fileList: [], request: store.currentEnvDetailInfo });
setState(true);
Message.success(t('common.saveSuccess'));
emit('ok');
} catch (error) {

View File

@ -14,14 +14,19 @@
</span>
</template>
</a-input-search>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
<batchAddKeyVal
:add-type-text="t('project.environmental.env.constantBatchAddTip')"
:params="innerParams"
no-param-type
@apply="handleBatchParamApply"
/>
</div>
<paramsTable
v-model:params="innerParams"
:table-key="props.tableKey"
:columns="columns"
show-setting
:selectable="false"
:selectable="true"
:default-param-item="defaultParamItem"
@change="handleParamTableChange"
/>
@ -67,6 +72,7 @@
value: '',
description: '',
tags: [],
enable: true,
};
const columns: ParamTableColumn[] = [
@ -84,7 +90,7 @@
slotName: 'paramType',
showInTable: true,
showDrag: true,
hasRequired: true,
hasRequired: false,
columnSelectorDisabled: true,
typeOptions: [
{
@ -162,7 +168,9 @@
if (!searchValue.value) {
innerParams.value = [...backupParams.value];
} else {
const result = backupParams.value.filter((item) => item.key.includes(searchValue.value));
const result = backupParams.value.filter(
(item) => item.key.includes(searchValue.value) || item.tags.includes(searchValue.value)
);
innerParams.value = [...result];
}
}

View File

@ -22,6 +22,12 @@
><span class="one-line-text text-xs text-[var(--color-text-4)]">{{
t('project.environmental.nonClose')
}}</span></a-divider
>
<VueDraggable
v-model="couldCloseColumn"
class="ms-assertion-body-left"
ghost-class="ghost"
handle=".column-drag-item"
>
<div v-for="element in couldCloseColumn" :key="element.value" class="column-drag-item">
<div class="flex w-[90%] items-center">
@ -29,12 +35,14 @@
</div>
<a-switch v-model="element.isShow" size="small" type="line" @change="handleSwitchChange" />
</div>
</VueDraggable>
</div>
</MsDrawer>
</template>
<script lang="ts" setup>
import { defineModel, onBeforeMount, ref } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';

View File

@ -10,14 +10,14 @@
t('project.environmental.addHttp')
}}</a-button>
<div class="flex flex-row gap-[8px]">
<a-input-number v-model:model-value="form.requestTimeout" class="w-[180px]">
<a-input-number v-model:model-value="form.requestTimeout" :min="0" class="w-[180px]">
<template #prefix>
<span class="text-[var(--color-text-3)]">{{ t('project.environmental.http.linkTimeOut') }}</span>
</template>
</a-input-number>
<a-input-number v-model:model-value="form.responseTimeout" class="w-[180px]">
<a-input-number v-model:model-value="form.responseTimeout" :min="0" class="w-[180px]">
<template #prefix>
<span class="text-[var(--color-text-3)]">{{ t('project.environmental.http.linkTimeOut') }}</span>
<span class="text-[var(--color-text-3)]">{{ t('project.environmental.http.resTimeOut') }}</span>
</template>
</a-input-number>
<!-- TOTO 第一个版本不做 -->
@ -130,11 +130,11 @@
];
await tableStore.initColumn(TableKeyEnum.PROJECT_MANAGEMENT_ENV_ENV_HTTP, columns);
const { propsRes, propsEvent } = useTable(undefined, {
tableKey: TableKeyEnum.PROJECT_MANAGEMENT_ENV_ENV_HTTP,
columns,
scroll: { x: '100%' },
selectable: false,
noDisable: true,
showSetting: true,
showSetting: false,
showPagination: false,
enableDrag: true,
showMode: false,
@ -255,12 +255,30 @@
}
await initModuleTree();
const OPERATOR_MAP = [
{
value: 'CONTAINS',
label: '包含',
},
{
value: 'EQUALS',
label: '等于',
},
];
function getCondition(condition: string) {
return OPERATOR_MAP.find((item) => item.value === condition)?.label;
}
function getModuleName(record: HttpForm) {
if (record.type === 'MODULE') {
const moduleIds: string[] = record.moduleMatchRule.modules.map((item) => item.moduleId);
const result = findNodeNames(moduleTree.value, moduleIds);
return result.join(',');
}
if (record.type === 'PATH') {
return `${getCondition(record.pathMatchRule.condition)}${record.pathMatchRule.path}`;
}
return '-';
}
</script>

View File

@ -155,7 +155,7 @@
</a-input-group>
</a-form-item>
</a-form>
<RequestHeader v-model:params="form.headers" />
<RequestHeader v-model:params="form.headers" :no-param-type="true" />
</MsDrawer>
</template>

View File

@ -0,0 +1,173 @@
<template>
<a-modal
v-model:visible="innerVisible"
class="ms-modal-form ms-modal-large"
title-align="start"
:ok-text="t('system.userGroup.add')"
unmount-on-close
@cancel="handleCancel"
>
<template #title> 域名列表 </template>
<div>
<MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
<template #type="{ record }">
<span>{{ getEnableScope(record.type) }}</span>
</template>
<template #moduleValue="{ record }">
<a-tooltip :content="getModuleName(record)" position="left">
<span class="one-line-text max-w-[300px]">{{ getModuleName(record) }}</span>
</a-tooltip>
</template>
</MsBaseTable>
</div>
<template #footer>
<a-button type="secondary" @click="handleCancel">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" @click="handleCancel">
{{ t('common.confirm') }}
</a-button>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import { getEnvModules } from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore, useTableStore } from '@/store';
import { findNodeNames } from '@/utils';
import type { ModuleTreeNode } from '@/models/common';
import { HttpForm } from '@/models/projectManagement/environmental';
const appStore = useAppStore();
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
data: HttpForm[];
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean);
}>();
const innerVisible = useVModel(props, 'visible', emit);
function handleCancel() {
innerVisible.value = false;
}
const columns: MsTableColumn = [
{
title: 'project.environmental.http.host',
dataIndex: 'hostname',
slotName: 'hostname',
showTooltip: true,
showDrag: true,
showInTable: true,
},
{
title: 'project.environmental.http.desc',
dataIndex: 'description',
showDrag: true,
showInTable: true,
},
{
title: 'project.environmental.http.enableScope',
dataIndex: 'type',
slotName: 'type',
showDrag: true,
showInTable: true,
},
{
title: 'project.environmental.http.value',
dataIndex: 'value',
slotName: 'moduleValue',
showTooltip: false,
showDrag: true,
showInTable: true,
},
];
const { propsRes, propsEvent } = useTable(undefined, {
columns,
scroll: { x: '100%' },
selectable: false,
noDisable: true,
showSetting: false,
showPagination: false,
enableDrag: false,
showMode: false,
heightUsed: 644,
debug: true,
});
function getEnableScope(type: string) {
switch (type) {
case 'NONE':
return t('project.environmental.http.none');
case 'MODULE':
return t('project.environmental.http.module');
case 'PATH':
return t('project.environmental.http.path');
default:
break;
}
}
const moduleTree = ref<ModuleTreeNode[]>([]);
async function initModuleTree() {
try {
const res = await getEnvModules({
projectId: appStore.currentProjectId,
});
moduleTree.value = res.moduleTree;
} catch (error) {
console.log(error);
}
}
const OPERATOR_MAP = [
{
value: 'CONTAINS',
label: '包含',
},
{
value: 'EQUALS',
label: '等于',
},
];
function getCondition(condition: string) {
return OPERATOR_MAP.find((item) => item.value === condition)?.label;
}
function getModuleName(record: HttpForm) {
if (record.type === 'MODULE') {
const moduleIds: string[] = record.moduleMatchRule.modules.map((item) => item.moduleId);
const result = findNodeNames(moduleTree.value, moduleIds);
return result.join(',');
}
if (record.type === 'PATH') {
return `${getCondition(record.pathMatchRule.condition)}${record.pathMatchRule.path}`;
}
return '-';
}
watch(
() => props.data,
(val) => {
if (val) {
propsRes.value.data = val;
}
}
);
</script>
<style scoped></style>

View File

@ -1,7 +1,7 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<div class="font-medium">{{ t('apiTestDebug.header') }}</div>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
<batchAddKeyVal :no-param-type="props.noParamType" :params="innerParams" @apply="handleBatchParamApply" />
</div>
<paramsTable
v-model:params="innerParams"
@ -30,6 +30,7 @@
const props = defineProps<{
params: any[];
noParamType?: boolean;
}>();
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;

View File

@ -41,7 +41,7 @@ export default {
'project.environmental.httpNoWarning': 'No warning',
'project.environmental.addHttp': 'Add HTTP',
'project.environmental.http.linkTimeOut': 'Link Timeout (ms):',
'project.environmental.http.timeTimeOut': 'Timeout Time (ms):',
'project.environmental.http.resTimeOut': 'Response timeout (ms) :',
'project.environmental.http.authType': 'Authentication Type:',
'project.environmental.http.host': 'Host',
'project.environmental.http.desc': 'Description',
@ -96,11 +96,11 @@ export default {
'project.environmental.postScriptBefore': 'Post script front',
'project.environmental.postScriptAfter': 'After the script',
'project.environmental.http.preTextPreTip':
'Before pre script, environment scripts executed before buy scripts in the request;',
'Before the pre-script, the script in the environment is executed before the requested pre-script is executed.',
'project.environmental.http.preTextPostTip':
'After the preset script, the script in the environment is executed after the requested preset script is executed;',
'project.environmental.http.postTextPreTip':
'The script in the environment is executed before the requested script is executed.',
'The script in the environment is executed before the requested postscript is executed;',
'project.environmental.http.postTextPostTip':
'After rear script, environment a prerequisite for the script in the request after the script execution;',
'project.environmental.preOrPost.ignoreProtocols': 'Ignore request:',
@ -111,4 +111,5 @@ export default {
'project.environmental.env.selectableTitle': 'Optional Environments',
'project.environmental.env.systemTitle': 'environment',
'project.environmental.env.selectedTitle': 'Selected environment',
'project.environmental.env.constantBatchAddTip': 'Only supports batch adding const type',
};

View File

@ -45,8 +45,8 @@ export default {
'project.environmental.httpTitle': '当满足多个启用条件时,按从上到下的顺序匹配',
'project.environmental.httpNoWarning': '不在提醒',
'project.environmental.addHttp': '添加 HTTP',
'project.environmental.http.linkTimeOut': '接超时(ms):',
'project.environmental.http.timeTimeOut': '超时时间(ms):',
'project.environmental.http.linkTimeOut': '接超时(ms):',
'project.environmental.http.resTimeOut': '响应超时(ms):',
'project.environmental.http.authType': '认证方式:',
'project.environmental.http.host': '域名',
'project.environmental.http.desc': '描述',
@ -109,9 +109,9 @@ export default {
'project.environmental.preScriptAfter': '前置脚本后',
'project.environmental.postScriptBefore': '后置脚本前',
'project.environmental.postScriptAfter': '后置脚本后',
'project.environmental.http.preTextPreTip': '前置脚本前,环境中脚本在请求的置脚本执行前执行;',
'project.environmental.http.preTextPreTip': '前置脚本前,环境中脚本在请求的置脚本执行前执行;',
'project.environmental.http.preTextPostTip': '前置置脚本后,环境中脚本在请求的前置脚本执行后执行;',
'project.environmental.http.postTextPreTip': '后置脚本前,环境中脚本在请求的置脚本执行前执行;',
'project.environmental.http.postTextPreTip': '后置脚本前,环境中脚本在请求的置脚本执行前执行;',
'project.environmental.http.postTextPostTip': '后置脚本后,环境中脚本在请求的前置脚本执行后执行;',
'project.environmental.preOrPost.ignoreProtocols': '忽略请求:',
'project.environmental.preOrPost.pre': '脚本前',
@ -122,4 +122,5 @@ export default {
'project.environmental.env.selectableTitle': '可选环境',
'project.environmental.env.systemTitle': '环境',
'project.environmental.env.selectedTitle': '已选环境',
'project.environmental.env.constantBatchAddTip': '只支持常量类型批量添加',
};