feat(缺陷管理): 支持Jira默认模板

--bug=1036246 --user=宋昌昌 【缺陷管理】使用JIRA默认模板创建缺陷失败 https://www.tapd.cn/55049933/s/1473618
This commit is contained in:
song-cc-rock 2024-03-12 21:00:36 +08:00 committed by 刘瑞斌
parent effb35b4e1
commit c5602b2033
26 changed files with 341 additions and 121 deletions

View File

@ -13,4 +13,6 @@ public class PlatformCustomFieldItemDTO extends PlatformCustomFieldDTO {
private String defaultValue; private String defaultValue;
private Boolean supportSearch; private Boolean supportSearch;
private String searchMethod; private String searchMethod;
private String placeHolder;
private Boolean systemField;
} }

View File

@ -8,70 +8,69 @@ public enum PlatformCustomFieldType {
/** /**
* 输入框 * 输入框
*/ */
INPUT(false, "input"), INPUT(false),
/** /**
* 文本框 * 文本框
*/ */
TEXTAREA(false, "textarea"), TEXTAREA(false),
/** /**
* 单选下拉框框 * 单选下拉框框
*/ */
SELECT(true, "select"), SELECT(true),
/** /**
* 多选下拉框框 * 多选下拉框框
*/ */
MULTIPLE_SELECT(true, "multipleSelect"), MULTIPLE_SELECT(true),
/** /**
* 单选框 * 单选框
*/ */
RADIO(true, "radio"), RADIO(true),
/** /**
* 复选框 * 复选框
*/ */
CHECKBOX(true, "checkbox"), CHECKBOX(true),
/** /**
* 单选成员 * 单选成员
*/ */
MEMBER(true, "member"), MEMBER(true),
/** /**
* 多选成员 * 多选成员
*/ */
MULTIPLE_MEMBER(true, "multipleMember"), MULTIPLE_MEMBER(true),
/** /**
* 日期 * 日期
*/ */
DATE(false, "date"), DATE(false),
/** /**
* 日期时间 * 日期时间
*/ */
DATETIME(false, "datetime"), DATETIME(false),
/** /**
* 整型 * 整型
*/ */
INT(false, "int"), INT(false),
/** /**
* 浮点型 * 浮点型
*/ */
FLOAT(false, "float"), FLOAT(false),
/** /**
* 多值输入框标签输入框 * 多值输入框标签输入框
*/ */
MULTIPLE_INPUT(false, "multipleInput"), MULTIPLE_INPUT(false),
// (第三方平台单独的自定义类型)
/** /**
* 级联选择 * 级联选择
*/ */
CASCADE_SELECT(true, "cascadingSelect"), CASCADER(true),
/** /**
* 富文本 * 富文本
*/ */
RICH_TEXT(false, "richText"); RICH_TEXT(false);
private final Boolean hasOption; private final Boolean hasOption;
private final String type; PlatformCustomFieldType(Boolean hasOption) {
PlatformCustomFieldType(Boolean hasOption, String type) {
this.hasOption = hasOption; this.hasOption = hasOption;
this.type = type;
} }
} }

View File

