fix: 修复接口测试报告bug&公共脚本bug&三方插件bug

This commit is contained in:
xinxin.wu 2024-04-06 10:41:55 +08:00 committed by 刘瑞斌
parent 1a2b88f7a2
commit d6cc864783
24 changed files with 371 additions and 186 deletions

View File

@ -3,6 +3,7 @@ import {
AddCommonScriptUrl, AddCommonScriptUrl,
ConnectionWebsocketUrl, ConnectionWebsocketUrl,
DeleteCommonScriptUrl, DeleteCommonScriptUrl,
getChangeHistoryUrl,
GetCommonScriptDetailUrl, GetCommonScriptDetailUrl,
GetCommonScriptPageUrl, GetCommonScriptPageUrl,
GetCommonScriptStatusUrl, GetCommonScriptStatusUrl,
@ -19,6 +20,7 @@ import type { ModulesTreeType } from '@/models/caseManagement/featureCase';
import { CommonList, TableQueryParams } from '@/models/common'; import { CommonList, TableQueryParams } from '@/models/common';
import type { import type {
AddOrUpdateCommonScript, AddOrUpdateCommonScript,
changeHistory,
CommonScriptItem, CommonScriptItem,
TestScriptType, TestScriptType,
} from '@/models/projectManagement/commonScript'; } from '@/models/projectManagement/commonScript';
@ -57,6 +59,10 @@ export function getCommonScriptStatus(data: AddOrUpdateCommonScript) {
export function getInsertCommonScriptPage(data: TableQueryParams) { export function getInsertCommonScriptPage(data: TableQueryParams) {
return MSR.post<CommonList<CommonScriptItem[]>>({ url: GetInsertCommonScriptPageUrl, data }); return MSR.post<CommonList<CommonScriptItem[]>>({ url: GetInsertCommonScriptPageUrl, data });
} }
// 获取公共脚本变更历史详情
export function getChangeHistory(data: TableQueryParams) {
return MSR.post<CommonList<changeHistory[]>>({ url: getChangeHistoryUrl, data });
}
/** /**
* *

View File

@ -25,3 +25,5 @@ export const GetFormApiImportModuleCountUrl = '/api/definition/module/count';
export const TestScriptUrl = '/api/test/custom/func/run'; export const TestScriptUrl = '/api/test/custom/func/run';
// websoket连接 // websoket连接
export const ConnectionWebsocketUrl = '/ws/api'; export const ConnectionWebsocketUrl = '/ws/api';
// 公共脚本变更历史详情
export const getChangeHistoryUrl = '/project/custom/func/history/page';

View File

@ -1,14 +1,5 @@
<template> <template>
<div class="h-full w-full"> <conditionContent v-model:data="condition" :disabled="props.disabled" />
<a-scrollbar
:style="{
overflow: 'auto',
height: 'calc(100vh - 490px)',
}"
>
<conditionContent v-model:data="condition" :disabled="props.disabled" />
</a-scrollbar>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -38,12 +29,6 @@
const condition = useVModel(props, 'data', emit); const condition = useVModel(props, 'data', emit);
// function handleChange() {
// // eslint-disable-next-line no-use-before-define
// emit('update:data');
// emit('change', { ...condition.value });
// }
const currentEnvConfig = ref({}); const currentEnvConfig = ref({});
async function initEnvironment() { async function initEnvironment() {

View File

@ -81,7 +81,6 @@
:style="{ :style="{
overflow: 'auto', overflow: 'auto',
height: '100%', height: '100%',
width: '100%',
}" }"
> >
<!-- 响应头 --> <!-- 响应头 -->
@ -123,13 +122,13 @@
@change="handleChange" @change="handleChange"
/> />
<!-- 脚本 --> <!-- 脚本 -->
<ScriptTab
v-if="getCurrentItemState.assertionType === ResponseAssertionType.SCRIPT"
v-model:data="getCurrentItemState"
:disabled="props.disabled"
@change="handleChange"
/>
</a-scrollbar> </a-scrollbar>
<ScriptTab
v-if="getCurrentItemState.assertionType === ResponseAssertionType.SCRIPT"
v-model:data="getCurrentItemState"
:disabled="props.disabled"
@change="handleChange"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -383,7 +383,7 @@
treeRef.value?.checkAll(val); treeRef.value?.checkAll(val);
} }
function expandNode(key: string | number, expanded: boolean) { function expandNode(key: (string | number)[] | (string | number), expanded: boolean) {
treeRef.value?.expandNode(key, expanded); treeRef.value?.expandNode(key, expanded);
} }

View File

@ -136,7 +136,7 @@ export const INT = {
}; };
export const MULTIPLE_INPUT = { export const MULTIPLE_INPUT = {
type: 'a-input-tag', type: 'MsTagsInput',
field: 'fieldName', field: 'fieldName',
title: '', title: '',
value: [], value: [],

View File

@ -15,6 +15,7 @@
*/ */
import { ref, watch, watchEffect } from 'vue'; import { ref, watch, watchEffect } from 'vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import JiraKey from './comp/jiraKey.vue'; import JiraKey from './comp/jiraKey.vue';
import PassWord from './formcreate-password.vue'; import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue'; import SearchSelect from './searchSelect.vue';
@ -24,6 +25,7 @@
formCreate.component('PassWord', PassWord); formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect); formCreate.component('SearchSelect', SearchSelect);
formCreate.component('JiraKey', JiraKey); formCreate.component('JiraKey', JiraKey);
formCreate.component('MsTagsInput', MsTagsInput);
const FormCreate = formCreate.$form(); const FormCreate = formCreate.$form();
const props = defineProps<{ const props = defineProps<{

View File

@ -11,6 +11,7 @@
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create'; import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import JiraKey from './comp/jiraKey.vue'; import JiraKey from './comp/jiraKey.vue';
import PassWord from './formcreate-password.vue'; import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue'; import SearchSelect from './searchSelect.vue';
@ -27,6 +28,7 @@
formCreate.component('PassWord', PassWord); formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect); formCreate.component('SearchSelect', SearchSelect);
formCreate.component('JiraKey', JiraKey); formCreate.component('JiraKey', JiraKey);
formCreate.component('MsTagsInput', MsTagsInput);
const FormCreate = formCreate.$form(); const FormCreate = formCreate.$form();
// //

View File

@ -1,37 +1,39 @@
<template> <template>
<div :class="`flex w-full items-center ${props.class}`"> <div class="w-full">
<a-input-tag <div :class="`flex w-full items-center ${props.class}`">
v-model:model-value="innerModelValue" <a-input-tag
v-model:input-value="innerInputValue" v-model:model-value="innerModelValue"
:error="isError" v-model:input-value="innerInputValue"
:placeholder="t(props.placeholder || 'ms.tagsInput.tagsInputPlaceholder')" :error="isError"
:allow-clear="props.allowClear" :placeholder="t(props.placeholder || 'ms.tagsInput.tagsInputPlaceholder')"
:retain-input-value="props.retainInputValue" :allow-clear="props.allowClear"
:unique-value="props.uniqueValue" :retain-input-value="props.retainInputValue"
:max-tag-count="props.maxTagCount" :unique-value="props.uniqueValue"
:readonly="props.readonly" :max-tag-count="props.maxTagCount"
:class="props.inputClass" :readonly="props.readonly"
:size="props.size" :class="props.inputClass"
:disabled="props.disabled" :size="props.size"
@press-enter="tagInputEnter" :disabled="props.disabled"
@blur="tagInputBlur" @press-enter="tagInputEnter"
@clear="emit('clear')" @blur="tagInputBlur"
@click="emit('click')" @clear="emit('clear')"
> @click="emit('click')"
<template v-if="$slots.prefix" #prefix> >
<slot name="prefix"></slot> <template v-if="$slots.prefix" #prefix>
</template> <slot name="prefix"></slot>
<template v-if="$slots.tag" #tag="{ data }"> </template>
<slot name="tag" :data="data"></slot> <template v-if="$slots.tag" #tag="{ data }">
</template> <slot name="tag" :data="data"></slot>
<template v-if="$slots.suffix" #suffix> </template>
<slot name="suffix"></slot> <template v-if="$slots.suffix" #suffix>
</template> <slot name="suffix"></slot>
</a-input-tag> </template>
</a-input-tag>
</div>
<div v-if="isError" class="ml-[1px] flex justify-start text-[12px] text-[rgb(var(--danger-6))]">
{{ t('common.tagInputMaxLength', { number: props.maxLength }) }}
</div>
</div> </div>
<span v-if="isError" class="ml-[1px] text-[12px] text-[rgb(var(--danger-6))]">
{{ t('common.tagInputMaxLength', { number: props.maxLength }) }}
</span>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -105,6 +105,7 @@ export interface ScenarioItemType {
children: ScenarioItemType[]; children: ScenarioItemType[];
level?: number; level?: number;
stepDetail: ReportStepDetailItem; stepDetail: ReportStepDetailItem;
stepChildren?: ScenarioItemType[]; // 步骤子步骤
} }
export type ScenarioDetailItem = Partial<ScenarioItemType>; export type ScenarioDetailItem = Partial<ScenarioItemType>;

View File

@ -66,3 +66,16 @@ export interface TestScriptType {
script: string; script: string;
projectId: string; projectId: string;
} }
// 变更历史详情
export interface changeHistory {
id: string;
projectId: string;
createTime: string;
createUser: string;
sourceId: string;
type: string;
module: string;
refId: string;
createUserName: string;
versionName: string;
}

