This commit is contained in:
fit2-zhao 2020-12-10 18:16:52 +08:00
commit 518898658b
9 changed files with 521 additions and 408 deletions

View File

@ -3,20 +3,25 @@ package io.metersphere.service;
import io.metersphere.api.dto.APITestResult; import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.QueryAPITestRequest; import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.base.domain.Project; import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.UserRole;
import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.ext.*; import io.metersphere.base.mapper.ext.*;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.LoadTestDTO; import io.metersphere.dto.LoadTestDTO;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestPlanDTOWithMetric; import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testplan.QueryTestPlanRequest; import io.metersphere.track.request.testplan.QueryTestPlanRequest;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Service @Service
public class CheckOwnerService { public class CheckOwnerService {
@ -34,16 +39,24 @@ public class CheckOwnerService {
private ExtTestCaseReviewMapper extTestCaseReviewMapper; private ExtTestCaseReviewMapper extTestCaseReviewMapper;
public void checkProjectOwner(String projectId) { public void checkProjectOwner(String projectId) {
String workspaceId = SessionUtils.getCurrentWorkspaceId(); Set<String> workspaceIds = getUserRelatedWorkspaceIds();
Project project = projectMapper.selectByPrimaryKey(projectId); Project project = projectMapper.selectByPrimaryKey(projectId);
if (project == null) { if (project == null) {
return; return;
} }
if (!StringUtils.equals(workspaceId, project.getWorkspaceId())) { if (!workspaceIds.contains(project.getWorkspaceId())) {
throw new UnauthorizedException(Translator.get("check_owner_project")); throw new UnauthorizedException(Translator.get("check_owner_project"));
} }
} }
private Set<String> getUserRelatedWorkspaceIds() {
return Objects.requireNonNull(SessionUtils.getUser()).getUserRoles().stream()
.filter(ur ->
StringUtils.equalsAny(ur.getRoleId(), RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER))
.map(UserRole::getSourceId)
.collect(Collectors.toSet());
}
public void checkApiTestOwner(String testId) { public void checkApiTestOwner(String testId) {
// 关联为其他时 // 关联为其他时
if (StringUtils.equals("other", testId)) { if (StringUtils.equals("other", testId)) {

View File

@ -15,7 +15,8 @@
<el-dropdown-item command="closeAll">{{$t('api_test.definition.request.close_all_label')}}</el-dropdown-item> <el-dropdown-item command="closeAll">{{$t('api_test.definition.request.close_all_label')}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 主框架列表 -->
<!-- 主框架列表 -->
<el-tabs v-model="apiDefaultTab" @edit="handleTabsEdit"> <el-tabs v-model="apiDefaultTab" @edit="handleTabsEdit">
<el-tab-pane <el-tab-pane
:key="item.name" :key="item.name"
@ -57,8 +58,8 @@
<ms-run-test-dubbo-page :currentProtocol="currentProtocol" :api-data="runTestData" @saveAsApi="editApi" v-if="currentProtocol==='DUBBO'"/> <ms-run-test-dubbo-page :currentProtocol="currentProtocol" :api-data="runTestData" @saveAsApi="editApi" v-if="currentProtocol==='DUBBO'"/>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ms-main-container> </ms-main-container>
@ -266,4 +267,11 @@
/deep/ .el-main { /deep/ .el-main {
overflow: hidden; overflow: hidden;
} }
/deep/ .el-card {
/*border: 1px solid #EBEEF5;*/
/*border-style: none;*/
border-top: none;
}
</style> </style>

View File

@ -1,161 +1,166 @@
<template> <template>
<div> <div v-if="visible" v-loading="result.loading">
<el-container style="padding-bottom: 200px"> <ms-drawer :size="40" direction="bottom">
<el-header style="width: 100% ;padding: 0px">
<el-card>
<el-row>
<el-col :span="api.protocol==='HTTP'? 3:5">
<div class="variable-combine"> {{api.name}}</div>
</el-col>
<el-col :span="api.protocol==='HTTP'? 1:3">
<ms-tag v-if="api.status == 'Prepare'" type="info" effect="plain" :content="$t('test_track.plan.plan_status_prepare')"/>
<ms-tag v-if="api.status == 'Underway'" type="warning" effect="plain" :content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="api.status == 'Completed'" type="success" effect="plain" :content="$t('test_track.plan.plan_status_completed')"/>
</el-col>
<el-col :span="api.protocol==='HTTP'? 4:0">
<div class="variable-combine" style="margin-left: 10px">{{api.path ===null ? " " : api.path}}</div>
</el-col>
<el-col :span="2">
<div>{{$t('test_track.plan_view.case_count')}}{{apiCaseList.length}}</div>
</el-col>
<el-col :span="3">
<div>
<el-select size="small" :placeholder="$t('api_test.definition.request.grade_info')" v-model="priorityValue"
class="ms-api-header-select" @change="getApiTest">
<el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</div>
</el-col>
<el-col :span="4">
<div>
<el-select :disabled="isReadOnly" v-model="environment" size="small" class="ms-api-header-select"
:placeholder="$t('api_test.definition.request.run_env')"
@change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index"
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
:value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
</div>
</el-col>
<el-col :span="3">
<div class="ms-api-header-select">
<el-input size="small" :placeholder="$t('api_test.definition.request.select_case')"
v-model="name" @blur="getApiTest"/>
</div>
</el-col>
<el-col :span="2">
<el-dropdown size="small" split-button type="primary" class="ms-api-header-select" @click="addCase"
@command="handleCommand">
+{{$t('api_test.definition.request.case')}}
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="run">{{$t('commons.test')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<template v-slot:header>
</el-col> <el-header style="width: 100% ;padding: 0px">
<el-col :span="2"> <el-card>
<button type="button" aria-label="Close" class="el-card-btn" @click="apiCaseClose()"><i
class="el-dialog__close el-icon el-icon-close"></i></button>
</el-col>
</el-row>
</el-card>
<!-- 环境 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-header>
<!-- 用例部分 -->
<el-main v-loading="loading" style="overflow: auto">
<div v-for="(item,index) in apiCaseList" :key="index">
<el-card style="margin-top: 5px" @click.native="selectTestCase(item,$event)">
<el-row> <el-row>
<el-col :span="1"> <el-col :span="api.protocol==='HTTP'? 3:5">
<el-checkbox v-if="visible" @change="caseChecked(item)"/> <div class="variable-combine"> {{api.name}}</div>
</el-col> </el-col>
<el-col :span="api.protocol==='HTTP'? 1:3">
<el-col :span="5"> <ms-tag v-if="api.status == 'Prepare'" type="info" effect="plain" :content="$t('test_track.plan.plan_status_prepare')"/>
<div class="el-step__icon is-text ms-api-col"> <ms-tag v-if="api.status == 'Underway'" type="warning" effect="plain" :content="$t('test_track.plan.plan_status_running')"/>
<div class="el-step__icon-inner">{{index+1}}</div> <ms-tag v-if="api.status == 'Completed'" type="success" effect="plain" :content="$t('test_track.plan.plan_status_completed')"/>
</el-col>
<el-col :span="api.protocol==='HTTP'? 4:0">
<div class="variable-combine" style="margin-left: 10px">{{api.path ===null ? " " : api.path}}</div>
</el-col>
<el-col :span="2">
<div>{{$t('test_track.plan_view.case_count')}}{{apiCaseList.length}}</div>
</el-col>
<el-col :span="3">
<div>
<el-select size="small" :placeholder="$t('api_test.definition.request.grade_info')" v-model="priorityValue"
class="ms-api-header-select" @change="getApiTest">
<el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</div> </div>
<label class="ms-api-label">{{$t('test_track.case.priority')}}</label>
<el-select size="small" v-model="item.priority" class="ms-api-select">
<el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="4">
<i class="icon el-icon-arrow-right" :class="{'is-active': item.active}" <div>
@click="active(item)"/> <el-select :disabled="isReadOnly" v-model="environment" size="small" class="ms-api-header-select"
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index" :key="index" :placeholder="$t('api_test.definition.request.run_env')"
class="ms-api-header-select" style="width: 180px" @change="environmentChange" clearable>
@blur="saveTestCase(item)"/> <el-option v-for="(environment, index) in environments" :key="index"
<span v-else> :label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
:value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
</div>
</el-col>
<el-col :span="3">
<div class="ms-api-header-select">
<el-input size="small" :placeholder="$t('api_test.definition.request.select_case')"
v-model="name" @blur="getApiTest"/>
</div>
</el-col>
<el-col :span="2">
<el-dropdown size="small" split-button type="primary" class="ms-api-header-select" @click="addCase"
@command="handleCommand">
+{{$t('api_test.definition.request.case')}}
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="run">{{$t('commons.test')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
<el-col :span="2">
<button type="button" aria-label="Close" class="el-card-btn" @click="apiCaseClose()"><i
class="el-dialog__close el-icon el-icon-close"></i></button>
</el-col>
</el-row>
</el-card>
<!-- 环境 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
</el-header>
</template>
<el-container style="padding-bottom: 200px">
<el-main v-loading="loading" style="overflow: auto">
<div v-for="(item,index) in apiCaseList" :key="index">
<el-card style="margin-top: 5px" @click.native="selectTestCase(item,$event)">
<el-row>
<el-col :span="1">
<el-checkbox v-if="visible" @change="caseChecked(item)"/>
</el-col>
<el-col :span="5">
<div class="el-step__icon is-text ms-api-col">
<div class="el-step__icon-inner">{{index+1}}</div>
</div>
<label class="ms-api-label">{{$t('test_track.case.priority')}}</label>
<el-select size="small" v-model="item.priority" class="ms-api-select">
<el-option v-for="grd in priority" :key="grd.id" :label="grd.name" :value="grd.id"/>
</el-select>
</el-col>
<el-col :span="10">
<i class="icon el-icon-arrow-right" :class="{'is-active': item.active}"
@click="active(item)"/>
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index" :key="index"
class="ms-api-header-select" style="width: 180px"
@blur="saveTestCase(item)"/>
<span v-else>
{{item.type!= 'create' ? item.name:''}} {{item.type!= 'create' ? item.name:''}}
<i class="el-icon-edit" style="cursor:pointer" @click="showInput(item)"/> <i class="el-icon-edit" style="cursor:pointer" @click="showInput(item)"/>
</span> </span>
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px"> <div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span> <span>
{{item.createTime | timestampFormatDate }} {{item.createTime | timestampFormatDate }}
{{item.createUser}} {{$t('api_test.definition.request.create_info')}} {{item.createUser}} {{$t('api_test.definition.request.create_info')}}
</span> </span>
<span> <span>
{{item.updateTime | timestampFormatDate }} {{item.updateTime | timestampFormatDate }}
{{item.updateUser}} {{$t('api_test.definition.request.update_info')}} {{item.updateUser}} {{$t('api_test.definition.request.update_info')}}
</span> </span>
</div>
</el-col>
<el-col :span="4">
<ms-tip-button @click="singleRun(item)" :tip="$t('api_test.run')" icon="el-icon-video-play"
style="background-color: #409EFF;color: white" size="mini" circle/>
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
size="mini" circle/>
<ms-tip-button @click="deleteCase(index,item)" :tip="$t('commons.delete')" icon="el-icon-delete"
size="mini" circle/>
<ms-api-extend-btns :row="item"/>
</el-col>
<el-col :span="3">
<div v-if="item.type!='create'">{{getResult(item.execResult)}}</div>
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span> {{item.updateTime | timestampFormatDate }}</span>
{{item.updateUser}}
</div>
</el-col>
</el-row>
<!-- 请求参数-->
<el-collapse-transition>
<div v-if="item.active">
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :is-read-only="isReadOnly" :headers="item.request.headers " :request="item.request" v-if="api.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :request="item.request" v-if="api.protocol==='TCP'"/>
<ms-sql-basis-parameters :request="item.request" v-if="api.protocol==='SQL'"/>
<ms-dubbo-basis-parameters :request="item.request" v-if="api.protocol==='DUBBO'"/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
{{$t('commons.save')}}
</el-button>
</div> </div>
</el-col> </el-collapse-transition>
</el-card>
</div>
</el-main>
<el-col :span="4"> </el-container>
<ms-tip-button @click="singleRun(item)" :tip="$t('api_test.run')" icon="el-icon-video-play" </ms-drawer>
style="background-color: #409EFF;color: white" size="mini" circle/>
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
size="mini" circle/>
<ms-tip-button @click="deleteCase(index,item)" :tip="$t('commons.delete')" icon="el-icon-delete"
size="mini" circle/>
<ms-api-extend-btns :row="item"/>
</el-col>
<el-col :span="3">
<div v-if="item.type!='create'">{{getResult(item.execResult)}}</div>
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
<span> {{item.updateTime | timestampFormatDate }}</span>
{{item.updateUser}}
</div>
</el-col>
</el-row>
<!-- 请求参数-->
<el-collapse-transition>
<div v-if="item.active">
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :is-read-only="isReadOnly" :headers="item.request.headers " :request="item.request" v-if="api.protocol==='HTTP'"/>
<ms-tcp-basis-parameters :request="item.request" v-if="api.protocol==='TCP'"/>
<ms-sql-basis-parameters :request="item.request" v-if="api.protocol==='SQL'"/>
<ms-dubbo-basis-parameters :request="item.request" v-if="api.protocol==='DUBBO'"/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
{{$t('commons.save')}}
</el-button>
</div>
</el-collapse-transition>
</el-card>
</div>
</el-main>
</el-container>
<!-- 执行组件 --> <!-- 执行组件 -->
<ms-run :debug="false" :environment="environment" :reportId="reportId" :run-data="runData" <ms-run :debug="false" :environment="environment" :reportId="reportId" :run-data="runData"
@ -177,11 +182,13 @@
import MsSqlBasisParameters from "./request/database/BasisParameters"; import MsSqlBasisParameters from "./request/database/BasisParameters";
import MsTcpBasisParameters from "./request/tcp/BasisParameters"; import MsTcpBasisParameters from "./request/tcp/BasisParameters";
import MsDubboBasisParameters from "./request/dubbo/BasisParameters"; import MsDubboBasisParameters from "./request/dubbo/BasisParameters";
import MsDrawer from "../../../common/components/MsDrawer";
import MsApiExtendBtns from "./reference/ApiExtendBtns"; import MsApiExtendBtns from "./reference/ApiExtendBtns";
export default { export default {
name: 'ApiCaseList', name: 'ApiCaseList',
components: { components: {
MsDrawer,
MsTag, MsTag,
MsTipButton, MsTipButton,
MsApiRequestForm, MsApiRequestForm,
@ -198,16 +205,14 @@
type: Object type: Object
}, },
createCase: String, createCase: String,
visible: {
type: Boolean,
default: false,
},
loaded: Boolean, loaded: Boolean,
currentProject: {},
refreshSign: String, refreshSign: String,
currentRow: Object, currentRow: Object,
}, },
data() { data() {
return { return {
result: {},
grades: [], grades: [],
environments: [], environments: [],
environment: {}, environment: {},
@ -222,6 +227,7 @@
reportId: "", reportId: "",
projectId: "", projectId: "",
checkedCases: new Set(), checkedCases: new Set(),
visible: false
} }
}, },
@ -233,6 +239,12 @@
} }
this.getApiTest(); this.getApiTest();
}, },
currentProject() {
if (this.currentRow) {
this.currentRow.cases = [];
}
this.getEnvironments();
},
refreshSign() { refreshSign() {
if (this.currentRow) { if (this.currentRow) {
this.currentRow.cases = []; this.currentRow.cases = [];
@ -253,6 +265,10 @@
} }
}, },
methods: { methods: {
open() {
// this.apiCaseList = [];
this.visible = true;
},
sysAddition() { sysAddition() {
let condition = {}; let condition = {};
condition.projectId = this.api.projectId; condition.projectId = this.api.projectId;
@ -287,7 +303,7 @@
}, },
apiCaseClose() { apiCaseClose() {
this.apiCaseList = []; this.apiCaseList = [];
this.$emit('apiCaseClose'); this.visible = false;
}, },
batchRun() { batchRun() {
if (!this.environment) { if (!this.environment) {
@ -398,7 +414,7 @@
condition.apiDefinitionId = this.api.id; condition.apiDefinitionId = this.api.id;
condition.priority = this.priorityValue; condition.priority = this.priorityValue;
condition.name = this.name; condition.name = this.name;
this.$post("/api/testcase/list", condition, response => { this.result = this.$post("/api/testcase/list", condition, response => {
for (let index in response.data) { for (let index in response.data) {
let test = response.data[index]; let test = response.data[index];
test.request = JSON.parse(test.request); test.request = JSON.parse(test.request);
@ -499,6 +515,9 @@
} }
let arr = Array.from(this.checkedCases); let arr = Array.from(this.checkedCases);
this.currentRow.cases = arr; this.currentRow.cases = arr;
},
handleClose() {
this.visible = false;
} }
} }
} }

View File

@ -1,8 +1,7 @@
<template> <template>
<div id="svgBox" style="overflow: auto"> <div>
<div id="svgTop" style="background-color: white"> <el-card class="card-content">
<el-card class="card-content"> <el-input placeholder="搜索" @blur="search" class="search-input" size="small" v-model="condition.name"/>
<el-input placeholder="搜索" @blur="search" style="float: right ;width: 300px;margin-bottom: 20px;margin-right: 20px" size="small" v-model="condition.name"/>
<el-table border :data="tableData" row-key="id" class="test-content adjust-table" <el-table border :data="tableData" row-key="id" class="test-content adjust-table"
@select-all="handleSelectAll" @select-all="handleSelectAll"
@ -88,13 +87,8 @@
<ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize" <ms-table-pagination :change="initApiTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/> :total="total"/>
</el-card> </el-card>
</div> <ms-api-case-list @refresh="initApiTable" :currentRow="currentRow"
<div id="svgResize"/> :api="selectApi" ref="caseList"/>
<div id="svgDown">
<ms-bottom-container v-bind:enableAsideHidden="isHide">
<ms-api-case-list @apiCaseClose="apiCaseClose" @refresh="initApiTable" :visible="visible" :currentRow="currentRow" :api="selectApi"/>
</ms-bottom-container>
</div>
</div> </div>
</template> </template>
@ -132,7 +126,6 @@
data() { data() {
return { return {
condition: {}, condition: {},
isHide: true,
selectApi: {}, selectApi: {},
moduleId: "", moduleId: "",
deletePath: "/test/case/delete", deletePath: "/test/case/delete",
@ -162,17 +155,12 @@
this.projectId = getCurrentProjectID(); this.projectId = getCurrentProjectID();
this.initApiTable(); this.initApiTable();
}, },
mounted() {
this.dragControllerDiv();
},
watch: { watch: {
currentModule() { currentModule() {
this.initApiTable(); this.initApiTable();
this.apiCaseClose();
}, },
currentProtocol() { currentProtocol() {
this.initApiTable(); this.initApiTable();
this.apiCaseClose();
}, },
}, },
methods: { methods: {
@ -296,17 +284,10 @@
} }
}, },
handleTestCase(testCase) { handleTestCase(testCase) {
let h = window.screen.height;
let svgTop = document.getElementById("svgTop");
svgTop.style.height = h / 2 - 200 + "px";
let svgDown = document.getElementById("svgDown");
svgDown.style.height = h / 2 + "px";
this.selectApi = testCase; this.selectApi = testCase;
let request = JSON.parse(testCase.request); let request = JSON.parse(testCase.request);
this.selectApi.url = request.path; this.selectApi.url = request.path;
this.isHide = false; this.$refs.caseList.open();
}, },
handleDelete(api) { handleDelete(api) {
if (this.currentModule != undefined && this.currentModule.id == "gc") { if (this.currentModule != undefined && this.currentModule.id == "gc") {
@ -329,48 +310,11 @@
} }
}); });
}, },
apiCaseClose() {
let h = window.screen.height;
let svgTop = document.getElementById("svgTop");
svgTop.style.height = h - 200 + "px";
let svgDown = document.getElementById("svgDown");
svgDown.style.height = 0 + "px";
this.isHide = true;
},
getColor(enable, method) { getColor(enable, method) {
if (enable) { if (enable) {
return this.methodColorMap.get(method); return this.methodColorMap.get(method);
} }
}, },
dragControllerDiv: function () {
let svgResize = document.getElementById("svgResize");
let svgTop = document.getElementById("svgTop");
let svgDown = document.getElementById("svgDown");
let svgBox = document.getElementById("svgBox");
svgResize.onmousedown = function (e) {
let startY = e.clientY;
svgResize.top = svgResize.offsetTop;
document.onmousemove = function (e) {
let endY = e.clientY;
let moveLen = svgResize.top + (endY - startY);
let maxT = svgBox.clientHeight - svgResize.offsetHeight;
if (moveLen < 30) moveLen = 30;
if (moveLen > maxT - 30) moveLen = maxT - 30;
svgResize.style.top = moveLen;
svgTop.style.height = moveLen + "px";
svgDown.style.height = (svgBox.clientHeight - moveLen - 5) + "px";
}
document.onmouseup = function (evt) {
document.onmousemove = null;
document.onmouseup = null;
svgResize.releaseCapture && svgResize.releaseCapture();
}
svgResize.setCapture && svgResize.setCapture();
return false;
}
},
} }
} }
</script> </script>
@ -390,32 +334,11 @@
color: white; color: white;
} }
#svgBox { .search-input {
width: 100%; float: right;
height: 100%; width: 300px;
position: relative; /*margin-bottom: 20px;*/
overflow: hidden; margin-right: 20px;
} }
#svgTop {
height: calc(30% - 5px);
width: 100%;
float: left;
overflow: auto;
}
#svgResize {
position: relative;
height: 5px;
width: 100%;
cursor: s-resize;
float: left;
}
#svgDown {
height: 70%;
width: 100%;
float: left;
overflow: hidden;
}
</style> </style>

