diff --git a/frontend/src/business/components/api/definition/ApiDefinition.vue b/frontend/src/business/components/api/definition/ApiDefinition.vue index 6a2a870049..e79c819510 100644 --- a/frontend/src/business/components/api/definition/ApiDefinition.vue +++ b/frontend/src/business/components/api/definition/ApiDefinition.vue @@ -29,7 +29,7 @@ class="ms-api-button" ref="environmentSelect"/> - + @@ -527,9 +527,58 @@ export default { }, handleTabClose() { let tabs = this.apiTabs[0]; - this.apiTabs = []; - this.apiDefaultTab = tabs.name; - this.apiTabs.push(tabs); + let message = ""; + let tab = this.apiTabs; + delete tab[0]; + tab.forEach(t => { + if (t.api && this.$store.state.apiMap.has(t.api.id) && (this.$store.state.apiMap.get(t.api.id).get("responseChange") === true || this.$store.state.apiMap.get(t.api.id).get("requestChange") === true || + this.$store.state.apiMap.get(t.api.id).get("fromChange") === true)) { + message += t.api.name + ","; + } + }) + if (message !== "") { + this.$alert("接口[ " + message.substr(0, message.length - 1) + " ]未保存,是否确认关闭全部?", '', { + confirmButtonText: this.$t('commons.confirm'), + cancelButtonText: this.$t('commons.cancel'), + callback: (action) => { + if (action === 'confirm') { + this.$store.state.apiMap.clear(); + this.apiTabs = []; + this.apiDefaultTab = tabs.name; + this.apiTabs.push(tabs); + } + } + }); + } else { + this.apiTabs = []; + this.apiDefaultTab = tabs.name; + this.apiTabs.push(tabs); + } + }, + closeConfirm(targetName) { + let tabs = this.apiTabs; + if(!tabs[1].api) { + this.handleTabRemove(targetName); + } + if (tabs[1].api && this.$store.state.apiMap.size > 0) { + if (this.$store.state.apiMap.get(tabs[1].api.id).get("responseChange") === true || this.$store.state.apiMap.get(tabs[1].api.id).get("requestChange") === true || + this.$store.state.apiMap.get(tabs[1].api.id).get("fromChange") === true) { + this.$alert("接口[ " + tabs[1].api.name + " ]未保存,是否确认关闭?", '', { + confirmButtonText: this.$t('commons.confirm'), + cancelButtonText: this.$t('commons.cancel'), + callback: (action) => { + if (action === 'confirm') { + this.$store.state.apiMap.delete(tabs[1].api.id); + this.handleTabRemove(targetName); + } + } + }); + } + } else{ + this.$store.state.apiMap.delete(tabs[1].api.id); + this.handleTabRemove(targetName); + } + }, handleTabRemove(targetName) { let tabs = this.apiTabs; diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue index a3f3b496c7..45ef6de2cd 100644 --- a/frontend/src/business/components/api/definition/components/ApiConfig.vue +++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue @@ -51,6 +51,8 @@ config: {}, response: {}, maintainerOptions: [], + count: 0, + responseCount: 0, } }, props: { @@ -60,6 +62,28 @@ syncTabs: Array, projectId: String }, + watch: { + request: { + handler(newObj, oldObj) { + this.count++ + if (this.count > 2) { + this.$store.state.apiStatus.set("requestChange", true); + this.$store.state.apiMap.set(this.currentApi.id, this.$store.state.apiStatus); + } + }, + deep: true + }, + }, + response: { + handler(newObj, oldObj) { + this.responseCount++; + if (this.responseCount > 1) { + this.$store.state.apiStatus.set("responseChange", true); + this.$store.state.apiMap.set(this.currentApi.id, this.$store.state.apiStatus); + } + }, + deep: true + }, created() { this.getMaintainerOptions(); switch (this.currentProtocol) { @@ -78,6 +102,12 @@ } this.formatApi(); this.addListener(); + if (!(this.$store.state.apiMap instanceof Map)) { + this.$store.state.apiMap = new Map(); + } + if (!(this.$store.state.apiStatus instanceof Map)) { + this.$store.state.apiStatus = new Map(); + } }, methods: { changeTab(type){ @@ -233,6 +263,7 @@ this.currentApi.isCopy = false; this.$emit('saveApi', data); }); + this.$store.state.apiMap.delete(this.currentApi.id); }, handleSave() { if (this.$refs.httpApi) { diff --git a/frontend/src/business/components/api/definition/components/complete/BasisApi.vue b/frontend/src/business/components/api/definition/components/complete/BasisApi.vue index 86e74ff19f..6dbcb62bc5 100644 --- a/frontend/src/business/components/api/definition/components/complete/BasisApi.vue +++ b/frontend/src/business/components/api/definition/components/complete/BasisApi.vue @@ -92,6 +92,48 @@ moduleOptions: Array, basisData: {}, }, + watch: { + 'basicForm.name': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.moduleId': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.status': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.follows': { + handler(v, v1) { + if (v && v1 && JSON.stringify(v) !== JSON.stringify(v1)) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.description': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + }, created() { this.getMaintainerOptions(); this.basicForm = this.basisData; diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue index a32e67b133..cd8e98888d 100644 --- a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue +++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue @@ -191,6 +191,62 @@ }, props: {moduleOptions: {}, request: {}, response: {}, basisData: {}, syncTabs: Array, projectId: String}, watch: { + 'httpForm.name': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.path': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.userId': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.moduleId': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.status': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.follows': { + handler(v, v1) { + if (v && v1 && JSON.stringify(v) !== JSON.stringify(v1)) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, + 'httpForm.description': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.httpForm.id, this.$store.state.apiStatus); + } + } + }, syncTabs() { if (this.basisData && this.syncTabs && this.syncTabs.includes(this.basisData.id)) { // 标示接口在其他地方更新过,当前页面需要同步 diff --git a/frontend/src/business/components/api/definition/components/complete/TCPBasicApi.vue b/frontend/src/business/components/api/definition/components/complete/TCPBasicApi.vue index 26d00731b4..50d54bb4d9 100644 --- a/frontend/src/business/components/api/definition/components/complete/TCPBasicApi.vue +++ b/frontend/src/business/components/api/definition/components/complete/TCPBasicApi.vue @@ -136,6 +136,56 @@ } }, + watch: { + 'basicForm.name': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.userId': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.moduleId': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.status': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.follows': { + handler(v, v1) { + if (v && v1 && JSON.stringify(v) !== JSON.stringify(v1)) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + 'basicForm.description': { + handler(v, v1) { + if (v && v1 && v !== v1) { + this.$store.state.apiStatus.set("fromChange", true); + this.$store.state.apiMap.set(this.basicForm.id, this.$store.state.apiStatus); + } + } + }, + }, methods: { getMaintainerOptions() { this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => { diff --git a/frontend/src/business/components/track/case/TestCase.vue b/frontend/src/business/components/track/case/TestCase.vue index 4d8c407e47..25415ac806 100644 --- a/frontend/src/business/components/track/case/TestCase.vue +++ b/frontend/src/business/components/track/case/TestCase.vue @@ -19,7 +19,7 @@ - + { + if (t && this.$store.state.testCaseMap.has(t.testCaseInfo.id) && this.$store.state.testCaseMap.get(t.testCaseInfo.id) > 1) { + message += t.testCaseInfo.name + ","; + } + }) + if (message !== "") { + this.$alert("用例[ " + message.substr(0, message.length - 1) + " ]未保存,是否确认关闭全部?", '', { + confirmButtonText: this.$t('commons.confirm'), + cancelButtonText: this.$t('commons.cancel'), + callback: (action) => { + if (action === 'confirm') { + this.$store.state.testCaseMap.clear(); + this.tabs = []; + this.activeName = "default"; + this.refresh(); + } + } + }); + } else { + this.tabs = []; + this.activeName = "default"; + this.refresh(); + } + }, + closeConfirm(targetName) { + let t = this.tabs.filter(tab => tab.name === targetName); + if (t && this.$store.state.testCaseMap.has(t[0].testCaseInfo.id) && this.$store.state.testCaseMap.get(t[0].testCaseInfo.id) > 1) { + this.$alert("用例[ " + t[0].testCaseInfo.name + " ]未保存,是否确认关闭?", '', { + confirmButtonText: this.$t('commons.confirm'), + cancelButtonText: this.$t('commons.cancel'), + callback: (action) => { + if (action === 'confirm') { + this.$store.state.testCaseMap.delete(t[0].testCaseInfo.id); + this.removeTab(targetName); + } + } + }); + } else { + this.$store.state.testCaseMap.delete(t[0].testCaseInfo.id); + this.removeTab(targetName); + } }, removeTab(targetName) { this.tabs = this.tabs.filter(tab => tab.name !== targetName); diff --git a/frontend/src/business/components/track/case/components/TestCaseCreate.vue b/frontend/src/business/components/track/case/components/TestCaseCreate.vue index f3f922c8b5..2e1aa2090c 100644 --- a/frontend/src/business/components/track/case/components/TestCaseCreate.vue +++ b/frontend/src/business/components/track/case/components/TestCaseCreate.vue @@ -96,7 +96,7 @@ export default { watch: { treeNodes() { this.getModuleOptions(); - } + }, }, computed: { projectId() { @@ -142,7 +142,7 @@ export default { // this.treeNodes.forEach(node => { // buildNodePath(node, {path: ''}, moduleOptions); // }); - if(this.currentModule!==undefined){ + if (this.currentModule !== undefined) { this.moduleOptions.forEach(item => { if (this.currentModule.id === item.id) { this.currentModule.path = item.path; diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue index c8b7d79f84..511c6bf85a 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue @@ -308,6 +308,18 @@ !hasPermission('PROJECT_TRACK_CASE:READ+EDIT'); } }, + watch: { + form: { + handler(val) { + if (this.$store.state.testCaseMap) { + let change = this.$store.state.testCaseMap.get(this.form.id); + change = change + 1; + this.$store.state.testCaseMap.set(this.form.id, change); + } + }, + deep: true + } + }, mounted() { this.getSelectOptions(); if (this.type === 'edit' || this.type === 'copy') { @@ -330,6 +342,10 @@ this.form.module = this.treeNodes[0].id; this.form.nodePath = this.treeNodes[0].path; } + if (!(this.$store.state.testCaseMap instanceof Map)) { + this.$store.state.testCaseMap = new Map(); + } + this.$store.state.testCaseMap.set(this.form.id, 0); }, created() { this.projectId = this.projectIds; @@ -557,7 +573,7 @@ this.$nextTick(() => { this.showInputTag = true; }); - + this.$store.state.testCaseMap.set(this.form.id, 0); }); }, async setFormData(testCase) { @@ -645,6 +661,7 @@ // 保存用例后刷新附件 this.$refs.otherInfo.getFileMetaData(this.form.id); }); + this.$store.state.testCaseMap.set(this.form.id, 0); } }, buildParam() { diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index d80079b50c..8c62778ba8 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -31,6 +31,9 @@ const state = { currentProjectIsCustomNum: false, testCaseTemplate: {}, scenarioMap: new Map(), + apiMap: new Map(), + apiStatus: new Map(), + testCaseMap: new Map() } const store = new Vuex.Store({