fix(缺陷管理): 修复缺陷更新刷新方式

--bug=1038739 --user=宋昌昌 【缺陷管理】优化更新缺陷刷新方式 https://www.tapd.cn/55049933/s/1491966
--bug=1038537 --user=宋昌昌 【项目管理】添加文件提示和逻辑优化 https://www.tapd.cn/55049933/s/1493052
--bug=1039006 --user=宋昌昌 【项目管理】项目与权限-应用设置-用例管理-关联需求输入错误的key提示优化 https://www.tapd.cn/55049933/s/1493115
This commit is contained in:
song-cc-rock 2024-04-10 18:43:34 +08:00 committed by 刘瑞斌
parent 0987e1fc82
commit 08826e6236
24 changed files with 285 additions and 239 deletions

View File

@ -432,6 +432,8 @@ message.domain.bug_updateTime=更新时间
message.domain.bug_deleteUser=删除人 message.domain.bug_deleteUser=删除人
message.domain.bug_deleteTime=删除时间 message.domain.bug_deleteTime=删除时间
message.domain.bug_handleUser=处理人 message.domain.bug_handleUser=处理人
message.domain.bug_sync_platform=同步平台
message.domain.bug_sync_total_count=同步数量
#UI #UI
message.domain.ui_name=场景名称 message.domain.ui_name=场景名称
message.domain.ui_level=用例等级 message.domain.ui_level=用例等级

View File

@ -468,6 +468,8 @@ message.domain.bug_updateTime=Update time
message.domain.bug_deleteUser=Delete user message.domain.bug_deleteUser=Delete user
message.domain.bug_deleteTime=Delete time message.domain.bug_deleteTime=Delete time
message.domain.bug_handleUser=Processor message.domain.bug_handleUser=Processor
message.domain.bug_sync_platform=Platform
message.domain.bug_sync_total_count=Total
#UI #UI
message.domain.ui_name=Scenario name message.domain.ui_name=Scenario name
message.domain.ui_level=Case level message.domain.ui_level=Case level

View File

@ -467,6 +467,8 @@ message.domain.bug_updateTime=更新时间
message.domain.bug_deleteUser=删除人 message.domain.bug_deleteUser=删除人
message.domain.bug_deleteTime=删除时间 message.domain.bug_deleteTime=删除时间
message.domain.bug_handleUser=处理人 message.domain.bug_handleUser=处理人
message.domain.bug_sync_platform=同步平台
message.domain.bug_sync_total_count=同步数量
#UI #UI
message.domain.ui_name=场景名称 message.domain.ui_name=场景名称
message.domain.ui_level=用例等级 message.domain.ui_level=用例等级

View File

@ -468,6 +468,8 @@ message.domain.bug_updateTime=更新時間
message.domain.bug_deleteUser=刪除人 message.domain.bug_deleteUser=刪除人
message.domain.bug_deleteTime=刪除時間 message.domain.bug_deleteTime=刪除時間
message.domain.bug_handleUser=處理人 message.domain.bug_handleUser=處理人
message.domain.bug_sync_platform=同步平臺
message.domain.bug_sync_total_count=同步數量
#UI #UI
message.domain.ui_name=場景名稱 message.domain.ui_name=場景名稱
message.domain.ui_level=用例等級 message.domain.ui_level=用例等級

View File

@ -1,5 +1,6 @@
package io.metersphere.bug.service; package io.metersphere.bug.service;
import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.NoticeModel; import io.metersphere.system.notice.NoticeModel;
@ -23,19 +24,24 @@ public class BugSyncNoticeService {
@Resource @Resource
private NoticeSendService noticeSendService; private NoticeSendService noticeSendService;
@Resource
private ProjectApplicationService projectApplicationService;
public void sendNotice(int total, String currentUser, String language, String projectId) { public void sendNotice(int total, String currentUser, String language, String projectId) {
String platformName = projectApplicationService.getPlatformName(projectId);
User user = userMapper.selectByPrimaryKey(currentUser); User user = userMapper.selectByPrimaryKey(currentUser);
Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap(); Map<String, String> defaultTemplateMap = MessageTemplateUtils.getDefaultTemplateMap();
String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED); String template = defaultTemplateMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED);
Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap(); Map<String, String> defaultSubjectMap = MessageTemplateUtils.getDefaultTemplateSubjectMap();
String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED); String subject = defaultSubjectMap.get(NoticeConstants.TemplateText.BUG_SYNC_TASK_EXECUTE_COMPLETED);
// ${OPERATOR}同步了${total}条缺陷 // ${OPERATOR}同步了${total}条缺陷
Map<String, Object> paramMap = new HashMap<>(3); Map<String, Object> paramMap = new HashMap<>(4);
paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName()); paramMap.put(NoticeConstants.RelatedUser.OPERATOR, user.getName());
paramMap.put("total", total); paramMap.put("total", total);
paramMap.put("projectId", projectId); paramMap.put("projectId", projectId);
paramMap.put("Language", language); paramMap.put("Language", language);
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser) paramMap.put("platform", platformName);
NoticeModel noticeModel = NoticeModel.builder().operator(currentUser).excludeSelf(false)
.context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.EXECUTE_COMPLETED).build(); .context(template).subject(subject).paramMap(paramMap).event(NoticeConstants.Event.EXECUTE_COMPLETED).build();
noticeSendService.send(NoticeConstants.TaskType.BUG_SYNC_TASK, noticeModel); noticeSendService.send(NoticeConstants.TaskType.BUG_SYNC_TASK, noticeModel);
} }