@ -8,73 +8,65 @@ public enum CustomFieldType {
/** /**
* 输入框 * 输入框
*/ */
INPUT(false, "input"), INPUT(false),
/** /**
* 文本框 * 文本框
*/ */
TEXTAREA(false, "textarea"), TEXTAREA(false),
/** /**
* 单选下拉框框 * 单选下拉框框
*/ */
SELECT(true, "select"), SELECT(true),
/** /**
* 多选下拉框框 * 多选下拉框框
*/ */
MULTIPLE_SELECT(true, "multipleSelect"), MULTIPLE_SELECT(true),
/** /**
* 单选框 * 单选框
*/ */
RADIO(true, "radio"), RADIO(true),
/** /**
* 复选框 * 复选框
*/ */
CHECKBOX(true, "checkbox"), CHECKBOX(true),
/** /**
* 单选成员 * 单选成员
*/ */
MEMBER(true, "member"), MEMBER(true),
/** /**
* 多选成员 * 多选成员
*/ */
MULTIPLE_MEMBER(true, "multipleMember"), MULTIPLE_MEMBER(true),
/** /**
* 日期 * 日期
*/ */
DATE(false, "date"), DATE(false),
/** /**
* 日期时间 * 日期时间
*/ */
DATETIME(false, "datetime"), DATETIME(false),
/** /**
* 整型 * 整型
*/ */
INT(false, "int"), INT(false),
/** /**
* 浮点型 * 浮点型
*/ */
FLOAT(false, "float"), FLOAT(false),
/** /**
* 多值输入框标签输入框 * 多值输入框标签输入框
*/ */
MULTIPLE_INPUT(false, "multipleInput"); MULTIPLE_INPUT(false);
private final Boolean hasOption; private final Boolean hasOption;
CustomFieldType(Boolean hasOption) {
private final String type;
CustomFieldType(Boolean hasOption, String type) {
this.hasOption = hasOption; this.hasOption = hasOption;
this.type = type;
} }
public Boolean getHasOption() { public Boolean getHasOption() {
return this.hasOption; return this.hasOption;
} }
public String getType() {
return this.type;
}
public static Set<String> getHasOptionValueSet() { public static Set<String> getHasOptionValueSet() {
return Arrays.stream(CustomFieldType.values()) return Arrays.stream(CustomFieldType.values())
.filter(CustomFieldType::getHasOption) .filter(CustomFieldType::getHasOption)

View File

@ -413,16 +413,16 @@
</if> </if>
select api_id from api_definition_custom_field where field_id = #{custom.id} select api_id from api_definition_custom_field where field_id = #{custom.id}
<choose> <choose>
<when test="custom.type == 'richText' or custom.type == 'textarea' or custom.operator == 'current user'"> <when test="custom.type == 'TEXTAREA' or custom.operator == 'current user'">
and `value` and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>
</include> </include>
</when> </when>
<when test="custom.type == 'multipleMember' or custom.type == 'checkbox' or custom.type == 'multipleSelect'"> <when test="custom.type == 'MULTIPLE_MEMBER' or custom.type == 'CHECKBOX' or custom.type == 'MULTIPLE_SELECT'">
and ${custom.value} and ${custom.value}
</when> </when>
<when test="custom.type == 'date' or custom.type == 'datetime'"> <when test="custom.type == 'DATE' or custom.type == 'DATETIME'">
and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>

View File

@ -266,16 +266,16 @@
</if> </if>
select api_id from api_definition_custom_field where field_id = #{custom.id} select api_id from api_definition_custom_field where field_id = #{custom.id}
<choose> <choose>
<when test="custom.type == 'richText' or custom.type == 'textarea' or custom.operator == 'current user'"> <when test="custom.type == 'TEXTAREA' or custom.operator == 'current user'">
and `value` and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>
</include> </include>
</when> </when>
<when test="custom.type == 'multipleMember' or custom.type == 'checkbox' or custom.type == 'multipleSelect'"> <when test="custom.type == 'MULTIPLE_MEMBER' or custom.type == 'CHECKBOX' or custom.type == 'MULTIPLE_SELECT'">
and ${custom.value} and ${custom.value}
</when> </when>
<when test="custom.type == 'date' or custom.type == 'datetime'"> <when test="custom.type == 'DATE' or custom.type == 'DATETIME'">
and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>

View File

@ -314,16 +314,16 @@
</if> </if>
select api_id from api_definition_custom_field where field_id = #{custom.id} select api_id from api_definition_custom_field where field_id = #{custom.id}
<choose> <choose>
<when test="custom.type == 'richText' or custom.type == 'textarea' or custom.operator == 'current user'"> <when test="custom.type == 'TEXTAREA' or custom.operator == 'current user'">
and `value` and `value`
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>
</include> </include>
</when> </when>
<when test="custom.type == 'multipleMember' or custom.type == 'checkbox' or custom.type == 'multipleSelect'"> <when test="custom.type == 'MULTIPLE_MEMBER' or custom.type == 'CHECKBOX' or custom.type == 'MULTIPLE_SELECT'">
and ${custom.value} and ${custom.value}
</when> </when>
<when test="custom.type == 'date' or custom.type == 'datetime'"> <when test="custom.type == 'DATE' or custom.type == 'DATETIME'">
and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13) and left(replace(unix_timestamp(trim(both '"' from `value`)), '.', ''), 13)
<include refid="io.metersphere.system.mapper.BaseMapper.condition"> <include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="custom"/> <property name="object" value="custom"/>

View File

@ -944,13 +944,13 @@ public class ApiDefinitionControllerTests extends BaseTest {
Map<String, Object> custom = new HashMap<>(); Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field"); custom.put("id", "test_field");
custom.put("operator", "in"); custom.put("operator", "in");
custom.put("type", "multipleSelect"); custom.put("type", "MULTIPLE_SELECT");
custom.put("value", JSON.toJSONString(List.of("test", "default"))); custom.put("value", JSON.toJSONString(List.of("test", "default")));
customs.add(custom); customs.add(custom);
Map<String, Object> currentUserCustom = new HashMap<>(); Map<String, Object> currentUserCustom = new HashMap<>();
currentUserCustom.put("id", "test_field"); currentUserCustom.put("id", "test_field");
currentUserCustom.put("operator", "current user"); currentUserCustom.put("operator", "current user");
currentUserCustom.put("type", "multipleMember"); currentUserCustom.put("type", "MULTIPLE_MEMBER");
currentUserCustom.put("value", "current user"); currentUserCustom.put("value", "current user");
customs.add(currentUserCustom); customs.add(currentUserCustom);
map.put("customs", customs); map.put("customs", customs);

View File

@ -259,9 +259,9 @@ public class BugService {
detail.setPlatformDefault(template.getPlatformDefault()); detail.setPlatformDefault(template.getPlatformDefault());
detail.setStatus(bug.getStatus()); detail.setStatus(bug.getStatus());
detail.setPlatformBugId(bug.getPlatformBugId()); detail.setPlatformBugId(bug.getPlatformBugId());
if (!detail.getPlatformDefault()) {
// 非平台默认模板 {标题, 内容, 标签, 自定义字段: 处理人, 状态}
detail.setTitle(bug.getTitle()); detail.setTitle(bug.getTitle());
if (!detail.getPlatformDefault()) {
// 非平台默认模板 {内容, 标签, 自定义字段: 处理人, 状态}
BugContent bugContent = bugContentMapper.selectByPrimaryKey(id); BugContent bugContent = bugContentMapper.selectByPrimaryKey(id);
detail.setDescription(bugContent.getDescription()); detail.setDescription(bugContent.getDescription());
detail.setTags(bug.getTags()); detail.setTags(bug.getTags());
@ -286,7 +286,7 @@ public class BugService {
} }
}); });
} else { } else {
// 平台默认模板 // 平台默认模板 {自定义字段}
allCustomFields.forEach(field -> template.getCustomFields().stream().filter(templateField -> StringUtils.equals(templateField.getFieldId(), field.getId())).findFirst().ifPresent(templateField -> { allCustomFields.forEach(field -> template.getCustomFields().stream().filter(templateField -> StringUtils.equals(templateField.getFieldId(), field.getId())).findFirst().ifPresent(templateField -> {
field.setName(templateField.getFieldName()); field.setName(templateField.getFieldName());
field.setType(templateField.getType()); field.setType(templateField.getType());
@ -697,7 +697,7 @@ public class BugService {
handleUserField.setFieldId(BugTemplateCustomField.HANDLE_USER.getId()); handleUserField.setFieldId(BugTemplateCustomField.HANDLE_USER.getId());
handleUserField.setFieldName(BugTemplateCustomField.HANDLE_USER.getName()); handleUserField.setFieldName(BugTemplateCustomField.HANDLE_USER.getName());
handleUserField.setFieldKey(BugTemplateCustomField.HANDLE_USER.getId()); handleUserField.setFieldKey(BugTemplateCustomField.HANDLE_USER.getId());
handleUserField.setType(CustomFieldType.SELECT.getType()); handleUserField.setType(CustomFieldType.SELECT.name());
List<SelectOption> localHandlerOption = bugCommonService.getLocalHandlerOption(projectId); List<SelectOption> localHandlerOption = bugCommonService.getLocalHandlerOption(projectId);
handleUserOption = localHandlerOption.stream().map(user -> { handleUserOption = localHandlerOption.stream().map(user -> {
CustomFieldOption option = new CustomFieldOption(); CustomFieldOption option = new CustomFieldOption();
@ -713,7 +713,7 @@ public class BugService {
// 成员类型的自定义字段, 选项值与处理人选项保持一致 // 成员类型的自定义字段, 选项值与处理人选项保持一致
final List<CustomFieldOption> memberOption = handleUserOption; final List<CustomFieldOption> memberOption = handleUserOption;
templateDTO.getCustomFields().forEach(field -> { templateDTO.getCustomFields().forEach(field -> {
if (StringUtils.equalsAny(field.getType(), CustomFieldType.MEMBER.getType(), CustomFieldType.MULTIPLE_MEMBER.getType())) { if (StringUtils.equalsAny(field.getType(), CustomFieldType.MEMBER.name(), CustomFieldType.MULTIPLE_MEMBER.name())) {
field.setPlatformOptionJson(JSON.toJSONString(memberOption)); field.setPlatformOptionJson(JSON.toJSONString(memberOption));
} }
}); });
@ -737,7 +737,7 @@ public class BugService {
statusField.setFieldId(BugTemplateCustomField.STATUS.getId()); statusField.setFieldId(BugTemplateCustomField.STATUS.getId());
statusField.setFieldName(BugTemplateCustomField.STATUS.getName()); statusField.setFieldName(BugTemplateCustomField.STATUS.getName());
statusField.setFieldKey(BugTemplateCustomField.STATUS.getId()); statusField.setFieldKey(BugTemplateCustomField.STATUS.getId());
statusField.setType(CustomFieldType.SELECT.getType()); statusField.setType(CustomFieldType.SELECT.name());
List<SelectOption> statusOption = bugStatusService.getToStatusItemOption(projectId, fromStatusId, platformBugKey); List<SelectOption> statusOption = bugStatusService.getToStatusItemOption(projectId, fromStatusId, platformBugKey);
List<CustomFieldOption> statusCustomOption = statusOption.stream().map(option -> { List<CustomFieldOption> statusCustomOption = statusOption.stream().map(option -> {
CustomFieldOption customFieldOption = new CustomFieldOption(); CustomFieldOption customFieldOption = new CustomFieldOption();
@ -1252,6 +1252,8 @@ public class BugService {
customField.setFieldId(platformCustomField.getId()); customField.setFieldId(platformCustomField.getId());
customField.setFieldName(platformCustomField.getName()); customField.setFieldName(platformCustomField.getName());
customField.setPlatformOptionJson(platformCustomField.getOptions()); customField.setPlatformOptionJson(platformCustomField.getOptions());
customField.setPlatformPlaceHolder(platformCustomField.getPlaceHolder());
customField.setPlatformSystemField(platformCustomField.getSystemField());
return customField; return customField;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
template.setCustomFields(customFields); template.setCustomFields(customFields);

View File

@ -588,7 +588,7 @@ public class BugControllerTests extends BaseTest {
BugCustomFieldDTO summary = new BugCustomFieldDTO(); BugCustomFieldDTO summary = new BugCustomFieldDTO();
summary.setId("summary"); summary.setId("summary");
summary.setName("摘要"); summary.setName("摘要");
summary.setType("input"); summary.setType("INPUT");
summary.setValue("这是一个系统Jira模板创建的缺陷"); summary.setValue("这是一个系统Jira模板创建的缺陷");
addRequest.getCustomFields().add(summary); addRequest.getCustomFields().add(summary);
MultiValueMap<String, Object> addParam3 = getMultiPartParam(addRequest, null); MultiValueMap<String, Object> addParam3 = getMultiPartParam(addRequest, null);
@ -667,7 +667,7 @@ public class BugControllerTests extends BaseTest {
field.setId("test_field"); field.setId("test_field");
field.setName("test"); field.setName("test");
field.setValue("test"); field.setValue("test");
field.setType("multipleSelect"); field.setType("MULTIPLE_SELECT");
bugService.transferCustomToPlatformField(null, List.of(field), true); bugService.transferCustomToPlatformField(null, List.of(field), true);
// 添加没有配置自定义映射字段的Jira缺陷 // 添加没有配置自定义映射字段的Jira缺陷
removeApiFieldTmp(); removeApiFieldTmp();
@ -694,8 +694,6 @@ public class BugControllerTests extends BaseTest {
this.requestMultipart(BUG_ADD, addParam).andExpect(status().is5xxServerError()); this.requestMultipart(BUG_ADD, addParam).andExpect(status().is5xxServerError());
// 获取禅道模板(删除默认项目模板) // 获取禅道模板(删除默认项目模板)
bugService.attachTemplateStatusField(null, null, null, null); bugService.attachTemplateStatusField(null, null, null, null);
// 获取处理人选项
this.requestGetWithOk(BUG_HEADER_COLUMNS_OPTION + "/default-project-for-bug");
// 批量删除 // 批量删除
BugBatchRequest request = new BugBatchRequest(); BugBatchRequest request = new BugBatchRequest();
request.setProjectId("default-project-for-bug"); request.setProjectId("default-project-for-bug");

View File

@ -66,7 +66,7 @@ INSERT INTO project_application (project_id, type, type_value) VALUES
INSERT INTO service_integration(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES INSERT INTO service_integration(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES
('621103810617344', 'jira', true, 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '100001'), ('621103810617344', 'jira', true, 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '100001'),
('652096294625281', 'zentao', true, 0x504B03041400080808003B939C57000000000000000000000000030000007A6970AB564A4C49294A2D2E56B252CA282929B0D2D7373437D23334D33334D03333D3AF4ACD2B49CCD757D2514A4C4ECE2FCD2B01AA4B4CC9CDCC038A1424161797E717A500859C1373F2F3D21D8C0C0C4D811245A985A5A9C525219505A940B900C7108F784F3F377FA55A00504B07081BBBB5766A0000006E000000504B010214001400080808003B939C571BBBB5766A0000006E0000000300000000000000000000000000000000007A6970504B05060000000001000100310000009B0000000000, '100001'); ('652096294625281', 'zentao', true, 0x504B030414000808080093756458000000000000000000000000030000007A6970AB564A4C49294A2D2E56B252CA282929B0D2D7373437D23334D3333230D033B3B4B230B0B050D2514A4C4ECE2FCD2B01AA4A4CC9CDCC038A1424161797E717A500859C1373F2F3D21D8C0C0C4D811245A985A5A9C525219505A940B900C7108F784F3F377FA55A00504B07088A813510680000006C000000504B01021400140008080800937564588A813510680000006C0000000300000000000000000000000000000000007A6970504B0506000000000100010031000000990000000000, '100001');

View File

@ -33,9 +33,6 @@ public class TemplateCustomFieldDTO {
@Schema(title = "选项值") @Schema(title = "选项值")
private List<CustomFieldOption> options; private List<CustomFieldOption> options;
@Schema(title = "平台选项值")
private String platformOptionJson;
@Schema(title = "是否支持搜索") @Schema(title = "是否支持搜索")
private Boolean supportSearch; private Boolean supportSearch;
@ -44,4 +41,16 @@ public class TemplateCustomFieldDTO {
@Schema(description = "是否内置字段") @Schema(description = "是否内置字段")
private Boolean internal; private Boolean internal;
/**
* 平台字段相关属性 -- start
*/
@Schema(title = "平台选项值")
private String platformOptionJson;
@Schema(description = "平台字段占位提示")
private String platformPlaceHolder;
@Schema(description = "是否平台系统字段")
private Boolean platformSystemField;
} }

View File

@ -69,8 +69,8 @@ public class CustomFieldUtils {
String fieldType = custom.get(COMBINE_CUSTOM_FIELD_TYPE).toString(); String fieldType = custom.get(COMBINE_CUSTOM_FIELD_TYPE).toString();
String fieldValue = custom.get(COMBINE_CUSTOM_FIELD_VALUE).toString(); String fieldValue = custom.get(COMBINE_CUSTOM_FIELD_VALUE).toString();
if (StringUtils.equalsAny(fieldType, CustomFieldType.MULTIPLE_MEMBER.getType(), if (StringUtils.equalsAny(fieldType, CustomFieldType.MULTIPLE_MEMBER.name(),
CustomFieldType.CHECKBOX.getType(), CustomFieldType.MULTIPLE_SELECT.getType()) && StringUtils.isNotEmpty(fieldValue)) { CustomFieldType.CHECKBOX.name(), CustomFieldType.MULTIPLE_SELECT.name()) && StringUtils.isNotEmpty(fieldValue)) {
List<String> customValues = JSON.parseArray(fieldValue, String.class); List<String> customValues = JSON.parseArray(fieldValue, String.class);
List<String> jsonValues = customValues.stream().map(item -> "JSON_CONTAINS(`value`, '[\"".concat(item).concat("\"]')")).toList(); List<String> jsonValues = customValues.stream().map(item -> "JSON_CONTAINS(`value`, '[\"".concat(item).concat("\"]')")).toList();
custom.put(COMBINE_CUSTOM_FIELD_VALUE, "(".concat(StringUtils.join(jsonValues, " OR ")).concat(")")); custom.put(COMBINE_CUSTOM_FIELD_VALUE, "(".concat(StringUtils.join(jsonValues, " OR ")).concat(")"));

View File

@ -38,13 +38,13 @@ class CustomFieldTests extends BaseTest {
Map<String, Object> custom = new HashMap<>(); Map<String, Object> custom = new HashMap<>();
custom.put("id", "test_field"); custom.put("id", "test_field");
custom.put("operator", "in"); custom.put("operator", "in");
custom.put("type", "multipleSelect"); custom.put("type", "MULTIPLE_SELECT");
custom.put("value", JSON.toJSONString(List.of("test", "default"))); custom.put("value", JSON.toJSONString(List.of("test", "default")));
customs.add(custom); customs.add(custom);
Map<String, Object> currentUserCustom = new HashMap<>(); Map<String, Object> currentUserCustom = new HashMap<>();
currentUserCustom.put("id", "test_field"); currentUserCustom.put("id", "test_field");
currentUserCustom.put("operator", "current user"); currentUserCustom.put("operator", "current user");
currentUserCustom.put("type", "multipleMember"); currentUserCustom.put("type", "MULTIPLE_MEMBER");
currentUserCustom.put("value", "current user"); currentUserCustom.put("value", "current user");
customs.add(currentUserCustom); customs.add(currentUserCustom);
currentUserCustom.put("value", ""); currentUserCustom.put("value", "");

View File

@ -180,6 +180,16 @@ export const PASSWORD = {
}, },
}; };
export const CASCADER = {
type: 'cascader',
field: 'fieldName',
title: '',
value: [],
props: {
'allow-clear': true,
},
};
export const FieldTypeFormRules: Record<string, FormRule> = { export const FieldTypeFormRules: Record<string, FormRule> = {
INPUT, INPUT,
SELECT, SELECT,
@ -196,4 +206,5 @@ export const FieldTypeFormRules: Record<string, FormRule> = {
TEXTAREA, TEXTAREA,
JIRAKEY, JIRAKEY,
PASSWORD, PASSWORD,
CASCADER,
}; };

View File

@ -16,9 +16,8 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import type { FormItem } from './types'; import { FormItem, FormRuleItem } from './types';
import { FormRuleItem } from './types'; import formCreate from '@form-create/arco-design';
import formCreate, { Rule } from '@form-create/arco-design';
defineOptions({ name: 'MsFormCreate' }); defineOptions({ name: 'MsFormCreate' });
@ -147,6 +146,20 @@
formItems.value = formItems.value.filter((item: FormItem) => !item.displayConditions?.field); formItems.value = formItems.value.filter((item: FormItem) => !item.displayConditions?.field);
} }
function mapOption(options: any[]) {
return options.map((optionsItem) => {
const mappedItem: any = {
label: optionsItem.text,
value: optionsItem.value,
};
if (optionsItem.children) {
mappedItem.children = mapOption(optionsItem.children);
}
return mappedItem;
});
}
function convertItem(item: FormItem) { function convertItem(item: FormItem) {
// //
let fieldType; let fieldType;
@ -162,12 +175,7 @@
fieldType = FieldTypeFormRules[currentTypeForm].type; fieldType = FieldTypeFormRules[currentTypeForm].type;
} }
const options = item?.options; const options = item?.options;
const currentOptions = options?.map((optionsItem: any) => { const currentOptions = mapOption(options || []);
return {
label: optionsItem.text,
value: optionsItem.value,
};
});
const ruleItem: any = { const ruleItem: any = {
type: fieldType, // type: fieldType, //
field: item.name, // field: item.name, //
@ -206,6 +214,10 @@
tooltip: item.tooltip, tooltip: item.tooltip,
}, },
}; };
// placeholder, placeholder
if (item.platformPlaceHolder) {
ruleItem.props.placeholder = item.platformPlaceHolder;
}
// namelink // namelink
if (!ruleItem.link.filter((ink: string) => ink).length) { if (!ruleItem.link.filter((ink: string) => ink).length) {
delete ruleItem.link; delete ruleItem.link;

View File

@ -18,6 +18,7 @@ export type FormItemType =
| 'FLOAT' | 'FLOAT'
| 'NUMBER' | 'NUMBER'
| 'PassWord' | 'PassWord'
| 'CASCADER'
| undefined; | undefined;
// 表单选项 // 表单选项
@ -64,6 +65,7 @@ export interface FormItem {
inputSearch?: boolean; // 是否支持远程搜索 inputSearch?: boolean; // 是否支持远程搜索
tooltip?: string; // 表单后边的提示info tooltip?: string; // 表单后边的提示info
instructionsIcon?: ''; // 是否有图片在表单后边展示 instructionsIcon?: ''; // 是否有图片在表单后边展示
platformPlaceHolder?: string; // 平台表单项占位符
optionMethod?: string; // 请求检索的方法 两个参数 表单项的所有值 optionMethod?: string; // 请求检索的方法 两个参数 表单项的所有值
options?: FormItemDefaultOptions[]; options?: FormItemDefaultOptions[];
required: boolean; required: boolean;

View File

@ -1,6 +1,6 @@
import {FormItemType} from '@/components/pure/ms-form-create/types'; import { FormItemType } from '@/components/pure/ms-form-create/types';
import {BatchApiParams} from './common'; import { BatchApiParams } from './common';
export interface BugListItem { export interface BugListItem {
id: string; // 缺陷id id: string; // 缺陷id
@ -47,6 +47,8 @@ export interface BugEditCustomField {
required: boolean; required: boolean;
isMutiple?: boolean; isMutiple?: boolean;
options?: any[]; options?: any[];
defaultValue: string;
platformSystemField: boolean;
} }
export interface BugEditFormObject { export interface BugEditFormObject {
[key: string]: any; [key: string]: any;
@ -54,7 +56,7 @@ export interface BugEditFormObject {
export interface BugEditCustomFieldItem { export interface BugEditCustomFieldItem {
id: string; id: string;
name: string; name: string;
type: string; type: string | undefined;
value: string; value: string;
} }
export type BugBatchUpdateFiledType = 'single_select' | 'multiple_select' | 'tags' | 'input' | 'user_selector' | 'date'; export type BugBatchUpdateFiledType = 'single_select' | 'multiple_select' | 'tags' | 'input' | 'user_selector' | 'date';

View File

@ -0,0 +1,29 @@
export interface Option {
label: string;
value: string;
children?: Option[];
}
// 递归函数,获取所有父级元素
export function findParents(data: Option[], targetId: string, parents: string[] = []) {
for (let i = 0; i < data.length; i++) {
const current = data[i];
if (current.value === targetId) {
// 第一层级, 直接返回
parents.push(current.value);
return parents;
}
if (current.children && current.children.length > 0) {
// 多层级, 递归查找
parents.push(current.value);
const result = findParents(current.children, targetId, parents);
if (result) {
// 子元素中有匹配的, 返回结果
return result;
}
// 子元素中没有匹配的, 队尾元素删除, 继续查找相邻元素
parents.pop();
}
}
return null;
}

View File

@ -100,6 +100,8 @@
:form-item="formItem" :form-item="formItem"
:allow-edit="true" :allow-edit="true"
:detail-info="detailInfo" :detail-info="detailInfo"
:is-platform-default-template="isPlatformDefaultTemplate"
:platform-system-fields="platformSystemFields"
@update-success="updateSuccess" @update-success="updateSuccess"
/> />
@ -118,7 +120,11 @@
</template> </template>
<template #second> <template #second>
<div class="rightWrapper p-[24px]"> <div class="rightWrapper p-[24px]">
<div class="mb-4 font-medium">{{ t('bugManagement.detail.basicInfo') }}</div> <div class="mb-4 font-medium">
<strong>
{{ t('bugManagement.detail.basicInfo') }}
</strong>
</div>
<!-- 自定义字段开始 --> <!-- 自定义字段开始 -->
<MsFormCreate <MsFormCreate
v-if="formRules.length" v-if="formRules.length"
@ -131,7 +137,8 @@
@change="handelFormCreateChange" @change="handelFormCreateChange"
/> />
<!-- 自定义字段结束 --> <!-- 自定义字段结束 -->
<div class="baseItem"> <!-- 内置基础信息开始 -->
<div v-if="!isPlatformDefaultTemplate" class="baseItem">
<a-form-item field="tags" :label="t('system.orgTemplate.tags')"> <a-form-item field="tags" :label="t('system.orgTemplate.tags')">
<MsTagsInput v-model:model-value="tags" /> <MsTagsInput v-model:model-value="tags" />
</a-form-item> </a-form-item>
@ -140,6 +147,7 @@
<!-- <MsTag v-for="item of tags" :key="item"> {{ item }} </MsTag>--> <!-- <MsTag v-for="item of tags" :key="item"> {{ item }} </MsTag>-->
<!-- </span>--> <!-- </span>-->
</div> </div>
<!-- 内置基础信息结束 -->
</div> </div>
</template> </template>
</MsSplitBox> </MsSplitBox>
@ -226,17 +234,19 @@
const currentProjectId = computed(() => appStore.currentProjectId); const currentProjectId = computed(() => appStore.currentProjectId);
const showDrawerVisible = defineModel<boolean>('visible', { default: false }); const showDrawerVisible = defineModel<boolean>('visible', { default: false });
const bugDetailTabRef = ref(); const bugDetailTabRef = ref();
const isPlatformDefaultTemplate = 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
const tags = ref([]); const tags = ref([]);
const platformSystemFields = ref<BugEditCustomField[]>([]); //
// //
const getFormRules = (arr: BugEditCustomField[], valueObj: BugEditFormObject) => { const getFormRules = (arr: BugEditCustomField[], valueObj: BugEditFormObject) => {
formRules.value = []; formRules.value = [];
if (Array.isArray(arr) && arr.length) { if (Array.isArray(arr) && arr.length) {
formRules.value = arr.map((item) => { formRules.value = arr.map((item: any) => {
return { return {
type: item.type, type: item.type,
name: item.fieldId, name: item.fieldId,
@ -244,6 +254,7 @@
value: valueObj[item.fieldId], value: valueObj[item.fieldId],
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options, options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
required: item.required as boolean, required: item.required as boolean,
platformPlaceHolder: item.platformPlaceHolder,
props: { props: {
modelValue: valueObj[item.fieldId], modelValue: valueObj[item.fieldId],
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options, options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
@ -262,7 +273,14 @@
fromStatusId: request.fromStatusId, fromStatusId: request.fromStatusId,
platformBugKey: request.platformBugKey, platformBugKey: request.platformBugKey,
}); });
getFormRules(res.customFields, valueObj); 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) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -272,16 +290,24 @@
async function loadedBug(detail: BugEditFormObject) { async function loadedBug(detail: BugEditFormObject) {
detailInfo.value = { ...detail }; detailInfo.value = { ...detail };
const { templateId } = detail; const { templateId } = detail;
// tag
//
isPlatformDefaultTemplate.value = detail.platformDefault;
// TAG
tags.value = detail.tags || []; tags.value = detail.tags || [];
caseCount.value = detail.linkCaseCount; caseCount.value = detail.linkCaseCount;
const tmpObj = {}; const tmpObj = { status: detail.status };
if (detail.customFields && Array.isArray(detail.customFields)) { if (detail.customFields && Array.isArray(detail.customFields)) {
detail.customFields.forEach((item) => { detail.customFields.forEach((item) => {
if (item.type === 'MULTIPLE_SELECT') { if (item.type === 'MULTIPLE_SELECT' || item.type === 'MULTIPLE_INPUT' || item.type === 'CHECKBOX') {
tmpObj[item.id] = JSON.parse(item.value); tmpObj[item.id] = JSON.parse(item.value);
} else if (item.type === 'INT') { } else if (item.type === 'INT') {
tmpObj[item.id] = Number(item.value); tmpObj[item.id] = Number(item.value);
} else if (item.type === 'CASCADER') {
const arr = JSON.parse(item.value);
if (arr && arr instanceof Array && arr.length > 0) {
tmpObj[item.id] = arr[arr.length - 1];
}
} else { } else {
tmpObj[item.id] = item.value; tmpObj[item.id] = item.value;
} }

View File

@ -1,7 +1,6 @@
<template> <template>
<div class="relative p-[16px] pb-[16px]"> <div class="relative p-[16px] pb-[16px]">
<div class="header"> <div class="header">
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
<div v-permission="['PROJECT_BUG:READ+UPDATE']" class="header-action"> <div v-permission="['PROJECT_BUG:READ+UPDATE']" class="header-action">
<a-button type="text" @click="contentEditAble = !contentEditAble"> <a-button type="text" @click="contentEditAble = !contentEditAble">
<template #icon> <MsIconfont type="icon-icon_edit_outlined" /> </template> <template #icon> <MsIconfont type="icon-icon_edit_outlined" /> </template>
@ -9,7 +8,10 @@
</a-button> </a-button>
</div> </div>
</div> </div>
<div class="mt-[16px]" :class="{ 'max-h-[260px]': contentEditAble }"> <!-- 左侧布局默认内容(非平台默认模板时默认展示) -->
<div v-if="!isPlatformDefaultTemplate" class="default-content">
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
<div class="mb-4 mt-[16px]" :class="{ 'max-h-[260px]': contentEditAble }">
<MsRichText <MsRichText
v-if="contentEditAble" v-if="contentEditAble"
v-model:raw="form.description" v-model:raw="form.description"
@ -26,7 +28,33 @@
{{ t('common.save') }} {{ t('common.save') }}
</a-button> </a-button>
</div> </div>
<div style="margin-top: 20px"> </div>
<!-- 特殊布局内容(平台默认模板时展示) -->
<div v-if="isPlatformDefaultTemplate" class="special-content">
<div v-for="(item, index) in platformSystemFields" :key="index">
<div v-if="item.fieldId !== 'summary'">
<h1 class="header-title">
<strong>{{ item.fieldName }}</strong>
</h1>
<div class="mb-4 mt-[16px]" :class="{ 'max-h-[260px]': contentEditAble }">
<MsRichText
v-if="contentEditAble"
v-model:raw="item.defaultValue"
:disabled="!contentEditAble"
:placeholder="t('bugManagement.edit.contentPlaceholder')"
/>
<div v-else v-dompurify-html="item?.defaultValue || '-'" class="markdown-body"></div>
</div>
</div>
</div>
<div v-if="contentEditAble" class="mt-[8px] flex justify-end">
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleSave">
{{ t('common.save') }}
</a-button>
</div>
</div>
<div class="mt-4">
<AddAttachment v-model:file-list="fileList" @link-file="associatedFile" /> <AddAttachment v-model:file-list="fileList" @link-file="associatedFile" />
</div> </div>
<MsFileList <MsFileList
@ -180,8 +208,9 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { downloadByteFile, sleep } from '@/utils'; import { downloadByteFile, sleep } from '@/utils';
import { findParents, Option } from '@/utils/recursion';
import { BugEditCustomFieldItem, BugEditFormObject } from '@/models/bug-management'; import { BugEditCustomField, BugEditCustomFieldItem, BugEditFormObject } from '@/models/bug-management';
import { AssociatedList, AttachFileInfo } from '@/models/caseManagement/featureCase'; import { AssociatedList, AttachFileInfo } from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common'; import { TableQueryParams } from '@/models/common';
@ -197,6 +226,8 @@
detailInfo: BugEditFormObject; detailInfo: BugEditFormObject;
formItem: FormRuleItem[]; formItem: FormRuleItem[];
allowEdit?: boolean; // allowEdit?: boolean; //
isPlatformDefaultTemplate: boolean; //
platformSystemFields: BugEditCustomField[]; //
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -401,21 +432,42 @@
const customFields: BugEditCustomFieldItem[] = []; const customFields: BugEditCustomFieldItem[] = [];
if (formItem && formItem.length) { if (formItem && formItem.length) {
formItem.forEach((item: FormRuleItem) => { formItem.forEach((item: FormRuleItem) => {
let itemVal = item.value;
if (item.sourceType === 'CASCADER') {
itemVal = findParents(item.options as Option[], item.value as string, []);
}
customFields.push({ customFields.push({
id: item.field as string, id: item.field as string,
name: item.title as string, name: item.title as string,
type: item.sourceType as string, type: item.sourceType as string,
value: Array.isArray(item.value) ? JSON.stringify(item.value) : (item.value as string), value: Array.isArray(itemVal) ? JSON.stringify(itemVal) : (itemVal as string),
});
});
}
if (props.isPlatformDefaultTemplate) {
//
props.platformSystemFields.forEach((item) => {
customFields.push({
id: item.fieldId,
name: item.fieldName,
type: item.type,
value: item.defaultValue,
}); });
}); });
} }
const tmpObj: BugEditFormObject = { const tmpObj: BugEditFormObject = {
...form.value,
id: props.detailInfo.id, id: props.detailInfo.id,
projectId: currentProjectId.value, projectId: currentProjectId.value,
templateId: props.detailInfo.templateId, templateId: props.detailInfo.templateId,
deleteLocalFileIds: form.value.deleteLocalFileIds,
unLinkRefIds: form.value.unLinkRefIds,
linkFileIds: form.value.linkFileIds,
customFields, customFields,
}; };
if (!props.isPlatformDefaultTemplate) {
tmpObj.description = form.value.description;
tmpObj.title = form.value.title;
}
// //
const res = await createOrUpdateBug({ request: tmpObj, fileList: [] as unknown as File[] }); const res = await createOrUpdateBug({ request: tmpObj, fileList: [] as unknown as File[] });
if (res) { if (res) {

View File

@ -24,7 +24,9 @@
<a-form ref="formRef" :model="form" layout="vertical"> <a-form ref="formRef" :model="form" layout="vertical">
<div class="flex flex-row"> <div class="flex flex-row">
<div class="left mt-[16px] min-w-[732px] grow pl-[24px]"> <div class="left mt-[16px] min-w-[732px] grow pl-[24px]">
<!-- 平台默认模板不展示缺陷名称, 描述 -->
<a-form-item <a-form-item
v-if="!isPlatformDefaultTemplate"
field="title" field="title"
:label="t('bugManagement.bugName')" :label="t('bugManagement.bugName')"
:rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]" :rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]"
@ -32,13 +34,38 @@
> >
<a-input v-model="form.title" :max-length="255" /> <a-input v-model="form.title" :max-length="255" />
</a-form-item> </a-form-item>
<a-form-item field="description" :label="t('bugManagement.edit.content')"> <a-form-item v-if="!isPlatformDefaultTemplate" field="description" :label="t('bugManagement.edit.content')">
<MsRichText <MsRichText
v-model:raw="form.description" v-model:raw="form.description"
v-model:filed-ids="richTextFileIds" v-model:filed-ids="richTextFileIds"
:upload-image="handleUploadImage" :upload-image="handleUploadImage"
/> />
</a-form-item> </a-form-item>
<!-- 平台默认模板展示字段, 暂时支持输入框, 富文本类型 -->
<div v-if="isPlatformDefaultTemplate">
<a-form-item
v-for="(value, key) in form.platformSystemFields"
:key="key"
:field="'platformSystemFields.' + key"
:label="platformSystemFieldMap[key].fieldName"
:rules="[
{
required: platformSystemFieldMap[key].required,
message: `${platformSystemFieldMap[key].fieldName}` + t('bugManagement.edit.cannotBeNull'),
},
]"
>
<a-input
v-if="platformSystemFieldMap[key].type === 'INPUT'"
v-model="form.platformSystemFields[key]"
:max-length="255"
/>
<MsRichText
v-if="platformSystemFieldMap[key].type === 'RICH_TEXT'"
v-model:raw="form.platformSystemFields[key]"
/>
</a-form-item>
</div>
<a-form-item field="attachment"> <a-form-item field="attachment">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="mb-1"> <div class="mb-1">
@ -124,7 +151,8 @@
<div class="right mt-[16px] max-w-[433px] grow pr-[24px]"> <div class="right mt-[16px] max-w-[433px] grow pr-[24px]">
<div style="min-width: 250px; overflow: auto"> <div style="min-width: 250px; overflow: auto">
<MsFormCreate ref="formCreateRef" v-model:formItem="formItem" v-model:api="fApi" :form-rule="formRules" /> <MsFormCreate ref="formCreateRef" v-model:formItem="formItem" v-model:api="fApi" :form-rule="formRules" />
<a-form-item field="tag" :label="t('bugManagement.tag')"> <!-- 平台默认模板不展示标签, 与第三方保持一致 -->
<a-form-item v-if="!isPlatformDefaultTemplate" field="tag" :label="t('bugManagement.tag')">
<MsTagsInput <MsTagsInput
v-model:model-value="form.tags" v-model:model-value="form.tags"
:placeholder="t('bugManagement.edit.tagPlaceholder')" :placeholder="t('bugManagement.edit.tagPlaceholder')"
@ -200,6 +228,7 @@
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { downloadByteFile } from '@/utils'; import { downloadByteFile } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import { findParents, Option } from '@/utils/recursion';
import { import {
BugEditCustomField, BugEditCustomField,
@ -237,8 +266,13 @@
deleteLocalFileIds: [], deleteLocalFileIds: [],
unLinkRefIds: [], unLinkRefIds: [],
linkFileIds: [], linkFileIds: [],
//
platformSystemFields: {},
}); });
//
const platformSystemFieldMap = {};
const getListFunParams = ref<TableQueryParams>({ const getListFunParams = ref<TableQueryParams>({
combine: { combine: {
hiddenIds: [], hiddenIds: [],
@ -262,6 +296,7 @@
const bugId = computed(() => route.query.id || ''); const bugId = computed(() => route.query.id || '');
const isEditOrCopy = computed(() => !!bugId.value); const isEditOrCopy = computed(() => !!bugId.value);
const isCopy = computed(() => route.params.mode === 'copy'); const isCopy = computed(() => route.params.mode === 'copy');
const isPlatformDefaultTemplate = ref(false);
const imageUrl = ref(''); const imageUrl = ref('');
const previewVisible = ref<boolean>(false); const previewVisible = ref<boolean>(false);
const richTextFileIds = ref<string[]>([]); const richTextFileIds = ref<string[]>([]);
@ -323,6 +358,7 @@
value: item.defaultValue, value: item.defaultValue,
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options, options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
required: item.required as boolean, required: item.required as boolean,
platformPlaceHolder: item.platformPlaceHolder,
props: { props: {
modelValue: item.defaultValue, modelValue: item.defaultValue,
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options, options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
@ -335,12 +371,22 @@
const templateChange = async (v: SelectValue, request?: BugTemplateRequest) => { const templateChange = async (v: SelectValue, request?: BugTemplateRequest) => {
if (v) { if (v) {
try { try {
loading.value = true;
let param = { projectId: appStore.currentProjectId, id: v }; let param = { projectId: appStore.currentProjectId, id: v };
if (request) { if (request) {
param = { ...param, ...request }; param = { ...param, ...request };
} }
const res = await getTemplateById(param); const res = await getTemplateById(param);
getFormRules(res.customFields); isPlatformDefaultTemplate.value = res.platformDefault;
if (isPlatformDefaultTemplate.value) {
const systemFields = res.customFields.filter((field) => field.platformSystemField);
systemFields.forEach((field) => {
form.value.platformSystemFields[field.fieldId] = field.defaultValue;
platformSystemFieldMap[field.fieldId] = field;
});
}
getFormRules(res.customFields.filter((field) => !field.platformSystemField));
loading.value = false;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -461,6 +507,9 @@
const customFields: BugEditCustomFieldItem[] = []; const customFields: BugEditCustomFieldItem[] = [];
if (formItem.value && formItem.value.length) { if (formItem.value && formItem.value.length) {
formItem.value.forEach((item: FormRuleItem) => { formItem.value.forEach((item: FormRuleItem) => {
if (item.sourceType === 'CASCADER') {
item.value = findParents(item.options as Option[], item.value as string, []);
}
customFields.push({ customFields.push({
id: item.field as string, id: item.field as string,
name: item.title as string, name: item.title as string,
@ -469,6 +518,21 @@
}); });
}); });
} }
if (isPlatformDefaultTemplate.value && form.value.platformSystemFields) {
Object.keys(form.value.platformSystemFields).forEach((key) => {
customFields.push({
id: platformSystemFieldMap[key].fieldId,
name: platformSystemFieldMap[key].fieldName,
type: platformSystemFieldMap[key].type,
value: form.value.platformSystemFields[key],
});
});
delete form.value.platformSystemFields;
// , ,
delete form.value.title;
delete form.value.description;
delete form.value.tags;
}
// //
const copyFileList = fileList.value.filter((item) => item.isCopyFlag); const copyFileList = fileList.value.filter((item) => item.isCopyFlag);
let copyFiles: { refId: string; fileId: string; local: boolean }[] = []; let copyFiles: { refId: string; fileId: string; local: boolean }[] = [];
@ -550,6 +614,7 @@
}; };
// //
const getDetailInfo = async () => { const getDetailInfo = async () => {
loading.value = true;
const id = route.query.id as string; const id = route.query.id as string;
if (!id) return; if (!id) return;
const res = await getBugDetail(id); const res = await getBugDetail(id);
@ -580,16 +645,24 @@
}); });
} }
const tmpObj = {}; let tmpObj = {};
if (isEdit.value) {
tmpObj = { status: res.status };
}
if (customFields && Array.isArray(customFields)) { if (customFields && Array.isArray(customFields)) {
customFields.forEach((item) => { customFields.forEach((item) => {
if (item.id === 'status' && isCopy.value) { if (item.id === 'status' && isCopy.value) {
// , // ,
tmpObj[item.id] = ''; tmpObj[item.id] = '';
} else if (item.type === 'MULTIPLE_SELECT') { } else if (item.type === 'MULTIPLE_SELECT' || item.type === 'MULTIPLE_INPUT' || item.type === 'CHECKBOX') {
tmpObj[item.id] = JSON.parse(item.value); tmpObj[item.id] = JSON.parse(item.value);
} else if (item.type === 'INT') { } else if (item.type === 'INT') {
tmpObj[item.id] = Number(item.value); tmpObj[item.id] = Number(item.value);
} else if (item.type === 'CASCADER') {
const arr = JSON.parse(item.value);
if (arr && arr instanceof Array && arr.length > 0) {
tmpObj[item.id] = arr[arr.length - 1];
}
} else { } else {
tmpObj[item.id] = item.value; tmpObj[item.id] = item.value;
} }
@ -597,6 +670,13 @@
} }
// //
fApi.value.setValue(tmpObj); fApi.value.setValue(tmpObj);
//
if (isPlatformDefaultTemplate && form.value.platformSystemFields) {
Object.keys(form.value.platformSystemFields).forEach((key) => {
form.value.platformSystemFields[key] = tmpObj[key];
});
}
const { platformSystemFields } = form.value;
// //
form.value = { form.value = {
id: res.id, id: res.id,
@ -605,7 +685,9 @@
templateId: res.templateId, templateId: res.templateId,
tags: res.tags, tags: res.tags,
projectId: res.projectId, projectId: res.projectId,
platformSystemFields,
}; };
loading.value = false;
}; };
const initDefaultFields = async () => { const initDefaultFields = async () => {

View File

@ -378,7 +378,7 @@
{ {
title: 'bugManagement.status', title: 'bugManagement.status',
dataIndex: 'statusName', dataIndex: 'statusName',
width: 84, width: 100,
slotName: 'status', slotName: 'status',
titleSlotName: 'statusFilter', titleSlotName: 'statusFilter',
showDrag: true, showDrag: true,
@ -390,7 +390,7 @@
slotName: 'handleUser', slotName: 'handleUser',
showTooltip: true, showTooltip: true,
titleSlotName: 'handleUserFilter', titleSlotName: 'handleUserFilter',
width: 75, width: 100,
showDrag: true, showDrag: true,
showInTable: true, showInTable: true,
}, },
@ -404,7 +404,7 @@
}, },
{ {
title: 'bugManagement.belongPlatform', title: 'bugManagement.belongPlatform',
width: 135, width: 100,
showDrag: true, showDrag: true,
dataIndex: 'platform', dataIndex: 'platform',
showInTable: true, showInTable: true,
@ -421,7 +421,7 @@
title: 'bugManagement.creator', title: 'bugManagement.creator',
dataIndex: 'createUser', dataIndex: 'createUser',
slotName: 'createUser', slotName: 'createUser',
width: 112, width: 125,
showTooltip: true, showTooltip: true,
showDrag: true, showDrag: true,
titleSlotName: 'createUserFilter', titleSlotName: 'createUserFilter',
@ -445,7 +445,7 @@
{ {
title: 'bugManagement.updateUser', title: 'bugManagement.updateUser',
dataIndex: 'updateUser', dataIndex: 'updateUser',
width: 112, width: 125,
showTooltip: true, showTooltip: true,
showDrag: true, showDrag: true,
titleSlotName: 'updateUserFilter', titleSlotName: 'updateUserFilter',

View File

@ -53,6 +53,7 @@ export default {
linkFile: 'Link file', linkFile: 'Link file',
contentEdit: 'Content edit', contentEdit: 'Content edit',
linkCase: 'Link case', linkCase: 'Link case',
cannotBeNull: 'is empty',
}, },
detail: { detail: {
title: '【{id}】{name}', title: '【{id}】{name}',

View File

@ -53,6 +53,7 @@ export default {
linkFile: '关联文件', linkFile: '关联文件',
contentEdit: '内容编辑', contentEdit: '内容编辑',
linkCase: '关联用例', linkCase: '关联用例',
cannotBeNull: '不能为空',
}, },
detail: { detail: {
title: '【{id}】{name}', title: '【{id}】{name}',