diff --git a/frontend/src/business/components/common/components/MsTableHeader.vue b/frontend/src/business/components/common/components/MsTableHeader.vue
index b21893453a..554b033e4f 100644
--- a/frontend/src/business/components/common/components/MsTableHeader.vue
+++ b/frontend/src/business/components/common/components/MsTableHeader.vue
@@ -15,8 +15,8 @@
-
+
@@ -156,6 +156,7 @@
projectId() {
return getCurrentProjectID();
},
+
}
}
diff --git a/frontend/src/business/components/performance/test/DiffVersion.vue b/frontend/src/business/components/performance/test/DiffVersion.vue
new file mode 100644
index 0000000000..ca588a44f8
--- /dev/null
+++ b/frontend/src/business/components/performance/test/DiffVersion.vue
@@ -0,0 +1,226 @@
+
+
+
+ 1
+ v1
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v1
+ 1
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/performance/test/EditPerformanceTest.vue b/frontend/src/business/components/performance/test/EditPerformanceTest.vue
index 40e5470f06..a41510fbf3 100644
--- a/frontend/src/business/components/performance/test/EditPerformanceTest.vue
+++ b/frontend/src/business/components/performance/test/EditPerformanceTest.vue
@@ -76,6 +76,18 @@
+
+
+
+
+
@@ -91,6 +103,7 @@ import MsScheduleConfig from "../../common/components/MsScheduleConfig";
import MsChangeHistory from "../../history/ChangeHistory";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import MsTipButton from "@/business/components/common/components/MsTipButton";
+import DiffVersion from "@/business/components/performance/test/DiffVersion";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const versionHistory = requireComponent.keys().length > 0 ? requireComponent("./version/VersionHistory.vue") : {};
@@ -108,14 +121,19 @@ export default {
MsMainContainer,
MsChangeHistory,
'MsVersionHistory': versionHistory.default,
+ DiffVersion
},
inject: [
'reload'
],
data() {
return {
+ dialogVisible:false,
result: {},
test: {schedule: {}, follows: []},
+ oldData: {schedule: {}, follows: []},
+ newData:{schedule: {}, follows: []},
+ newShowFollow:false,
savePath: "/performance/save",
editPath: "/performance/edit",
runPath: "/performance/run",
@@ -183,7 +201,10 @@ export default {
},
importAPITest() {
let apiTest = this.$store.state.test;
+ console.log("输出vuex的test")
+ console.log(apiTest)
if (apiTest && apiTest.name) {
+ console.log("set test name")
this.$set(this.test, "name", apiTest.name);
if (apiTest.jmx.scenarioId) {
this.$refs.basicConfig.importScenario(apiTest.jmx.scenarioId);
@@ -220,7 +241,10 @@ export default {
this.$store.commit("clearTest");
} else {
let scenarioJmxs = this.$store.state.scenarioJmxs;
+ console.log("输出vuex的scenarioJmxs")
+ console.log(scenarioJmxs)
if (scenarioJmxs && scenarioJmxs.name) {
+ console.log("set scenarioJmxs name")
this.$set(this.test, "name", scenarioJmxs.name);
let relateApiList = [];
if (scenarioJmxs.jmxs) {
@@ -444,6 +468,7 @@ export default {
};
},
fileChange(threadGroups) {
+ console.log("zou")
let handler = this.$refs.pressureConfig;
let csvSet = new Set;
@@ -524,7 +549,27 @@ export default {
});
},
compare(row) {
- // console.log(row);
+ this.oldData = this.test;
+ this.$get('/performance/get/' + row.id+"/"+this.test.refId, response => {
+ this.$get('/performance/get/' + response.data.id, res => {
+ if (res.data) {
+ this.newData = res.data;
+ this.$get('/performance/test/follow/' + response.data.id, resp => {
+ if(resp.data&&resp.data.follows){
+ for (let i = 0; i v.versionId === row.id)[0];
@@ -580,4 +625,5 @@ export default {
margin-right: 25px;
margin-top: 5px;
}
+
diff --git a/frontend/src/business/components/performance/v_node_diff.js b/frontend/src/business/components/performance/v_node_diff.js
new file mode 100644
index 0000000000..8feeed4d49
--- /dev/null
+++ b/frontend/src/business/components/performance/v_node_diff.js
@@ -0,0 +1,347 @@
+
+function isUndef (v) {
+ return v === undefined || v === null
+}
+
+function isDef (v){
+ return v !== undefined && v !== null
+}
+
+function isTrue (v){
+ return v === true
+}
+
+function makeMap (str, expectsLowerCase){
+ const map = Object.create(null)
+ const list= str.split(',')
+ for (let i = 0; i < list.length; i++) {
+ map[list[i]] = true
+ }
+ return expectsLowerCase
+ ? val => map[val.toLowerCase()]
+ : val => map[val]
+}
+
+const isTextInputType = makeMap('text,number,password,search,email,tel,url')
+
+/**
+ * 该比较方法认为 1,2 -> 2,1 版本是有改变的
+ * @param oldDom 被比较的节点
+ * @param newDom 比较的节点
+ */
+export function diff (oldDom, newDom) {
+ let diffNode = {
+ oldNodeArray:[],
+ nodeArray:[],
+ }
+ //两个独立的节点传过来 首先单独比较一下他们最外层的 dom的_vnode 如果 _vnode 相同 则认为最外层节点相同,否则更改样式 return
+ let oldVnode = oldDom._vnode;
+ let newVnode = newDom._vnode;
+
+ if (isUndef(newVnode)) { //如果新节点不存在
+ //如果旧节点存在, 旧节点加背景颜色
+ if (isDef(oldVnode)) {
+ diffNode.oldNodeArray.push(oldVnode.elm)
+ }
+ return
+ }
+ if (isUndef(oldVnode)) {//如果旧节点不存在
+ //如果新节点存在, 新节点加背景颜色
+ if (isDef(newVnode)) {
+ diffNode.nodeArray.push(newVnode.elm)
+ }
+ return
+ }
+
+ if (sameVnode(oldVnode, newVnode)) {
+ //逐层比较节点 这里涉及到 children 的length 可能不同的情况
+ diffDetail(oldVnode, newVnode,diffNode)
+
+ } else {
+ diffNode.oldNodeArray.push(oldVnode.elm)
+ diffNode.nodeArray.push(newVnode.elm)
+ }
+
+ changeStyle(diffNode);
+}
+
+function changeStyle(diffNode){
+ console.log("查看结果");
+ console.log(diffNode.oldNodeArray);
+ console.log(diffNode.nodeArray);
+ for (let i = 0; i < diffNode.oldNodeArray.length; i++) {
+ if(diffNode.oldNodeArray[i]==='comment'||isUndef(diffNode.oldNodeArray[i].style)){
+ continue
+ }
+ if(diffNode.oldNodeArray[i].className==='cell'){
+ let rowVnodeElm = findRowVnodeElm(diffNode.oldNodeArray[i]);
+ rowVnodeElm.style.setProperty("background-color","rgb(241,200,196)",'important')
+ }else{
+ changeStyleBySubset(diffNode.oldNodeArray[i],"rgb(241,200,196)");
+ }
+ }
+
+ for (let i = 0; i < diffNode.nodeArray.length; i++) {
+ if(diffNode.nodeArray[i]==='comment'||isUndef(diffNode.nodeArray[i].style)){
+ continue
+ }
+ if(diffNode.nodeArray[i].className==='cell'){
+ let rowVnodeElm = findRowVnodeElm(diffNode.nodeArray[i]);
+ rowVnodeElm.style.setProperty("background-color","rgb(215, 243, 215)",'important')
+ }else{
+ changeStyleBySubset(diffNode.nodeArray[i],"rgb(215, 243, 215)");
+ }
+ }
+}
+
+function changeStyleBySubset(vnodeElm,color){
+ if(isDef(vnodeElm.children)&&vnodeElm.children.length>0){
+ if(isDef(vnodeElm.style)){
+ vnodeElm.style.setProperty("background-color",color,'important')
+ }
+ for (let i = 0; i < vnodeElm.children.length; i++) {
+ changeStyleBySubset(vnodeElm.children[i],color);
+ }
+ }else {
+ if(isDef(vnodeElm.style)){
+ vnodeElm.style.setProperty("background-color",color,'important')
+ }else {
+ vnodeElm.parentNode.style.setProperty("background-color",color,'important')
+ }
+ }
+}
+
+/**
+ *
+ * @param oldChildren Array
+ * @param newChildren Array
+ * @param diffNode
+ * @param isCompareChildren
+ */
+function diffChildren(oldChildren,newChildren,diffNode,isCompareChildren){
+ let oldLength = oldChildren.length;
+ let newLength = newChildren.length;
+ //如果isCompareChildren===true,证明是需要轮巡比较table内容是否有重复的,方法是比较每个元素的最子dom,tr的td的length是相等的;
+ if(isCompareChildren===true){
+ let oldIndexArray = [];
+ let newIndexArray = [];
+ //现在oldChildren,newChildren是tbody的所有数据,
+ for (let i = 0; i < oldLength; i++) {
+ for (let j = 0; j < newLength; j++) {
+ let sameNode = {
+ nodeArray:[],
+ }
+ //找每行的数据,但是行是确定的,这里应该直接比较每个tr的 children
+ sameDetail(oldChildren[i],newChildren[j],sameNode)
+ if(sameNode.nodeArray.length>0){
+ //证明oldChildren[i]与 newChildren[j] 是相同的 内容
+ if(oldIndexArray.indexOf(i) === -1){
+ oldIndexArray.push(i);
+ }
+ if(newIndexArray.indexOf(j) === -1){
+ newIndexArray.push(j);
+ }
+ }
+ }
+ }
+ for (let j = 0; j < oldLength; j++) {
+ if(oldIndexArray.indexOf(j)===-1){
+ diffNode.oldNodeArray.push(oldChildren[j].elm);
+ }
+ }
+
+ for (let j = 0; j < newLength; j++) {
+ if(newIndexArray.indexOf(j)===-1){
+ diffNode.nodeArray.push(newChildren[j].elm);
+ }
+ }
+
+ }else {
+ //取二者公共长度
+ let childrenLength = Math.min(oldLength, newLength);
+ for (let i = 0; i < childrenLength; i++) {
+ let oldVnode = oldChildren[i]
+ let newVnode = newChildren[i]
+ diffDetail(oldVnode,newVnode,diffNode)
+ }
+ for (let i = childrenLength; i <= (oldLength - childrenLength); i++) {
+ if(oldChildren[i]){
+ diffNode.oldNodeArray.push(oldChildren[i].elm);
+ }
+ }
+ for (let i = childrenLength; i <= (newLength - childrenLength); i++) {
+ if(newChildren[i]){
+ diffNode.nodeArray.push(newChildren[i].elm);
+ }
+ }
+ }
+}
+function sameChildren(oldChildren,newChildren,sameNode){
+ let oldLength = oldChildren.length;
+ for (let i = 0; i < oldLength; i++) {
+ let oldVnode = oldChildren[i]
+ let newVnode = newChildren[i]
+ sameDetail(oldVnode,newVnode,sameNode)
+ if(sameNode.nodeArray.length===0){
+ return;
+ }
+ }
+}
+
+function sameDetail(oldVnode,newVnode,sameNode){
+ if(isDef(oldVnode.child)&&isDef(newVnode.child)){
+ let ovnode = oldVnode.child._vnode;
+ let nvnode = newVnode.child._vnode;
+ sameDetail(ovnode,nvnode,sameNode)
+ if(sameNode.nodeArray.length===0){
+ return;
+ }
+ }
+ if(isDef(oldVnode.children)&&isDef(newVnode.children)){
+ sameChildren(oldVnode.children,newVnode.children,sameNode)
+ }
+ //剩最后的子节点的时候,分类型做判断
+ if(isUndef(oldVnode.child)&&isUndef(newVnode.child)&&isUndef(oldVnode.children)&&isUndef(newVnode.children)){
+ if(isDef(oldVnode.text)&&isDef(newVnode.text)){
+ if(oldVnode.text===newVnode.text){
+ sameNode.nodeArray.push(newVnode.elm);
+ }else{
+ sameNode.nodeArray = [];
+ }
+ }else if(isDef(oldVnode.tag)&&isDef(newVnode.tag)){
+ if(oldVnode.tag==='input'&&newVnode.tag==='input'){
+ if(oldVnode.elm.value===newVnode.elm.value){
+ if(oldVnode.elm.checked!==undefined&&newVnode.elm.checked!==undefined){
+ if(oldVnode.elm.checked===newVnode.elm.checked){
+ sameNode.nodeArray.push(newVnode.elm);
+ }else {
+ sameNode.nodeArray = [];
+ }
+ }else {
+ sameNode.nodeArray.push(newVnode.elm);
+ }
+ }else{
+ sameNode.nodeArray = [];
+ }
+ }
+ }
+ else {
+ if(sameVnode(oldVnode,newVnode)){
+ sameNode.nodeArray.push(newVnode.elm);
+ }else{
+ sameNode.nodeArray = [];
+ }
+ }
+ }
+
+}
+
+
+function diffDetail(oldVnode,newVnode,diffNode){
+ if(isDef(oldVnode.child)&&isUndef(newVnode.child)){
+ diffNode.oldNodeArray.push(oldVnode.child._vnode.elm);
+ }
+ if(isDef(oldVnode.children)&&isUndef(newVnode.children)){
+ diffNode.oldNodeArray.push(oldVnode.elm);
+ }
+ if(isUndef(oldVnode.child)&&isDef(newVnode.child)){
+ diffNode.nodeArray.push(newVnode.child._vnode.elm);
+ }
+ if(isUndef(oldVnode.children)&&isDef(newVnode.children)){
+ diffNode.nodeArray.push(newVnode.elm);
+ }
+ if(isDef(oldVnode.child)&&isDef(newVnode.child)){
+ let ovnode = oldVnode.child._vnode;
+ let nvnode = newVnode.child._vnode;
+ diffDetail(ovnode,nvnode,diffNode)
+ }
+ if(isDef(oldVnode.children)&&isDef(newVnode.children)){
+ //处理节点数据结构为table的情况
+ let isCompareChildren = false;
+ if(oldVnode.tag==='tbody'&&newVnode.tag==='tbody'){
+ isCompareChildren = true;
+ }else
+ if(isDef(oldVnode.elm.className)&&isDef(newVnode.elm.className)){
+ if(oldVnode.elm.className==='el-collapse'&&newVnode.elm.className==='el-collapse'){
+ isCompareChildren = true;
+ }
+ }
+ diffChildren(oldVnode.children,newVnode.children,diffNode,isCompareChildren)
+ }
+ //剩最后的子节点的时候,分类型做判断
+ if(isUndef(oldVnode.child)&&isUndef(newVnode.child)&&isUndef(oldVnode.children)&&isUndef(newVnode.children)){
+
+ if(isDef(oldVnode.text)&&isDef(newVnode.text)){
+ if(oldVnode.text!==newVnode.text){
+ if(isDef(oldVnode.elm.style)){
+ diffNode.oldNodeArray.push(oldVnode.elm);
+ }else {
+ diffNode.oldNodeArray.push(oldVnode.elm.parentNode);
+ }
+ if(isDef(newVnode.elm.style)){
+ diffNode.nodeArray.push(newVnode.elm);
+ }else {
+ diffNode.nodeArray.push(newVnode.elm.parentNode);
+ }
+ }
+ }else if(isDef(oldVnode.tag)&&isDef(newVnode.tag)){
+ if(oldVnode.tag==='input'&&newVnode.tag==='input'){
+ if(oldVnode.elm.value!==newVnode.elm.value){
+ diffNode.oldNodeArray.push(oldVnode.elm);
+ diffNode.nodeArray.push(newVnode.elm);
+ }else {
+ if(oldVnode.elm.checked!==undefined&&newVnode.elm.checked!==undefined){
+ if(oldVnode.elm.checked!==newVnode.elm.checked){
+ diffNode.oldNodeArray.push(oldVnode.elm);
+ diffNode.nodeArray.push(newVnode.elm);
+ }
+ }
+ }
+ }
+ }
+ else {
+ if(!sameVnode(oldVnode,newVnode)){
+ diffNode.oldNodeArray.push(oldVnode.elm);
+ diffNode.nodeArray.push(newVnode.elm);
+ }
+ }
+ }
+
+}
+
+function sameVnode (a, b) {
+ return (
+ a.key === b.key &&
+ a.asyncFactory === b.asyncFactory && (
+ (
+ a.tag === b.tag &&
+ a.isComment === b.isComment &&
+ isDef(a.data) === isDef(b.data) &&
+ sameInputType(a, b)
+ ) || (
+ isTrue(a.isAsyncPlaceholder) &&
+ isUndef(b.asyncFactory.error)
+ )
+ )
+ )
+}
+
+function findRowVnodeElm(nodeElm){
+ if(nodeElm.localName==="td"||nodeElm.className==="cell"){
+ return findRowVnodeElm(nodeElm.parentNode)
+ }else if(nodeElm.localName==="tr"){
+ return nodeElm;
+ }else {
+ return nodeElm;
+ }
+}
+
+
+function sameInputType (a, b) {
+ if (a.tag !== 'input') return true
+ let i
+ const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
+ const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
+ return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
+}
+
diff --git a/frontend/src/business/components/performance/vnode.ts b/frontend/src/business/components/performance/vnode.ts
new file mode 100644
index 0000000000..feb8ab3fdb
--- /dev/null
+++ b/frontend/src/business/components/performance/vnode.ts
@@ -0,0 +1,99 @@
+/* @flow */
+import {Component, ComponentOptions, VNodeComponentOptions, VNodeData} from "vue";
+
+export default class VNode {
+ tag: string | void;
+ data: VNodeData | void;
+ children: VNode[];
+ text: string | void;
+ elm: Node | void;
+ ns: string | void;
+ context: Component | void; // rendered in this component's scope
+ key: string | number | void;
+ componentOptions: VNodeComponentOptions | void;
+ componentInstance: Component | void; // component instance
+ parent: VNode | void; // component placeholder node
+
+ // strictly internal
+ raw: boolean; // contains raw HTML? (server only)
+ isStatic: boolean; // hoisted static node
+ isRootInsert: boolean; // necessary for enter transition check
+ isComment: boolean; // empty comment placeholder?
+ isCloned: boolean; // is a cloned node?
+ isOnce: boolean; // is a v-once node?
+ asyncFactory: Function | void; // async component factory function
+ asyncMeta: Object | void;
+ isAsyncPlaceholder: boolean;
+ ssrContext: Object | void;
+ fnContext: Component | void; // real context vm for functional nodes
+ fnOptions: any; // for SSR caching
+ devtoolsMeta: Object; // used to store functional render context for devtools
+ fnScopeId: string; // functional scope id support
+
+ constructor(
+ tag?: string | void,
+ data?: VNodeData | void,
+ children?: VNode[],
+ text?: string | void,
+ elm?: Node | void,
+ context?: Component | void,
+ componentOptions?: VNodeComponentOptions | void,
+ asyncFactory?: Function | void
+ ) {
+ this.tag = tag
+ this.data = data
+ this.children = children
+ this.text = text
+ this.elm = elm
+ this.ns = undefined
+ this.context = context
+ this.fnContext = undefined
+ this.fnOptions = undefined
+ this.fnScopeId = undefined
+ this.key = data && data.key
+ this.componentOptions = componentOptions
+ this.componentInstance = undefined
+ this.parent = undefined
+ this.raw = false
+ this.isStatic = false
+ this.isRootInsert = true
+ this.isComment = false
+ this.isCloned = false
+ this.isOnce = false
+ this.asyncFactory = asyncFactory
+ this.asyncMeta = undefined
+ this.isAsyncPlaceholder = false
+ }
+
+ // DEPRECATED: alias for componentInstance for backwards compat.
+ /* istanbul ignore next */
+ get child (): Component | void {
+ return this.componentInstance
+ }
+}
+
+export function cloneVNode (vnode: VNode): VNode {
+ const cloned = new VNode(
+ vnode.tag,
+ vnode.data,
+ // #7975
+ // clone children array to avoid mutating original in case of cloning
+ // a child.
+ vnode.children && vnode.children.slice(),
+ vnode.text,
+ vnode.elm,
+ vnode.context,
+ vnode.componentOptions,
+ vnode.asyncFactory
+ )
+ cloned.ns = vnode.ns
+ cloned.isStatic = vnode.isStatic
+ cloned.key = vnode.key
+ cloned.isComment = vnode.isComment
+ cloned.fnContext = vnode.fnContext
+ cloned.fnOptions = vnode.fnOptions
+ cloned.fnScopeId = vnode.fnScopeId
+ cloned.asyncMeta = vnode.asyncMeta
+ cloned.isCloned = true
+ return cloned
+}