View File

@ -0,0 +1,90 @@
<template>
<div direction="vertical" :class="direction" @mousedown="mouseDown"></div>
</template>
<script>
export default {
name: "MsDragMoveBar",
data() {
return {
lastX: '',
lastY: '',
};
},
props: {
direction: {
type: String,
default() {
return 'vertical';
}
}
},
created() {
document.addEventListener("mouseup", this.mouseUp);
},
destroyed() {
document.removeEventListener("mouseup", this.mouseUp);
},
methods: {
mouseDown(event) {
document.addEventListener("mousemove", this.mouseMove);
this.lastX = event.screenX;
this.lastY = event.screenY;
},
mouseMove(event) {
this.$emit("widthChange", this.lastX - event.screenX);
this.$emit("heightChange", this.lastY - event.screenY);
this.lastX = event.screenX;
this.lastY = event.screenY;
},
mouseUp() {
this.lastX = "";
this.lastY = "";
document.removeEventListener("mousemove", this.mouseMove);
}
}
};
</script>
<style >
.drag-bar {
width: 100%;
height: 2px;
cursor: row-resize;
z-index: 10;
background: #ccc;
}
.horizontal {
width: 2px;
height: 100%;
cursor: col-resize;
z-index: 10;
}
.vertical {
width: 100%;
height: 2px;
cursor: row-resize;
z-index: 10;
}
.vertical:hover {
height: 3px;
background-color: #ccc;
/*-webkit-box-shadow: 0 8px 10px -5px rgba(0,0,0,.2), 0 16px 24px 2px rgba(0,0,0,.14), 0 6px 30px 5px rgba(0,0,0,.12);*/
/*box-shadow: 0 8px 10px -5px rgba(0,0,0,.2), 0 16px 24px 2px rgba(0,0,0,.14), 0 6px 30px 5px rgba(0,0,0,.12);*/
}
.horizontal:hover {
width: 3px;
/*background-color: #7C3985;*/
background-color: #ccc;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<div id="ms-drawer" class="ms-drawer" :class="directionStyle" :style="{width: w + 'px', height: h + 'px'}" ref="msDrawer">
<ms-drag-move-bar :direction="dragBarDirection" @widthChange="widthChange" @heightChange="heightChange"/>
<div class="ms-drawer-header" >
<slot name="header"></slot>
</div>
<div class="ms-drawer-body">
<slot></slot>
</div>
</div>
</template>
<script>
import MsDragMoveBar from "./MsDragMoveBar";
export default {
name: "MsDrawer",
components: {MsDragMoveBar},
data() {
return {
x: 0,
y: 0,
w: 100,
h: 100,
directionStyle: 'left-style',
dragBarDirection: 'vertical',
}
},
props: {
direction: {
type: String,
default() {
return "left";
}
},
size: {
type: Number,
default() {
return 40;
}
}
},
mounted() {
this.init();
},
methods: {
init() {
// todo
switch (this.direction) {
case 'left':
this.w = this.getWidthPercentage(this.size);
this.h = this.getHeightPercentage(100);
this.x = 0;
this.y = 0;
this.directionStyle = 'left-style';
this.dragBarDirection = 'horizontal';
break;
case 'right':
this.w = this.getWidthPercentage(this.size);
this.h = this.getHeightPercentage(100);
this.x = document.body.clientWidth - this.w;
this.y = 0;
this.directionStyle = 'right-style';
this.dragBarDirection = 'horizontal';
break;
case 'top':
this.w = this.getWidthPercentage(100);
this.h = this.getHeightPercentage(this.size);
this.x = 0;
this.y = 0;
this.directionStyle = 'top-style';
this.dragBarDirection = 'vertical';
break;
case 'bottom':
this.w = this.getWidthPercentage(100);
this.h = this.getHeightPercentage(this.size);
this.x = 0;
this.y = document.body.clientHeight - this.h;
this.directionStyle = 'bottom-style';
this.dragBarDirection = 'vertical';
break;
}
},
resize() {
},
getWidthPercentage(per) {
return document.body.clientWidth * per / 100.0;
},
getHeightPercentage(per) {
return document.body.clientHeight * per / 100.0;
},
widthChange(movement) {
if (this.direction != 'left' && this.direction != 'right') {
return;
}
switch (this.direction) {
case 'top':
this.w -= movement;
break;
case 'bottom':
this.w += movement;
break;
}
this._widthChange();
},
heightChange(movement) {
if (this.direction != 'top' && this.direction != 'bottom') {
return;
}
switch (this.direction) {
case 'top':
this.h -= movement;
break;
case 'bottom':
this.h += movement;
break;
}
this._heightChange();
},
_heightChange() {
if (this.h < 0) {
this.h = 0;
}
if (this.h > document.body.clientHeight) {
this.h = document.body.clientHeight;
}
},
_widthChange() {
if (this.w < 0) {
this.w = 0;
}
if (this.w > document.body.clientWidth) {
this.w = document.body.clientWidth;
}
}
}
}
</script>
<style scoped>
.ms-drawer {
background-color: white;
border: 1px #DCDFE6 solid;
-webkit-box-shadow: 0 8px 10px -5px rgba(0,0,0,.2), 0 16px 24px 2px rgba(0,0,0,.14), 0 6px 30px 5px rgba(0,0,0,.12);
box-shadow: 0 8px 10px -5px rgba(0,0,0,.2), 0 16px 24px 2px rgba(0,0,0,.14), 0 6px 30px 5px rgba(0,0,0,.12);
z-index: 999 !important;
position: fixed;
overflow: auto;
}
.left-style {
top: 0;
left: 0;
}
.right-style {
top: 0;
right: 0;
}
.top-style {
top: 0;
left: 0;
}
.bottom-style {
bottom: 0;
left: 0;
border-top: 5px;
}
.ms-drawer-body {
overflow: scroll;
}
.ms-drawer-header {
position: relative;
}
</style>

View File

@ -15,7 +15,7 @@
#body { #body {
width: 100%; width: 100%;
height: calc(100vh - 40px); height: calc(100vh - 40px);
background-color: #F5F5F5; /*background-color: #F5F5F5;*/
} }
.sidebar { .sidebar {

View File

@ -1,6 +1,10 @@
<template> <template>
<test-case-relevance-base ref="baseRelevance"> <test-case-relevance-base
@setProject="setProject"
@save="saveCaseRelevance"
:plan-id="planId"
ref="baseRelevance">
<template v-slot:aside> <template v-slot:aside>
<node-tree class="node-tree" <node-tree class="node-tree"
@ -11,7 +15,9 @@
</template> </template>
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/> <ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
<el-table <el-table
v-loading="result.loading"
:data="testCases" :data="testCases"
@filter-change="filter" @filter-change="filter"
row-key="id" row-key="id"
@ -58,104 +64,19 @@
<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div> <div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
<div style="text-align: center"> {{total}} </div> <div style="text-align: center"> {{total}} </div>
</test-case-relevance-base> </test-case-relevance-base>
<!--<div>-->
<!--<el-dialog :title="$t('test_track.plan_view.relevance_test_case')"-->
<!--:visible.sync="dialogFormVisible"-->
<!--@close="close"-->
<!--width="60%" v-loading="result.loading"-->
<!--:close-on-click-modal="false"-->
<!--top="50px">-->
<!--<el-container class="main-content">-->
<!--<el-aside class="tree-aside" width="250px">-->
<!--<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :-->
<!--$t('test_track.switch_project') }}-->
<!--</el-link>-->
<!--<node-tree class="node-tree"-->
<!--@nodeSelectEvent="nodeChange"-->
<!--@refresh="refresh"-->
<!--:tree-nodes="treeNodes"-->
<!--ref="nodeTree"/>-->
<!--</el-aside>-->
<!--<el-container>-->
<!--<el-main class="case-content">-->
<!--<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>-->
<!--<el-table-->
<!--:data="testCases"-->
<!--@filter-change="filter"-->
<!--row-key="id"-->
<!--@mouseleave.passive="leave"-->
<!--v-el-table-infinite-scroll="scrollLoading"-->
<!--@select-all="handleSelectAll"-->
<!--@select="handleSelectionChange"-->
<!--height="50vh"-->
<!--ref="table">-->
<!--<el-table-column-->
<!--type="selection"></el-table-column>-->
<!--<el-table-column-->
<!--prop="name"-->
<!--:label="$t('test_track.case.name')"-->
<!--style="width: 100%">-->
<!--<template v-slot:default="scope">-->
<!--{{scope.row.name}}-->
<!--</template>-->
<!--</el-table-column>-->
<!--<el-table-column-->
<!--prop="priority"-->
<!--:filters="priorityFilters"-->
<!--column-key="priority"-->
<!--:label="$t('test_track.case.priority')"-->
<!--show-overflow-tooltip>-->
<!--<template v-slot:default="scope">-->
<!--<priority-table-item :value="scope.row.priority"/>-->
<!--</template>-->
<!--</el-table-column>-->
<!--<el-table-column-->
<!--prop="type"-->
<!--:filters="typeFilters"-->
<!--column-key="type"-->
<!--:label="$t('test_track.case.type')"-->
<!--show-overflow-tooltip>-->
<!--<template v-slot:default="scope">-->
<!--<type-table-item :value="scope.row.type"/>-->
<!--</template>-->
<!--</el-table-column>-->
<!--</el-table>-->
<!--<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>-->
<!--<div style="text-align: center"> {{total}} </div>-->
<!--</el-main>-->
<!--</el-container>-->
<!--</el-container>-->
<!--<template v-slot:footer>-->
<!--<ms-dialog-footer @cancel="dialogFormVisible = false" @confirm="saveCaseRelevance"/>-->
<!--</template>-->
<!--</el-dialog>-->
<!--<switch-project ref="switchProject" @getProjectNode="getProjectNode"/>-->
<!--</div>-->
</template> </template>
<script> <script>
import NodeTree from '../../../../common/NodeTree'; import NodeTree from '../../../../common/NodeTree';
import MsDialogFooter from '../../../../../common/components/MsDialogFooter'
import PriorityTableItem from "../../../../common/tableItems/planview/PriorityTableItem"; import PriorityTableItem from "../../../../common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "../../../../common/tableItems/planview/TypeTableItem"; import TypeTableItem from "../../../../common/tableItems/planview/TypeTableItem";
import MsTableSearchBar from "../../../../../common/components/MsTableSearchBar"; import MsTableSearchBar from "../../../../../common/components/MsTableSearchBar";
import MsTableAdvSearchBar from "../../../../../common/components/search/MsTableAdvSearchBar"; import MsTableAdvSearchBar from "../../../../../common/components/search/MsTableAdvSearchBar";
import MsTableHeader from "../../../../../common/components/MsTableHeader"; import MsTableHeader from "../../../../../common/components/MsTableHeader";
import {TEST_CASE_CONFIGS} from "../../../../../common/components/search/search-components"; import {TEST_CASE_CONFIGS} from "../../../../../common/components/search/search-components";
import SwitchProject from "../../../../case/components/SwitchProject";
import elTableInfiniteScroll from 'el-table-infinite-scroll'; import elTableInfiniteScroll from 'el-table-infinite-scroll';
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase"; import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
import {_filter} from "../../../../../../../common/js/utils"; import {_filter} from "../../../../../../../common/js/utils";
@ -165,13 +86,11 @@
components: { components: {
TestCaseRelevanceBase, TestCaseRelevanceBase,
NodeTree, NodeTree,
MsDialogFooter,
PriorityTableItem, PriorityTableItem,
TypeTableItem, TypeTableItem,
MsTableSearchBar, MsTableSearchBar,
MsTableAdvSearchBar, MsTableAdvSearchBar,
MsTableHeader, MsTableHeader,
SwitchProject
}, },
directives: { directives: {
'el-table-infinite-scroll': elTableInfiniteScroll 'el-table-infinite-scroll': elTableInfiniteScroll
@ -179,7 +98,6 @@
data() { data() {
return { return {
result: {}, result: {},
dialogFormVisible: false,
isCheckAll: false, isCheckAll: false,
testCases: [], testCases: [],
selectIds: new Set(), selectIds: new Set(),
@ -219,13 +137,12 @@
this.condition.planId = this.planId; this.condition.planId = this.planId;
}, },
selectNodeIds() { selectNodeIds() {
if (this.dialogFormVisible) { this.search();
this.search();
}
}, },
projectId() { projectId() {
this.condition.projectId = this.projectId; this.condition.projectId = this.projectId;
this.getProjectNode(); this.getProjectNode();
this.search();
} }
}, },
updated() { updated() {
@ -235,8 +152,10 @@
open() { open() {
this.$refs.baseRelevance.open(); this.$refs.baseRelevance.open();
},
// setProject(projectId) {
this.projectId = projectId;
}, },
saveCaseRelevance() { saveCaseRelevance() {
@ -251,7 +170,9 @@
this.result = this.$post('/test/plan/relevance', param, () => { this.result = this.$post('/test/plan/relevance', param, () => {
this.selectIds.clear(); this.selectIds.clear();
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.dialogFormVisible = false;
this.$refs.baseRelevance.close();
this.$emit('refresh'); this.$emit('refresh');
}); });
}, },
@ -274,7 +195,27 @@
} }
if (this.projectId) { if (this.projectId) {
this.condition.projectId = this.projectId; this.condition.projectId = this.projectId;
this.result = this.$post(this.buildPagePath('/test/case/name'), this.condition, response => { // this.result = this.$post(this.buildPagePath('/test/case/name'), this.condition, response => {
// let data = response.data;
// this.total = data.itemCount;
// let tableData = data.listObject;
// tableData.forEach(item => {
// item.checked = false;
// });
// flag ? this.testCases = tableData : this.testCases = this.testCases.concat(tableData);
// //
// let hash = {}
// this.testCases = this.testCases.reduce((item, next) => {
// if (!hash[next.id]) {
// hash[next.id] = true
// item.push(next)
// }
// return item
// }, [])
//
// this.lineStatus = tableData.length === 50 && this.testCases.length < this.total;
// });
this.result = this.$post('/api/definition/list/1/10', this.condition, response => {
let data = response.data; let data = response.data;
this.total = data.itemCount; this.total = data.itemCount;
let tableData = data.listObject; let tableData = data.listObject;
@ -295,7 +236,6 @@
this.lineStatus = tableData.length === 50 && this.testCases.length < this.total; this.lineStatus = tableData.length === 50 && this.testCases.length < this.total;
}); });
} }
}, },
handleSelectAll(selection) { handleSelectAll(selection) {
if (selection.length > 0) { if (selection.length > 0) {
@ -325,7 +265,7 @@
this.close(); this.close();
}, },
scrollLoading() { scrollLoading() {
if (this.dialogFormVisible && this.lineStatus) { if (this.lineStatus) {
this.currentPage += 1; this.currentPage += 1;
this.getTestCases(); this.getTestCases();
} }
@ -336,7 +276,7 @@
testPlanId: this.planId, testPlanId: this.planId,
projectId: this.projectId projectId: this.projectId
}; };
this.result = this.$post("/case/node/list/all/plan", param, response => { this.result = this.$get('/api/module/list/' + this.project + '/HTTP', response => {
this.treeNodes = response.data; this.treeNodes = response.data;
}); });
} }
@ -361,24 +301,6 @@
}) })
}) })
}, },
getProject() {
if (this.planId) {
this.result = this.$post("/test/plan/project/", {planId: this.planId}, res => {
let data = res.data;
if (data) {
this.projects = data;
this.projectId = data[0].id;
this.projectName = data[0].name;
this.search();
//
this.getProjectNode(this.projectId)
}
})
}
},
switchProject() {
this.$refs.switchProject.open({id: this.planId, url: '/test/plan/project/', type: 'plan'});
},
getProjectNode(projectId) { getProjectNode(projectId) {
const index = this.projects.findIndex(project => project.id === projectId); const index = this.projects.findIndex(project => project.id === projectId);
if (index !== -1) { if (index !== -1) {
@ -387,11 +309,13 @@
if (projectId) { if (projectId) {
this.projectId = projectId; this.projectId = projectId;
} }
this.result = this.$post("/case/node/list/all/plan", this.$refs.nodeTree.result = this.$get('/api/module/list/' + this.projectId + '/HTTP', response => {
{testPlanId: this.planId, projectId: this.projectId}, response => {
this.treeNodes = response.data; this.treeNodes = response.data;
}); });
// this.$refs.nodeTree.result = this.$post('/api/module/list/' + this.project + '/HTTP',
// {testPlanId: this.planId, projectId: this.projectId}, response => {
// this.treeNodes = response.data;
// });
this.selectNodeIds = []; this.selectNodeIds = [];
} }
} }
@ -399,53 +323,4 @@
</script> </script>
<style scoped> <style scoped>
.tb-edit .el-input {
display: none;
color: black;
}
.tb-edit .current-row .el-input {
display: block;
}
.tb-edit .current-row .el-input + span {
display: none;
}
.node-tree {
margin-right: 10px;
}
.el-header {
background-color: darkgrey;
color: #333;
line-height: 60px;
}
.case-content {
padding: 0px 20px;
height: 100%;
/*border: 1px solid #EBEEF5;*/
}
.tree-aside {
min-height: 300px;
max-height: 100%;
}
.main-content {
min-height: 300px;
height: 100%;
/*border: 1px solid #EBEEF5;*/
}
.project-link {
float: right;
margin-right: 12px;
margin-bottom: 10px;
}
</style> </style>

View File

@ -16,6 +16,11 @@ body {
margin: 0; margin: 0;
} }
/* 解决 document.body.clientHeight 为0 */
html,body {
height:100%
}
.main-content span.title { .main-content span.title {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;