diff --git a/test-track/frontend/src/business/case/components/TestCaseEdit.vue b/test-track/frontend/src/business/case/components/TestCaseEdit.vue index 91c003c90b..296584093a 100644 --- a/test-track/frontend/src/business/case/components/TestCaseEdit.vue +++ b/test-track/frontend/src/business/case/components/TestCaseEdit.vue @@ -445,23 +445,6 @@ - - + + + @@ -577,7 +580,7 @@ import {buildTree} from "metersphere-frontend/src/model/NodeTree"; import {versionEnableByProjectId} from "@/api/project"; import {openCaseEdit} from "@/business/case/test-case"; import ListItemDeleteConfirm from "metersphere-frontend/src/components/ListItemDeleteConfirm"; - +import CaseDiffSideViewer from "./case/diff/CaseDiffSideViewer" export default { name: "TestCaseEdit", @@ -610,7 +613,8 @@ export default { MsAsideContainer, MsMainContainer, MxVersionHistory, - ListItemDeleteConfirm + ListItemDeleteConfirm, + CaseDiffSideViewer }, data() { return { @@ -1564,27 +1568,31 @@ export default { } return versionName; }, - async compareBranch(t1, t2) { - let t1Case = await testCaseGetByVersionId(t1.id, this.currentTestCaseInfo.id); - let t2Case = await testCaseGetByVersionId(t2.id, this.currentTestCaseInfo.id); + compareBranch(t1, t2) { + // 打开对比 + this.dialogVisible = true; + this.$refs.caseDiffViewerRef.open(t1.id, t2.id, this.currentTestCaseInfo.id) - let p1 = getTestCase(t1Case.data.id); - let p2 = getTestCase(t2Case.data.id); - let that = this; - Promise.all([p1, p2]).then((r) => { - if (r[0] && r[1]) { - that.newData = r[0].data; - that.oldData = r[1].data; - that.newData.createTime = t1.createTime; - that.oldData.createTime = t2.createTime; - that.newData.versionName = t1.name; - that.oldData.versionName = t2.name; - that.newData.userName = t1Case.data.createName; - that.oldData.userName = t2Case.data.createName; - this.setSpecialPropForCompare(that); - that.dialogVisible = true; - } - }); + // let t1Case = await testCaseGetByVersionId(t1.id, this.currentTestCaseInfo.id); + // let t2Case = await testCaseGetByVersionId(t2.id, this.currentTestCaseInfo.id); + + // let p1 = getTestCase(t1Case.data.id); + // let p2 = getTestCase(t2Case.data.id); + // let that = this; + // Promise.all([p1, p2]).then((r) => { + // if (r[0] && r[1]) { + // that.newData = r[0].data; + // that.oldData = r[1].data; + // that.newData.createTime = t1.createTime; + // that.oldData.createTime = t2.createTime; + // that.newData.versionName = t1.name; + // that.oldData.versionName = t2.name; + // that.newData.userName = t1Case.data.createName; + // that.oldData.userName = t2Case.data.createName; + // this.setSpecialPropForCompare(that); + // that.dialogVisible = true; + // } + // }); }, compare(row) { testCaseGetByVersionId(row.id, this.currentTestCaseInfo.refId).then( @@ -2268,6 +2276,7 @@ export default { font-size: 14px; line-height: 22px; text-align: center; + justify-content: flex-end; // 底部按钮激活样式 .opt-active-primary { background: #783887; @@ -2287,7 +2296,7 @@ export default { } .save-btn-row { - margin-left: px2rem(24); + margin: 0 24px 0 12px; el-button { } } diff --git a/test-track/frontend/src/business/case/components/case/CaseAttachmentItem.vue b/test-track/frontend/src/business/case/components/case/CaseAttachmentItem.vue index d80f02f4d2..b553a9e70e 100644 --- a/test-track/frontend/src/business/case/components/case/CaseAttachmentItem.vue +++ b/test-track/frontend/src/business/case/components/case/CaseAttachmentItem.vue @@ -5,7 +5,18 @@
-
{{ fileItem.name }}
+
+
+ {{ fileItem.name }} +
+ +
{{ fileItem.size }}
|
@@ -80,8 +91,12 @@ + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationship.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationship.vue new file mode 100644 index 0000000000..6a7499a2fb --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationship.vue @@ -0,0 +1,158 @@ + + + + + + + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipList.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipList.vue new file mode 100644 index 0000000000..51ed1d1c98 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipList.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipTableList.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipTableList.vue new file mode 100644 index 0000000000..922029c5d0 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffRelationshipTableList.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffSideViewer.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffSideViewer.vue new file mode 100644 index 0000000000..b204802c30 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffSideViewer.vue @@ -0,0 +1,122 @@ + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffStatus.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffStatus.vue new file mode 100644 index 0000000000..50200f2e07 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffStatus.vue @@ -0,0 +1,80 @@ + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffTestRelate.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffTestRelate.vue new file mode 100644 index 0000000000..f646533f06 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffTestRelate.vue @@ -0,0 +1,109 @@ + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffText.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffText.vue new file mode 100644 index 0000000000..0e5ca98471 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffText.vue @@ -0,0 +1,180 @@ + + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/CaseDiffViewer.vue b/test-track/frontend/src/business/case/components/case/diff/CaseDiffViewer.vue new file mode 100644 index 0000000000..17a0dd4afc --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/CaseDiffViewer.vue @@ -0,0 +1,974 @@ + + + + + diff --git a/test-track/frontend/src/business/case/components/case/diff/version_diff.js b/test-track/frontend/src/business/case/components/case/diff/version_diff.js new file mode 100644 index 0000000000..0d9b45bf34 --- /dev/null +++ b/test-track/frontend/src/business/case/components/case/diff/version_diff.js @@ -0,0 +1,523 @@ +/** + * 存储版本信息的数据结构 + */ +class VersionData { + constructor({ diffArr }) { + this.diffArr = diffArr || []; + } +} + +/** + * 对比状态枚举 + */ +class StatusType { + /** + * 无差异 + */ + static NORMAL = 0; + + /** + * 创建 + */ + static CREATE = 1; + + /** + * 删除 + */ + static DELETE = 2; + + /** + * 格式变化 + */ + static FORMAT_CHANGE = 3; +} + +/** + * 内容格式组件枚举 + */ +class ContentType { + /** + * 文本类型 + */ + static TEXT = "TEXT"; + + /** + * 数组类型 + */ + static ARRAY = "ARRAY"; + + /** + * 自定义字段 + */ + static FIELD = "FIELD"; + + /** + * 文件对比 + */ + static FILE = "FILE"; + + /** + * 表格数据对比 + */ + static TABLE = "TABLE"; +} + +class AbstractVersionDiffExecutor { + diff() {} +} +/** + * 版本对比执行器 + */ +export default class DefaultDiffExecutor extends AbstractVersionDiffExecutor { + /** + * 构造器 + * @param {*} origin 原始对象 + * @param {*} target 对比对象 + * @param {*} extra 扩展属性 + */ + constructor(origin, target, extra = {}) { + super(); + this.origin = origin || {}; + this.target = target || {}; + + /** + * 基础信息 + * VersionData + */ + this.baseInfoDiffData = {}; + + this.tagDiffData = {}; + + /** + * 详细信息 + */ + this.contentDiffData = {}; + /** + * 自定义信息 + */ + this.customDiffData = []; + + /** + * 附件信息对比 + */ + this.attachmentDiffData = []; + } + + diff() { + // 处理 基础信息对比 + //模块变更检测 + this.baseInfoDiffData.modules = [ + { + diffArr: DiffUtil.diffText(this.origin.nodePath, this.target.nodePath), + }, + ]; + // 关联需求检测 + this.baseInfoDiffData.story = [ + { + diffArr: DiffUtil.diffText(this.origin.demandId, this.target.demandId), + }, + ]; + // 需求id检测 + this.baseInfoDiffData.storyId = [ + { + diffArr: DiffUtil.diffText( + this.origin.demandName, + this.target.demandName + ), + }, + ]; + // 标签信息处理 + this.tagDiffData.tags = [ + { + diffArr: DiffUtil.diffArray(this.origin.tags, this.target.tags), + }, + ]; + + // // 自定义信息处理 + // this.customDiffData = DiffUtil.diffCustomData( + // this.origin.customFieldForm, + // this.target.customFieldForm + // ); + + // 详细信息对比 + //名称对比 + this.contentDiffData.stepModel = "TEXT"; + this.contentDiffData.caseName = [ + { + diffArr: DiffUtil.diffText(this.origin.name, this.target.name), + }, + ]; + //前置条件对比 + this.contentDiffData.prerequisite = [ + { + diffArr: DiffUtil.diffText( + this.origin.prerequisite, + this.target.prerequisite, + true + ), + }, + ]; + //文本描述 + this.contentDiffData.stepDescription = [ + { + diffArr: DiffUtil.diffText( + this.origin.stepDescription, + this.target.stepDescription, + true + ), + }, + ]; + //预期结果 + this.contentDiffData.expectedResult = [ + { + diffArr: DiffUtil.diffText( + this.origin.expectedResult, + this.target.expectedResult, + true + ), + }, + ]; + //备注 + this.contentDiffData.remark = [ + { + diffArr: DiffUtil.diffText( + this.origin.remark, + this.target.remark, + true + ), + }, + ]; + + // {"name":"open——2614e2dd-bcf9-4bb1-88ec-9737940ad7fc——1673837163926——screenshot.png","size":"0 B","updateTime":1675700468279,"progress":100,"status":"error","creator":"Administrator","type":"PNG","isLocal":true} + // this.attachmentDiffData.attachment = DiffUtil.diffAttachment( + // this.origin, + // this.target + // ); + } + + diffAttachment(origin, target) { + this.attachmentDiffData.attachment = DiffUtil.diffAttachment( + origin, + target + ); + return this.attachmentDiffData.attachment; + } + + diffTableData(origin, target, key, props = []) { + return DiffUtil.diffTableData(origin, target, key, props); + } + + diffCustomData(fields1, fields2) { + // 自定义信息处理 + this.customDiffData = DiffUtil.diffCustomData(fields1, fields2); + return this.customDiffData; + } +} + +class DiffUtil { + static buildDiffData(status, content = "", type = ContentType.TEXT) { + let res = {}; + res.status = status; + res.body = { + type: type, + content: content, + }; + return res; + } + /** + * 对比 文本内容 + */ + static diffText(s1, s2, format = false) { + let resArr = []; + + //统一空参数 + if (s1 == "" || s1 == null || s1 == undefined) { + s1 = ""; + } + if (s2 == "" || s2 == null || s2 == undefined) { + s2 = ""; + } + // 无变化 -- s1===s2 + if (s1 == s2) { + //s1 s2 均可 + resArr.push(this.buildDiffData(StatusType.NORMAL, s1)); + return resArr; + } + + // 新增 -- s1不存在 s2存在 + if (!s1 && s2) { + resArr.push(this.buildDiffData(StatusType.CREATE, s2)); + return resArr; + } + + // 删除 -- s1存在 s2不存在 + if (s1 && !s2) { + resArr.push(this.buildDiffData(StatusType.DELETE, s1)); + return resArr; + } + + // 都不为空 + // 格式变化 -- s1、s2 均存在 且内容不一致 + if (format) { + resArr.push(this.buildDiffData(StatusType.FORMAT_CHANGE, s2)); + return resArr; + } + + // 差异按照 新增、删除 进行标记 + resArr.push(this.buildDiffData(StatusType.CREATE, s2)); + resArr.push(this.buildDiffData(StatusType.DELETE, s1)); + return resArr; + } + + /** + * 对比 数组 + * + * 从数组总找出 新增和删除的 + */ + static diffArray(arr1, arr2) { + let resArr = []; + //矫正参数 + if (!Array.isArray(arr1)) { + arr1 = []; + } + if (!Array.isArray(arr2)) { + arr2 = []; + } + //返回原始数据 + if ((!arr1 && !arr2) || arr1 == arr2) { + resArr.push( + this.buildDiffData(StatusType.NORMAL, arr1, ContentType.ARRAY) + ); + return resArr; + } + + let createArr = []; + let deleteArr = []; + let normalArr = []; + + if (arr1.length <= 0 && arr2.length > 0) { + // arr2 全部为新增 + createArr = arr2; + resArr.push( + this.buildDiffData(StatusType.CREATE, createArr, ContentType.ARRAY) + ); + return resArr; + } + + if (arr1.length > 0 && arr2.length <= 0) { + //arr1 全部为删除 + deleteArr = arr1; + resArr.push( + this.buildDiffData(StatusType.DELETE, deleteArr, ContentType.ARRAY) + ); + return resArr; + } + + //以旧数组为基准 判断新数组 新增或删除的 + for (let i = 0; i < arr2.length; i++) { + // 检测新增 + let f1 = arr1.find((v) => v == arr2[i]); + if (!f1) { + createArr.push(arr2[i]); + } else { + normalArr.push(arr2[i]); + } + } + + for (let i = 0; i < arr1.length; i++) { + // 检测删除 + let f2 = arr2.find((v) => v == arr1[i]); + if (!f2) { + deleteArr.push(arr1[i]); + } + } + + if (createArr.length > 0) { + resArr.push( + this.buildDiffData(StatusType.CREATE, createArr, ContentType.ARRAY) + ); + } + if (deleteArr.length > 0) { + resArr.push( + this.buildDiffData(StatusType.DELETE, deleteArr, ContentType.ARRAY) + ); + } + + // if (normalArr.length > 0) { + // resArr.push( + // this.buildDiffData(StatusType.NORMAL, normalArr, ContentType.ARRAY) + // ); + // } + + //无差异返回原来的 + if (createArr.length <= 0 && deleteArr.length <= 0) { + resArr.push( + this.buildDiffData(StatusType.NORMAL, arr2, ContentType.ARRAY) + ); + } + return resArr; + } + + /** + * 对比自定义字段信息 + */ + static diffCustomData(fields1, fields2) { + let resArr = []; + + if (!fields1) { + fields1 = {}; + } + if (!fields2) { + fields2 = {}; + } + if ((!fields1 && !fields2) || fields1 == fields2) { + // 无差异 + Object.keys(fields2).forEach((e) => { + resArr.push({ key: e, value: this.diffText(fields2[e], fields2[e]) }); + }); + return resArr; + } + + // fields1 不存在 fields2 存在 则fields2均为 新增字段 + // fields1 存在 fields2 不存在 则fields1均为 删除字段 + // 对比新增删除 + + Object.keys(fields2).forEach((e) => { + let findKey = Object.prototype.hasOwnProperty.call(fields1, e); + if (!findKey) { + resArr.push({ + key: e, + value: this.diffText(undefined, fields2[e]), + }); + } else { + //找到了 判断是否变更 + let oldData = fields1[e]; + let newData = fields2[e]; + resArr.push({ key: e, value: this.diffText(oldData, newData) }); + } + }); + + return resArr; + } + + /** + * 对比 文件 + */ + static diffAttachment(origin, target) { + //矫正参数 + if (!Array.isArray(origin)) { + origin = []; + } + if (!Array.isArray(target)) { + target = []; + } + let resArr = []; + let targetMap = new Map(); + let originMap = new Map(); + target.forEach((t) => { + targetMap.set(t.name, t); + }); + originMap.forEach((o) => { + originMap.set(o.name, o); + }); + + // 判读 + target.forEach((t) => { + let o = originMap.get(t.name); + if (!o) { + //新增 + t.diffStatus = StatusType.CREATE; + resArr.push(t); + } else { + //存在则对比 是否变更 + if ( + t.size !== o.size || + t.progress !== o.progress || + t.type !== o.type || + t.creator !== o.creator || + t.updateTime !== o.updateTime + ) { + // 格式变化 + t.diffStatus = StatusType.FORMAT_CHANGE; + resArr.push(t); + } else { + t.diffStatus = StatusType.NORMAL; + resArr.push(t); + } + } + }); + + origin.forEach((o) => { + let t = targetMap.get(o.name); + if (!t) { + //标识已经删除 + o.diffStatus = StatusType.DELETE; + resArr.push(o); + } + }); + return resArr; + } + + /** + * 对比表格数据 + */ + static diffTableData(origin, target, key, props = []) { + //对比两个表格数组 并填充 diffStatus属性 + if (!key) { + throw new Error("Diff key is undefined, please check it~"); + } + //矫正参数 + if (!Array.isArray(origin)) { + origin = []; + } + if (!Array.isArray(target)) { + target = []; + } + if (!Array.isArray(props)) { + props = []; + } + + let resArr = []; + //首先 基于key 将数组转为map备用 + let originMap = new Map(); + let targetMap = new Map(); + origin.forEach((o) => { + originMap.set(o[key], o); + }); + target.forEach((t) => { + targetMap.set(t[key], t); + }); + + target.forEach((t) => { + //从原始数组中找是否存在,不存在则为新建状态 + let o = originMap.get(t[key]); + if (!o) { + t.diffStatus = StatusType.CREATE; + resArr.push(t); + } else { + //存在则对比 props中的项目 查看差异 + let factor = true; + for (let i = 0; i < props.length; i++) { + let p = props[i]; + if (t[p] !== o[p]) { + factor = false; + break; + } + } + t.diffStatus = factor ? StatusType.NORMAL : StatusType.FORMAT_CHANGE; + resArr.push(t); + } + }); + + //逆向查找 找到已删除的 + origin.forEach((o) => { + let t = targetMap.get(o[key]); + if (!t) { + o.diffStatus = StatusType.DELETE; + resArr.push(o); + } + }); + + return resArr; + } +} diff --git a/test-track/frontend/src/business/case/components/common/CaseVersionHistory.vue b/test-track/frontend/src/business/case/components/common/CaseVersionHistory.vue index ef2f472232..aca433e9f1 100644 --- a/test-track/frontend/src/business/case/components/common/CaseVersionHistory.vue +++ b/test-track/frontend/src/business/case/components/common/CaseVersionHistory.vue @@ -147,6 +147,7 @@ import { getProjectVersions, isProjectVersionEnable, } from "metersphere-frontend/src/api/version"; +import { getTestCaseVersions } from "@/api/testCase"; export default { name: "CaseVersionHistory", @@ -224,13 +225,27 @@ export default { ); this.clearSelectData(); }, - getVersionOptionList(callback) { - getProjectVersions(this.currentProjectId).then((response) => { - this.versionOptions = response.data.filter((v) => v.status === "open"); - if (callback) { - callback(this.versionOptions); - } + async getVersionOptionList(callback) { + // getProjectVersions(this.currentProjectId).then((response) => { + // this.versionOptions = response.data.filter((v) => v.status === "open"); + // if (callback) { + // callback(this.versionOptions); + // } + // }); + let response = await getProjectVersions(this.currentProjectId); + let versions = response.data || []; + let getAllVersions = await getTestCaseVersions(this.currentId); + let allVersionCases = getAllVersions.data || []; + let tempMap = new Map(); + allVersionCases.forEach((c) => { + tempMap.set(c.versionId, c); }); + this.versionOptions = versions.filter((v) => { + return tempMap.get(v.id); + }); + if (callback) { + callback(this.versionOptions); + } }, updateUserDataByExternal() { if (this.testUsers && this.testUsers.length > 0) { @@ -440,8 +455,9 @@ export default { .icon { margin-left: 11.67px; margin-right: 4.6px; - width: 14.67px; - height: 13.33px; + margin-top: 3px; + /* width: 14.67px; + height: 13.33px; */ img { width: 100%; height: 100%; diff --git a/test-track/frontend/src/business/case/components/common/MsDrawerComponent.vue b/test-track/frontend/src/business/case/components/common/MsDrawerComponent.vue index f235447e67..4ac83fd59a 100644 --- a/test-track/frontend/src/business/case/components/common/MsDrawerComponent.vue +++ b/test-track/frontend/src/business/case/components/common/MsDrawerComponent.vue @@ -126,7 +126,7 @@ export default { return { isEmpty: false, imgUrl: "/assets/module/figma/icon_none.svg", - label: "暂无数据", + label: this.$t("case.no_data"), }; }, },