View File

@ -17,7 +17,7 @@
<slot name="titleRight"></slot> <slot name="titleRight"></slot>
</div> </div>
</div> </div>
<div v-if="data.length > 0 && activeItem" class="flex h-[calc(100%-40px)] gap-[8px]"> <div v-if="data.length > 0 && activeItem" class="flex h-[calc(100%-40px)] w-full gap-[8px]">
<div class="h-full w-[20%] min-w-[220px]"> <div class="h-full w-[20%] min-w-[220px]">
<conditionList <conditionList
v-model:list="data" v-model:list="data"

View File

@ -310,14 +310,16 @@
const isShowLoopControl = computed(() => { const isShowLoopControl = computed(() => {
return ( return (
props.stepItem?.children && props.stepItem.children.length && showApiType.value.includes(props.stepItem.stepType) props.stepItem?.stepChildren &&
props.stepItem?.stepChildren.length &&
showApiType.value.includes(props.stepItem.stepType)
); );
}); });
const controlCurrent = ref<number>(1); const controlCurrent = ref<number>(1);
const controlTotal = computed(() => { const controlTotal = computed(() => {
if (props.stepItem?.children) { if (props.stepItem?.stepChildren) {
return props.stepItem.children.length || 0; return props.stepItem?.stepChildren.length || 0;
} }
return 0; return 0;
}); });
@ -326,8 +328,8 @@
* 循环次数控制器 * 循环次数控制器
*/ */
function loadControlLoop() { function loadControlLoop() {
if (isShowLoopControl.value) { if (isShowLoopControl.value && props.stepItem?.stepChildren) {
const loopStepId = props.stepItem?.children[controlCurrent.value - 1].stepId; const loopStepId = props.stepItem?.stepChildren[controlCurrent.value - 1].stepId;
if (loopStepId) { if (loopStepId) {
getStepDetail(loopStepId); getStepDetail(loopStepId);
} }
@ -340,8 +342,8 @@
() => props.stepItem?.stepId, () => props.stepItem?.stepId,
(val) => { (val) => {
if (val) { if (val) {
if (isShowLoopControl.value) { if (isShowLoopControl.value && props.stepItem?.stepChildren) {
getStepDetail(props.stepItem?.children[controlCurrent.value - 1].stepId as string); getStepDetail(props.stepItem?.stepChildren[controlCurrent.value - 1].stepId as string);
} else { } else {
getStepDetail(val); getStepDetail(val);
} }

View File

@ -58,7 +58,7 @@
required: true, required: true,
}); });
const reportStepDetail = ref<ReportDetail>({ const initReportStepDetail = {
id: '', id: '',
name: '', // name: '', //
testPlanId: '', testPlanId: '',
@ -96,6 +96,9 @@
children: [], // children: [], //
stepTotal: 0, // stepTotal: 0, //
console: '', console: '',
};
const reportStepDetail = ref<ReportDetail>({
...initReportStepDetail,
}); });
async function getReportCaseDetail() { async function getReportCaseDetail() {
try { try {
@ -110,6 +113,7 @@
() => innerVisible.value, () => innerVisible.value,
async (val) => { async (val) => {
if (val) { if (val) {
reportStepDetail.value = { ...initReportStepDetail };
await getReportCaseDetail(); await getReportCaseDetail();
} }
} }

View File

@ -3,13 +3,39 @@
<div class="relative mr-4"> <div class="relative mr-4">
<div class="charts absolute text-center"> <div class="charts absolute text-center">
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div> <div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
<div class="text-[18px] font-medium">{{ props.requestTotal }}</div> <a-popover position="bottom" content-class="response-popover-content">
<div class="flex justify-center text-[18px] font-medium">
<div class="one-line-text max-w-[60px]">{{ getIndicators(requestTotal) }}</div>
</div>
<template #content>
<div class="min-w-[176px] max-w-[400px] p-4 text-[14px]">
<div class="text-[12px] font-medium text-[var(--color-text-4)]">{{ t('report.detail.api.total') }}</div>
<div class="mt-2 text-[18px] font-medium text-[var(--color-text-1)]">{{
getIndicators(addCommasToNumber(requestTotal))
}}</div>
</div>
</template>
</a-popover>
</div> </div>
<MsChart width="110px" height="110px" :options="props.options" /> <a-popover position="bottom" content-class="response-popover-content">
<div> <MsChart width="110px" height="110px" :options="props.options" /></div>
<template #content>
<div class="min-w-[176px] max-w-[400px] p-4">
<div v-for="item of legendData" :key="item.value" class="mb-2 flex justify-between">
<div class="chart-flag flex items-center">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div>
</div>
<div class="count font-medium">{{ item.count || 0 }}</div>
</div>
</div>
</template>
</a-popover>
</div> </div>
<div class="chart-legend grid flex-1 gap-y-3"> <div class="chart-legend grid flex-1 gap-y-3">
<!-- 图例开始 --> <!-- 图例开始 -->
<div v-for="item of props.legendData" :key="item.value" class="chart-legend-item"> <div v-for="item of legendData" :key="item.value" class="chart-legend-item">
<div class="chart-flag"> <div class="chart-flag">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div> <div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div> <div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div>
@ -30,9 +56,12 @@
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber, formatDuration } from '@/utils';
import type { LegendData } from '@/models/apiTest/report'; import type { LegendData } from '@/models/apiTest/report';
import { getIndicators } from '../../utils';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
options: Record<string, any>; options: Record<string, any>;
@ -58,6 +87,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 99;
margin: auto; margin: auto;
} }
</style> </style>

View File

@ -228,6 +228,7 @@
const charOptions = ref({ const charOptions = ref({
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
show: false,
}, },
legend: { legend: {
show: false, show: false,

View File

@ -195,10 +195,6 @@
reportStepDetail.value = cloneDeep(detail); reportStepDetail.value = cloneDeep(detail);
} }
onBeforeUnmount(() => {
detailDrawerRef.value?.destroy();
});
watch( watch(
() => showDrawer.value, () => showDrawer.value,
(val) => { (val) => {

View File

@ -203,10 +203,6 @@
const detailDrawerRef = ref(); const detailDrawerRef = ref();
onBeforeUnmount(() => {
detailDrawerRef.value?.destroy();
});
watch( watch(
() => showDrawer.value, () => showDrawer.value,
(val) => { (val) => {

View File

@ -47,8 +47,12 @@
</div> </div>
<div> <div>
<a-popover position="bottom" content-class="response-popover-content"> <a-popover position="bottom" content-class="response-popover-content">
<span class="ml-4 text-[18px] font-medium">{{ getTotalTime.split('-')[0] || '-' }}</span> <div class="flex items-center">
<span class="ml-1 text-[var(--color-text-4)]">{{ getTotalTime.split('-')[1] || 'ms' }}</span> <div class="one-line-text ml-4 max-w-[80px] text-[18px] font-medium">{{
getTotalTime.split('-')[0] || '-'
}}</div>
<div class="ml-1 text-[var(--color-text-4)]">{{ getTotalTime.split('-')[1] || 'ms' }}</div>
</div>
<template #content> <template #content>
<div class="min-w-[140px] max-w-[400px] p-4 text-[14px]"> <div class="min-w-[140px] max-w-[400px] p-4 text-[14px]">
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.totalTime') }}</div> <div class="text-[var(--color-text-4)]">{{ t('report.detail.api.totalTime') }}</div>
@ -68,7 +72,7 @@
</div> </div>
<div> <div>
<a-popover position="bottom" content-class="response-popover-content"> <a-popover position="bottom" content-class="response-popover-content">
<span class="ml-4 text-[18px] font-medium">{{ <span class="one-line-text ml-4 inline-block max-w-[80px] align-middle text-[18px] font-medium">{{
detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[0] : '-' detail.requestDuration !== null ? formatDuration(detail.requestDuration).split('-')[0] : '-'
}}</span> }}</span>
@ -147,26 +151,11 @@
<div class="request-analyze"> <div class="request-analyze">
<div class="block-title">{{ t('report.detail.api.requestAnalysis') }}</div> <div class="block-title">{{ t('report.detail.api.requestAnalysis') }}</div>
<div class="flex min-h-[110px] items-center"> <SetReportChart
<div class="relative mr-4"> :legend-data="legendData"
<div class="charts absolute text-center"> :options="charOptions"
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div> :request-total="getIndicators(detail.requestTotal)"
<div class="text-[18px] font-medium">{{ getIndicators(detail.requestTotal) }}</div> />
</div>
<MsChart width="110px" height="110px" :options="charOptions" />
</div>
<div class="chart-legend grid flex-1 gap-y-3">
<!-- 图例开始 -->
<div v-for="item of legendData" :key="item.value" class="chart-legend-item">
<div class="chart-flag">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full" :class="item.class"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ item.label }}</div>
</div>
<div class="count">{{ item.count || 0 }}</div>
<div class="count">{{ item.rote || 0 }}%</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- 报告步骤分析和请求分析结束 --> <!-- 报告步骤分析和请求分析结束 -->
@ -182,10 +171,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import dayjs from 'dayjs';
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import MsColorLine from '@/components/pure/ms-color-line/index.vue'; import SetReportChart from './case/setReportChart.vue';
import ReportDetailHeader from './reportDetailHeader.vue'; import ReportDetailHeader from './reportDetailHeader.vue';
import reportInfoHeader from './step/reportInfoHeaders.vue'; import reportInfoHeader from './step/reportInfoHeaders.vue';
import StepProgress from './stepProgress.vue'; import StepProgress from './stepProgress.vue';
@ -265,6 +253,7 @@
const legendData = ref<LegendData[]>([]); const legendData = ref<LegendData[]>([]);
const charOptions = ref({ const charOptions = ref({
tooltip: { tooltip: {
show: false,
trigger: 'item', trigger: 'item',
}, },
legend: { legend: {
@ -449,6 +438,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 99;
margin: auto; margin: auto;
} }
</style> </style>

View File

@ -18,6 +18,7 @@
isStaticItemHeight: true, isStaticItemHeight: true,
estimatedSize: 48, estimatedSize: 48,
}" }"
:animation="false"
action-on-node-click="expand" action-on-node-click="expand"
disabled-title-tooltip disabled-title-tooltip
block-node block-node
@ -26,7 +27,8 @@
@more-actions-close="() => setFocusNodeKey('')" @more-actions-close="() => setFocusNodeKey('')"
> >
<template #title="step"> <template #title="step">
<div class="flex flex-col"> <!-- <div class="absolute h-full w-full top-0" style="border: 1px solid #0cc"></div> -->
<div class="flex flex-col" @click="handleStop($event, step)">
<div class="flex w-full items-center gap-[8px]"> <div class="flex w-full items-center gap-[8px]">
<div <div
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white" class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
@ -44,7 +46,10 @@
}) })
" "
> >
<div class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-4)]"> <div
v-if="!(step.children && step.children.length && showApiType.includes(step.stepType))"
class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-4)]"
>
<MsIcon <MsIcon
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'" :type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
:size="14" :size="14"
@ -66,8 +71,14 @@
</div> </div>
<a-tooltip :content="step.name" position="tl"> <a-tooltip :content="step.name" position="tl">
<div class="step-name-container w-full flex-grow" @click.stop="showDetail(step)"> <div
<div class="one-line-text mx-[4px] max-w-[150px] text-[var(--color-text-1)]"> class="step-name-container"
:class="{
'w-full flex-grow': showApiType.includes(step.stepType),
}"
@click="showDetail($event, step)"
>
<div class="one-line-text mx-[4px] max-w-[300px] text-[var(--color-text-1)]">
{{ step.name }} {{ step.name }}
</div> </div>
</div> </div>
@ -186,6 +197,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import { MsTreeExpandedData } from '@/components/business/ms-tree/types'; import { MsTreeExpandedData } from '@/components/business/ms-tree/types';
@ -226,14 +238,19 @@
const innerExpandedKeys = defineModel<(string | number)[]>('expandedKeys', { const innerExpandedKeys = defineModel<(string | number)[]>('expandedKeys', {
required: false, required: false,
}); });
const showApiType = ref<string[]>([ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST]);
const innerNumber = ref<number>(0);
/** /**
* 处理步骤展开折叠 * 处理步骤展开折叠
*/ */
function handleStepExpand(data: MsTreeExpandedData) { function handleStepExpand(data: MsTreeExpandedData) {
const realStep = findNodeByKey<ScenarioItemType>(steps.value, data.node?.stepId, 'stepId'); const isNotAllowExpand =
if (realStep) { data.node?.children && data.node?.children.length && showApiType.value.includes(data.node?.stepType);
realStep.expanded = !realStep.expanded; if (isNotAllowExpand && data.node && data.node.children) {
data.node.stepChildren = cloneDeep(data.node.children);
data.node.children = [];
} }
} }
@ -255,12 +272,17 @@
function expandHandler(item: ScenarioItemType) { function expandHandler(item: ScenarioItemType) {
const realStep = findNodeByKey<ScenarioItemType>(steps.value, item.stepId, 'stepId'); const realStep = findNodeByKey<ScenarioItemType>(steps.value, item.stepId, 'stepId');
// TODO
if (realStep) { if (realStep) {
const isNotAllowExpand =
realStep.children && realStep?.children.length && showApiType.value.includes(realStep?.stepType);
if (isNotAllowExpand) {
realStep.stepChildren = cloneDeep(realStep.children);
realStep.children = [];
}
realStep.fold = !realStep.fold; realStep.fold = !realStep.fold;
} }
} }
const showApiType = ref<string[]>([ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST]);
const showCondition = ref<string[]>([ const showCondition = ref<string[]>([
ScenarioStepType.API, ScenarioStepType.API,
ScenarioStepType.API_CASE, ScenarioStepType.API_CASE,
@ -276,7 +298,7 @@
return props.activeType === 'tab'; return props.activeType === 'tab';
} }
const activeItem = ref(); const activeItem = ref();
function showDetail(item: ScenarioItemType) { function showDetail(event: Event, item: ScenarioItemType) {
if (props.activeType === 'tab') { if (props.activeType === 'tab') {
return; return;
} }
@ -285,6 +307,7 @@
} }
activeItem.value = item; activeItem.value = item;
emit('detail', activeItem.value); emit('detail', activeItem.value);
event.stopPropagation();
} }
// //
@ -315,6 +338,12 @@
} }
return item.children && item.children.length > 0; return item.children && item.children.length > 0;
} }
function handleStop(event: Event, step: ScenarioItemType) {
if (step.children && step.children.length && showApiType.value.includes(step.stepType)) {
event.stopPropagation();
}
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,14 +1,32 @@
<template> <template>
<MsDrawer <MsDrawer v-model:visible="scriptDetailDrawer" :title="form.name" :width="800" :footer="false" unmount-on-close>
v-model:visible="scriptDetailDrawer"
:title="t('project.commonScript.publicScriptName')"
:width="768"
:footer="false"
unmount-on-close
>
<template #headerLeft> <template #headerLeft>
<MsTag class="ml-1" type="success" theme="light">{{ t('project.commonScript.testSuccess') }}</MsTag> <MsTag class="ml-1" type="success" theme="light">{{ t('project.commonScript.testSuccess') }}</MsTag>
</template> </template>
<template #tbutton>
<div class="flex">
<MsButton
v-permission="['PROJECT_API_REPORT:READ+SHARE']"
type="icon"
status="secondary"
class="mr-4 !rounded-[var(--border-radius-small)]"
@click="editHandler"
>
<MsIcon type="icon-icon_edit_outlined" class="mr-2 font-[16px]" />
{{ t('common.edit') }}
</MsButton>
<MsButton
v-permission="['PROJECT_API_REPORT:READ+SHARE']"
type="icon"
status="secondary"
:loading="loading"
class="mr-4 !rounded-[var(--border-radius-small)]"
@click="testHandler"
>
{{ t('apiTestDebug.test') }}
</MsButton>
</div>
</template>
<a-radio-group v-model="showType" type="button" size="small"> <a-radio-group v-model="showType" type="button" size="small">
<a-radio value="detail">{{ t('project.commonScript.detail') }}</a-radio> <a-radio value="detail">{{ t('project.commonScript.detail') }}</a-radio>
<a-radio value="changeHistory">{{ t('project.commonScript.changeHistory') }}</a-radio> <a-radio value="changeHistory">{{ t('project.commonScript.changeHistory') }}</a-radio>
@ -27,11 +45,11 @@
</span> </span>
</div> </div>
</div> </div>
<span>{{ t('project.commonScript.inputParams') }}</span> <div class="mb-4">{{ t('project.commonScript.inputParams') }}</div>
<ms-base-table v-bind="propsRes" ref="tableRef" class="mb-4" no-disable v-on="propsEvent"> <ms-base-table v-bind="propsRes" ref="tableRef" class="mb-4" no-disable v-on="propsEvent">
<template #mustContain="{ record }"> <template #mustContain="{ record }">
<a-checkbox v-model:model-value="record.mustContain" :disabled="true"></a-checkbox> <a-checkbox v-model:model-value="record.required" :disabled="true"></a-checkbox>
</template> </template>
</ms-base-table> </ms-base-table>
@ -46,7 +64,7 @@
width="100%" width="100%"
height="calc(100vh - 155px)" height="calc(100vh - 155px)"
theme="MS-text" theme="MS-text"
:read-only="false" :read-only="true"
:show-full-screen="false" :show-full-screen="false"
:show-theme-change="false" :show-theme-change="false"
/> />
@ -55,12 +73,15 @@
v-else v-else
v-bind="changeHistoryPropsRes" v-bind="changeHistoryPropsRes"
ref="tableRef" ref="tableRef"
class="mb-4" class="mt-4"
no-disable no-disable
v-on="changeHistoryPropsEvent" v-on="changeHistoryPropsEvent"
> >
<template #changeSerialNumber="{ record }" <template #id="{ record }">
><div class="flex items-center"> {{ record.changeSerialNumber }} <MsTag>当前</MsTag> </div> <div class="flex items-center">
<div class="one-line-text mr-2 max-w-[200px]"> {{ record.id }} </div>
<MsTag>{{ t('project.processor.current') }}</MsTag>
</div>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton status="primary" @click="recoverHandler(record)"> <MsButton status="primary" @click="recoverHandler(record)">
@ -84,12 +105,21 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue'; import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { getCommonScriptDetail } from '@/api/modules/project-management/commonScript'; import {
getChangeHistory,
getCommonScriptDetail,
getSocket,
testCommonScript,
} from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { getGenerateId } from '@/utils';
import type { AddOrUpdateCommonScript } from '@/models/projectManagement/commonScript'; import type { AddOrUpdateCommonScript } from '@/models/projectManagement/commonScript';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
@ -97,7 +127,7 @@
scriptId: string; // id scriptId: string; // id
}>(); }>();
const emit = defineEmits(['update:visible', 'change']); const emit = defineEmits(['update:visible', 'change', 'update']);
const showType = ref('detail'); const showType = ref('detail');
@ -113,8 +143,8 @@
const columns: MsTableColumn = [ const columns: MsTableColumn = [
{ {
title: 'project.commonScript.ParameterNames', title: 'project.commonScript.ParameterNames',
slotName: 'name', slotName: 'key',
dataIndex: 'name', dataIndex: 'key',
showTooltip: true, showTooltip: true,
showInTable: true, showInTable: true,
}, },
@ -127,8 +157,8 @@
}, },
{ {
title: 'project.commonScript.description', title: 'project.commonScript.description',
slotName: 'desc', slotName: 'description',
dataIndex: 'desc', dataIndex: 'description',
showTooltip: true, showTooltip: true,
}, },
{ {
@ -139,7 +169,7 @@
}, },
]; ];
const { propsRes, propsEvent, setProps } = useTable(undefined, { const { propsRes, propsEvent } = useTable(undefined, {
columns, columns,
selectable: false, selectable: false,
showSetting: false, showSetting: false,
@ -149,70 +179,61 @@
const scriptType = ref<'commonScript' | 'executionResult'>('commonScript'); const scriptType = ref<'commonScript' | 'executionResult'>('commonScript');
const detailValue = ref('');
const changeHistoryColumns: MsTableColumn = [ const changeHistoryColumns: MsTableColumn = [
{ {
title: 'project.commonScript.changeSerialNumber', title: 'project.commonScript.changeSerialNumber',
slotName: 'changeSerialNumber', slotName: 'id',
dataIndex: 'changeSerialNumber', dataIndex: 'id',
showTooltip: true, showTooltip: true,
showInTable: true, showInTable: true,
width: 300,
}, },
{ {
title: 'project.commonScript.actionType', title: 'project.commonScript.actionType',
slotName: 'actionType', slotName: 'type',
dataIndex: 'actionType', dataIndex: 'type',
showInTable: true, showInTable: true,
width: 200,
}, },
{ {
title: 'project.commonScript.actionUser', title: 'project.commonScript.actionUser',
dataIndex: 'actionUser', dataIndex: 'createUserName',
slotName: 'actionUser', slotName: 'createUserName',
showTooltip: true, showTooltip: true,
showInTable: true, showInTable: true,
}, },
{ {
title: 'project.commonScript.updateTime', title: 'project.commonScript.updateTime',
dataIndex: 'updateTime', dataIndex: 'createTime',
slotName: 'updateTime', slotName: 'createTime',
showTooltip: true, showTooltip: true,
showInTable: true, showInTable: true,
}, },
{ // TODO:
title: 'project.commonScript.tableColumnActions', // {
slotName: 'operation', // title: 'project.commonScript.tableColumnActions',
dataIndex: 'operation', // slotName: 'operation',
fixed: 'right', // dataIndex: 'operation',
width: 140, // fixed: 'right',
showInTable: true, // width: 140,
showDrag: false, // showInTable: true,
}, // showDrag: false,
// },
]; ];
const { const {
propsRes: changeHistoryPropsRes, propsRes: changeHistoryPropsRes,
propsEvent: changeHistoryPropsEvent, propsEvent: changeHistoryPropsEvent,
loadList: changeHistoryloadList, setLoadListParams: setHistoryLoadListParams,
setLoadListParams: changeHistorySetLoadListParams, loadList: loadHistoryList,
resetSelector: changeHistoryResetSelector, } = useTable(getChangeHistory, {
} = useTable( columns: changeHistoryColumns,
() => tableKey: TableKeyEnum.PROJECT_MANAGEMENT_COMMON_SCRIPT_CHANGE_HISTORY,
Promise.resolve({ selectable: false,
list: [], showSetting: false,
current: 1, scroll: { x: '100%' },
pageSize: 10, heightUsed: 300,
total: 2, });
}),
{
columns: changeHistoryColumns,
tableKey: TableKeyEnum.PROJECT_MANAGEMENT_COMMON_SCRIPT_CHANGE_HISTORY,
selectable: false,
showSetting: false,
scroll: { x: '100%' },
heightUsed: 300,
}
);
function recoverHandler(record: any) {} function recoverHandler(record: any) {}
@ -230,27 +251,37 @@
const form = ref<AddOrUpdateCommonScript>({ ...initForm }); const form = ref<AddOrUpdateCommonScript>({ ...initForm });
async function getDetail(scriptId: string) { const detailValue = computed({
get: () => {
return scriptType.value === 'commonScript' ? form.value.script : form.value.result;
},
set: (val) => val,
});
async function getDetail() {
try { try {
const result = await getCommonScriptDetail(scriptId); const result = await getCommonScriptDetail(props.scriptId);
form.value = cloneDeep(result); form.value = cloneDeep(result);
detailValue.value = form.value.script;
const innerTableData = JSON.parse(form.value.params); const innerTableData = JSON.parse(form.value.params);
setProps({ data: innerTableData.slice(0, innerTableData.length - 1) }); propsRes.value.data = innerTableData;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
const originScript = ref<string>('');
watch( function loadChangeHistoryPage() {
() => props.scriptId, setHistoryLoadListParams({
(val) => { projectId: appStore.getCurrentProjectId,
if (val && originScript.value !== val) { sourceId: props.scriptId,
getDetail(val); });
} loadHistoryList();
}
watchEffect(() => {
if (props.scriptId) {
getDetail();
} }
); });
watch( watch(
() => scriptType.value, () => scriptType.value,
@ -260,8 +291,86 @@
} }
); );
onMounted(() => { function editHandler() {
originScript.value = props.scriptId; emit('update', props.scriptId);
}
const websocket = ref<any>();
const reportId = ref('');
function testHandler() {
reportId.value = getGenerateId();
}
const loading = ref<boolean>(false);
async function run() {
try {
const { type, script } = form.value;
const parameters = JSON.parse(form.value.params);
parameters
.filter((item: any) => item.key && item.value)
.map((item) => {
return {
key: item.key,
value: item.value,
valid: item.mustContain,
};
});
const params = {
type,
script,
params: parameters,
projectId: appStore.currentProjectId,
reportId: reportId.value,
};
await testCommonScript(params);
} catch (error) {
loading.value = false;
}
}
function onOpen() {
run();
}
function debugSocket() {
loading.value = true;
websocket.value = getSocket(reportId.value);
websocket.value.onopen = onOpen;
websocket.value.addEventListener('message', (event: any) => {
const result = JSON.parse(event.data);
if (result.msgType === 'EXEC_RESULT') {
form.value.result = result.taskResult.console;
scriptType.value = 'executionResult';
websocket.value.close();
loading.value = false;
}
});
}
watch(
() => reportId.value,
(val) => {
if (val) {
debugSocket();
}
}
);
watch(
() => showType.value,
(val) => {
if (val === 'changeHistory') {
loadChangeHistoryPage();
} else {
getDetail();
}
}
);
defineExpose({
getDetail,
}); });
</script> </script>

View File

@ -99,7 +99,12 @@
:enable-radio-selected="radioSelected" :enable-radio-selected="radioSelected"
@save="saveHandler" @save="saveHandler"
/> />
<ScriptDetailDrawer v-model:visible="showDetailDrawer" :script-id="scriptId" /> <ScriptDetailDrawer
ref="scriptDetailDrawer"
v-model:visible="showDetailDrawer"
:script-id="scriptId"
@update="updateHandler"
/>
</MsCard> </MsCard>
</template> </template>
@ -329,6 +334,7 @@
const paramsList = ref<ParamsRequestType[]>([]); const paramsList = ref<ParamsRequestType[]>([]);
const confirmLoading = ref<boolean>(false); const confirmLoading = ref<boolean>(false);
const scriptDetailDrawer = ref();
// //
async function saveHandler(form: AddOrUpdateCommonScript) { async function saveHandler(form: AddOrUpdateCommonScript) {
@ -351,6 +357,9 @@
? t('project.commonScript.saveDraftSuccessfully') ? t('project.commonScript.saveDraftSuccessfully')
: t('project.commonScript.appliedSuccessfully') : t('project.commonScript.appliedSuccessfully')
); );
if (showDetailDrawer.value) {
scriptDetailDrawer.value.getDetail();
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
@ -363,6 +372,11 @@
showScriptDrawer.value = true; showScriptDrawer.value = true;
} }
function updateHandler(id: string) {
isEditId.value = id;
showScriptDrawer.value = true;
}
const radioSelected = ref<boolean>(false); const radioSelected = ref<boolean>(false);
function editHandler(record: AddOrUpdateCommonScript) { function editHandler(record: AddOrUpdateCommonScript) {

View File

@ -65,4 +65,5 @@ export default {
'project.processor.terminationTest': 'Termination test', 'project.processor.terminationTest': 'Termination test',
'project.processor.code_hide_report_length': 'Hide report length', 'project.processor.code_hide_report_length': 'Hide report length',
'project.processor.code_add_report_length': 'Add report length to head', 'project.processor.code_add_report_length': 'Add report length to head',
'project.processor.current': 'current',
}; };

View File

@ -66,4 +66,5 @@ export default {
'project.processor.terminationTest': '终止测试', 'project.processor.terminationTest': '终止测试',
'project.processor.code_hide_report_length': '隐藏报文长度', 'project.processor.code_hide_report_length': '隐藏报文长度',
'project.processor.code_add_report_length': '报文头添加长度', 'project.processor.code_add_report_length': '报文头添加长度',
'project.processor.current': '当前',
}; };