feat(测试计划): 测试计划报告自定义保存&报告预览&编辑报告内容联调
This commit is contained in:
parent
c8c096b86a
commit
432ad3f4dd
|
@ -11,7 +11,7 @@ import {
|
||||||
UpdateReportDetailParams,
|
UpdateReportDetailParams,
|
||||||
} from '@/models/testPlan/report';
|
} from '@/models/testPlan/report';
|
||||||
import type { ExecuteHistoryItem } from '@/models/testPlan/testPlan';
|
import type { ExecuteHistoryItem } from '@/models/testPlan/testPlan';
|
||||||
import { PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import { manualReportGenParams, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
// 报告列表
|
// 报告列表
|
||||||
export function reportList(data: TableQueryParams) {
|
export function reportList(data: TableQueryParams) {
|
||||||
|
@ -160,5 +160,17 @@ export function getFunctionalExecuteStep(data: { reportId: string; shareId?: str
|
||||||
}
|
}
|
||||||
return MSR.get<ExecuteHistoryItem>({ url: `${reportUrl.ReportFunctionalStepUrl}/${data.reportId}` });
|
return MSR.get<ExecuteHistoryItem>({ url: `${reportUrl.ReportFunctionalStepUrl}/${data.reportId}` });
|
||||||
}
|
}
|
||||||
|
// 手动生成报告
|
||||||
|
export function manualReportGen(data: manualReportGenParams) {
|
||||||
|
return MSR.post({ url: reportUrl.ManualReportGenUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取报告布局
|
||||||
|
export function getReportLayout(reportId: string, shareId?: string) {
|
||||||
|
if (shareId) {
|
||||||
|
return MSR.get({ url: `${reportUrl.getReportShareLayoutUrl}/${shareId}/${reportId}` });
|
||||||
|
}
|
||||||
|
return MSR.get({ url: `${reportUrl.getReportLayoutUrl}/${reportId}` });
|
||||||
|
}
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -62,3 +62,9 @@ export const ReportPlanPreviewImageUrl = '/test-plan/report/preview/md';
|
||||||
export const ReportFunctionalStepUrl = '/test-plan/report/detail/functional/case/step';
|
export const ReportFunctionalStepUrl = '/test-plan/report/detail/functional/case/step';
|
||||||
// 测试计划-报告-详情-功能用例明细-执行历史步骤-分享
|
// 测试计划-报告-详情-功能用例明细-执行历史步骤-分享
|
||||||
export const ReportShareFunctionalStepUrl = '/test-plan/report/share/detail/functional/case/step';
|
export const ReportShareFunctionalStepUrl = '/test-plan/report/share/detail/functional/case/step';
|
||||||
|
// 测试计划-报告-详情-手动生成报告
|
||||||
|
export const ManualReportGenUrl = '/test-plan/report/manual-gen';
|
||||||
|
// 测试计划-报告-详情-获取报告布局
|
||||||
|
export const getReportLayoutUrl = '/test-plan/report/get-layout';
|
||||||
|
// 测试计划-报告-详情-获取报告布局-分享
|
||||||
|
export const getReportShareLayoutUrl = '/test-plan/report/share/get-layout';
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const planDetailBugPageUrl = '/test-plan/bug/page';
|
||||||
// 关注测试计划
|
// 关注测试计划
|
||||||
export const followPlanUrl = '/test-plan/edit/follower';
|
export const followPlanUrl = '/test-plan/edit/follower';
|
||||||
// 生成报告
|
// 生成报告
|
||||||
export const GenerateReportUrl = '/test-plan/report/gen';
|
export const GenerateReportUrl = '/test-plan/report/auto-gen';
|
||||||
// 复制测试计划
|
// 复制测试计划
|
||||||
export const copyTestPlanUrl = '/test-plan/copy';
|
export const copyTestPlanUrl = '/test-plan/copy';
|
||||||
// 测试计划通过率执行进度
|
// 测试计划通过率执行进度
|
||||||
|
|
|
@ -51,6 +51,7 @@ export const defaultDetailCount: PassRateCountDetail = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nextTriggerTime: 0,
|
nextTriggerTime: 0,
|
||||||
|
status: 'PREPARED',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExecuteForm = {
|
export const defaultExecuteForm = {
|
||||||
|
@ -91,6 +92,7 @@ export const defaultReportDetail: PlanReportDetail = {
|
||||||
apiBugCount: 0, // 接口用例明细bug总数
|
apiBugCount: 0, // 接口用例明细bug总数
|
||||||
scenarioBugCount: 0, // 场景用例明细bug总数
|
scenarioBugCount: 0, // 场景用例明细bug总数
|
||||||
testPlanName: '',
|
testPlanName: '',
|
||||||
|
defaultLayout: true, // 是否是默认布局
|
||||||
};
|
};
|
||||||
|
|
||||||
export const statusConfig: StatusListType[] = [
|
export const statusConfig: StatusListType[] = [
|
||||||
|
|
|
@ -7,5 +7,9 @@ export enum ReportCardTypeEnum {
|
||||||
SUB_PLAN_DETAIL = 'SUB_PLAN_DETAIL', // 计划组子计划详情
|
SUB_PLAN_DETAIL = 'SUB_PLAN_DETAIL', // 计划组子计划详情
|
||||||
CUSTOM_CARD = 'CUSTOM_CARD', // 自定义卡片
|
CUSTOM_CARD = 'CUSTOM_CARD', // 自定义卡片
|
||||||
}
|
}
|
||||||
|
export enum FieldTypeEnum {
|
||||||
|
SYSTEM = 'SYSTEM',
|
||||||
|
RICH_TEXT = 'RICH_TEXT',
|
||||||
|
}
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -21,8 +21,9 @@ export interface FeatureCaseItem {
|
||||||
|
|
||||||
export interface UpdateReportDetailParams {
|
export interface UpdateReportDetailParams {
|
||||||
id: string;
|
id: string;
|
||||||
summary: string;
|
componentId: string;
|
||||||
richTextTmpFileIds: string[];
|
componentValue?: string;
|
||||||
|
richTextTmpFileIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiOrScenarioCaseItem {
|
export interface ApiOrScenarioCaseItem {
|
||||||
|
|
|
@ -238,6 +238,7 @@ export interface PassRateCountDetail {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
nextTriggerTime: number;
|
nextTriggerTime: number;
|
||||||
|
status: planStatusType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行历史
|
// 执行历史
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
import { TriggerModeLabelEnum } from '@/enums/reportEnum';
|
||||||
|
import { FieldTypeEnum, ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
||||||
|
|
||||||
export interface countDetail {
|
export interface countDetail {
|
||||||
success: number;
|
success: number;
|
||||||
|
@ -31,6 +32,7 @@ export interface PlanReportDetail {
|
||||||
scenarioBugCount: number; // 场景用例明细bug总数
|
scenarioBugCount: number; // 场景用例明细bug总数
|
||||||
testPlanName: string;
|
testPlanName: string;
|
||||||
resultStatus?: string; // 报告结果
|
resultStatus?: string; // 报告结果
|
||||||
|
defaultLayout: boolean; // 报告布局
|
||||||
}
|
}
|
||||||
|
|
||||||
export type detailCountKey = 'functionalCount' | 'apiCaseCount' | 'apiScenarioCount';
|
export type detailCountKey = 'functionalCount' | 'apiCaseCount' | 'apiScenarioCount';
|
||||||
|
@ -60,8 +62,9 @@ export interface configItem {
|
||||||
value: ReportCardTypeEnum;
|
value: ReportCardTypeEnum;
|
||||||
label: string;
|
label: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
system: boolean;
|
type: FieldTypeEnum;
|
||||||
enableEdit: boolean;
|
enableEdit: boolean;
|
||||||
|
richTextTmpFileIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface customValueForm {
|
export interface customValueForm {
|
||||||
|
@ -69,3 +72,26 @@ export interface customValueForm {
|
||||||
label: string;
|
label: string;
|
||||||
richTextTmpFileIds?: string[];
|
richTextTmpFileIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface componentItem {
|
||||||
|
name: ReportCardTypeEnum; // 组件名称
|
||||||
|
label: string; // 组件标题
|
||||||
|
type: FieldTypeEnum; // 组件分类
|
||||||
|
value?: string; // 组件内容
|
||||||
|
pos: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动生成报告
|
||||||
|
export interface manualReportGenParams {
|
||||||
|
projectId: string;
|
||||||
|
testPlanId: string;
|
||||||
|
triggerMode: TriggerModeLabelEnum; // 触发方式
|
||||||
|
reportName: string;
|
||||||
|
components: componentItem[]; // 自定义组件
|
||||||
|
richTextTmpFileIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectedReportCardTypes =
|
||||||
|
| ReportCardTypeEnum.FUNCTIONAL_DETAIL
|
||||||
|
| ReportCardTypeEnum.API_CASE_DETAIL
|
||||||
|
| ReportCardTypeEnum.SCENARIO_CASE_DETAIL;
|
||||||
|
|
|
@ -26,7 +26,9 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="ml-[12px]">
|
<div class="ml-[12px]">
|
||||||
<a-button type="secondary" @click="cancelHandler">{{ t('common.cancel') }}</a-button>
|
<a-button type="secondary" @click="cancelHandler">{{ t('common.cancel') }}</a-button>
|
||||||
<a-button class="ml-[12px]" type="primary" @click="handleSave">{{ t('common.save') }}</a-button>
|
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleSave">{{
|
||||||
|
t('common.save')
|
||||||
|
}}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +72,7 @@
|
||||||
t(item.label)
|
t(item.label)
|
||||||
}}</div>
|
}}</div>
|
||||||
<icon-close
|
<icon-close
|
||||||
v-if="!item.system"
|
v-if="item.type !== FieldTypeEnum.SYSTEM"
|
||||||
:style="{ 'font-size': '14px' }"
|
:style="{ 'font-size': '14px' }"
|
||||||
class="cursor-pointer text-[var(--color-text-3)]"
|
class="cursor-pointer text-[var(--color-text-3)]"
|
||||||
@click.stop="removeField(item)"
|
@click.stop="removeField(item)"
|
||||||
|
@ -107,7 +109,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep, isEqual } from 'lodash-es';
|
import { cloneDeep, isEqual } from 'lodash-es';
|
||||||
import { VueDraggable } from 'vue-draggable-plus';
|
import { VueDraggable } from 'vue-draggable-plus';
|
||||||
|
@ -115,20 +117,30 @@
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import ViewReport from '@/views/test-plan/report/detail/component/viewReport.vue';
|
import ViewReport from '@/views/test-plan/report/detail/component/viewReport.vue';
|
||||||
|
|
||||||
import { updateReportDetail } from '@/api/modules/test-plan/report';
|
import { manualReportGen } from '@/api/modules/test-plan/report';
|
||||||
import { defaultReportDetail } from '@/config/testPlan';
|
import { defaultReportDetail } from '@/config/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
import { getGenerateId } from '@/utils';
|
import { getGenerateId } from '@/utils';
|
||||||
|
|
||||||
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import type {
|
||||||
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
configItem,
|
||||||
|
manualReportGenParams,
|
||||||
|
PlanReportDetail,
|
||||||
|
SelectedReportCardTypes,
|
||||||
|
} from '@/models/testPlan/testPlanReport';
|
||||||
|
import { TriggerModeLabelEnum } from '@/enums/reportEnum';
|
||||||
|
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||||
|
import { FieldTypeEnum, ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
||||||
|
|
||||||
import { defaultCustomConfig, defaultGroupConfig, defaultSingleConfig } from './reportConfig';
|
import { defaultCustomConfig, defaultGroupCardConfig, defaultGroupConfig, defaultSingleConfig } from './reportConfig';
|
||||||
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
detailInfo: PlanReportDetail;
|
detailInfo: PlanReportDetail;
|
||||||
isDrawer?: boolean;
|
isDrawer?: boolean;
|
||||||
|
@ -140,11 +152,6 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
|
||||||
const showButton = ref<boolean>(false);
|
|
||||||
|
|
||||||
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
|
|
||||||
summary: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const isError = ref(false);
|
const isError = ref(false);
|
||||||
const hasChange = ref(false);
|
const hasChange = ref(false);
|
||||||
|
@ -159,22 +166,6 @@
|
||||||
isError.value = false;
|
isError.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateReportDetail() {
|
|
||||||
try {
|
|
||||||
await updateReportDetail({
|
|
||||||
id: detail.value.id,
|
|
||||||
summary: richText.value.summary,
|
|
||||||
richTextTmpFileIds: richText.value.richTextTmpFileIds ?? [],
|
|
||||||
});
|
|
||||||
Message.success(t('common.updateSuccess'));
|
|
||||||
showButton.value = false;
|
|
||||||
emit('updateSuccess');
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.detailInfo) {
|
if (props.detailInfo) {
|
||||||
detail.value = cloneDeep(props.detailInfo);
|
detail.value = cloneDeep(props.detailInfo);
|
||||||
|
@ -182,14 +173,47 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const functionalCaseTotal = computed(() => getSummaryDetail(detail.value.functionalCount).caseTotal);
|
||||||
|
const apiCaseTotal = computed(() => getSummaryDetail(detail.value.apiCaseCount).caseTotal);
|
||||||
|
const scenarioCaseTotal = computed(() => getSummaryDetail(detail.value.apiScenarioCount).caseTotal);
|
||||||
|
|
||||||
const configList = ref<configItem[]>([]);
|
const configList = ref<configItem[]>([]);
|
||||||
const cardItemList = ref<configItem[]>([]);
|
const cardItemList = ref<configItem[]>([]);
|
||||||
|
|
||||||
|
const detailType = [
|
||||||
|
ReportCardTypeEnum.FUNCTIONAL_DETAIL,
|
||||||
|
ReportCardTypeEnum.API_CASE_DETAIL,
|
||||||
|
ReportCardTypeEnum.SCENARIO_CASE_DETAIL,
|
||||||
|
];
|
||||||
|
|
||||||
|
const hasCaseList = computed<Record<SelectedReportCardTypes, number>>(() => {
|
||||||
|
return {
|
||||||
|
[ReportCardTypeEnum.SCENARIO_CASE_DETAIL]: scenarioCaseTotal.value,
|
||||||
|
[ReportCardTypeEnum.FUNCTIONAL_DETAIL]: functionalCaseTotal.value,
|
||||||
|
[ReportCardTypeEnum.API_CASE_DETAIL]: apiCaseTotal.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function filterNotHasCase(list: configItem[]) {
|
||||||
|
return list.filter((item: any) => {
|
||||||
|
if (detailType.includes(item.value)) {
|
||||||
|
return hasCaseList.value[item.value as SelectedReportCardTypes] > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDefaultConfig() {
|
||||||
|
const tempDefaultGroupConfig = filterNotHasCase(defaultGroupConfig);
|
||||||
|
const tempSingleGroupConfig = filterNotHasCase(defaultSingleConfig);
|
||||||
|
configList.value = props.isGroup ? cloneDeep(tempDefaultGroupConfig) : cloneDeep(tempSingleGroupConfig);
|
||||||
|
cardItemList.value = props.isGroup ? cloneDeep(defaultGroupCardConfig) : cloneDeep(configList.value);
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.isGroup,
|
() => props.isGroup,
|
||||||
() => {
|
() => {
|
||||||
configList.value = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
initDefaultConfig();
|
||||||
cardItemList.value = cloneDeep(configList.value);
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
@ -201,17 +225,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHoverClass(cardItem: configItem) {
|
function getHoverClass(cardItem: configItem) {
|
||||||
if (getExist(cardItem) && !cardItem.system) {
|
if (getExist(cardItem) && cardItem.type !== FieldTypeEnum.SYSTEM) {
|
||||||
return 'hover-selected-item-class';
|
return 'hover-selected-item-class';
|
||||||
}
|
}
|
||||||
if (!getExist(cardItem) && cardItem.system) {
|
if (!getExist(cardItem) && cardItem.type === FieldTypeEnum.SYSTEM) {
|
||||||
return 'hover-item-class';
|
return 'hover-item-class';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLabelClass(cardItem: configItem) {
|
function getLabelClass(cardItem: configItem) {
|
||||||
const isSystemColor = cardItem.system ? 'cursor-not-allowed' : '';
|
const isSystemColor = cardItem.type === FieldTypeEnum.SYSTEM ? 'cursor-not-allowed' : '';
|
||||||
return getExist(cardItem)
|
return getExist(cardItem)
|
||||||
? `text-[var(--color-text-4)] ${isSystemColor}`
|
? `text-[var(--color-text-4)] ${isSystemColor}`
|
||||||
: `text-[var(--color-text-1)] cursor-pointer hover:text-[rgb(var(--primary-4))]`;
|
: `text-[var(--color-text-1)] cursor-pointer hover:text-[rgb(var(--primary-4))]`;
|
||||||
|
@ -238,8 +262,7 @@
|
||||||
|
|
||||||
// 恢复默认
|
// 恢复默认
|
||||||
function handleReset() {
|
function handleReset() {
|
||||||
configList.value = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
initDefaultConfig();
|
||||||
cardItemList.value = cloneDeep(configList.value);
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
hasChange.value = false;
|
hasChange.value = false;
|
||||||
});
|
});
|
||||||
|
@ -300,10 +323,6 @@
|
||||||
configList.value.splice(currentIndex, 1, currentItem);
|
configList.value.splice(currentIndex, 1, currentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
const functionalCaseTotal = computed(() => getSummaryDetail(detail.value.functionalCount).caseTotal);
|
|
||||||
const apiCaseTotal = computed(() => getSummaryDetail(detail.value.apiCaseCount).caseTotal);
|
|
||||||
const scenarioCaseTotal = computed(() => getSummaryDetail(detail.value.apiScenarioCount).caseTotal);
|
|
||||||
|
|
||||||
function showItem(item: configItem) {
|
function showItem(item: configItem) {
|
||||||
switch (item.value) {
|
switch (item.value) {
|
||||||
case ReportCardTypeEnum.FUNCTIONAL_DETAIL:
|
case ReportCardTypeEnum.FUNCTIONAL_DETAIL:
|
||||||
|
@ -316,8 +335,61 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeParams() {
|
||||||
|
const richFileIds: string[] = [];
|
||||||
|
const addComponentList = cardItemList.value.map((item, index) => {
|
||||||
|
if (item.richTextTmpFileIds) {
|
||||||
|
richFileIds.concat(item.richTextTmpFileIds);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
// id: item.id,
|
||||||
|
name: item.value,
|
||||||
|
label: t(item.label),
|
||||||
|
type: item.type,
|
||||||
|
value: item.content || '',
|
||||||
|
pos: index + 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
testPlanId: route.query.id as string,
|
||||||
|
triggerMode: TriggerModeLabelEnum.MANUAL,
|
||||||
|
reportName: reportForm.value.reportName,
|
||||||
|
components: addComponentList,
|
||||||
|
richTextTmpFileIds: richFileIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
// 保存配置
|
// 保存配置
|
||||||
function handleSave() {}
|
async function handleSave() {
|
||||||
|
if (!reportForm.value.reportName) {
|
||||||
|
isError.value = true;
|
||||||
|
Message.error(t('report.detail.reportNameNotEmpty'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
const params: manualReportGenParams = makeParams();
|
||||||
|
const reportId = await manualReportGen(params);
|
||||||
|
Message.success(t('report.detail.manualGenReportSuccess'));
|
||||||
|
if (reportId) {
|
||||||
|
router.push({
|
||||||
|
name: TestPlanRouteEnum.TEST_PLAN_REPORT_DETAIL,
|
||||||
|
query: {
|
||||||
|
id: reportId,
|
||||||
|
type: props.isGroup ? 'GROUP' : 'TEST_PLAN',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -26,6 +26,13 @@
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && props.canEdit"
|
||||||
|
class="mt-[16px] flex items-center gap-[12px]"
|
||||||
|
>
|
||||||
|
<a-button type="primary" @click="handleUpdateReportDetail">{{ t('common.save') }}</a-button>
|
||||||
|
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -52,6 +59,7 @@
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'updateCustom', formValue: customValueForm): void;
|
(e: 'updateCustom', formValue: customValueForm): void;
|
||||||
(e: 'dblclick'): void;
|
(e: 'dblclick'): void;
|
||||||
|
(e: 'cancel'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const innerTextForm = ref<customValueForm>({
|
const innerTextForm = ref<customValueForm>({
|
||||||
|
@ -78,10 +86,23 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function emitDoubleClick() {
|
function emitDoubleClick() {
|
||||||
emit('dblclick');
|
if (!props.shareId) {
|
||||||
|
emit('dblclick');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateReportDetail() {
|
||||||
|
emit('updateCustom', {
|
||||||
|
...innerTextForm.value,
|
||||||
|
label: innerTextForm.value.label || t('report.detail.customDefaultCardName'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handleClick } = useDoubleClick(emitDoubleClick);
|
const { handleClick } = useDoubleClick(emitDoubleClick);
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
@ -3,42 +3,55 @@ import { defaultCount } from '@/config/testPlan';
|
||||||
import { ApiOrScenarioCaseItem, FeatureCaseItem, ReportBugItem } from '@/models/testPlan/report';
|
import { ApiOrScenarioCaseItem, FeatureCaseItem, ReportBugItem } from '@/models/testPlan/report';
|
||||||
import type { configItem } from '@/models/testPlan/testPlanReport';
|
import type { configItem } from '@/models/testPlan/testPlanReport';
|
||||||
import { PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import { PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
import { FieldTypeEnum, ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
||||||
|
|
||||||
export const commonDefaultConfig: configItem[] = [
|
const summaryConfig: configItem[] = [
|
||||||
{
|
{
|
||||||
id: ReportCardTypeEnum.SUMMARY,
|
id: ReportCardTypeEnum.SUMMARY,
|
||||||
value: ReportCardTypeEnum.SUMMARY,
|
value: ReportCardTypeEnum.SUMMARY,
|
||||||
label: 'report.detail.reportSummary',
|
label: 'report.detail.reportSummary',
|
||||||
system: true,
|
type: FieldTypeEnum.SYSTEM,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
const subPlanConfig: configItem[] = [
|
||||||
|
{
|
||||||
|
id: ReportCardTypeEnum.SUB_PLAN_DETAIL,
|
||||||
|
value: ReportCardTypeEnum.SUB_PLAN_DETAIL,
|
||||||
|
label: 'report.detail.subPlanDetails',
|
||||||
|
type: FieldTypeEnum.SYSTEM,
|
||||||
|
enableEdit: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const commonDefaultConfig: configItem[] = [
|
||||||
|
...summaryConfig,
|
||||||
{
|
{
|
||||||
id: ReportCardTypeEnum.BUG_DETAIL,
|
id: ReportCardTypeEnum.BUG_DETAIL,
|
||||||
value: ReportCardTypeEnum.BUG_DETAIL,
|
value: ReportCardTypeEnum.BUG_DETAIL,
|
||||||
label: 'report.detail.bugDetails',
|
label: 'report.detail.bugDetails',
|
||||||
system: true,
|
type: FieldTypeEnum.SYSTEM,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ReportCardTypeEnum.FUNCTIONAL_DETAIL,
|
id: ReportCardTypeEnum.FUNCTIONAL_DETAIL,
|
||||||
value: ReportCardTypeEnum.FUNCTIONAL_DETAIL,
|
value: ReportCardTypeEnum.FUNCTIONAL_DETAIL,
|
||||||
label: 'report.detail.featureCaseDetails',
|
label: 'report.detail.featureCaseDetails',
|
||||||
system: true,
|
type: FieldTypeEnum.SYSTEM,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ReportCardTypeEnum.API_CASE_DETAIL,
|
id: ReportCardTypeEnum.API_CASE_DETAIL,
|
||||||
value: ReportCardTypeEnum.API_CASE_DETAIL,
|
value: ReportCardTypeEnum.API_CASE_DETAIL,
|
||||||
label: 'report.detail.apiCaseDetails',
|
label: 'report.detail.apiCaseDetails',
|
||||||
system: true,
|
type: FieldTypeEnum.SYSTEM,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ReportCardTypeEnum.SCENARIO_CASE_DETAIL,
|
id: ReportCardTypeEnum.SCENARIO_CASE_DETAIL,
|
||||||
value: ReportCardTypeEnum.SCENARIO_CASE_DETAIL,
|
value: ReportCardTypeEnum.SCENARIO_CASE_DETAIL,
|
||||||
label: 'report.detail.scenarioCaseDetails',
|
label: 'report.detail.scenarioCaseDetails',
|
||||||
system: true,
|
type: FieldTypeEnum.SYSTEM,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -47,24 +60,17 @@ export const defaultCustomConfig: configItem = {
|
||||||
id: '',
|
id: '',
|
||||||
value: ReportCardTypeEnum.CUSTOM_CARD,
|
value: ReportCardTypeEnum.CUSTOM_CARD,
|
||||||
label: 'report.detail.customDefaultCardName',
|
label: 'report.detail.customDefaultCardName',
|
||||||
system: false,
|
type: FieldTypeEnum.RICH_TEXT,
|
||||||
enableEdit: false,
|
enableEdit: false,
|
||||||
content: '',
|
content: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 独立报告默认配置
|
// 独立报告默认选择列表配置
|
||||||
export const defaultSingleConfig: configItem[] = [...commonDefaultConfig];
|
export const defaultSingleConfig: configItem[] = [...commonDefaultConfig];
|
||||||
// 集合报告默认配置
|
// 集合报告默认选择列表配置
|
||||||
export const defaultGroupConfig: configItem[] = [
|
export const defaultGroupConfig: configItem[] = [...subPlanConfig, ...commonDefaultConfig];
|
||||||
{
|
// 集合报告默认展示卡片列表配置
|
||||||
id: ReportCardTypeEnum.SUB_PLAN_DETAIL,
|
export const defaultGroupCardConfig: configItem[] = [...subPlanConfig, ...summaryConfig];
|
||||||
value: ReportCardTypeEnum.SUB_PLAN_DETAIL,
|
|
||||||
label: 'report.detail.subPlanDetails',
|
|
||||||
system: true,
|
|
||||||
enableEdit: false,
|
|
||||||
},
|
|
||||||
...commonDefaultConfig,
|
|
||||||
];
|
|
||||||
|
|
||||||
interface NamedItem {
|
interface NamedItem {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -115,6 +121,7 @@ const subPlanList: PlanReportDetail = {
|
||||||
apiBugCount: 0,
|
apiBugCount: 0,
|
||||||
scenarioBugCount: 0,
|
scenarioBugCount: 0,
|
||||||
resultStatus: 'SUCCESS',
|
resultStatus: 'SUCCESS',
|
||||||
|
defaultLayout: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 功能用例明细
|
// 功能用例明细
|
||||||
|
|
|
@ -58,9 +58,11 @@
|
||||||
reportId: string;
|
reportId: string;
|
||||||
shareId?: string;
|
shareId?: string;
|
||||||
isPreview?: boolean;
|
isPreview?: boolean;
|
||||||
|
isGroup?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const columns: MsTableColumn = [
|
|
||||||
|
const staticColumns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'num',
|
dataIndex: 'num',
|
||||||
|
@ -97,6 +99,8 @@
|
||||||
},
|
},
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
const lastStaticColumns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'common.belongModule',
|
title: 'common.belongModule',
|
||||||
dataIndex: 'moduleName',
|
dataIndex: 'moduleName',
|
||||||
|
@ -123,12 +127,29 @@
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
// TODO 计划组用例明细字段接口目前还没有
|
||||||
|
const testPlanNameColumns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'report.plan.name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
if (props.isGroup) {
|
||||||
|
return [...staticColumns, ...testPlanNameColumns, ...lastStaticColumns];
|
||||||
|
}
|
||||||
|
return [...staticColumns, ...lastStaticColumns];
|
||||||
|
});
|
||||||
|
|
||||||
const reportFeatureCaseList = () => {
|
const reportFeatureCaseList = () => {
|
||||||
return !props.shareId ? getReportFeatureCaseList : getReportShareFeatureCaseList;
|
return !props.shareId ? getReportFeatureCaseList : getReportShareFeatureCaseList;
|
||||||
};
|
};
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(reportFeatureCaseList(), {
|
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(reportFeatureCaseList(), {
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
columns,
|
columns: columns.value,
|
||||||
heightUsed: 20,
|
heightUsed: 20,
|
||||||
showSelectorAll: false,
|
showSelectorAll: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`">
|
<div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`">
|
||||||
<MsRichText
|
<MsRichText
|
||||||
v-model:raw="innerSummary.summary"
|
v-model:raw="innerSummary.content"
|
||||||
v-model:filedIds="innerSummary.richTextTmpFileIds"
|
v-model:filedIds="innerSummary.richTextTmpFileIds"
|
||||||
:upload-image="handleUploadImage"
|
:upload-image="handleUploadImage"
|
||||||
:preview-url="ReportPlanPreviewImageUrl"
|
:preview-url="ReportPlanPreviewImageUrl"
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
richText: { summary: string; richTextTmpFileIds?: string[] };
|
richText: { content: string; label: string; richTextTmpFileIds?: string[] };
|
||||||
shareId?: string;
|
shareId?: string;
|
||||||
showButton: boolean;
|
showButton: boolean;
|
||||||
isPlanGroup: boolean;
|
isPlanGroup: boolean;
|
||||||
|
@ -128,7 +128,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitDoubleClick() {
|
function emitDoubleClick() {
|
||||||
emit('dblclick');
|
if (!props.shareId) {
|
||||||
|
emit('dblclick');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const { handleClick } = useDoubleClick(emitDoubleClick);
|
const { handleClick } = useDoubleClick(emitDoubleClick);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -157,7 +157,7 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-divider v-if="allowEdit(item.value)" direction="vertical" class="!m-0 !mx-2" />
|
<a-divider v-if="allowEdit(item.value)" direction="vertical" class="!m-0 !mx-2" />
|
||||||
<a-tooltip :content="t('common.delete')">
|
<a-tooltip :content="t('common.delete')">
|
||||||
<MsIcon type="icon-icon_delete-trash_outlined" size="16" @click="deleteCard(item)" />
|
<MsIcon type="icon-icon_delete-trash_filled" size="16" @click="deleteCard(item)" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -174,15 +174,19 @@
|
||||||
/>
|
/>
|
||||||
<Summary
|
<Summary
|
||||||
v-else-if="item.value === ReportCardTypeEnum.SUMMARY"
|
v-else-if="item.value === ReportCardTypeEnum.SUMMARY"
|
||||||
v-model:richText="richText"
|
:rich-text="{
|
||||||
|
content: item.content || '',
|
||||||
|
label: t(item.label),
|
||||||
|
richTextTmpFileIds: [],
|
||||||
|
}"
|
||||||
:share-id="shareId"
|
:share-id="shareId"
|
||||||
:can-edit="item.enableEdit"
|
:can-edit="item.enableEdit"
|
||||||
:show-button="showButton"
|
:show-button="showButton"
|
||||||
:is-plan-group="props.isGroup"
|
:is-plan-group="props.isGroup"
|
||||||
:detail="detail"
|
:detail="detail"
|
||||||
@update-summary="handleUpdateReportDetail"
|
@update-summary="handleUpdateReportDetail(item)"
|
||||||
@cancel="() => handleCancel(item)"
|
@cancel="() => handleCancelCustom(item)"
|
||||||
@handle-summary="handleSummary"
|
@handle-summary="(value:string) => handleSummary(value,item)"
|
||||||
@dblclick="handleDoubleClick(item)"
|
@dblclick="handleDoubleClick(item)"
|
||||||
/>
|
/>
|
||||||
<BugTable
|
<BugTable
|
||||||
|
@ -197,6 +201,7 @@
|
||||||
:report-id="detail.id"
|
:report-id="detail.id"
|
||||||
:share-id="shareId"
|
:share-id="shareId"
|
||||||
:is-preview="props.isPreview"
|
:is-preview="props.isPreview"
|
||||||
|
:is-group="props.isGroup"
|
||||||
/>
|
/>
|
||||||
<ApiAndScenarioTable
|
<ApiAndScenarioTable
|
||||||
v-else-if="
|
v-else-if="
|
||||||
|
@ -220,6 +225,7 @@
|
||||||
}"
|
}"
|
||||||
@update-custom="(formValue:customValueForm)=>updateCustom(formValue,item)"
|
@update-custom="(formValue:customValueForm)=>updateCustom(formValue,item)"
|
||||||
@dblclick="handleDoubleClick(item)"
|
@dblclick="handleDoubleClick(item)"
|
||||||
|
@cancel="() => handleCancelCustom(item)"
|
||||||
/>
|
/>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
</div>
|
</div>
|
||||||
|
@ -249,11 +255,12 @@
|
||||||
import Summary from '@/views/test-plan/report/detail/component/system-card/summary.vue';
|
import Summary from '@/views/test-plan/report/detail/component/system-card/summary.vue';
|
||||||
import SystemTrigger from '@/views/test-plan/report/detail/component/system-card/systemTrigger.vue';
|
import SystemTrigger from '@/views/test-plan/report/detail/component/system-card/systemTrigger.vue';
|
||||||
|
|
||||||
import { updateReportDetail } from '@/api/modules/test-plan/report';
|
import { getReportLayout, updateReportDetail } from '@/api/modules/test-plan/report';
|
||||||
import { commonConfig, defaultCount, defaultReportDetail, seriesConfig, statusConfig } from '@/config/testPlan';
|
import { commonConfig, defaultCount, defaultReportDetail, seriesConfig, statusConfig } from '@/config/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { addCommasToNumber } from '@/utils';
|
import { addCommasToNumber } from '@/utils';
|
||||||
|
|
||||||
|
import { UpdateReportDetailParams } from '@/models/testPlan/report';
|
||||||
import type {
|
import type {
|
||||||
configItem,
|
configItem,
|
||||||
countDetail,
|
countDetail,
|
||||||
|
@ -264,6 +271,7 @@
|
||||||
import { customValueForm } from '@/models/testPlan/testPlanReport';
|
import { customValueForm } from '@/models/testPlan/testPlanReport';
|
||||||
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
import { ReportCardTypeEnum } from '@/enums/testPlanReportEnum';
|
||||||
|
|
||||||
|
import { defaultGroupConfig, defaultSingleConfig } from './reportConfig';
|
||||||
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
import { getSummaryDetail } from '@/views/test-plan/report/utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -277,7 +285,6 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'updateSuccess'): void;
|
|
||||||
(e: 'updateSuccess'): void;
|
(e: 'updateSuccess'): void;
|
||||||
(e: 'updateCustom', item: configItem): void;
|
(e: 'updateCustom', item: configItem): void;
|
||||||
}>();
|
}>();
|
||||||
|
@ -293,18 +300,10 @@
|
||||||
summary: '',
|
summary: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isError = ref(false);
|
|
||||||
const reportForm = ref({
|
const reportForm = ref({
|
||||||
reportName: '',
|
reportName: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
function inputHandler(value: string) {
|
|
||||||
if (value.trim().length === 0) {
|
|
||||||
isError.value = true;
|
|
||||||
}
|
|
||||||
isError.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAnalysisHover = computed(() => (props.isPreview ? '' : 'hover-analysis cursor-not-allowed'));
|
const getAnalysisHover = computed(() => (props.isPreview ? '' : 'hover-analysis cursor-not-allowed'));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -388,22 +387,6 @@
|
||||||
scenarioCaseOptions.value.series.data = getPassRateData(apiScenarioCount);
|
scenarioCaseOptions.value.series.data = getPassRateData(apiScenarioCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateReportDetail() {
|
|
||||||
try {
|
|
||||||
await updateReportDetail({
|
|
||||||
id: detail.value.id,
|
|
||||||
summary: richText.value.summary,
|
|
||||||
richTextTmpFileIds: richText.value.richTextTmpFileIds ?? [],
|
|
||||||
});
|
|
||||||
Message.success(t('common.updateSuccess'));
|
|
||||||
showButton.value = false;
|
|
||||||
emit('updateSuccess');
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportAnalysisList = computed<ReportMetricsItemModel[]>(() => [
|
const reportAnalysisList = computed<ReportMetricsItemModel[]>(() => [
|
||||||
{
|
{
|
||||||
name: t('report.detail.threshold'),
|
name: t('report.detail.threshold'),
|
||||||
|
@ -473,12 +456,42 @@
|
||||||
return count;
|
return count;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const originLayoutInfo = ref([]);
|
||||||
|
|
||||||
|
async function getDefaultLayout() {
|
||||||
|
try {
|
||||||
|
const res = await getReportLayout(detail.value.id, shareId.value);
|
||||||
|
const result = res.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
value: item.name,
|
||||||
|
label: item.label,
|
||||||
|
content: item.value || '',
|
||||||
|
type: item.type,
|
||||||
|
enableEdit: false,
|
||||||
|
richTextTmpFileIds: item.richTextTmpFileIds,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
innerCardList.value = result;
|
||||||
|
originLayoutInfo.value = cloneDeep(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.detailInfo) {
|
if (props.detailInfo) {
|
||||||
detail.value = cloneDeep(props.detailInfo);
|
detail.value = cloneDeep(props.detailInfo);
|
||||||
richText.value.summary = detail.value.summary;
|
richText.value.summary = detail.value.summary;
|
||||||
reportForm.value.reportName = detail.value.name;
|
reportForm.value.reportName = detail.value.name;
|
||||||
initOptionsData();
|
initOptionsData();
|
||||||
|
if (props.isPreview) {
|
||||||
|
if (!detail.value.defaultLayout && detail.value.id) {
|
||||||
|
getDefaultLayout();
|
||||||
|
} else {
|
||||||
|
innerCardList.value = props.isGroup ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -491,14 +504,18 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleCancel(cardItem: configItem) {
|
function handleCancelCustom(cardItem: configItem) {
|
||||||
richText.value = { summary: detail.value.summary };
|
const originItem = originLayoutInfo.value.find((item: configItem) => item.id === cardItem.id);
|
||||||
|
const index = originLayoutInfo.value.findIndex((e: configItem) => e.id === cardItem.id);
|
||||||
|
if (originItem && index !== -1) {
|
||||||
|
innerCardList.value.splice(index, 1, originItem);
|
||||||
|
}
|
||||||
showButton.value = false;
|
showButton.value = false;
|
||||||
cardItem.enableEdit = false;
|
cardItem.enableEdit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSummary(content: string) {
|
function handleSummary(content: string, cardItem: configItem) {
|
||||||
richText.value.summary = content;
|
cardItem.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentMode = ref<string>('drawer');
|
const currentMode = ref<string>('drawer');
|
||||||
|
@ -512,7 +529,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowEditType = [ReportCardTypeEnum.SUMMARY, ReportCardTypeEnum.CUSTOM_CARD];
|
const allowEditType = [ReportCardTypeEnum.CUSTOM_CARD];
|
||||||
function allowEdit(value: ReportCardTypeEnum) {
|
function allowEdit(value: ReportCardTypeEnum) {
|
||||||
return allowEditType.includes(value);
|
return allowEditType.includes(value);
|
||||||
}
|
}
|
||||||
|
@ -537,26 +554,51 @@
|
||||||
const deleteCard = (cardItem: configItem) => {
|
const deleteCard = (cardItem: configItem) => {
|
||||||
innerCardList.value = innerCardList.value.filter((item) => item.id !== cardItem.id);
|
innerCardList.value = innerCardList.value.filter((item) => item.id !== cardItem.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑模式和预览模式切换
|
// 编辑模式和预览模式切换
|
||||||
function editField(cardItem: configItem) {
|
function editField(cardItem: configItem) {
|
||||||
if (allowEditType.includes(cardItem.value)) {
|
if (allowEditType.includes(cardItem.value)) {
|
||||||
cardItem.enableEdit = !cardItem.enableEdit;
|
cardItem.enableEdit = !cardItem.enableEdit;
|
||||||
}
|
}
|
||||||
if (cardItem.value === ReportCardTypeEnum.SUMMARY) {
|
|
||||||
showButton.value = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDoubleClick(cardItem: configItem) {
|
function handleDoubleClick(cardItem: configItem) {
|
||||||
editField(cardItem);
|
if (props.isPreview) {
|
||||||
|
if (cardItem.value === ReportCardTypeEnum.SUMMARY) {
|
||||||
|
showButton.value = true;
|
||||||
|
}
|
||||||
|
cardItem.enableEdit = !cardItem.enableEdit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdateReportDetail(currentItem: configItem) {
|
||||||
|
try {
|
||||||
|
const params: UpdateReportDetailParams = {
|
||||||
|
id: detail.value.id,
|
||||||
|
componentId: currentItem.type,
|
||||||
|
componentValue: currentItem.content,
|
||||||
|
richTextTmpFileIds: currentItem.richTextTmpFileIds,
|
||||||
|
};
|
||||||
|
await updateReportDetail(params);
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
if (currentItem.value === ReportCardTypeEnum.SUMMARY) {
|
||||||
|
showButton.value = false;
|
||||||
|
} else {
|
||||||
|
currentItem.enableEdit = !currentItem.enableEdit;
|
||||||
|
}
|
||||||
|
// TODO 此处的更新后后台数据未更新需要验证接口
|
||||||
|
emit('updateSuccess');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCustom(formValue: customValueForm, currentItem: configItem) {
|
function updateCustom(formValue: customValueForm, currentItem: configItem) {
|
||||||
const newCurrentItem = {
|
const newCurrentItem: configItem = {
|
||||||
...currentItem,
|
...currentItem,
|
||||||
...formValue,
|
...formValue,
|
||||||
};
|
};
|
||||||
innerCardList.value = innerCardList.value.map((item) => {
|
innerCardList.value = innerCardList.value.map((item: configItem) => {
|
||||||
if (item.id === currentItem.id) {
|
if (item.id === currentItem.id) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
@ -566,7 +608,11 @@
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
emit('updateCustom', newCurrentItem);
|
if (!props.isPreview) {
|
||||||
|
emit('updateCustom', newCurrentItem);
|
||||||
|
} else {
|
||||||
|
handleUpdateReportDetail(newCurrentItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
apiBugCount: 0,
|
apiBugCount: 0,
|
||||||
scenarioBugCount: 0,
|
scenarioBugCount: 0,
|
||||||
testPlanName: '',
|
testPlanName: '',
|
||||||
|
defaultLayout: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isGroup = computed(() => route.query.type === 'GROUP');
|
const isGroup = computed(() => route.query.type === 'GROUP');
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<ViewReport v-model:card-list="cardItemList" :detail-info="detail" :is-group="isGroup" is-preview />
|
<ViewReport
|
||||||
|
v-model:card-list="cardItemList"
|
||||||
|
:detail-info="detail"
|
||||||
|
:is-group="isGroup"
|
||||||
|
is-preview
|
||||||
|
@update-success="getDetail()"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO 待联调 需要接口更新后联调下
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
@ -15,8 +20,6 @@
|
||||||
|
|
||||||
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
import { defaultGroupConfig, defaultSingleConfig } from '@/views/test-plan/report/detail/component/reportConfig';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const reportId = ref<string>(route.query.id as string);
|
const reportId = ref<string>(route.query.id as string);
|
||||||
|
|
||||||
|
@ -27,7 +30,6 @@
|
||||||
const cardItemList = ref<configItem[]>([]);
|
const cardItemList = ref<configItem[]>([]);
|
||||||
|
|
||||||
async function getDetail() {
|
async function getDetail() {
|
||||||
cardItemList.value = isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
|
||||||
try {
|
try {
|
||||||
detail.value = await getReportDetail(reportId.value);
|
detail.value = await getReportDetail(reportId.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO 待联调 分享页面也要调整
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
@ -16,8 +15,6 @@
|
||||||
|
|
||||||
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
import { defaultGroupConfig, defaultSingleConfig } from '@/views/test-plan/report/detail/component/reportConfig';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const reportId = ref<string>(route.query.id as string);
|
const reportId = ref<string>(route.query.id as string);
|
||||||
|
@ -26,7 +23,6 @@
|
||||||
|
|
||||||
const cardItemList = ref<configItem[]>([]);
|
const cardItemList = ref<configItem[]>([]);
|
||||||
async function getShareDetail() {
|
async function getShareDetail() {
|
||||||
cardItemList.value = isGroup.value ? cloneDeep(defaultGroupConfig) : cloneDeep(defaultSingleConfig);
|
|
||||||
try {
|
try {
|
||||||
const hrefShareDetail = await planGetShareHref(route.query.shareId as string);
|
const hrefShareDetail = await planGetShareHref(route.query.shareId as string);
|
||||||
reportId.value = hrefShareDetail.reportId;
|
reportId.value = hrefShareDetail.reportId;
|
||||||
|
@ -51,6 +47,7 @@
|
||||||
}
|
}
|
||||||
detail.value = await getReportDetail(reportId.value, route.query.shareId as string);
|
detail.value = await getReportDetail(reportId.value, route.query.shareId as string);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,4 +55,6 @@ export default {
|
||||||
'report.detail.customMaxNumber': 'A maximum of 10 can be added',
|
'report.detail.customMaxNumber': 'A maximum of 10 can be added',
|
||||||
'report.detail.enterReportNamePlaceHolder': 'Please enter a report name',
|
'report.detail.enterReportNamePlaceHolder': 'Please enter a report name',
|
||||||
'report.detail.systemInternalTooltip': 'System built-in, not editable',
|
'report.detail.systemInternalTooltip': 'System built-in, not editable',
|
||||||
|
'report.detail.reportNameNotEmpty': 'The report name cannot be empty',
|
||||||
|
'report.detail.manualGenReportSuccess': 'The custom generated report was successful',
|
||||||
};
|
};
|
||||||
|
|
|
@ -55,4 +55,6 @@ export default {
|
||||||
'report.detail.customMaxNumber': '最多可添加10个',
|
'report.detail.customMaxNumber': '最多可添加10个',
|
||||||
'report.detail.enterReportNamePlaceHolder': '请输入报告名称',
|
'report.detail.enterReportNamePlaceHolder': '请输入报告名称',
|
||||||
'report.detail.systemInternalTooltip': '系统内置,不可编辑',
|
'report.detail.systemInternalTooltip': '系统内置,不可编辑',
|
||||||
|
'report.detail.reportNameNotEmpty': '报告名称不能为空',
|
||||||
|
'report.detail.manualGenReportSuccess': '自定义生成报告成功',
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
/>
|
/>
|
||||||
<span :class="getIconClass">{{ record.childrenCount || (record.children || []).length || 0 }}</span>
|
<span :class="getIconClass">{{ record.childrenCount || (record.children || []).length || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="record.type === testPlanTypeEnum.TEST_PLAN || !record.integrated" :class="`one-line-text ${hasIndent}`">
|
<div v-if="showButton" :class="`one-line-text ${hasIndent}`">
|
||||||
<MsButton type="text" @click="handleAction">
|
<MsButton type="text" @click="handleAction">
|
||||||
<a-tooltip :content="content">
|
<a-tooltip :content="content">
|
||||||
<span>{{ record[props.numKey || 'num'] }}</span>
|
<span>{{ record[props.numKey || 'num'] }}</span>
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
const hasIndent = computed(() =>
|
const hasIndent = computed(() =>
|
||||||
(props.record.type === testPlanTypeEnum.TEST_PLAN && props.record.groupId && props.record.groupId !== 'NONE') ||
|
(props.record.type === testPlanTypeEnum.TEST_PLAN && props.record.groupId && props.record.groupId !== 'NONE') ||
|
||||||
(!props.record.integrated && props.record.parentId)
|
(!props.record.integrated && props.record.parent)
|
||||||
? 'pl-[36px]'
|
? 'pl-[36px]'
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
|
@ -76,6 +76,13 @@
|
||||||
emit('action');
|
emit('action');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showButton = computed(() => {
|
||||||
|
if (props.record.type) {
|
||||||
|
return props.record.type === testPlanTypeEnum.TEST_PLAN;
|
||||||
|
}
|
||||||
|
return !props.record.integrated;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less"></style>
|
<style scoped lang="less"></style>
|
||||||
|
|
|
@ -129,7 +129,8 @@
|
||||||
<MsStatusTag :status="filterContent.value" />
|
<MsStatusTag :status="filterContent.value" />
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<MsStatusTag :status="record.status" />
|
<MsStatusTag v-if="getStatus(record.id)" :status="getStatus(record.id)" />
|
||||||
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
<template #createUser="{ record }">
|
<template #createUser="{ record }">
|
||||||
<a-tooltip :content="`${record.createUserName}`" position="tl">
|
<a-tooltip :content="`${record.createUserName}`" position="tl">
|
||||||
|
@ -244,13 +245,13 @@
|
||||||
></a-divider>
|
></a-divider>
|
||||||
|
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && record.status !== 'ARCHIVED'"
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'"
|
||||||
class="!mx-0"
|
class="!mx-0"
|
||||||
@click="emit('edit', record)"
|
@click="emit('edit', record)"
|
||||||
>{{ t('common.edit') }}</MsButton
|
>{{ t('common.edit') }}</MsButton
|
||||||
>
|
>
|
||||||
<a-divider
|
<a-divider
|
||||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && record.status !== 'ARCHIVED'"
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && getStatus(record.id) !== 'ARCHIVED'"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
:margin="8"
|
:margin="8"
|
||||||
></a-divider>
|
></a-divider>
|
||||||
|
@ -259,7 +260,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
!isShowExecuteButton(record) &&
|
!isShowExecuteButton(record) &&
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
||||||
record.status !== 'ARCHIVED'
|
getStatus(record.id) !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
class="!mx-0"
|
class="!mx-0"
|
||||||
@click="copyTestPlanOrGroup(record.id)"
|
@click="copyTestPlanOrGroup(record.id)"
|
||||||
|
@ -269,7 +270,7 @@
|
||||||
v-if="
|
v-if="
|
||||||
!isShowExecuteButton(record) &&
|
!isShowExecuteButton(record) &&
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
||||||
record.status !== 'ARCHIVED'
|
getStatus(record.id) !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
:margin="8"
|
:margin="8"
|
||||||
|
@ -738,17 +739,20 @@
|
||||||
function getScheduleEnable(id: string) {
|
function getScheduleEnable(id: string) {
|
||||||
return defaultCountDetailMap.value[id].scheduleConfig.enable;
|
return defaultCountDetailMap.value[id].scheduleConfig.enable;
|
||||||
}
|
}
|
||||||
|
function getStatus(id: string) {
|
||||||
|
return defaultCountDetailMap.value[id]?.status;
|
||||||
|
}
|
||||||
|
|
||||||
function isShowExecuteButton(record: TestPlanItem) {
|
function isShowExecuteButton(record: TestPlanItem) {
|
||||||
return (
|
return (
|
||||||
((record.type === testPlanTypeEnum.TEST_PLAN && getFunctionalCount(record.id) > 0) ||
|
((record.type === testPlanTypeEnum.TEST_PLAN && getFunctionalCount(record.id) > 0) ||
|
||||||
(record.type === testPlanTypeEnum.GROUP && record.childrenCount)) &&
|
(record.type === testPlanTypeEnum.GROUP && record.childrenCount)) &&
|
||||||
record.status !== 'ARCHIVED'
|
getStatus(record.id) !== 'ARCHIVED'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMoreActions(record: TestPlanItem) {
|
function getMoreActions(record: TestPlanItem) {
|
||||||
const { status: planStatus } = record;
|
const planStatus = getStatus(record.id);
|
||||||
|
|
||||||
// 有用例数量才可以执行 否则不展示执行
|
// 有用例数量才可以执行 否则不展示执行
|
||||||
const copyAction =
|
const copyAction =
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
hide-back
|
hide-back
|
||||||
>
|
>
|
||||||
<template #headerLeft>
|
<template #headerLeft>
|
||||||
<MsStatusTag :status="detail.status || 'PREPARED'" />
|
<MsStatusTag :status="countDetail.status" />
|
||||||
<a-tooltip :content="`[${detail.num}]${detail.name}`">
|
<a-tooltip :content="`[${detail.num}]${detail.name}`">
|
||||||
<div class="one-line-text ml-[8px] max-w-[360px] gap-[4px] font-medium text-[var(--color-text-1)]">
|
<div class="one-line-text ml-[8px] max-w-[360px] gap-[4px] font-medium text-[var(--color-text-1)]">
|
||||||
<span>[{{ detail.num }}]</span>
|
<span>[{{ detail.num }}]</span>
|
||||||
|
@ -33,7 +33,6 @@
|
||||||
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
|
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<!-- TODO 等待联调 接口需要调整和增加 -->
|
|
||||||
<MsTableMoreAction :list="reportMoreAction" @select="handleMoreReportSelect">
|
<MsTableMoreAction :list="reportMoreAction" @select="handleMoreReportSelect">
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) && detail.status !== 'ARCHIVED'"
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) && detail.status !== 'ARCHIVED'"
|
||||||
|
|
|
@ -30,8 +30,6 @@
|
||||||
|
|
||||||
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
import type { configItem, PlanReportDetail } from '@/models/testPlan/testPlanReport';
|
||||||
|
|
||||||
import { defaultSingleConfig } from '@/views/test-plan/report/detail/component/reportConfig';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reportId: string;
|
reportId: string;
|
||||||
}>();
|
}>();
|
||||||
|
@ -42,7 +40,7 @@
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const cardItemList = ref<configItem[]>(cloneDeep(defaultSingleConfig));
|
const cardItemList = ref<configItem[]>([]);
|
||||||
|
|
||||||
const shareId = ref<string>(route.query.shareId as string);
|
const shareId = ref<string>(route.query.shareId as string);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue