feat(接口测试): 报告详情tab展示调整
This commit is contained in:
parent
83fedebb8a
commit
98e7677ad2
|
@ -1,6 +1,7 @@
|
|||
import MSR from '@/api/http';
|
||||
import * as reportUrl from '@/api/requrls/api-test/report';
|
||||
|
||||
import type { ReportDetail, ReportStepDetail } from '@/models/apiTest/report';
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
import { ReportEnum } from '@/enums/reportEnum';
|
||||
|
||||
|
@ -38,7 +39,11 @@ export function reportBathDelete(moduleType: string, data: TableQueryParams) {
|
|||
|
||||
// 报告详情
|
||||
export function reportDetail(reportId: string) {
|
||||
return MSR.get<Record<string, any>>({ url: `${reportUrl.ScenarioReportDetailUrl}/${reportId}` });
|
||||
return MSR.get<ReportDetail>({ url: `${reportUrl.ScenarioReportDetailUrl}/${reportId}` });
|
||||
}
|
||||
// 报告步骤详情
|
||||
export function reportStepDetail(reportId: string, stepId: string) {
|
||||
return MSR.get<ReportStepDetail>({ url: `${reportUrl.ScenarioReportDetailStepUrl}/${reportId}/${stepId}` });
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
BatchMoveCaseUrl,
|
||||
CancelAssociatedDebuggerUrl,
|
||||
CancelAssociationDemandUrl,
|
||||
cancelDisassociate,
|
||||
cancelPreAndPostCaseUrl,
|
||||
checkFileIsUpdateUrl,
|
||||
CreateCaseModuleTreeUrl,
|
||||
|
@ -364,6 +365,10 @@ export function associatedDrawerDebug(data: TableQueryParams) {
|
|||
export function cancelAssociatedDebug(id: string) {
|
||||
return MSR.get({ url: `${CancelAssociatedDebuggerUrl}/${id}` });
|
||||
}
|
||||
// 取消关联用例
|
||||
export function cancelAssociatedCase(data: TableQueryParams) {
|
||||
return MSR.post({ url: `${cancelDisassociate}`, data });
|
||||
}
|
||||
|
||||
// 获取已关联缺陷列表
|
||||
export function getLinkedCaseBugList(data: TableQueryParams) {
|
||||
|
|
|
@ -16,5 +16,7 @@ export const ApiDeleteUrl = '/api/report/case/delete';
|
|||
// 批量删除接口用例报告
|
||||
export const ApiBatchDeleteUrl = '/api/report/case/batch/delete';
|
||||
|
||||
// 场景报告拔高详情获取
|
||||
// 场景报告详情
|
||||
export const ScenarioReportDetailUrl = '/api/report/scenario/get';
|
||||
// 报告详情步骤
|
||||
export const ScenarioReportDetailStepUrl = '/api/report/scenario/get/detail';
|
||||
|
|
|
@ -78,3 +78,5 @@ export const getChangeHistoryListUrl = '/bug/history/page';
|
|||
|
||||
// 缺陷用例跳转用例是否具备权限
|
||||
export const checkCasePermissionUrl = '/bug/case/check-permission';
|
||||
// 缺陷预览富文本url
|
||||
export const EditorPreviewFileUrl = '/bug/attachment/preview/md';
|
||||
|
|
|
@ -147,3 +147,5 @@ export const importExcelCaseUrl = '/functional/case/import/excel';
|
|||
export const dragSortUrl = '/functional/case/edit/pos';
|
||||
// 获取变更历史
|
||||
export const getChangeHistoryListUrl = '/functional/case/operation-history';
|
||||
// 取消关联用例
|
||||
export const cancelDisassociate = '/functional/case/test/disassociate/case';
|
||||
|
|
|
@ -148,6 +148,7 @@
|
|||
|
||||
import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||
import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
|
|
|
@ -24,6 +24,10 @@ export default defineComponent({
|
|||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
default: (file: File) => Promise<any>,
|
||||
},
|
||||
previewUrl: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
@ -31,7 +35,7 @@ export default defineComponent({
|
|||
delete: (value: string) => true, // 删除评论
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { commentList, disabled, uploadImage } = toRefs(props);
|
||||
const { commentList, disabled, uploadImage, previewUrl } = toRefs(props);
|
||||
const currentItem = reactive<{ id: string; commentType: CommentType; commentStatus: string }>({
|
||||
id: '',
|
||||
commentType: 'ADD',
|
||||
|
@ -129,6 +133,7 @@ export default defineComponent({
|
|||
noticeUserIds.value = ids;
|
||||
}}
|
||||
uploadImage={uploadImage.value}
|
||||
previewUrl={previewUrl.value}
|
||||
onCancel={() => resetCurrentItem()}
|
||||
{...item}
|
||||
/>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
v-model:raw="currentContent"
|
||||
v-model:commentIds="commentIds"
|
||||
:upload-image="props.uploadImage"
|
||||
:preview-url="props.previewUrl"
|
||||
class="w-full"
|
||||
placeholder="ms.comment.enterPlaceHolderTip"
|
||||
/>
|
||||
|
@ -48,6 +49,7 @@
|
|||
isShowAvatar: boolean; // 是否显示评论人头像
|
||||
isUseBottom: boolean; // 是否被用于底部
|
||||
uploadImage?: (file: File) => Promise<any>;
|
||||
previewUrl?: string;
|
||||
}>();
|
||||
|
||||
const currentContent = defineModel<string>('defaultValue', { default: '' });
|
||||
|
|
|
@ -96,11 +96,14 @@
|
|||
commentIds?: string[];
|
||||
wrapperClass?: string;
|
||||
placeholder?: string;
|
||||
draggable?: boolean;
|
||||
previewUrl?: string;
|
||||
}>(),
|
||||
{
|
||||
raw: '',
|
||||
uploadImage: undefined,
|
||||
placeholder: 'editor.placeholder',
|
||||
draggable: false,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -122,7 +125,8 @@
|
|||
}
|
||||
const uploadFileId = await props.uploadImage(arg.file);
|
||||
if (uploadFileId) {
|
||||
const permanentUrl = `${PreviewEditorImageUrl}/${appStore.currentProjectId}/${uploadFileId}/${true}`;
|
||||
// const permanentUrl = `${PreviewEditorImageUrl}/${appStore.currentProjectId}/${uploadFileId}/${true}`;
|
||||
const permanentUrl = `${props.previewUrl}/${appStore.currentProjectId}/${uploadFileId}/${true}`;
|
||||
arg.process(permanentUrl, uploadFileId);
|
||||
}
|
||||
}
|
||||
|
@ -540,6 +544,9 @@
|
|||
:deep(.halo-rich-text-editor .ProseMirror) {
|
||||
word-break: break-word;
|
||||
}
|
||||
:deep(.halo-rich-text-editor .ProseMirror + .draggable) {
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.editor-header) {
|
||||
svg {
|
||||
color: var(--color-text-3) !important;
|
||||
|
|
|
@ -256,6 +256,13 @@ export enum ScenarioStepType {
|
|||
ONLY_ONCE_CONTROL = 'ONLY_ONCE_CONTROL',
|
||||
SCRIPT_OPERATION = 'SCRIPT_OPERATION',
|
||||
CUSTOM_API = 'CUSTOM_API',
|
||||
API_CASE = 'API_CASE', // 接口用例
|
||||
LOOP_CONTROLLER = 'LOOP_CONTROLLER', // 循环控制器
|
||||
API = 'API', // 接口定义
|
||||
CUSTOM_REQUEST = 'CUSTOM_REQUEST', // 自定义请求
|
||||
API_SCENARIO = ' API_SCENARIO', // 场景
|
||||
IF_CONTROLLER = 'IF_CONTROLLER', // 条件控制器
|
||||
ONCE_ONLY_CONTROLLER = 'ONCE_ONLY_CONTROLLER', // 一次控制器
|
||||
}
|
||||
// 场景添加步骤操作类型
|
||||
export enum ScenarioAddStepActionType {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// 步骤
|
||||
export interface ScenarioItemType {
|
||||
stepId: string;
|
||||
reportId: string;
|
||||
|
@ -12,4 +13,127 @@ export interface ScenarioItemType {
|
|||
code: string; // 请求响应码
|
||||
responseSize: number; // 响应内容大小
|
||||
scriptIdentifier: string; // 脚本标识
|
||||
fold: boolean; // 是否展示折叠
|
||||
children: ScenarioItemType[];
|
||||
level?: number;
|
||||
}
|
||||
|
||||
export type ScenarioDetailItem = Partial<ScenarioItemType>;
|
||||
// 报告场景的详情
|
||||
export interface ReportDetail {
|
||||
id: string;
|
||||
name: string; // 报告名称
|
||||
testPlanId: string;
|
||||
createUser: string;
|
||||
deleteTime: number;
|
||||
deleteUser: string;
|
||||
deleted: boolean;
|
||||
updateUser: string;
|
||||
updateTime: number;
|
||||
startTime: number; // 开始时间/同创建时间一致
|
||||
endTime: number; // 结束时间/报告执行完成
|
||||
requestDuration: number; // 请求总耗时
|
||||
status: string; // 报告状态/SUCCESS/ERROR
|
||||
triggerMode: string; // 触发方式
|
||||
runMode: string; // 执行模式
|
||||
poolId: string; // 资源池
|
||||
poolName: string; // 资源池名称
|
||||
versionId: string;
|
||||
integrated: boolean; // 是否是集成报告
|
||||
projectId: string;
|
||||
environmentId: string; // 环境id
|
||||
environmentName: string; // 环境名称
|
||||
errorCount: number; // 失败数
|
||||
fakeErrorCount: number; // 误报数
|
||||
pendingCount: number; // 未执行数
|
||||
successCount: number; // 成功数
|
||||
assertionCount: number; // 总断言数
|
||||
assertionSuccessCount: number; // 成功断言数
|
||||
requestErrorRate: string; // 请求失败率
|
||||
requestPendingRate: string; // 请求未执行率
|
||||
requestFakeErrorRate: string; // 请求误报率
|
||||
requestPassRate: string; // 请求通过率
|
||||
assertionPassRate: string; // 断言通过率
|
||||
scriptIdentifier: string; // 脚本标识
|
||||
children: ScenarioItemType[]; // 步骤列表
|
||||
stepTotal: number; // 步骤总数
|
||||
}
|
||||
export interface LegendData {
|
||||
label: string;
|
||||
value: string;
|
||||
rote: number;
|
||||
count: number;
|
||||
class: string;
|
||||
}
|
||||
|
||||
export interface AssertionItem {
|
||||
name: string;
|
||||
content: string;
|
||||
script: string;
|
||||
message: string;
|
||||
pass: boolean;
|
||||
}
|
||||
// 响应结果
|
||||
export interface ResponseResult {
|
||||
responseCode: string;
|
||||
responseMessage: string;
|
||||
responseTime: number;
|
||||
latency: number;
|
||||
responseSize: number;
|
||||
headers: string;
|
||||
body: string;
|
||||
contentType: string;
|
||||
vars: string;
|
||||
imageUrl: string;
|
||||
socketInitTime: number;
|
||||
dnsLookupTime: number;
|
||||
tcpHandshakeTime: number;
|
||||
sslHandshakeTime: number;
|
||||
transferStartTime: number;
|
||||
downloadTime: number;
|
||||
bodySize: number;
|
||||
headerSize: number;
|
||||
assertions: AssertionItem[];
|
||||
}
|
||||
|
||||
export interface StepContent {
|
||||
resourceId: string;
|
||||
projectId: string;
|
||||
stepId: string;
|
||||
threadName: string;
|
||||
name: string;
|
||||
url: string;
|
||||
requestSize: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
error: number;
|
||||
headers: string;
|
||||
cookies: string;
|
||||
body: string;
|
||||
status: string;
|
||||
method: string;
|
||||
assertionTotal: number;
|
||||
passAssertionsTotal: number;
|
||||
subRequestResults: string[];
|
||||
responseResult: ResponseResult;
|
||||
isSuccessful: boolean;
|
||||
fakeErrorCode: string;
|
||||
scriptIdentifier: string;
|
||||
}
|
||||
|
||||
// 步骤详情
|
||||
export interface ReportStepDetailItem {
|
||||
id: string;
|
||||
reportId: string;
|
||||
stepId: string;
|
||||
status: string;
|
||||
fakeCode: string;
|
||||
requestName: string;
|
||||
requestTime: number;
|
||||
code: string;
|
||||
responseSize: number;
|
||||
scriptIdentifier: string;
|
||||
content: StepContent;
|
||||
}
|
||||
|
||||
export type ReportStepDetail = Partial<ReportStepDetailItem>;
|
||||
|
|
|
@ -745,3 +745,49 @@ export function findNodeNames<T>(trees: TreeNode<T>[], targetIds: string[]) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每三位使用逗号隔开数字格式
|
||||
* @param number 目标值
|
||||
*/
|
||||
|
||||
export function addCommasToNumber(number: number) {
|
||||
if (number === 0 || number === undefined) {
|
||||
return '0';
|
||||
}
|
||||
// 将数字转换为字符串
|
||||
const numberStr = number.toString();
|
||||
|
||||
// 分割整数部分和小数部分
|
||||
const parts = numberStr.split('.');
|
||||
const integerPart = parts[0];
|
||||
const decimalPart = parts[1] || ''; // 如果没有小数部分,则设为空字符串
|
||||
|
||||
// 对整数部分添加逗号分隔
|
||||
const integerWithCommas = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
|
||||
// 拼接整数部分和小数部分(如果有)
|
||||
const result = decimalPart ? `${integerWithCommas}.${decimalPart}` : integerWithCommas;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给树添加深度
|
||||
* @param number 目标值
|
||||
*/
|
||||
export function addLevelToTree<T>(tree: TreeNode<T>[], level = 0): TreeNode<T>[] {
|
||||
if (!tree || !Array.isArray(tree)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tree.map((node) => {
|
||||
const newNode = { ...node, level };
|
||||
|
||||
if (newNode.children && newNode.children.length > 0) {
|
||||
newNode.children = addLevelToTree(newNode.children, level + 1);
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,18 +22,22 @@
|
|||
[ScenarioStepType.QUOTE_SCENARIO]: { label: 'apiScenario.quoteScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.COPY_SCENARIO]: { label: 'apiScenario.copyScenario', color: 'rgb(var(--primary-7))' },
|
||||
[ScenarioStepType.WAIT_TIME]: { label: 'apiScenario.waitTime', color: 'rgb(var(--warning-6))' },
|
||||
[ScenarioStepType.LOOP_CONTROL]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.CONDITION_CONTROL]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONLY_ONCE_CONTROL]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.LOOP_CONTROLLER]: { label: 'apiScenario.loopControl', color: 'rgba(167, 98, 191, 1)' },
|
||||
[ScenarioStepType.IF_CONTROLLER]: { label: 'apiScenario.conditionControl', color: 'rgba(238, 80, 163, 1)' },
|
||||
[ScenarioStepType.ONCE_ONLY_CONTROLLER]: { label: 'apiScenario.onlyOnceControl', color: 'rgba(211, 68, 0, 1)' },
|
||||
[ScenarioStepType.SCRIPT_OPERATION]: { label: 'apiScenario.scriptOperation', color: 'rgba(20, 225, 198, 1)' },
|
||||
[ScenarioStepType.CUSTOM_API]: { label: 'apiScenario.customApi', color: 'rgb(var(--link-4))' },
|
||||
[ScenarioStepType.API_CASE]: { label: 'report.detail.api.apiCase', color: 'rgb(var(--link-4))' },
|
||||
[ScenarioStepType.CUSTOM_REQUEST]: { label: 'report.detail.api.apiCase', color: 'rgb(var(--link-4))' },
|
||||
};
|
||||
|
||||
const getClass = computed(() => {
|
||||
return {
|
||||
color: scenarioStepMap[props.status].color,
|
||||
border: `1px solid ${scenarioStepMap[props.status].color}`,
|
||||
};
|
||||
if (props.status) {
|
||||
return {
|
||||
color: scenarioStepMap[props.status].color,
|
||||
border: `1px solid ${scenarioStepMap[props.status].color}`,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -41,140 +41,123 @@
|
|||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<template #default="{ detail }">
|
||||
<div class="report-container h-full">
|
||||
<!-- 报告参数开始 -->
|
||||
<div class="report-header flex items-center justify-between">
|
||||
<!-- TODO 虚拟数据替换接口后边 -->
|
||||
<span>
|
||||
dev环境
|
||||
{{ detail.environmentName || '-' }}
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
66 资源池
|
||||
{{ detail.poolName || '-' }}
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
1000ms
|
||||
{{ detail.requestDuration || '-' }}
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
admin
|
||||
{{ detail.createUser || '-' }}
|
||||
</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 class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
|
||||
{{ dayjs(detail.startTime).format('YYYY-MM-DD HH:mm:ss') || '-' }}
|
||||
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
|
||||
{{ dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 报告参数结束 -->
|
||||
<!-- 报告步骤分析和请求分析开始 -->
|
||||
<div class="analyze">
|
||||
<div class="analyze mb-1">
|
||||
<div class="step-analyze min-w-[522px]">
|
||||
<div class="block-title">步骤分析</div>
|
||||
<div class="block-title">{{ t('report.detail.api.stepAnalysis') }}</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 }}
|
||||
{{ detail.stepTotal || 0 }}
|
||||
</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 }}
|
||||
{{ detail.successCount || 0 }}
|
||||
</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 }}
|
||||
{{ detail.fakeErrorCount || 0 }}
|
||||
</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 }}
|
||||
{{ detail.errorCount || 0 }}
|
||||
</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 }}
|
||||
{{ detail.pendingCount || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
<StepProgress :report-detail="reportStepDetail" height="8px" radius="var(--border-radius-mini)" />
|
||||
<StepProgress :report-detail="detail" height="8px" radius="var(--border-radius-mini)" />
|
||||
<div class="card">
|
||||
<div class="timer-card mr-2">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_time_outlined" class="text-[var(--color-text-4)]x mr-[4px]" size="16" />
|
||||
总耗时
|
||||
{{ t('report.detail.api.totalTime') }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-4 text-[18px] font-medium">{{ detail.requestDuration || 0 }}</span
|
||||
>s
|
||||
</div>
|
||||
<div> <span class="ml-4 text-[18px] font-medium">3</span>s </div>
|
||||
</div>
|
||||
<div class="timer-card mr-2">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_time_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
请求总耗时
|
||||
{{ t('report.detail.api.requestTotalTime') }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-4 text-[18px] font-medium">{{ detail.requestDuration }}</span
|
||||
>s
|
||||
</div>
|
||||
<div> <span class="ml-4 text-[18px] font-medium">3</span>s </div>
|
||||
</div>
|
||||
<div class="timer-card min-w-[200px]">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<MsIcon type="icon-icon_yes_outlined" class="mr-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
断言通过率
|
||||
{{ t('report.detail.api.assertPass') }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[18px] font-medium text-[var(--color-text-1)]">99.99 <span>%</span></span>
|
||||
<span class="text-[18px] font-medium text-[var(--color-text-1)]"
|
||||
>{{ detail.assertionPassRate || 0 }} <span>%</span></span
|
||||
>
|
||||
<a-divider direction="vertical" :margin="0" class="!mx-1"></a-divider>
|
||||
<span class="text-[var(--color-text-1)]">1,000</span>
|
||||
<span class="text-[var(--color-text-4)]">/ 1,000</span>
|
||||
<span class="text-[var(--color-text-1)]">{{
|
||||
addCommasToNumber(detail.assertionSuccessCount || 0)
|
||||
}}</span>
|
||||
<span class="text-[var(--color-text-4)]">/ {{ addCommasToNumber(detail.assertionCount) || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-analyze">
|
||||
<div class="block-title">请求分析</div>
|
||||
<div class="block-title">{{ t('report.detail.api.requestAnalysis') }}</div>
|
||||
<div class="flex min-h-[110px] items-center">
|
||||
<div class="relative mr-4">
|
||||
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
|
||||
<div class="text-[12px] text-[(var(--color-text-4))]">总数 (个)</div>
|
||||
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
|
||||
<div class="text-[18px] font-medium">4</div>
|
||||
</div>
|
||||
<MsChart width="110px" height="110px" :options="charOptions" />
|
||||
</div>
|
||||
<div class="chart-legend grid flex-1 gap-y-4">
|
||||
<div class="chart-legend-item">
|
||||
<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 bg-[rgb(var(--success-6))]"></div>
|
||||
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.successCount') }}</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>
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<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>
|
||||
</div>
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
</div>
|
||||
<div class="chart-legend-item">
|
||||
<div class="chart-flag">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="count">24</div>
|
||||
<div class="count">99.99%</div>
|
||||
<div class="count">{{ item.count || 0 }}</div>
|
||||
<div class="count">{{ item.rote || 0 }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -185,18 +168,21 @@
|
|||
<div class="report-info">
|
||||
<div class="mb-4 flex h-[36px] items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-2 font-medium leading-[36px]">报告明细</div>
|
||||
<div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div>
|
||||
<a-radio-group v-model:model-value="activeTab" type="button" size="small">
|
||||
<a-radio v-for="item of methods" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-select v-model="condition" class="w-[240px]" placeholder="请选择过滤条件">
|
||||
<a-select v-model="condition" class="w-[240px]" :placeholder="t('report.detail.api.filterPlaceholder')">
|
||||
<a-option :key="1" :value="1"> 1 </a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<TiledList v-show="activeTab === 'tiled'" />
|
||||
<!-- 平铺模式 -->
|
||||
<TiledList v-show="activeTab === 'tiled'" :active-type="activeTab" :report-detail="detail || []" />
|
||||
<!-- tab展示 -->
|
||||
<TiledList v-show="activeTab === 'tab'" :active-type="activeTab" :report-detail="detail || []" />
|
||||
</div>
|
||||
<!-- 报告明细结束 -->
|
||||
</div>
|
||||
|
@ -206,6 +192,8 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -216,6 +204,9 @@
|
|||
|
||||
import { reportDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { addCommasToNumber } from '@/utils';
|
||||
|
||||
import type { LegendData, ReportDetail } from '@/models/apiTest/report';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
|
@ -240,8 +231,163 @@
|
|||
},
|
||||
});
|
||||
const innerFileId = ref(props.reportId);
|
||||
function loadedReport(detail: Record<string, any>) {
|
||||
|
||||
const reportStepDetail = ref<ReportDetail>({
|
||||
id: '',
|
||||
name: '', // 报告名称
|
||||
testPlanId: '',
|
||||
createUser: '',
|
||||
deleteTime: 0,
|
||||
deleteUser: '',
|
||||
deleted: false,
|
||||
updateUser: '',
|
||||
updateTime: 0,
|
||||
startTime: 0, // 开始时间/同创建时间一致
|
||||
endTime: 0, // 结束时间/报告执行完成
|
||||
requestDuration: 0, // 请求总耗时
|
||||
status: '', // 报告状态/SUCCESS/ERROR
|
||||
triggerMode: '', // 触发方式
|
||||
runMode: '', // 执行模式
|
||||
poolId: '', // 资源池
|
||||
poolName: '', // 资源池名称
|
||||
versionId: '',
|
||||
integrated: false, // 是否是集成报告
|
||||
projectId: '',
|
||||
environmentId: '', // 环境id
|
||||
environmentName: '', // 环境名称
|
||||
errorCount: 0, // 失败数
|
||||
fakeErrorCount: 0, // 误报数
|
||||
pendingCount: 0, // 未执行数
|
||||
successCount: 0, // 成功数
|
||||
assertionCount: 0, // 总断言数
|
||||
assertionSuccessCount: 0, // 成功断言数
|
||||
requestErrorRate: '', // 请求失败率
|
||||
requestPendingRate: '', // 请求未执行率
|
||||
requestFakeErrorRate: '', // 请求误报率
|
||||
requestPassRate: '', // 请求通过率
|
||||
assertionPassRate: '', // 断言通过率
|
||||
scriptIdentifier: '', // 脚本标识
|
||||
children: [], // 步骤列表
|
||||
stepTotal: 0, // 步骤总数
|
||||
});
|
||||
|
||||
const charOptions = ref({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
series: {
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: ['65%', '80%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 0,
|
||||
name: t('report.detail.api.pass'),
|
||||
itemStyle: {
|
||||
color: '#00C261',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('report.detail.api.misstatement'),
|
||||
itemStyle: {
|
||||
color: '#FFC14E',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('report.detail.api.error'),
|
||||
itemStyle: {
|
||||
color: '#ED0303',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('report.detail.api.pending'),
|
||||
itemStyle: {
|
||||
color: '#D4D4D8',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const legendData = ref<LegendData[]>([]);
|
||||
|
||||
function initOptionsData() {
|
||||
const tempArr = [
|
||||
{
|
||||
label: 'report.detail.api.pass',
|
||||
value: 'successCount',
|
||||
color: '#00C261',
|
||||
class: 'bg-[rgb(var(--success-6))]',
|
||||
rateKey: 'requestPassRate',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.misstatement',
|
||||
value: 'fakeErrorCount',
|
||||
color: '#FFC14E',
|
||||
class: 'bg-[rgb(var(--warning-6))]',
|
||||
rateKey: 'requestFakeErrorRate',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.error',
|
||||
value: 'successCount',
|
||||
color: '#ED0303',
|
||||
class: 'bg-[rgb(var(--danger-6))]',
|
||||
rateKey: 'requestErrorRate',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.pending',
|
||||
value: 'pendingCount',
|
||||
color: '#D4D4D8',
|
||||
class: 'bg-[var(--color-text-input-border)]',
|
||||
rateKey: 'requestPendingRate',
|
||||
},
|
||||
];
|
||||
|
||||
charOptions.value.series.data = tempArr.map((item: any) => {
|
||||
return {
|
||||
value: reportStepDetail.value[item.value] || 0,
|
||||
name: t(item.label),
|
||||
itemStyle: {
|
||||
color: item.color,
|
||||
},
|
||||
};
|
||||
});
|
||||
legendData.value = tempArr.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
label: t(item.label),
|
||||
count: reportStepDetail.value[item.value] || 0,
|
||||
rote: reportStepDetail.value[item.rateKey],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 详情
|
||||
function loadedReport(detail: ReportDetail) {
|
||||
innerFileId.value = detail.id;
|
||||
reportStepDetail.value = cloneDeep(detail);
|
||||
initOptionsData();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,88 +402,22 @@
|
|||
const exportLoading = ref<boolean>(false);
|
||||
function exportHandler() {}
|
||||
|
||||
const reportStepDetail = ref({
|
||||
stepTotal: 8,
|
||||
errorCount: 2,
|
||||
fakeErrorCount: 8,
|
||||
pendingCount: 9,
|
||||
successCount: 9,
|
||||
});
|
||||
|
||||
const charOptions = ref({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: ['65%', '80%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 1048,
|
||||
name: '通过',
|
||||
itemStyle: {
|
||||
color: '#00C261',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 735,
|
||||
name: '误报',
|
||||
itemStyle: {
|
||||
color: '#FFC14E',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 580,
|
||||
name: '失败',
|
||||
itemStyle: {
|
||||
color: '#ED0303',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 484,
|
||||
name: '未执行',
|
||||
itemStyle: {
|
||||
color: '#D4D4D8',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const activeTab = ref('tiled');
|
||||
const condition = ref('');
|
||||
|
||||
const methods = ref([
|
||||
{
|
||||
label: '平铺展示',
|
||||
label: t('report.detail.api.tiledDisplay'),
|
||||
value: 'tiled',
|
||||
},
|
||||
{
|
||||
label: 'Tab展示',
|
||||
label: t('report.detail.api.tabDisplay'),
|
||||
value: 'tab',
|
||||
},
|
||||
]);
|
||||
onMounted(() => {
|
||||
initOptionsData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
@ -355,7 +435,7 @@
|
|||
.analyze {
|
||||
min-height: 196px;
|
||||
border-radius: 4px;
|
||||
@apply mb-4 flex justify-between;
|
||||
@apply mb-2 flex justify-between;
|
||||
.step-analyze {
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -1,65 +1,240 @@
|
|||
<template>
|
||||
<div
|
||||
class="scenario-class cursor-pointer"
|
||||
:class="[
|
||||
props.showBorder ? 'border border-solid border-[var(--color-text-n8)]' : '',
|
||||
props.hasBottomMargin ? 'mb-1' : '',
|
||||
]"
|
||||
>
|
||||
<div class="flex h-[46px] items-center">
|
||||
<!-- 序号 -->
|
||||
<span class="index text-[var(--color-text-4)]">{{ props.item.sort }}</span>
|
||||
<MsIcon type="icon-icon_split_turn-down_arrow" class="mx-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
<!-- 场景count -->
|
||||
<span class="mr-2 text-[var(--color-text-4)]">8</span>
|
||||
<!-- 循环控制器 -->
|
||||
<ConditionStatus :status="props.item.stepType" />
|
||||
<span class="ml-2">{{ props.item.name }}</span>
|
||||
<template v-for="(item, index) in list" :key="item.stepId">
|
||||
<div
|
||||
:style="{
|
||||
'padding-left': `${16 * (item.level as number)}px`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="scenario-class cursor-pointer rounded-t-md px-8"
|
||||
:class="[
|
||||
item.level !== 0 ? 'border border-solid border-[var(--color-text-n8)]' : '',
|
||||
...getBorderAndRadius(item),
|
||||
]"
|
||||
@click="showDetail(item)"
|
||||
>
|
||||
<div class="flex h-[46px] items-center">
|
||||
<!-- 序号 -->
|
||||
<span class="index mr-2 text-[var(--color-text-4)]">{{ index }}</span>
|
||||
<!-- 展开折叠控制器 -->
|
||||
<div
|
||||
v-if="item.level !== 0 && showApiType.includes(item.stepType) && props.activeType === 'tab'"
|
||||
class="mx-2"
|
||||
>
|
||||
<span
|
||||
v-if="item.fold"
|
||||
class="collapsebtn flex items-center justify-center"
|
||||
@click.stop="expandHandler(item)"
|
||||
>
|
||||
<icon-right class="text-[var(--color-text-4)]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
<span v-else class="expand flex items-center justify-center" @click.stop="expandHandler(item)">
|
||||
<icon-down class="text-[rgb(var(--primary-6))]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<MsIcon type="icon-icon_split_turn-down_arrow" class="mx-[4px] text-[var(--color-text-4)]" size="16" />
|
||||
<!-- 场景count -->
|
||||
<span class="mr-2 text-[var(--color-text-4)]">{{ (item.children || []).length }}</span>
|
||||
<!-- 循环控制器 -->
|
||||
<ConditionStatus :status="item.stepType || ''" />
|
||||
<span class="ml-2">{{ item.name || '-' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<MsTag class="cursor-pointer" :type="item.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
|
||||
{{ item.status === 'SUCCESS' ? t('report.detail.api.pass') : t('report.detail.api.resError') }}
|
||||
</MsTag>
|
||||
<span class="statusCode">
|
||||
{{ t('report.detail.api.statusCode') }} <span class="code">{{ item.code || '-' }}</span></span
|
||||
>
|
||||
<span class="resTime">
|
||||
{{ t('report.detail.api.responseTime') }}
|
||||
<span class="resTimeCount">{{ item.requestTime || 0 }}ms</span></span
|
||||
>
|
||||
<span class="resSize">
|
||||
{{ t('report.detail.api.responseSize') }}
|
||||
<span class="resTimeCount">{{ item.responseSize || 0 }} bytes</span></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<MsTag class="cursor-pointer" :type="props.item.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
|
||||
通过
|
||||
</MsTag>
|
||||
<span class="statusCode"
|
||||
>状态码 <span class="code">{{ props.item.code }}</span></span
|
||||
>
|
||||
<span class="resTime"
|
||||
>响应时间 <span class="resTimeCount">{{ props.item.requestTime }}ms</span></span
|
||||
>
|
||||
<span class="resSize"
|
||||
>响应大小 <span class="resTimeCount">{{ props.item.responseSize }} bytes</span></span
|
||||
>
|
||||
<a-divider v-if="item.level === 0" :margin="0" class="!mb-4"></a-divider>
|
||||
<!-- 响应内容开始 -->
|
||||
<div
|
||||
v-if="item.level !== 0 && showApiType.includes(item.stepType) && props.activeType === 'tab' && !item.fold"
|
||||
:style="{
|
||||
'padding-left': `${16 * (item.level as number)}px`,
|
||||
}"
|
||||
>
|
||||
<div class="resContentWrapper">
|
||||
<!-- 循环计数器 -->
|
||||
<div v-if="item.stepType === 'LOOP_CONTROLLER'" class="mb-4 flex justify-start">
|
||||
<MsPagination
|
||||
v-model:page-size="pageNation.pageSize"
|
||||
v-model:current="pageNation.current"
|
||||
:total="pageNation.total"
|
||||
size="mini"
|
||||
@change="loadLoop"
|
||||
@page-size-change="loadLoop"
|
||||
/>
|
||||
</div>
|
||||
<div class="resContent">
|
||||
<div class="flex h-full w-full items-center justify-between rounded bg-[var(--color-text-n9)] px-4">
|
||||
<div class="font-medium">{{ t('report.detail.api.resContent') }}</div>
|
||||
<div class="grid grid-cols-5 gap-2 text-center">
|
||||
<span>401</span>
|
||||
<span class="text-[rgb(var(--success-6))]">247ms</span>
|
||||
<span class="text-[rgb(var(--success-6))]">50bytes</span>
|
||||
<span>Mock</span>
|
||||
<span>66</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 响应内容tab开始 -->
|
||||
<div>
|
||||
<a-tabs v-model:active-key="showTab" class="no-content">
|
||||
<a-tab-pane v-for="it of tabList" :key="it.key" :title="t(it.title)" />
|
||||
</a-tabs>
|
||||
<a-divider :margin="0"></a-divider>
|
||||
<div v-if="showTab !== 'assertions'">
|
||||
<ResContent :script="showContent || ''" language="JSON" show-charset-change
|
||||
/></div>
|
||||
<div v-else>
|
||||
<assertTable :data="showContent || []" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 响应内容tab结束 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 响应内容结束 -->
|
||||
<ScenarioItem
|
||||
v-if="'children' in item"
|
||||
:list="item.children"
|
||||
:active-type="props.activeType"
|
||||
@detail="showDetail"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
import ConditionStatus from './conditionStatus.vue';
|
||||
import assertTable from './step/assertTable.vue';
|
||||
import ResContent from './step/resContent.vue';
|
||||
|
||||
import { reportStepDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||
import type { ReportStepDetail, ScenarioItemType } from '@/models/apiTest/report';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
showBorder?: boolean;
|
||||
hasBottomMargin?: boolean;
|
||||
item: ScenarioItemType;
|
||||
list: ScenarioItemType[];
|
||||
activeType: string;
|
||||
}>(),
|
||||
{
|
||||
showBorder: true,
|
||||
hasBottomMargin: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['expand', 'detail']);
|
||||
const activeItem = ref();
|
||||
function showDetail(item: ScenarioItemType) {
|
||||
activeItem.value = item;
|
||||
emit('detail', activeItem.value);
|
||||
}
|
||||
|
||||
const pageNation = ref({
|
||||
total: 1000,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
});
|
||||
// 加载用例列表
|
||||
async function loadLoop() {}
|
||||
|
||||
// const scenarioItem = computed({
|
||||
// get: () => {
|
||||
// return props.list;
|
||||
// },
|
||||
// set: (val) => {
|
||||
// scenarioItem.value = val;
|
||||
// },
|
||||
// });
|
||||
|
||||
const showApiType = ref<string[]>(['API', 'API_CASE', 'CUSTOM_API', 'LOOP_CONTROLLER']);
|
||||
|
||||
const stepDetail = ref<ReportStepDetail>({});
|
||||
|
||||
async function getStepDetail(item: ScenarioItemType) {
|
||||
try {
|
||||
const result = await reportStepDetail(item.reportId, item.stepId);
|
||||
stepDetail.value = result;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function expandHandler(item: ScenarioItemType) {
|
||||
item.fold = !item.fold;
|
||||
// 如果展开则获取报告步骤详情
|
||||
if (!item.fold) {
|
||||
getStepDetail(item);
|
||||
}
|
||||
}
|
||||
|
||||
function getBorderAndRadius(item: ScenarioItemType) {
|
||||
if (props.activeType === 'tab') {
|
||||
if (!item.fold && showApiType.value.includes(item.stepType)) {
|
||||
return ['rounded-b-none', 'mb-0'];
|
||||
}
|
||||
return ['mb-1', 'rounded-[4px]'];
|
||||
}
|
||||
return ['mb-1', 'rounded-[4px]'];
|
||||
}
|
||||
|
||||
const showTab = ref('body');
|
||||
const tabList = ref([
|
||||
{
|
||||
key: 'body',
|
||||
title: 'report.detail.api.resBody',
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
title: 'report.detail.api.resHeader',
|
||||
},
|
||||
{
|
||||
key: 'realReq',
|
||||
title: 'report.detail.api.realReq',
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
title: 'report.detail.api.console',
|
||||
},
|
||||
{
|
||||
key: 'extract',
|
||||
title: 'report.detail.api.extract',
|
||||
},
|
||||
{
|
||||
key: 'assertions',
|
||||
title: 'report.detail.api.assert',
|
||||
},
|
||||
]);
|
||||
|
||||
const showContent = computed(() => {
|
||||
return stepDetail.value.content?.responseResult[showTab.value];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.scenario-class {
|
||||
border-radius: 4px;
|
||||
// border-radius: 4px;
|
||||
@apply flex items-center justify-between px-2;
|
||||
.index {
|
||||
width: 16px;
|
||||
|
@ -80,4 +255,40 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.resContentWrapper {
|
||||
position: relative;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
@apply mb-4 bg-white p-4;
|
||||
.resContent {
|
||||
height: 38px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
:deep(.expand) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: rgb(var(--primary-1));
|
||||
}
|
||||
:deep(.collapsebtn) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-text-n8) !important;
|
||||
@apply bg-white;
|
||||
}
|
||||
:deep(.arco-table-expand-btn) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: var(--color-text-n8) !important;
|
||||
}
|
||||
:deep(.no-content) {
|
||||
.arco-tabs-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ms-base-table ref="tableRef" v-bind="propsRes" no-disable :indent-size="0" v-on="propsEvent">
|
||||
<template #pass="{ record }">
|
||||
<MsTag theme="light" :type="record.pass ? 'success' : 'danger'">
|
||||
{{ record.pass ? '成功' : '失败' }}
|
||||
{{ record.pass ? t('report.detail.api.resSuccess') : t('report.detail.api.resError') }}
|
||||
</MsTag>
|
||||
</template>
|
||||
<template #script="{ record }">
|
||||
|
@ -21,27 +21,31 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { AssertionItem } from '@/models/apiTest/report';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
data: any[];
|
||||
data: AssertionItem[];
|
||||
}>();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
title: 'report.detail.api.content',
|
||||
dataIndex: 'content',
|
||||
slotName: 'content',
|
||||
showTooltip: true,
|
||||
headerCellClass: 'assertTitleClass',
|
||||
bodyCellClass: 'assertCellClass',
|
||||
},
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
title: 'report.detail.api.assertStatus',
|
||||
dataIndex: 'pass',
|
||||
slotName: 'pass',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'report.detail.api.resContent',
|
||||
title: '',
|
||||
dataIndex: 'script',
|
||||
slotName: 'script',
|
||||
showTooltip: true,
|
||||
|
@ -67,4 +71,14 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-table-th) {
|
||||
background: var(--color-text-n9) !important;
|
||||
}
|
||||
:deep(.assertTitleClass.arco-table-th) {
|
||||
padding-left: 36px;
|
||||
}
|
||||
:deep(.arco-table-td.assertCellClass) {
|
||||
padding-left: 36px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from '@/components/pure/ms-code-editor/types';
|
||||
import { Language } from '@/components/pure/ms-code-editor/types';
|
||||
|
||||
const props = defineProps<{
|
||||
script: string;
|
||||
|
|
|
@ -12,6 +12,17 @@
|
|||
<div class="scene-type"> API </div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-4 flex justify-start">
|
||||
<MsPagination
|
||||
v-if="props.scenarioDetail.stepType === 'LOOP_CONTROLLER'"
|
||||
v-model:page-size="pageNation.pageSize"
|
||||
v-model:current="pageNation.current"
|
||||
:total="pageNation.total"
|
||||
size="mini"
|
||||
@change="loadLoop"
|
||||
@page-size-change="loadLoop"
|
||||
/></div>
|
||||
|
||||
<ms-base-table
|
||||
ref="tableRef"
|
||||
v-bind="propsRes"
|
||||
|
@ -22,7 +33,7 @@
|
|||
>
|
||||
<template #titleName>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class="font-medium">响应内容</div>
|
||||
<div class="font-medium">{{ t('report.detail.api.resContent') }}</div>
|
||||
<div class="grid grid-cols-5 gap-2 text-center">
|
||||
<span>401</span>
|
||||
<span class="text-[rgb(var(--success-6))]">247ms</span>
|
||||
|
@ -53,8 +64,9 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsPaginationI, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import assertTable from './assertTable.vue';
|
||||
import ResContent from './resContent.vue';
|
||||
|
@ -62,11 +74,14 @@
|
|||
import { reportDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { ScenarioDetailItem } from '@/models/apiTest/report';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
stepId: string;
|
||||
activeStepIndex: number;
|
||||
scenarioDetail: ScenarioDetailItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -82,10 +97,17 @@
|
|||
},
|
||||
});
|
||||
const innerFileId = ref(props.stepId);
|
||||
|
||||
function loadedStep(detail: Record<string, any>) {
|
||||
innerFileId.value = detail.id;
|
||||
}
|
||||
|
||||
const pageNation = ref({
|
||||
total: 1000,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
});
|
||||
|
||||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
|
@ -121,24 +143,7 @@
|
|||
},
|
||||
});
|
||||
|
||||
const listMap = [
|
||||
{
|
||||
title: '响应体',
|
||||
value: 'body',
|
||||
},
|
||||
{
|
||||
title: '响应头',
|
||||
value: 'headers',
|
||||
},
|
||||
{
|
||||
title: '实际请求',
|
||||
value: 'request',
|
||||
},
|
||||
{
|
||||
title: '控制台',
|
||||
value: 'request',
|
||||
},
|
||||
];
|
||||
async function loadLoop() {}
|
||||
|
||||
onMounted(() => {
|
||||
// 虚拟数据
|
||||
|
@ -260,7 +265,7 @@
|
|||
:deep(.titleClass .arco-table-th-title) {
|
||||
@apply w-full;
|
||||
}
|
||||
:deep(.cellClassWrapper .arco-table-cell) {
|
||||
:deep(.cellClassWrapper > .arco-table-cell) {
|
||||
padding: 0 !important;
|
||||
span {
|
||||
padding-left: 0 !important;
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
<template>
|
||||
<div class="tiled-wrap">
|
||||
<ScenarioItem :item="scenario" :show-border="false" />
|
||||
<a-divider :margin="0" class="!mb-4"></a-divider>
|
||||
<div class="pl-[32px] pr-4">
|
||||
<MsList
|
||||
v-model:data="tiledList"
|
||||
mode="static"
|
||||
item-key-field="stepId"
|
||||
:item-border="false"
|
||||
class="w-full rounded-[var(--border-radius-small)]"
|
||||
:no-more-data="noMoreData"
|
||||
:draggable="false"
|
||||
:virtual-list-props="{
|
||||
height: 'calc(100vh - 438px)',
|
||||
}"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<ScenarioItem :item="item" @click="showDetail(item)" />
|
||||
</template>
|
||||
</MsList>
|
||||
</div>
|
||||
<StepDrawer v-model:visible="showStepDrawer" :step-id="activeDetailId" :active-step-index="activeStepIndex" />
|
||||
<ScenarioItem :list="tiledList" :show-border="true" :active-type="props.activeType" @detail="showDetail" />
|
||||
<StepDrawer
|
||||
v-model:visible="showStepDrawer"
|
||||
:step-id="activeDetailId"
|
||||
:active-step-index="activeStepIndex"
|
||||
:scenario-detail="scenarioDetail"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
import ScenarioItem from './scenarioItem.vue';
|
||||
import StepDrawer from './step/stepDrawer.vue';
|
||||
|
||||
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||
import { addLevelToTree } from '@/utils';
|
||||
|
||||
const noMoreData = ref<boolean>(false);
|
||||
import type { ReportDetail, ScenarioDetailItem, ScenarioItemType } from '@/models/apiTest/report';
|
||||
|
||||
const props = defineProps<{
|
||||
reportDetail: ReportDetail;
|
||||
activeType: string;
|
||||
}>();
|
||||
|
||||
// TODO 虚拟数据
|
||||
// const tiledList = ref<ScenarioItemType[]>([]);
|
||||
// watchEffect(() => {
|
||||
// if (props.reportDetail && props.reportDetail.children) {
|
||||
// tiledList.value = props.reportDetail.children || [];
|
||||
// }
|
||||
// });
|
||||
const tiledList = ref<ScenarioItemType[]>([
|
||||
{
|
||||
stepId: '步骤id',
|
||||
reportId: '报告id',
|
||||
stepId: '1001',
|
||||
reportId: '12345657687',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'QUOTE_API',
|
||||
|
@ -49,93 +48,96 @@
|
|||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'LOOP_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'CONDITION_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'ERROR',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
},
|
||||
{
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'ERROR',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
fold: true,
|
||||
children: [
|
||||
{
|
||||
stepId: '1001102',
|
||||
reportId: '12345657687',
|
||||
name: '场景名称1-1',
|
||||
sort: 0,
|
||||
stepType: 'LOOP_CONTROLLER',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
fold: true,
|
||||
children: [
|
||||
{
|
||||
stepId: '100103',
|
||||
reportId: '12345657687',
|
||||
name: '场景名称1-1-1',
|
||||
sort: 0,
|
||||
stepType: 'CUSTOM_API',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
fold: true,
|
||||
children: [
|
||||
{
|
||||
stepId: '100104',
|
||||
reportId: '12345657687',
|
||||
name: '场景名称1-1-1-1',
|
||||
sort: 0,
|
||||
stepType: 'LOOP_CONTROLLER',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
fold: true,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
stepId: '步骤id',
|
||||
reportId: '12345657687',
|
||||
name: '场景名称1-1-1',
|
||||
sort: 0,
|
||||
stepType: 'QUOTE_API',
|
||||
parentId: 'string',
|
||||
status: 'SUCCESS',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
fold: true,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const scenario = ref<ScenarioItemType>({
|
||||
stepId: '步骤id1',
|
||||
reportId: '报告id',
|
||||
name: '场景名称',
|
||||
sort: 0,
|
||||
stepType: 'ONLY_ONCE_CONTROL',
|
||||
parentId: 'string',
|
||||
status: 'string',
|
||||
fakeCode: 'string',
|
||||
requestName: 'string',
|
||||
requestTime: 3000,
|
||||
code: '200',
|
||||
responseSize: 234543,
|
||||
scriptIdentifier: 'string',
|
||||
});
|
||||
|
||||
const showStepDrawer = ref<boolean>(false);
|
||||
const activeDetailId = ref<string>('');
|
||||
const activeStepIndex = ref<number>(0);
|
||||
const scenarioDetail = ref<ScenarioDetailItem>({});
|
||||
function showDetail(item: ScenarioItemType) {
|
||||
showStepDrawer.value = true;
|
||||
scenarioDetail.value = cloneDeep(item);
|
||||
activeDetailId.value = item.stepId;
|
||||
activeStepIndex.value = item.sort;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tiledList.value = addLevelToTree<ScenarioItemType>(tiledList.value) as ScenarioItemType[];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -33,4 +33,27 @@ export default {
|
|||
'There is a pre/post script running error in the execution step of the current scenario.',
|
||||
'report.detail.api.errorTip': 'There are pre/post script running errors in the current use case execution.',
|
||||
'report.detail.api.resContent': 'Response content',
|
||||
'report.detail.api.executionTime': 'Execution time',
|
||||
'report.detail.api.executionTimeTo': 'To',
|
||||
'report.detail.api.stepAnalysis': 'step Analysis',
|
||||
'report.detail.api.content': 'content',
|
||||
'report.detail.api.assertStatus': 'status',
|
||||
'report.detail.api.totalTime': 'total time',
|
||||
'report.detail.api.requestTotalTime': 'req total time',
|
||||
'report.detail.api.assertPass': 'Assert pass',
|
||||
'report.detail.api.requestAnalysis': 'Request analysis',
|
||||
'report.detail.api.total': 'total',
|
||||
'report.detail.api.reportDetail': 'Report detail',
|
||||
'report.detail.api.filterPlaceholder': 'Please select a filter conditions',
|
||||
'report.detail.api.pass': 'pass',
|
||||
'report.detail.api.misstatement': 'misstatement',
|
||||
'report.detail.api.error': 'error',
|
||||
'report.detail.api.pending': 'pending',
|
||||
'report.detail.api.tiledDisplay': 'tiled display',
|
||||
'report.detail.api.tabDisplay': 'Tab display',
|
||||
'report.detail.api.statusCode': 'Code',
|
||||
'report.detail.api.responseTime': 'Response time',
|
||||
'report.detail.api.responseSize': 'Response size',
|
||||
'report.detail.api.resSuccess': 'Success',
|
||||
'report.detail.api.resError': 'Error',
|
||||
};
|
||||
|
|
|
@ -32,4 +32,34 @@ export default {
|
|||
'report.detail.scenario.errorTip': '当前场景的执行步骤中存在 前/后置脚本运行错误',
|
||||
'report.detail.api.errorTip': '当前用例执行存在 前/后置脚本运行错误',
|
||||
'report.detail.api.resContent': '响应内容',
|
||||
'report.detail.api.executionTime': '执行时间',
|
||||
'report.detail.api.executionTimeTo': '至',
|
||||
'report.detail.api.stepAnalysis': '步骤分析',
|
||||
'report.detail.api.content': '内容',
|
||||
'report.detail.api.assertStatus': '状态',
|
||||
'report.detail.api.totalTime': '总耗时',
|
||||
'report.detail.api.requestTotalTime': '请求总耗时',
|
||||
'report.detail.api.assertPass': '断言通过率',
|
||||
'report.detail.api.requestAnalysis': '请求分析',
|
||||
'report.detail.api.total': '总数(个)',
|
||||
'report.detail.api.reportDetail': '报告明细',
|
||||
'report.detail.api.filterPlaceholder': '请选择过滤条件',
|
||||
'report.detail.api.pass': '通过',
|
||||
'report.detail.api.misstatement': '误报',
|
||||
'report.detail.api.error': '失败',
|
||||
'report.detail.api.pending': '未执行',
|
||||
'report.detail.api.tiledDisplay': '平铺展示',
|
||||
'report.detail.api.tabDisplay': 'Tab展示',
|
||||
'report.detail.api.statusCode': '状态码',
|
||||
'report.detail.api.responseTime': '响应时间',
|
||||
'report.detail.api.responseSize': '响应大小',
|
||||
'report.detail.api.resSuccess': '成功',
|
||||
'report.detail.api.resError': '失败',
|
||||
'report.detail.api.apiCase': '接口用例',
|
||||
'report.detail.api.resBody': '响应体',
|
||||
'report.detail.api.resHeader': '响应头',
|
||||
'report.detail.api.realReq': '实际请求',
|
||||
'report.detail.api.console': '控制台',
|
||||
'report.detail.api.extract': '提取',
|
||||
'report.detail.api.assert': '断言',
|
||||
};
|
||||
|
|
|
@ -210,6 +210,7 @@
|
|||
:upload-image="handleUploadImage"
|
||||
is-use-bottom
|
||||
:notice-user-ids="noticeUserIds"
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
@publish="publishHandler"
|
||||
/>
|
||||
</template>
|
||||
|
@ -247,6 +248,7 @@
|
|||
getBugDetail,
|
||||
getTemplateById,
|
||||
} from '@/api/modules/bug-management/index';
|
||||
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
:disabled="!contentEditAble"
|
||||
:placeholder="t('editor.placeholder')"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
/>
|
||||
<div v-else v-dompurify-html="form?.description || '-'" class="markdown-body"></div>
|
||||
</div>
|
||||
|
@ -48,6 +49,7 @@
|
|||
v-model:raw="item.defaultValue"
|
||||
:disabled="!contentEditAble"
|
||||
:placeholder="t('editor.placeholder')"
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
/>
|
||||
<div v-else v-dompurify-html="item?.defaultValue || '-'" class="markdown-body"></div>
|
||||
</div>
|
||||
|
@ -218,6 +220,7 @@
|
|||
uploadOrAssociationFile,
|
||||
} from '@/api/modules/bug-management';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import { downloadByteFile, sleep } from '@/utils';
|
||||
|
|
|
@ -8,7 +8,14 @@
|
|||
}"
|
||||
>
|
||||
<MsEmpty v-if="commentList.length === 0" />
|
||||
<MsComment v-else :comment-list="commentList" @delete="handleDelete" @update-or-add="handleUpdate" />
|
||||
<MsComment
|
||||
v-else
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
:comment-list="commentList"
|
||||
:upload-image="handleUploadImage"
|
||||
@delete="handleDelete"
|
||||
@update-or-add="handleUpdate"
|
||||
/>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -18,7 +25,13 @@
|
|||
import MsComment from '@/components/business/ms-comment/comment';
|
||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||
|
||||
import { createOrUpdateComment, deleteComment, getCommentList } from '@/api/modules/bug-management/index';
|
||||
import {
|
||||
createOrUpdateComment,
|
||||
deleteComment,
|
||||
editorUploadFile,
|
||||
getCommentList,
|
||||
} from '@/api/modules/bug-management/index';
|
||||
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
|
||||
|
@ -81,6 +94,13 @@
|
|||
}
|
||||
};
|
||||
|
||||
async function handleUploadImage(file: File) {
|
||||
const { data } = await editorUploadFile({
|
||||
fileList: [file],
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.bugId) {
|
||||
initData(props.bugId);
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
v-model:raw="form.description"
|
||||
v-model:filed-ids="descriptionFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 平台默认模板展示字段, 暂时支持输入框, 富文本类型 -->
|
||||
|
@ -234,6 +235,7 @@
|
|||
updateFile,
|
||||
} from '@/api/modules/bug-management';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
|
||||
import useVisit from '@/hooks/useVisit';
|
||||
|
|
|
@ -228,6 +228,7 @@
|
|||
v-model:content="content"
|
||||
v-model:notice-user-ids="noticeUserIds"
|
||||
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
:is-active="isActive"
|
||||
is-show-avatar
|
||||
is-use-bottom
|
||||
|
@ -268,6 +269,7 @@
|
|||
getCaseDetail,
|
||||
getCaseModuleTree,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
v-model:raw="form.prerequisite"
|
||||
v-model:filed-ids="prerequisiteFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
|
@ -56,6 +57,7 @@
|
|||
v-model:raw="form.textDescription"
|
||||
v-model:filed-ids="textDescriptionFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
|
@ -67,6 +69,7 @@
|
|||
v-model:raw="form.expectedResult"
|
||||
v-model:filed-ids="expectedResultFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="description" :label="t('caseManagement.featureCase.remark')">
|
||||
|
@ -74,6 +77,7 @@
|
|||
v-model:raw="form.description"
|
||||
v-model:filed-ids="descriptionFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<AddAttachment v-model:file-list="fileList" multiple @change="handleChange" @link-file="associatedFile" />
|
||||
|
@ -267,6 +271,7 @@
|
|||
updateFile,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
|
|
|
@ -23,7 +23,11 @@
|
|||
<a-input v-model="form.title" :max-length="255" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('bugManagement.edit.content')">
|
||||
<MsRichText v-model:raw="form.description" />
|
||||
<MsRichText
|
||||
v-model:raw="form.description"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="EditorPreviewFileUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</MsDrawer>
|
||||
|
@ -36,7 +40,13 @@
|
|||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
|
||||
import { createOrUpdateBug, getTemplateDetailInfo, getTemplateOption } from '@/api/modules/bug-management/index';
|
||||
import {
|
||||
createOrUpdateBug,
|
||||
editorUploadFile,
|
||||
getTemplateDetailInfo,
|
||||
getTemplateOption,
|
||||
} from '@/api/modules/bug-management/index';
|
||||
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
|
@ -130,6 +140,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function handleUploadImage(file: File) {
|
||||
const { data } = await editorUploadFile({
|
||||
fileList: [file],
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => showDrawer.value,
|
||||
(val) => {
|
||||
|
|
|
@ -21,10 +21,6 @@
|
|||
></a-input-search>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" v-on="propsEvent">
|
||||
<template #defectName="{ record }">
|
||||
<span class="one-line-text max-w[300px]"> {{ record.name }}</span
|
||||
><span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="cancelLink(record)">{{
|
||||
t('caseManagement.featureCase.cancelLink')
|
||||
|
@ -49,6 +45,9 @@
|
|||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<template #sourceType="{ record }">
|
||||
{{ caseTypeOptions.find((e) => e.value === record.sourceType)?.label }}
|
||||
</template>
|
||||
</ms-base-table>
|
||||
<MsCaseAssociate
|
||||
v-model:visible="innerVisible"
|
||||
|
@ -84,6 +83,7 @@
|
|||
|
||||
import {
|
||||
associationPublicCase,
|
||||
cancelAssociatedCase,
|
||||
getAssociatedCasePage,
|
||||
getPublicLinkCaseList,
|
||||
getPublicLinkModuleTree,
|
||||
|
@ -120,22 +120,20 @@
|
|||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
dataIndex: 'num',
|
||||
dataIndex: 'sourceNum',
|
||||
slotName: 'sourceNum',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
slotName: 'sourceName',
|
||||
dataIndex: 'sourceName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.projectName',
|
||||
|
@ -144,28 +142,24 @@
|
|||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnVersion',
|
||||
slotName: 'version',
|
||||
dataIndex: 'version',
|
||||
slotName: 'versionName',
|
||||
dataIndex: 'versionName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.changeType',
|
||||
slotName: 'type',
|
||||
dataIndex: 'type',
|
||||
slotName: 'sourceType',
|
||||
dataIndex: 'sourceType',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnActions',
|
||||
|
@ -182,8 +176,9 @@
|
|||
columns,
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEPENDENCY_PRE_CASE,
|
||||
scroll: { x: '100%' },
|
||||
showSelectorAll: false,
|
||||
heightUsed: 340,
|
||||
enableDrag: true,
|
||||
enableDrag: false,
|
||||
});
|
||||
|
||||
const innerVisible = ref(false);
|
||||
|
@ -197,8 +192,6 @@
|
|||
|
||||
const currentSelectCase = ref<string>('');
|
||||
|
||||
const countParams = ref<TableQueryParams>({});
|
||||
|
||||
const modulesTreeParams = ref<TableQueryParams>({});
|
||||
|
||||
const getTableParams = ref<TableQueryParams>({});
|
||||
|
@ -208,8 +201,6 @@
|
|||
innerVisible.value = true;
|
||||
}
|
||||
|
||||
function cancelLink(record: any) {}
|
||||
|
||||
const caseTypeOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
const modulesCount = ref<Record<string, any>>({});
|
||||
|
@ -275,6 +266,20 @@
|
|||
featureCaseStore.getCaseCounts(props.caseId);
|
||||
}
|
||||
|
||||
async function cancelLink(record: any) {
|
||||
try {
|
||||
await cancelAssociatedCase({
|
||||
selectIds: [record.sourceId],
|
||||
caseId: props.caseId,
|
||||
sourceType: record.sourceType,
|
||||
});
|
||||
getFetch();
|
||||
Message.success(t('caseManagement.featureCase.cancelLinkSuccess'));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchCase() {
|
||||
setKeyword(keyword.value);
|
||||
await loadList();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<MsComment
|
||||
:upload-image="handleUploadImage"
|
||||
:comment-list="commentList"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
@delete="handleDelete"
|
||||
@update-or-add="handleUpdateOrAdd"
|
||||
/>
|
||||
|
@ -88,6 +89,7 @@
|
|||
getCommentList,
|
||||
getReviewCommentList,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
v-model:raw="detailForm.prerequisite"
|
||||
v-model:filed-ids="prerequisiteFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
class="mt-2"
|
||||
/>
|
||||
|
||||
|
@ -70,6 +71,7 @@
|
|||
v-model:raw="detailForm.textDescription"
|
||||
v-model:filed-ids="textDescriptionFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
<div
|
||||
v-if="detailForm.caseEditType === 'TEXT' && !isEditPreposition"
|
||||
|
@ -87,6 +89,7 @@
|
|||
v-model:raw="detailForm.expectedResult"
|
||||
v-model:filed-ids="expectedResultFileIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
|
@ -100,6 +103,7 @@
|
|||
v-model:filed-ids="descriptionFileIds"
|
||||
v-model:raw="detailForm.description"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
/>
|
||||
<div v-else v-dompurify-html="detailForm.description || '-'" class="markdown-body !break-words break-all"></div>
|
||||
</a-form-item>
|
||||
|
@ -296,6 +300,7 @@
|
|||
uploadOrAssociationFile,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { downloadByteFile, getGenerateId } from '@/utils';
|
||||
|
|
|
@ -198,6 +198,7 @@
|
|||
v-model:raw="dialogForm.reason"
|
||||
v-model:commentIds="dialogForm.commentIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
@ -303,6 +304,7 @@
|
|||
} from '@/api/modules/case-management/caseReview';
|
||||
import { editorUploadFile, getCaseDefaultFields } from '@/api/modules/case-management/featureCase';
|
||||
import { getProjectMemberCommentOptions } from '@/api/modules/project-management/projectMember';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { reviewResultMap } from '@/config/caseManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
v-model:raw="caseResultForm.reason"
|
||||
v-model:commentIds="caseResultForm.commentIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
class="w-full"
|
||||
/>
|
||||
</a-modal>
|
||||
|
@ -86,6 +87,7 @@
|
|||
|
||||
import { saveCaseReviewResult } from '@/api/modules/case-management/caseReview';
|
||||
import { editorUploadFile } from '@/api/modules/case-management/featureCase';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
ref="fieldDrawerRef"
|
||||
v-model:visible="showFieldDrawer"
|
||||
:mode="props.mode"
|
||||
:data="totalData"
|
||||
@success="okHandler"
|
||||
/>
|
||||
<div>
|
||||
|
|
|
@ -194,6 +194,7 @@
|
|||
ref="fieldDrawerRef"
|
||||
v-model:visible="showFieldDrawer"
|
||||
:mode="props.mode"
|
||||
:data="(totalTemplateField as DefinedFieldItem[])"
|
||||
@success="updateFieldHandler"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
:ok-text="t(isEdit ? 'system.orgTemplate.update' : 'system.orgTemplate.addField')"
|
||||
:ok-loading="drawerLoading"
|
||||
:width="800"
|
||||
:show-continue="!isEdit"
|
||||
:show-continue="!isEdit && data.length < 20"
|
||||
:ok-disabled="data.length >= 20"
|
||||
@confirm="handleDrawerConfirm"
|
||||
@continue="saveAndContinue"
|
||||
@cancel="handleDrawerCancel"
|
||||
|
@ -155,7 +156,12 @@
|
|||
import { useAppStore } from '@/store';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import type { AddOrUpdateField, fieldIconAndNameModal, FieldOptions } from '@/models/setting/template';
|
||||
import type {
|
||||
AddOrUpdateField,
|
||||
DefinedFieldItem,
|
||||
fieldIconAndNameModal,
|
||||
FieldOptions,
|
||||
} from '@/models/setting/template';
|
||||
|
||||
import { dateOptions, fieldIconAndName, getFieldRequestApi, getFieldType, numberTypeOptions } from './fieldSetting';
|
||||
|
||||
|
@ -167,6 +173,7 @@
|
|||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
mode: 'organization' | 'project';
|
||||
data: DefinedFieldItem[];
|
||||
}>();
|
||||
const emit = defineEmits(['success', 'update:visible']);
|
||||
|
||||
|
|
|
@ -95,7 +95,13 @@
|
|||
<span>{{ getIconType(record.type)?.label }}</span>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" :mode="props.mode" @success="successHandler" />
|
||||
<EditFieldDrawer
|
||||
ref="fieldDrawerRef"
|
||||
v-model:visible="showDrawer"
|
||||
:data="propsRes.data"
|
||||
:mode="props.mode"
|
||||
@success="successHandler"
|
||||
/>
|
||||
<MsDrawer
|
||||
ref="detailDrawerRef"
|
||||
v-model:visible="showDetailVisible"
|
||||
|
|
Loading…
Reference in New Issue