diff --git a/backend/framework/sdk/pom.xml b/backend/framework/sdk/pom.xml index 6bdb841b3c..fe22f7a8ef 100644 --- a/backend/framework/sdk/pom.xml +++ b/backend/framework/sdk/pom.xml @@ -34,6 +34,12 @@ org.apache.httpcomponents.client5 httpclient5 + + + com.github.eljah + xmindjbehaveplugin + ${xmindjbehaveplugin.version} + diff --git a/backend/framework/sdk/src/main/resources/i18n/case_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/case_en_US.properties index 8d028c8793..38972ba026 100644 --- a/backend/framework/sdk/src/main/resources/i18n/case_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/case_en_US.properties @@ -256,4 +256,14 @@ case.review.status.re_reviewed=Re reviewed case.execute.status.pending=Pending functional_case_comment_template=【评论:%s(%s)】\n%s\n functional_case_execute_comment_template=[Execute comment:%s %s(%s)]\n%s\n -functional_case_review_comment_template=[Review comment:%s %s(%s)]\n%s\n \ No newline at end of file +functional_case_review_comment_template=[Review comment:%s %s(%s)]\n%s\n +functional_case_xmind_template=Functional case xmind template +download_template_failed=Download template failed +functional_case=Functional case +xmind_prerequisite=Prerequisite +xmind_description=Remark +xmind_tags=tags +xmind_textDescription=Text description +xmind_expectedResult=Expected result +xmind_step=Step +xmind_stepDescription=Step description \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/case_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/case_zh_CN.properties index ca8855aa06..1eacd1ed3e 100644 --- a/backend/framework/sdk/src/main/resources/i18n/case_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/case_zh_CN.properties @@ -254,4 +254,14 @@ case.review.status.re_reviewed=重新提审 case.execute.status.pending=未执行 functional_case_comment_template=【评论:%s(%s)】\n%s\n functional_case_execute_comment_template=【执行评论:%s %s(%s)】\n%s\n -functional_case_review_comment_template=【评审评论:%s %s(%s)】\n%s\n \ No newline at end of file +functional_case_review_comment_template=【评审评论:%s %s(%s)】\n%s\n +functional_case_xmind_template=思维导图用例模版 +download_template_failed=下载思维导图模版失败 +functional_case=功能用例 +xmind_prerequisite=前置条件 +xmind_description=备注 +xmind_tags=标签 +xmind_textDescription=文本描述 +xmind_expectedResult=预期结果 +xmind_step=用例步骤 +xmind_stepDescription=步骤描述 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/case_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/case_zh_TW.properties index d8985e485a..6f56c26b06 100644 --- a/backend/framework/sdk/src/main/resources/i18n/case_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/case_zh_TW.properties @@ -256,4 +256,13 @@ case.execute.status.pending=未執行 functional_case_comment_template=【评论:%s(%s)】\n%s\n functional_case_execute_comment_template=【執行評論:%s %s(%s)】\n%s\n functional_case_review_comment_template=【評審評論:%s %s(%s)】\n%s\n - +functional_case_xmind_template=思維導圖用例模板 +download_template_failed=下載思維導圖模板失敗 +functional_case=功能用例 +xmind_prerequisite=前置條件 +xmind_description=備注 +xmind_tags=標簽 +xmind_textDescription=文本描述 +xmind_expectedResult=預期結果 +xmind_step=用例步驟 +xmind_stepDescription=步驟描述 \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java index af0992ab30..398de21753 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java @@ -9,10 +9,7 @@ import io.metersphere.functional.dto.FunctionalCasePageDTO; import io.metersphere.functional.dto.FunctionalCaseVersionDTO; import io.metersphere.functional.dto.response.FunctionalCaseImportResponse; import io.metersphere.functional.request.*; -import io.metersphere.functional.service.FunctionalCaseFileService; -import io.metersphere.functional.service.FunctionalCaseLogService; -import io.metersphere.functional.service.FunctionalCaseNoticeService; -import io.metersphere.functional.service.FunctionalCaseService; +import io.metersphere.functional.service.*; import io.metersphere.project.dto.CustomFieldOptions; import io.metersphere.project.service.ProjectTemplateService; import io.metersphere.sdk.constants.PermissionConstants; @@ -33,7 +30,6 @@ import io.metersphere.system.utils.SessionUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotBlank; import org.apache.shiro.authz.annotation.Logical; @@ -60,6 +56,8 @@ public class FunctionalCaseController { private ProjectTemplateService projectTemplateService; @Resource private FunctionalCaseFileService functionalCaseFileService; + @Resource + private FunctionalCaseXmindService functionalCaseXmindService; //TODO 获取模板列表(多模板功能暂时不做) @@ -257,4 +255,12 @@ public class FunctionalCaseController { public void testCaseExport(@Validated @RequestBody FunctionalCaseExportRequest request) { functionalCaseFileService.exportFunctionalCaseZip(request); } + + @GetMapping("/download/xmind/template/{projectId}") + @Operation(summary = "用例管理-功能用例-xmind导入-下载模板") + @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ) + @CheckOwner(resourceId = "#projectId", resourceType = "project") + public void xmindTemplateExport(@PathVariable String projectId, HttpServletResponse response) { + functionalCaseXmindService.downloadXmindTemplate(projectId, response); + } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java index e7ec14c5e4..38fff91237 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseFileService.java @@ -280,7 +280,7 @@ public class FunctionalCaseFileService { } } - private List getCustomFields(String projectId) { + public List getCustomFields(String projectId) { TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(projectId, TemplateScene.FUNCTIONAL.name()); List customFields = Optional.ofNullable(defaultTemplateDTO.getCustomFields()).orElse(new ArrayList<>()); return customFields; diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java new file mode 100644 index 0000000000..ca963fb38e --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseXmindService.java @@ -0,0 +1,60 @@ +package io.metersphere.functional.service; + +import io.metersphere.functional.xmind.domain.FunctionalCaseXmindData; +import io.metersphere.functional.xmind.utils.XmindExportUtil; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStream; +import java.util.List; + +/** + * @author wx + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class FunctionalCaseXmindService { + + public static final String template = "/template/template.json"; + + @Resource + private FunctionalCaseFileService functionalCaseFileService; + + + public void downloadXmindTemplate(String projectId, HttpServletResponse response) { + List customFields = functionalCaseFileService.getCustomFields(projectId); + try (InputStream stream = FunctionalCaseXmindService.class.getResourceAsStream(template)) { + FunctionalCaseXmindData functionalCaseXmindData = JSON.parseObject(stream, FunctionalCaseXmindData.class); + setTemplateCustomFields(functionalCaseXmindData.getChildren(), customFields); + + XmindExportUtil.downloadTemplate(response, functionalCaseXmindData, true); + + } catch (Exception e) { + LogUtils.error(e.getMessage()); + throw new MSException(Translator.get("download_template_failed")); + } + } + + private void setTemplateCustomFields(List children, List customFields) { + if (CollectionUtils.isNotEmpty(children)) { + children.forEach(data -> { + data.getFunctionalCaseList().forEach(item -> { + item.setTemplateCustomFieldDTOList(customFields); + }); + if (CollectionUtils.isNotEmpty(data.getChildren())) { + setTemplateCustomFields(data.getChildren(), customFields); + } + }); + } + } + + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java new file mode 100644 index 0000000000..277542a347 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindDTO.java @@ -0,0 +1,54 @@ +package io.metersphere.functional.xmind.domain; + +import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO; +import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * @author wx + */ +@Data +public class FunctionalCaseXmindDTO { + + @Schema(description = "ID") + private String id; + + @Schema(description = "业务ID") + private String num; + + @Schema(description = "项目ID") + private String projectId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "标签(JSON)") + private String tags; + + @Schema(description = "编辑模式:步骤模式/文本模式") + private String caseEditType; + + @Schema(description = "用例步骤(JSON),step_model 为 Step 时启用") + private String steps; + + @Schema(description = "步骤描述,step_model 为 Text 时启用") + private String textDescription; + + @Schema(description = "预期结果,step_model 为 Text 时启用") + private String expectedResult; + + @Schema(description = "前置条件") + private String prerequisite; + + @Schema(description = "备注") + private String description; + + @Schema(description = "自定义字段") + private List customFieldDTOList; + + @Schema(description = "模板自定义字段") + private List templateCustomFieldDTOList; +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java new file mode 100644 index 0000000000..3b1ce2ffc8 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/domain/FunctionalCaseXmindData.java @@ -0,0 +1,24 @@ +package io.metersphere.functional.xmind.domain; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author wx + */ +@Data +public class FunctionalCaseXmindData implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private List functionalCaseList; + private String moduleName; + private String moduleId; + private List children = new ArrayList<>(); + + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java new file mode 100644 index 0000000000..4e9711d6bd --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/xmind/utils/XmindExportUtil.java @@ -0,0 +1,334 @@ +package io.metersphere.functional.xmind.utils; + +import io.metersphere.functional.constants.FunctionalCaseTypeConstants; +import io.metersphere.functional.xmind.domain.FunctionalCaseXmindDTO; +import io.metersphere.functional.xmind.domain.FunctionalCaseXmindData; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import io.metersphere.sdk.util.Translator; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.xmind.core.*; +import org.xmind.core.style.IStyle; +import org.xmind.core.style.IStyleSheet; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author wx + */ +public class XmindExportUtil { + + + /** + * 下载xmind模板 + * + * @param response + * @param caseData + * @param template + */ + public static void downloadTemplate(HttpServletResponse response, FunctionalCaseXmindData caseData, boolean template) { + IWorkbook workBook = createXmindByCaseData(caseData, template); + + response.setContentType("application/octet-stream"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + try { + response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(Translator.get("functional_case_xmind_template"), StandardCharsets.UTF_8.name()) + ".xmind"); + workBook.save(response.getOutputStream()); + } catch (UnsupportedEncodingException e) { + LogUtils.error(e.getMessage(), e); + throw new MSException("Utf-8 encoding is not supported"); + } catch (Exception e) { + LogUtils.error(e.getMessage(), e); + throw new MSException("IO exception"); + } + } + + private static IWorkbook createXmindByCaseData(FunctionalCaseXmindData caseData, boolean template) { + // 创建思维导图的工作空间 + IWorkbookBuilder workbookBuilder = Core.getWorkbookBuilder(); + IWorkbook workbook = workbookBuilder.createWorkbook(); + + Map styleMap = initTheme(workbook); + + // 获得默认sheet + ISheet primarySheet = workbook.getPrimarySheet(); + if (styleMap.containsKey("mapStyle")) { + primarySheet.setStyleId(styleMap.get("mapStyle").getId()); + } + // 获得根主题 + ITopic rootTopic = primarySheet.getRootTopic(); + if (styleMap.containsKey("centralTopicStyle")) { + rootTopic.setStyleId(styleMap.get("centralTopicStyle").getId()); + } + // 设置根主题的标题 + rootTopic.setTitleText(Translator.get("functional_case")); + + if (CollectionUtils.isNotEmpty(caseData.getChildren())) { + for (FunctionalCaseXmindData data : caseData.getChildren()) { + addItemTopic(rootTopic, workbook, styleMap, data, true, template); + } + } + return workbook; + } + + private static void addItemTopic(ITopic parentTpoic, IWorkbook workbook, Map styleMap, FunctionalCaseXmindData xmindData, boolean isFirstLevel, boolean template) { + ITopic topic = workbook.createTopic(); + topic.setTitleText(xmindData.getModuleName()); + if (isFirstLevel) { + if (styleMap.containsKey("mainTopicStyle")) { + topic.setStyleId(styleMap.get("mainTopicStyle").getId()); + } + } else { + if (styleMap.containsKey("subTopicStyle")) { + topic.setStyleId(styleMap.get("subTopicStyle").getId()); + } + } + parentTpoic.add(topic); + + if (CollectionUtils.isNotEmpty(xmindData.getFunctionalCaseList())) { + IStyle style = null; + if (styleMap.containsKey("subTopicStyle")) { + style = styleMap.get("subTopicStyle"); + } + for (FunctionalCaseXmindDTO dto : xmindData.getFunctionalCaseList()) { + // 创建小节节点 + ITopic itemTopic = workbook.createTopic(); + if (style != null) { + itemTopic.setStyleId(style.getId()); + } + if (template) { + // 模板 + buildTemplateTopic(topic, style, dto, itemTopic, workbook); + } + } + } + + if (CollectionUtils.isNotEmpty(xmindData.getChildren())) { + for (FunctionalCaseXmindData data : xmindData.getChildren()) { + addItemTopic(topic, workbook, styleMap, data, false, template); + } + } + } + + private static void buildTemplateTopic(ITopic topic, IStyle style, FunctionalCaseXmindDTO dto, ITopic itemTopic, IWorkbook workbook) { + + //用例名称 + itemTopic.setTitleText("case-P0: " + dto.getName()); + + //前置条件 + if (StringUtils.isNotBlank(dto.getPrerequisite())) { + ITopic preTopic = workbook.createTopic(); + preTopic.setTitleText(Translator.get("xmind_prerequisite") + ": " + dto.getPrerequisite()); + if (style != null) { + preTopic.setStyleId(style.getId()); + } + itemTopic.add(preTopic, ITopic.ATTACHED); + } + + //备注 + if (StringUtils.isNotBlank(dto.getDescription())) { + ITopic deTopic = workbook.createTopic(); + deTopic.setTitleText(Translator.get("xmind_description") + ": " + dto.getDescription()); + if (style != null) { + deTopic.setStyleId(style.getId()); + } + itemTopic.add(deTopic, ITopic.ATTACHED); + } + + //标签 + if (StringUtils.isNotBlank(dto.getTags())) { + try { + List arr = JSON.parseArray(dto.getTags()); + String tagStr = StringUtils.EMPTY; + for (int i = 0; i < arr.size(); i++) { + tagStr = tagStr + arr.get(i) + "|"; + } + if (tagStr.endsWith("|")) { + tagStr = tagStr.substring(0, tagStr.length() - 1); + } + ITopic tagTopic = workbook.createTopic(); + tagTopic.setTitleText(Translator.get("xmind_tags") + ":" + tagStr); + if (style != null) { + tagTopic.setStyleId(style.getId()); + } + itemTopic.add(tagTopic, ITopic.ATTACHED); + } catch (Exception e) { + } + } + + if (StringUtils.equalsIgnoreCase(dto.getCaseEditType(), FunctionalCaseTypeConstants.CaseEditType.TEXT.name())) { + //文本描述 + ITopic textDesTopic = workbook.createTopic(); + String desc = dto.getTextDescription(); + textDesTopic.setTitleText(desc == null ? Translator.get("xmind_textDescription") + ": " : Translator.get("xmind_textDescription") + ": " + desc); + if (style != null) { + textDesTopic.setStyleId(style.getId()); + } + + String result = dto.getExpectedResult(); + ITopic resultTopic = workbook.createTopic(); + resultTopic.setTitleText(result == null ? Translator.get("xmind_expectedResult") + ": " : Translator.get("xmind_expectedResult") + ": " + result); + if (style != null) { + resultTopic.setStyleId(style.getId()); + } + textDesTopic.add(resultTopic, ITopic.ATTACHED); + + if (StringUtils.isNotEmpty(desc) || StringUtils.isNotEmpty(result)) { + itemTopic.add(textDesTopic, ITopic.ATTACHED); + } + } else { + //步骤描述 + try { + ITopic stepDesTopic = workbook.createTopic(); + stepDesTopic.setTitleText(Translator.get("xmind_stepDescription")); + if (style != null) { + stepDesTopic.setStyleId(style.getId()); + } + List arr = JSON.parseArray(dto.getSteps()); + for (int i = 0; i < arr.size(); i++) { + Map obj = arr.get(i); + if (obj.containsKey("desc")) { + ITopic stepTopic = workbook.createTopic(); + String desc = obj.get("desc"); + stepTopic.setTitleText(Translator.get("xmind_step") + ": " + desc); + if (style != null) { + stepTopic.setStyleId(style.getId()); + } + + boolean hasResult = false; + if (obj.containsKey("result")) { + String result = obj.get("result"); + if (StringUtils.isNotEmpty(result)) { + hasResult = true; + ITopic resultTopic = workbook.createTopic(); + resultTopic.setTitleText(Translator.get("xmind_expectedResult") + ": " + result); + if (style != null) { + resultTopic.setStyleId(style.getId()); + } + stepTopic.add(resultTopic, ITopic.ATTACHED); + } + } + + if (StringUtils.isNotEmpty(desc) || hasResult) { + stepDesTopic.add(stepTopic, ITopic.ATTACHED); + } + + } + } + itemTopic.add(stepDesTopic, ITopic.ATTACHED); + } catch (Exception e) { + } + } + + //自定义字段 + dto.getTemplateCustomFieldDTOList().forEach(item -> { + if (!StringUtils.equalsIgnoreCase(item.getFieldName(), "functional_priority")) { + ITopic customTopic = workbook.createTopic(); + customTopic.setTitleText(item.getFieldName() + ":"); + if (style != null) { + customTopic.setStyleId(style.getId()); + } + itemTopic.add(customTopic, ITopic.ATTACHED); + } + }); + + topic.add(itemTopic); + } + + + private static Map initTheme(IWorkbook workbook) { + Map styleMap = new HashMap<>(); + + IStyleSheet styleSheet = workbook.getStyleSheet(); + IStyle mapStyle = styleSheet.createStyle(IStyle.MAP); + mapStyle.setProperty("line-tapered", "none"); + mapStyle.setProperty("multi-line-colors", "none"); + mapStyle.setProperty("svg:fill", "#FFFFFF"); + mapStyle.setProperty("color-gradient", "none"); + styleSheet.addStyle(mapStyle, IStyleSheet.NORMAL_STYLES); + styleMap.put("mapStyle", mapStyle); + + IStyle centralTopicStyle = styleSheet.createStyle(IStyle.TOPIC); + centralTopicStyle.setProperty("line-width", "1pt"); + centralTopicStyle.setProperty("svg:fill", "#DCE6F2"); + centralTopicStyle.setProperty("fo:font-family", "Microsoft YaHei"); + centralTopicStyle.setProperty("border-line-width", "5pt"); + centralTopicStyle.setProperty("shape-class", "org.xmind.topicShape.roundedRect"); + centralTopicStyle.setProperty("fo:color", "#376092"); + centralTopicStyle.setProperty("line-class", "org.xmind.branchConnection.curve"); + centralTopicStyle.setProperty("border-line-color", "#558ED5"); + centralTopicStyle.setProperty("line-color", "#558ED5"); + styleSheet.addStyle(centralTopicStyle, IStyleSheet.NORMAL_STYLES); + styleMap.put("centralTopicStyle", centralTopicStyle); + + IStyle mainTopicStyle = styleSheet.createStyle(IStyle.TOPIC); + mainTopicStyle.setProperty("line-width", "1pt"); + mainTopicStyle.setProperty("svg:fill", "#DCE6F2"); + mainTopicStyle.setProperty("fo:font-family", "Microsoft YaHei"); + mainTopicStyle.setProperty("border-line-width", "2pt"); + mainTopicStyle.setProperty("shape-class", "org.xmind.topicShape.roundedRect"); + mainTopicStyle.setProperty("fo:color", "#17375E"); + mainTopicStyle.setProperty("line-class", "org.xmind.branchConnection.curve"); + mainTopicStyle.setProperty("border-line-color", "#558ED5"); + mainTopicStyle.setProperty("line-color", "#558ED5"); + styleSheet.addStyle(mainTopicStyle, IStyleSheet.NORMAL_STYLES); + styleMap.put("mainTopicStyle", mainTopicStyle); + + IStyle subTopicStyle = styleSheet.createStyle(IStyle.TOPIC); + subTopicStyle.setProperty("line-width", "1pt"); + subTopicStyle.setProperty("fo:font-family", "Microsoft YaHei"); + subTopicStyle.setProperty("border-line-width", "3pt"); + subTopicStyle.setProperty("line-class", "org.xmind.branchConnection.curve"); + subTopicStyle.setProperty("border-line-color", "#558ED5"); + subTopicStyle.setProperty("line-color", "#558ED5"); + styleSheet.addStyle(subTopicStyle, IStyleSheet.NORMAL_STYLES); + styleMap.put("subTopicStyle", subTopicStyle); + + IStyle floatingTopicStyle = styleSheet.createStyle(IStyle.TOPIC); + floatingTopicStyle.setProperty("svg:fill", "#558ED5"); + floatingTopicStyle.setProperty("fo:font-family", "Microsoft YaHei"); + floatingTopicStyle.setProperty("border-line-width", "0pt"); + floatingTopicStyle.setProperty("fo:color", "#FFFFFF"); + floatingTopicStyle.setProperty("fo:font-weight", "bold"); + floatingTopicStyle.setProperty("line-color", "#558ED5"); + styleSheet.addStyle(floatingTopicStyle, IStyleSheet.NORMAL_STYLES); + styleMap.put("floatingTopicStyle", floatingTopicStyle); + + IStyle summaryTopic = styleSheet.createStyle(IStyle.TOPIC); + summaryTopic.setProperty("fo:font-style", "italic"); + summaryTopic.setProperty("svg:fill", "#77933C"); + summaryTopic.setProperty("fo:font-family", "Microsoft YaHei"); + summaryTopic.setProperty("border-line-width", "0pt"); + summaryTopic.setProperty("fo:font-size", "10pt"); + summaryTopic.setProperty("shape-class", "org.xmind.topicShape.roundedRect"); + summaryTopic.setProperty("fo:color", "#FFFFFF"); + summaryTopic.setProperty("line-class", "org.xmind.branchConnection.curve"); + styleSheet.addStyle(summaryTopic, IStyleSheet.NORMAL_STYLES); + styleMap.put("summaryTopic", floatingTopicStyle); + + IStyle itemTopic = styleSheet.createStyle(IStyle.TOPIC); + itemTopic.setProperty("fo:text-align", "center"); + itemTopic.setProperty("line-width", "1pt"); + itemTopic.setProperty("svg:fill", "none"); + itemTopic.setProperty("fo:font-family", "Microsoft YaHei"); + itemTopic.setProperty("border-line-width", "2pt"); + itemTopic.setProperty("shape-class", "org.xmind.topicShape.underline"); + itemTopic.setProperty("fo:font-size", "14pt"); + itemTopic.setProperty("fo:color", "#17375E"); + itemTopic.setProperty("line-class", "org.xmind.branchConnection.curve"); + itemTopic.setProperty("border-line-color", "#558ED5"); + itemTopic.setProperty("line-color", "#558ED5"); + styleSheet.addStyle(itemTopic, IStyleSheet.NORMAL_STYLES); + styleMap.put("itemTopic", itemTopic); + + return styleMap; + } +} diff --git a/backend/services/case-management/src/main/resources/template/template.json b/backend/services/case-management/src/main/resources/template/template.json new file mode 100644 index 0000000000..c14b9e0113 --- /dev/null +++ b/backend/services/case-management/src/main/resources/template/template.json @@ -0,0 +1,64 @@ +{ + "functionalCaseList": [], + "moduleName": "rootNode", + "moduleId": "rootNode", + "children": [ + { + "functionalCaseList": [ + { + "id": "1", + "num": 1, + "projectId": "12314", + "name": "导出模板用例A", + "tags": "[\"标签789\"]", + "caseEditType": "STEP", + "steps": "[{\"num\":1,\"desc\":\"123\",\"result\":\"123\"},{\"num\":2,\"desc\":\"SDFS\",\"result\":\"SDFS\"}]", + "textDescription": "", + "expectedResult": "", + "prerequisite": "这是前置", + "description": "这是备注", + "customFieldDTOList": [] + }, + { + "id": "2", + "num": 2, + "projectId": "12314", + "name": "导出模板用例B", + "tags": "[\"标签789\"]", + "caseEditType": "TEXT", + "steps": "", + "textDescription": "

akhfksajhfaskfha

", + "expectedResult": "

asdfsdfasfs

", + "prerequisite": "前置", + "description": "这是备注", + "customFieldDTOList": [] + } + ], + "moduleName": "模块1", + "moduleId": "module1", + "children": [ + { + "functionalCaseList": [ + { + "id": "3", + "num": 3, + "projectId": "12314", + "name": "导出模板用例C", + "tags": "[\"标签789\"]", + "caseEditType": "STEP", + "steps": "[{\"num\":1,\"desc\":\"123\",\"result\":\"123\"},{\"num\":2,\"desc\":\"SDFS\",\"result\":\"SDFS\"}]", + "textDescription": "", + "expectedResult": "", + "prerequisite": "前置", + "description": "这是备注", + "customFieldDTOList": [] + } + ], + "moduleName": "模块2", + "moduleId": "module2", + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java index bdab2875ed..6a6ecac064 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java @@ -87,6 +87,7 @@ public class FunctionalCaseControllerTests extends BaseTest { public static final String IMPORT_EXCEL_URL = "/functional/case/import/excel"; public static final String OPERATION_HISTORY_URL = "/functional/case/operation-history"; public static final String EXPORT_EXCEL_URL = "/functional/case/export/excel"; + public static final String DOWNLOAD_XMIND_TEMPLATE_URL = "/functional/case/download/xmind/template/"; @Resource private NotificationMapper notificationMapper; @@ -834,4 +835,11 @@ public class FunctionalCaseControllerTests extends BaseTest { request.setFileId("123142342"); this.requestPost(EXPORT_EXCEL_URL, request); } + + + @Test + @Order(22) + public void testDownloadXmindTemplate() throws Exception { + this.requestGetExcel(DOWNLOAD_XMIND_TEMPLATE_URL + DEFAULT_PROJECT_ID); + } }