feat(测试计划): 测试计划报告自定义保存&报告预览&编辑报告内容联调

This commit is contained in:
xinxin.wu 2024-07-07 10:04:25 +08:00 committed by 刘瑞斌
parent c8c096b86a
commit 432ad3f4dd
23 changed files with 371 additions and 138 deletions

View File

@ -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 {};

View File

@ -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';

View File

@ -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';
// 测试计划通过率执行进度 // 测试计划通过率执行进度

View File

@ -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[] = [

View File

@ -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 {};

View File

@ -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 {

View File

@ -238,6 +238,7 @@ export interface PassRateCountDetail {
}; };
}; };
nextTriggerTime: number; nextTriggerTime: number;
status: planStatusType;
} }
// 执行历史 // 执行历史

View File

@ -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;

View File

@ -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">

View File

@ -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>

View File

@ -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,
}; };
// 功能用例明细 // 功能用例明细

View File

@ -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,
}); });

View File

@ -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>

View File

@ -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>

View File

@ -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');

View File

@ -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) {

View File

@ -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);
} }
} }

View File

@ -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',
}; };

View File

@ -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': '自定义生成报告成功',
}; };

View File

@ -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>

View File

@ -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 =

View File

@ -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'"

View File

@ -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);