View File

@ -10,7 +10,10 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.CustomField; import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.CustomFieldExample; import io.metersphere.system.domain.CustomFieldExample;
import io.metersphere.system.domain.Schedule; import io.metersphere.system.domain.Schedule;
import io.metersphere.system.dto.BugNoticeDTO; import io.metersphere.system.dto.BugMessageDTO;
import io.metersphere.system.dto.BugSyncNoticeDTO;
import io.metersphere.system.dto.request.DefaultBugCustomField;
import io.metersphere.system.dto.request.DefaultFunctionalCustomField;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO; import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.ApiScenarioMessageDTO; import io.metersphere.system.dto.sdk.ApiScenarioMessageDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO; import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
@ -71,11 +74,16 @@ public class NoticeTemplateService {
//TODO获取报告 //TODO获取报告
} }
case NoticeConstants.TaskType.BUG_TASK -> { case NoticeConstants.TaskType.BUG_TASK -> {
Field[] allFields = FieldUtils.getAllFields(BugNoticeDTO.class); Field[] allFields = FieldUtils.getAllFields(BugMessageDTO.class);
addOptionDto(messageTemplateFieldDTOList, allFields, null); addOptionDto(messageTemplateFieldDTOList, allFields, null);
addCustomFiled(messageTemplateFieldDTOList, projectId, TemplateScene.BUG.toString()); addCustomFiled(messageTemplateFieldDTOList, projectId, TemplateScene.BUG.toString());
//TODO获取报告 //TODO获取报告
} }
case NoticeConstants.TaskType.BUG_SYNC_TASK -> {
Field[] allFields = FieldUtils.getAllFields(BugSyncNoticeDTO.class);
addOptionDto(messageTemplateFieldDTOList, allFields, null);
//TODO获取报告
}
case NoticeConstants.TaskType.UI_SCENARIO_TASK -> { case NoticeConstants.TaskType.UI_SCENARIO_TASK -> {
Field[] allFields = FieldUtils.getAllFields(UiScenario.class); Field[] allFields = FieldUtils.getAllFields(UiScenario.class);
addOptionDto(messageTemplateFieldDTOList, allFields, "ui_"); addOptionDto(messageTemplateFieldDTOList, allFields, "ui_");
@ -120,7 +128,13 @@ public class NoticeTemplateService {
for (CustomField customField : customFields) { for (CustomField customField : customFields) {
MessageTemplateFieldDTO messageTemplateFieldDTO = new MessageTemplateFieldDTO(); MessageTemplateFieldDTO messageTemplateFieldDTO = new MessageTemplateFieldDTO();
messageTemplateFieldDTO.setId(customField.getName()); messageTemplateFieldDTO.setId(customField.getName());
messageTemplateFieldDTO.setName(StringUtils.isBlank(customField.getRemark()) ? "-" : customField.getRemark()); if (StringUtils.equalsAnyIgnoreCase(customField.getName(),
DefaultBugCustomField.DEGREE.getName(), DefaultFunctionalCustomField.PRIORITY.getName())) {
// 缺陷严重程度, 用例等级 作为系统内置的自定义字段需要国际化后在模板展示
messageTemplateFieldDTO.setName(Translator.get("custom_field." + customField.getName()));
} else {
messageTemplateFieldDTO.setName(customField.getName());
}
messageTemplateFieldDTO.setFieldSource(NoticeConstants.FieldSource.CUSTOM_FIELD); messageTemplateFieldDTO.setFieldSource(NoticeConstants.FieldSource.CUSTOM_FIELD);
messageTemplateFieldDTOS.add(messageTemplateFieldDTO); messageTemplateFieldDTOS.add(messageTemplateFieldDTO);
} }

View File

@ -0,0 +1,44 @@
package io.metersphere.system.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BugMessageDTO {
@Schema(description ="message.domain.bug_num")
private String id;
@Schema(description ="message.domain.bug_title")
private String title;
@Schema(description ="message.domain.bug_handleUser")
private String handleUser;
@Schema(description ="message.domain.bug_status")
private String status;
@Schema(description = "message.domain.bug_createUser")
private String createUser;
@Schema(description = "message.domain.bug_updateUser")
private String updateUser;
@Schema(description = "message.domain.bug_deleteUser")
private String deleteUser;
@Schema(description = "message.domain.bug_createTime")
private Long createTime;
@Schema(description = "message.domain.bug_updateTime")
private Long updateTime;
@Schema(description = "message.domain.bug_deleteTime")
private Long deleteTime;
}

View File

@ -3,47 +3,15 @@ package io.metersphere.system.dto;
import io.metersphere.system.dto.sdk.OptionDTO; import io.metersphere.system.dto.sdk.OptionDTO;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
@Data @Data
@Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class BugNoticeDTO { public class BugNoticeDTO extends BugMessageDTO{
@Schema(description ="message.domain.bug_num")
private String id;
@Schema(description ="message.domain.bug_title")
private String title;
@Schema(description ="message.domain.bug_handleUser")
private String handleUser;
@Schema(description ="message.domain.bug_status")
private String status;
@Schema(description = "message.domain.bug_createUser")
private String createUser;
@Schema(description = "message.domain.bug_updateUser")
private String updateUser;
@Schema(description = "message.domain.bug_deleteUser")
private String deleteUser;
@Schema(description = "message.domain.bug_createTime")
private Long createTime;
@Schema(description = "message.domain.bug_updateTime")
private Long updateTime;
@Schema(description = "message.domain.bug_deleteTime")
private Long deleteTime;
@Schema(description = "自定义字段内容") @Schema(description = "自定义字段内容")
private List<OptionDTO> customFields; private List<OptionDTO> customFields;

View File

@ -1,47 +1,20 @@
package io.metersphere.system.dto; package io.metersphere.system.dto;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class BugSyncNoticeDTO { public class BugSyncNoticeDTO {
@Schema(description ="message.domain.bug_title") @Schema(description ="message.domain.bug_sync_platform")
private String title; private String platform;
@Schema(description ="message.domain.bug_handleUser") @Schema(description ="message.domain.bug_sync_total_count")
private String handleUser; private Integer total;
@Schema(description ="message.domain.bug_status")
private String status;
@Schema(description = "message.domain.bug_createUser")
private String createUser;
@Schema(description = "message.domain.bug_updateUser")
private String updateUser;
@Schema(description = "message.domain.bug_deleteUser")
private String deleteUser;
@Schema(description = "message.domain.bug_createTime")
private Long createTime;
@Schema(description = "message.domain.bug_updateTime")
private Long updateTime;
@Schema(description = "message.domain.bug_deleteTime")
private Long deleteTime;
@Schema(description = "自定义字段内容")
private List<OptionDTO> customFields;
} }

View File

@ -6,7 +6,7 @@ import io.metersphere.load.domain.LoadTest;
import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.TestPlan;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.Schedule; import io.metersphere.system.domain.Schedule;
import io.metersphere.system.dto.BugNoticeDTO; import io.metersphere.system.dto.BugMessageDTO;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO; import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO; import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.notice.constants.NoticeConstants;
@ -153,7 +153,7 @@ public class MessageTemplateUtils {
allFields = FieldUtils.getAllFields(FunctionalCaseMessageDTO.class); allFields = FieldUtils.getAllFields(FunctionalCaseMessageDTO.class);
} }
case NoticeConstants.TaskType.BUG_TASK -> { case NoticeConstants.TaskType.BUG_TASK -> {
allFields = FieldUtils.getAllFields(BugNoticeDTO.class); allFields = FieldUtils.getAllFields(BugMessageDTO.class);
} }
case NoticeConstants.TaskType.UI_SCENARIO_TASK -> { case NoticeConstants.TaskType.UI_SCENARIO_TASK -> {
allFields = FieldUtils.getAllFields(UiScenario.class); allFields = FieldUtils.getAllFields(UiScenario.class);

View File

@ -4,8 +4,8 @@ import io.metersphere.project.domain.*;
import io.metersphere.project.mapper.MessageTaskBlobMapper; import io.metersphere.project.mapper.MessageTaskBlobMapper;
import io.metersphere.project.mapper.MessageTaskMapper; import io.metersphere.project.mapper.MessageTaskMapper;
import io.metersphere.project.mapper.ProjectRobotMapper; import io.metersphere.project.mapper.ProjectRobotMapper;
import io.metersphere.system.notice.MessageDetail;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.notice.MessageDetail;
import io.metersphere.system.notice.utils.MessageTemplateUtils; import io.metersphere.system.notice.utils.MessageTemplateUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
@ -13,7 +13,10 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**

View File

@ -62,5 +62,4 @@ declare global {
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
} }

View File

@ -29,6 +29,7 @@
:detail-id="props.detailId" :detail-id="props.detailId"
:detail-index="props.detailIndex" :detail-index="props.detailIndex"
:table-data="props.tableData" :table-data="props.tableData"
@loading-detail="setDetailLoading"
@loaded="handleDetailLoaded" @loaded="handleDetailLoaded"
/> />
<div class="ml-auto flex items-center"> <div class="ml-auto flex items-center">
@ -59,7 +60,7 @@
getDetailFunc: (id: string) => Promise<any>; // getDetailFunc: (id: string) => Promise<any>; //
}>(); }>();
const emit = defineEmits(['update:visible', 'loaded']); const emit = defineEmits(['update:visible', 'loaded', 'loadingDetail']);
const prevNextButtonRef = ref<InstanceType<typeof MsPrevNextButton>>(); const prevNextButtonRef = ref<InstanceType<typeof MsPrevNextButton>>();
@ -99,6 +100,10 @@
emit('loaded', val); emit('loaded', val);
} }
function setDetailLoading() {
emit('loadingDetail');
}
watch( watch(
() => innerVisible.value, () => innerVisible.value,
(val) => { (val) => {
@ -128,4 +133,4 @@
}); });
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -52,7 +52,7 @@
getDetailFunc: (id: string) => Promise<any>; // getDetailFunc: (id: string) => Promise<any>; //
}>(); }>();
const emit = defineEmits(['update:loading', 'loaded']); const emit = defineEmits(['update:loading', 'loaded', 'loadingDetail']);
const { t } = useI18n(); const { t } = useI18n();
@ -94,6 +94,7 @@
async function initDetail() { async function initDetail() {
try { try {
innerLoading.value = true; innerLoading.value = true;
emit('loadingDetail');
const res = await props.getDetailFunc(activeDetailId.value); const res = await props.getDetailFunc(activeDetailId.value);
emit('loaded', res); emit('loaded', res);
} catch (error) { } catch (error) {
@ -171,4 +172,4 @@
}); });
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -225,6 +225,18 @@
tooltip: item.tooltip, tooltip: item.tooltip,
}, },
}; };
if (ruleItem.type === 'input') {
// input emit emit:['change', 'blur'],
ruleItem.on = {
blur: () => {
// value
if (item.value !== fApi.value.getValue(item.name)) {
fApi.value.validateField(item.name);
emit('change', fApi.value.getValue(item.name), ruleItem, fApi.value);
}
},
};
}
// placeholder, placeholder // placeholder, placeholder
if (item.platformPlaceHolder) { if (item.platformPlaceHolder) {
ruleItem.props.placeholder = item.platformPlaceHolder; ruleItem.props.placeholder = item.platformPlaceHolder;
@ -256,6 +268,15 @@
} }
} }
function changeHandler(value: any, defaultValue: any, formRuleItem: FormRuleItem, api: any) {
if (formRuleItem.type === 'input') {
//
return;
}
fApi.value.validateField(value);
emit('change', defaultValue, formRuleItem, api);
}
function getControlFormItems() { function getControlFormItems() {
const convertedData = formItems.value.map((item: FormItem) => convertItem(item)); const convertedData = formItems.value.map((item: FormItem) => convertItem(item));
formRuleList.value = convertedData; formRuleList.value = convertedData;
@ -270,7 +291,6 @@
} }
function setValue() { function setValue() {
nextTick(() => { nextTick(() => {
console.log(props.formRule);
const tempObj: Record<string, any> = {}; const tempObj: Record<string, any> = {};
props.formRule.forEach((item) => { props.formRule.forEach((item) => {
tempObj[item.name] = item.value; tempObj[item.name] = item.value;
@ -306,11 +326,6 @@
}, },
}; };
function changeHandler(value: any, defaultValue: any, formRuleItem: FormRuleItem, api: any) {
fApi.value.validateField(value);
emit('change', defaultValue, formRuleItem, api);
}
function handleMounted() { function handleMounted() {
// setValue(); // setValue();
emit('mounted'); emit('mounted');
@ -339,4 +354,4 @@
}); });
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -56,7 +56,7 @@
v-if="item.status === UploadStatus.init" v-if="item.status === UploadStatus.init"
class="text-[12px] leading-[16px] text-[var(--color-text-4)]" class="text-[12px] leading-[16px] text-[var(--color-text-4)]"
> >
{{ t('ms.upload.waiting_save') }} {{ initFileSaveTips ? initFileSaveTips : t('ms.upload.waiting') }}
</div> </div>
<div <div
v-else-if="item.status === UploadStatus.done" v-else-if="item.status === UploadStatus.done"
@ -76,8 +76,8 @@
}} }}
</div> </div>
</a-tooltip> </a-tooltip>
<div v-if="showUploadSuccess(item)" class="flex items-center"> <div v-if="showUploadSuccess(item)" class="ml-4 flex items-center">
<MsIcon type="icon-icon_succeed_colorful" /> <MsIcon type="icon-icon_succeed_colorful" class="mr-2" />
{{ t('ms.upload.uploadSuccess') }} {{ t('ms.upload.uploadSuccess') }}
</div> </div>
</div> </div>
@ -185,6 +185,7 @@
showDelete?: boolean; // showDelete?: boolean; //
handleView?: (item: MsFileItem) => void; // handleView?: (item: MsFileItem) => void; //
showUploadTypeDesc?: boolean; // & showUploadTypeDesc?: boolean; // &
initFileSaveTips?: string; //
}>(), }>(),
{ {
mode: 'remote', mode: 'remote',
@ -211,6 +212,7 @@
watch( watch(
() => props.fileList, () => props.fileList,
(val) => { (val) => {
console.log(props.initFileSaveTips);
innerFileList.value = val.sort((a, b) => { innerFileList.value = val.sort((a, b) => {
if (a.status === UploadStatus.init && b.status !== UploadStatus.init) { if (a.status === UploadStatus.init && b.status !== UploadStatus.init) {
return -1; // "init" return -1; // "init"
@ -381,4 +383,4 @@
justify-content: space-between; justify-content: space-between;
padding-bottom: 4px; padding-bottom: 4px;
} }
</style> </style>

View File

@ -15,6 +15,7 @@
show-full-screen show-full-screen
unmount-on-close unmount-on-close
:mask="false" :mask="false"
@loading-detail="setDetailLoading"
@loaded="loadedBug" @loaded="loadedBug"
> >
<template #titleLeft> <template #titleLeft>
@ -90,113 +91,115 @@
</div> </div>
</template> </template>
<template #default="{ loading }"> <template #default="{ loading }">
<div ref="wrapperRef" class="h-full bg-white"> <a-spin :loading="detailLoading" class="w-full">
<MsSplitBox <div ref="wrapperRef" class="h-full bg-white">
ref="wrapperRef" <MsSplitBox
expand-direction="right" ref="wrapperRef"
:max="0.7" expand-direction="right"
:min="0.7" :max="0.7"
:size="900" :min="0.7"
:class="{ 'left-bug-detail': activeTab === 'comment' }" :size="900"
> :class="{ 'left-bug-detail': activeTab === 'comment' }"
<template #first> >
<div class="leftWrapper h-full"> <template #first>
<div class="header h-[50px]"> <div class="leftWrapper h-full">
<MsTab <div class="header h-[50px]">
v-model:active-key="activeTab" <MsTab
:content-tab-list="contentTabList" v-model:active-key="activeTab"
:get-text-func="getTabBadge" :content-tab-list="contentTabList"
class="no-content relative mb-[8px]" :get-text-func="getTabBadge"
/> class="no-content relative mb-[8px]"
<div class="tab-pane-container">
<BugDetailTab
v-if="activeTab === 'detail'"
ref="bugDetailTabRef"
:form-item="formItem"
:allow-edit="hasAnyPermission(['PROJECT_BUG:READ+UPDATE'])"
:detail-info="detailInfo"
:is-platform-default-template="isPlatformDefaultTemplate"
:platform-system-fields="platformSystemFields"
:current-platform="props.currentPlatform"
@update-success="updateSuccess"
/> />
<div class="tab-pane-container">
<BugDetailTab
v-if="activeTab === 'detail'"
ref="bugDetailTabRef"
:form-item="formItem"
:allow-edit="hasAnyPermission(['PROJECT_BUG:READ+UPDATE'])"
:detail-info="detailInfo"
:is-platform-default-template="isPlatformDefaultTemplate"
:platform-system-fields="platformSystemFields"
:current-platform="props.currentPlatform"
@update-success="updateSuccess"
/>
<BugCaseTab <BugCaseTab
v-else-if="activeTab === 'case'" v-else-if="activeTab === 'case'"
:bug-id="detailInfo.id" :bug-id="detailInfo.id"
@update-case-success="updateSuccess" @update-case-success="updateSuccess"
/> />
<CommentTab v-else-if="activeTab === 'comment'" ref="commentRef" :bug-id="detailInfo.id" /> <CommentTab v-else-if="activeTab === 'comment'" ref="commentRef" :bug-id="detailInfo.id" />
<BugHistoryTab v-else-if="activeTab === 'history'" :bug-id="detailInfo.id" /> <BugHistoryTab v-else-if="activeTab === 'history'" :bug-id="detailInfo.id" />
</div>
</div> </div>
</div> </div>
</div> </template>
</template> <template #second>
<template #second> <a-spin :loading="rightLoading" class="w-full">
<a-spin :loading="rightLoading" class="w-full"> <!-- 所属平台一致, 详情展示 -->
<!-- 所属平台一致, 详情展示 --> <div v-if="props.currentPlatform === detailInfo.platform" class="rightWrapper p-[24px]">
<div v-if="props.currentPlatform === detailInfo.platform" class="rightWrapper p-[24px]"> <!-- 自定义字段开始 -->
<!-- 自定义字段开始 --> <div class="inline-block w-full break-words">
<div class="inline-block w-full break-words"> <a-skeleton v-if="loading" class="w-full" :loading="loading" :animation="true">
<a-skeleton v-if="loading" class="w-full" :loading="loading" :animation="true"> <a-space direction="vertical" class="w-[100%]" size="large">
<a-space direction="vertical" class="w-[100%]" size="large"> <a-skeleton-line :rows="14" :line-height="30" :line-spacing="30" />
<a-skeleton-line :rows="14" :line-height="30" :line-spacing="30" /> </a-space>
</a-space> </a-skeleton>
</a-skeleton> <div v-if="!loading" class="mb-4 font-medium">
<div v-if="!loading" class="mb-4 font-medium"> <strong>
<strong> {{ t('bugManagement.detail.basicInfo') }}
{{ t('bugManagement.detail.basicInfo') }} </strong>
</strong> </div>
</div> <MsFormCreate
<MsFormCreate v-if="!loading"
v-if="!loading" ref="formCreateRef"
ref="formCreateRef" v-model:form-item="formItem"
v-model:form-item="formItem" v-model:api="fApi"
v-model:api="fApi" :form-rule="formRules"
:form-rule="formRules" class="w-full"
class="w-full" :option="options"
:option="options" @change="handelFormCreateChange"
@change="handelFormCreateChange" />
/> <!-- 自定义字段结束 -->
<!-- 自定义字段结束 --> <div
<div v-if="!isPlatformDefaultTemplate && hasAnyPermission(['PROJECT_BUG:READ+UPDATE']) && !loading"
v-if="!isPlatformDefaultTemplate && hasAnyPermission(['PROJECT_BUG:READ+UPDATE']) && !loading" class="baseItem"
class="baseItem"
>
<a-form
:model="{}"
:label-col-props="{
span: 9,
}"
:wrapper-col-props="{
span: 15,
}"
label-align="left"
content-class="tags-class"
> >
<a-form-item field="tags" :label="t('system.orgTemplate.tags')"> <a-form
<MsTagsInput :model="{}"
v-model:model-value="tags" :label-col-props="{
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+UPDATE'])" span: 9,
@blur="changeTag" }"
/> :wrapper-col-props="{
</a-form-item> span: 15,
</a-form> }"
label-align="left"
content-class="tags-class"
>
<a-form-item field="tags" :label="t('system.orgTemplate.tags')">
<MsTagsInput
v-model:model-value="tags"
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+UPDATE'])"
@blur="changeTag"
/>
</a-form-item>
</a-form>
</div>
</div> </div>
</div>
<!-- 内置基础信息结束 --> <!-- 内置基础信息结束 -->
</div> </div>
<!-- 所属平台不一致, 详情不展示, 展示空面板 --> <!-- 所属平台不一致, 详情不展示, 展示空面板 -->
<div v-else> <div v-else>
<a-empty> {{ $t('messageBox.noContent') }} </a-empty> <a-empty> {{ $t('messageBox.noContent') }} </a-empty>
</div> </div>
</a-spin> </a-spin>
</template> </template>
</MsSplitBox> </MsSplitBox>
</div> </div>
</a-spin>
<CommentInput <CommentInput
v-if="activeTab === 'comment' && hasAnyPermission(['PROJECT_BUG:READ+COMMENT'])" v-if="activeTab === 'comment' && hasAnyPermission(['PROJECT_BUG:READ+COMMENT'])"
:content="commentContent" :content="commentContent"
@ -241,7 +244,7 @@
followBug, followBug,
getBugDetail, getBugDetail,
getTemplateById, getTemplateById,
} from '@/api/modules/bug-management/index'; } from '@/api/modules/bug-management';
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management'; import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
@ -251,8 +254,7 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { CustomFieldItem } from '@/models/bug-management'; import type { CustomFieldItem } from '@/models/bug-management';
import { BugEditCustomField, BugEditFormObject, BugTemplateRequest } from '@/models/bug-management'; import { BugEditCustomField, BugEditFormObject } from '@/models/bug-management';
import { SelectValue } from '@/models/projectManagement/menuManagement';
import { RouteEnum } from '@/enums/routeEnum'; import { RouteEnum } from '@/enums/routeEnum';
const router = useRouter(); const router = useRouter();
@ -290,7 +292,7 @@
const bugDetailTabRef = ref(); const bugDetailTabRef = ref();
const isPlatformDefaultTemplate = ref(false); const isPlatformDefaultTemplate = ref(false);
const rightLoading = ref(false); const rightLoading = ref(false);
const rowLength = ref<number>(0); const detailLoading = ref(false);
const activeTab = ref<string>('detail'); const activeTab = ref<string>('detail');
const detailInfo = ref<Record<string, any>>({ match: [] }); // loadBug const detailInfo = ref<Record<string, any>>({ match: [] }); // loadBug
@ -332,53 +334,33 @@
}); });
} }
}; };
const currentCustomFields = ref<CustomFieldItem[]>([]); const currentCustomFields = ref<CustomFieldItem[]>([]);
const templateChange = async (v: SelectValue, valueObj: BugEditFormObject, request: BugTemplateRequest) => {
if (v) {
try {
const res = await getTemplateById({
projectId: appStore.currentProjectId,
id: v,
fromStatusId: request.fromStatusId,
platformBugKey: request.platformBugKey,
});
platformSystemFields.value = res.customFields.filter((field) => field.platformSystemField);
platformSystemFields.value.forEach((item) => {
item.defaultValue = valueObj[item.fieldId];
});
getFormRules(
res.customFields.filter((field) => !field.platformSystemField),
valueObj
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
};
const getOptionFromTemplate = (field: CustomFieldItem | undefined) => { const getOptionFromTemplate = (field: CustomFieldItem | undefined) => {
if (field) { if (field) {
return field.options ? field.options : JSON.parse(field.platformOptionJson); return field.options ? field.options : JSON.parse(field.platformOptionJson);
} }
return []; return [];
}; };
async function loadedBug(detail: BugEditFormObject) { async function loadedBug(detail: BugEditFormObject) {
detailInfo.value = { ...detail };
const { templateId } = detailInfo.value;
// //
isPlatformDefaultTemplate.value = detail.platformDefault; isPlatformDefaultTemplate.value = detail.platformDefault;
// TAG // loading
tags.value = detail.tags || []; detailLoading.value = false;
caseCount.value = detailInfo.value.linkCaseCount;
const tmpObj = { status: detailInfo.value.status };
//
const customFieldsRes = await getTemplateById({ const customFieldsRes = await getTemplateById({
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
id: templateId, id: detail.templateId,
fromStatusId: detail.status, fromStatusId: detail.status,
platformBugKey: detail.platformBugId, platformBugKey: detail.platformBugId,
}); });
// , TAG
detailInfo.value = { ...detail };
tags.value = detail.tags || [];
caseCount.value = detailInfo.value.linkCaseCount;
const tmpObj = { status: detailInfo.value.status };
platformSystemFields.value = customFieldsRes.customFields.filter((field) => field.platformSystemField);
currentCustomFields.value = customFieldsRes.customFields || []; currentCustomFields.value = customFieldsRes.customFields || [];
if (detailInfo.value.customFields && Array.isArray(detailInfo.value.customFields)) { if (detailInfo.value.customFields && Array.isArray(detailInfo.value.customFields)) {
const MULTIPLE_TYPE = ['MULTIPLE_SELECT', 'MULTIPLE_INPUT', 'CHECKBOX', 'MULTIPLE_MEMBER']; const MULTIPLE_TYPE = ['MULTIPLE_SELECT', 'MULTIPLE_INPUT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
@ -392,8 +374,7 @@
const optionsIds = (multipleOptions || []).map((e: any) => e.value); const optionsIds = (multipleOptions || []).map((e: any) => e.value);
if (item.value) { if (item.value) {
if (item.type !== 'MULTIPLE_INPUT') { if (item.type !== 'MULTIPLE_INPUT') {
const currentDefaultValue = optionsIds.filter((e: any) => JSON.parse(item.value).includes(e)); tmpObj[item.id] = optionsIds.filter((e: any) => JSON.parse(item.value).includes(e));
tmpObj[item.id] = currentDefaultValue;
} else { } else {
tmpObj[item.id] = JSON.parse(item.value); tmpObj[item.id] = JSON.parse(item.value);
} }
@ -411,16 +392,29 @@
); );
// //
const optionsIds = (multipleOptions || []).map((e: any) => e.value); const optionsIds = (multipleOptions || []).map((e: any) => e.value);
const currentDefaultValue = optionsIds.find((e: any) => item.value === e) || ''; tmpObj[item.id] = optionsIds.find((e: any) => item.value === e) || '';
tmpObj[item.id] = currentDefaultValue;
} else { } else {
tmpObj[item.id] = item.value; tmpObj[item.id] = item.value;
} }
}); });
} }
// //
await templateChange(templateId, tmpObj, { platformBugKey: detail.platformBugId, fromStatusId: detail.status }); platformSystemFields.value.forEach((item) => {
item.defaultValue = tmpObj[item.fieldId];
});
getFormRules(
customFieldsRes.customFields.filter((field) => !field.platformSystemField),
tmpObj
);
} }
/**
* 详情加载中
*/
function setDetailLoading() {
detailLoading.value = true;
}
/** /**
* 获取 tab 的参数数量徽标 * 获取 tab 的参数数量徽标
*/ */
@ -443,7 +437,6 @@
function updateSuccess() { function updateSuccess() {
rightLoading.value = false; rightLoading.value = false;
detailDrawerRef.value?.initDetail();
emit('submit'); emit('submit');
} }
@ -774,4 +767,4 @@
// width: 100%; // width: 100%;
// word-wrap: break-word; // word-wrap: break-word;
//} //}
</style> </style>

View File

@ -84,6 +84,7 @@
:upload-func="uploadOrAssociationFile" :upload-func="uploadOrAssociationFile"
:handle-delete="deleteFileHandler" :handle-delete="deleteFileHandler"
:show-delete="props.allowEdit" :show-delete="props.allowEdit"
:init-file-save-tips="t('ms.upload.waiting_save')"
@finish="uploadFileOver" @finish="uploadFileOver"
> >
<template #actions="{ item }"> <template #actions="{ item }">
@ -570,4 +571,4 @@
:deep(.arco-form-item-label) { :deep(.arco-form-item-label) {
font-weight: bold !important; font-weight: bold !important;
} }
</style> </style>

View File

@ -76,7 +76,12 @@
</div> </div>
</div> </div>
</a-form-item> </a-form-item>
<MsFileList ref="fileListRef" v-model:file-list="fileList" mode="static"> <MsFileList
ref="fileListRef"
v-model:file-list="fileList"
:init-file-save-tips="t('ms.upload.waiting_save')"
mode="static"
>
<template #actions="{ item }"> <template #actions="{ item }">
<!-- 本地文件 --> <!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap"> <div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
@ -826,4 +831,4 @@
font-size: 14px; font-size: 14px;
color: var(--color-text-4); color: var(--color-text-4);
} }
</style> </style>

View File

@ -35,6 +35,7 @@ export function convertToFileByBug(fileInfo: AssociatedList): MsFileItem {
isCopyFlag, isCopyFlag,
associateId: refId, associateId: refId,
createUserName, createUserName,
createTime,
uploadedTime: createTime, uploadedTime: createTime,
}; };
} }

View File

@ -84,7 +84,13 @@
</a-form> </a-form>
<!-- 文件列表开始 --> <!-- 文件列表开始 -->
<div class="w-[90%]"> <div class="w-[90%]">
<MsFileList ref="fileListRef" v-model:file-list="fileList" mode="static" :show-upload-type-desc="true"> <MsFileList
ref="fileListRef"
v-model:file-list="fileList"
mode="static"
:init-file-save-tips="t('ms.upload.waiting_save')"
:show-upload-type-desc="true"
>
<template #actions="{ item }"> <template #actions="{ item }">
<!-- 本地文件 --> <!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap"> <div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
@ -283,8 +289,6 @@
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { downloadByteFile, getGenerateId } from '@/utils'; import { downloadByteFile, getGenerateId } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { hasAnyPermission } from '@/utils/permission';
import type { import type {
AssociatedList, AssociatedList,
@ -842,4 +846,4 @@
color: var(--color-text-4); color: var(--color-text-4);
background: var(--color-text-n8); background: var(--color-text-n8);
} }
</style> </style>

View File

@ -54,8 +54,8 @@
<a-tooltip position="tl" :content-style="{ maxWidth: '500px' }"> <a-tooltip position="tl" :content-style="{ maxWidth: '500px' }">
<template #content> <template #content>
<div class="flex flex-col"> <div class="flex flex-col">
<div>{{ t('project.menu.defect.enableTip') }}</div> <div>{{ t('project.menu.demand.enableTip') }}</div>
<div class="flex flex-nowrap">{{ t('project.menu.defect.closeTip') }}</div> <div class="flex flex-nowrap">{{ t('project.menu.demand.closeTip') }}</div>
</div> </div>
</template> </template>
<div> <div>
@ -216,4 +216,4 @@
} }
} }
); );
</script> </script>

View File

@ -74,5 +74,7 @@ export default {
'Turn on: The defects created by the platform are synced to the third-party project management platform', 'Turn on: The defects created by the platform are synced to the third-party project management platform',
'project.menu.defect.closeTip': 'project.menu.defect.closeTip':
'Turn off: The defects created by the platform cannot be synced to the third-party project management platform', 'Turn off: The defects created by the platform cannot be synced to the third-party project management platform',
'project.menu.demand.enableTip': 'On: functional cases can relate to third-party demands',
'project.menu.demand.closeTip': 'Off: functional cases cannot relate to third party demands',
'project.menu.defect.customLabel': 'Custom Frequency', 'project.menu.defect.customLabel': 'Custom Frequency',
}; };

View File

@ -67,6 +67,8 @@ export default {
// 同步缺陷 // 同步缺陷
'project.menu.defect.enableTip': '开启:平台创建的缺陷同步至第三方项目管理平台', 'project.menu.defect.enableTip': '开启:平台创建的缺陷同步至第三方项目管理平台',
'project.menu.defect.closeTip': '关闭:平台创建的缺陷则无法同步至第三方项目管理平台', 'project.menu.defect.closeTip': '关闭:平台创建的缺陷则无法同步至第三方项目管理平台',
'project.menu.demand.enableTip': '开启:平台创建的用例可关联第三方需求',
'project.menu.demand.closeTip': '关闭:平台创建的用例无法关联第三方的需求',
'project.menu.defect.customLabel': '自定义频率', 'project.menu.defect.customLabel': '自定义频率',
'project.menu.defect.enableAfterConfig': '配置第三方信息后可开启', 'project.menu.defect.enableAfterConfig': '配置第三方信息后可开启',
// 误报规则 // 误报规则