This commit is contained in:
2025-03-04 16:47:26 +08:00
parent afdf60bcc7
commit d03fa26ef3
11 changed files with 664 additions and 26 deletions

View File

@@ -0,0 +1,74 @@
package com.test.test.controller;
import com.test.common.annotation.Log;
import com.test.common.core.controller.BaseController;
import com.test.common.core.domain.AjaxResult;
import com.test.common.core.page.TableDataInfo;
import com.test.common.enums.BusinessType;
import com.test.common.utils.DateUtils;
import com.test.test.domain.TestCase;
import com.test.test.domain.TestCaseStep;
import com.test.test.domain.qo.IDQO;
import com.test.test.domain.qo.TestCaseListQO;
import com.test.test.service.ITestCaseService;
import com.test.test.service.ITestCaseStepService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 步骤Controller
*/
@RestController
@RequestMapping("/test/caseStep")
public class TestCaseStepController extends BaseController {
@Resource
private ITestCaseStepService testCaseStepService;
/**
* 查询步骤列表
*/
@GetMapping("/list")
public AjaxResult list(@Validated TestCaseStep qo) {
return success(testCaseStepService.selectTestCaseStepList(qo));
}
/**
* 获取步骤详细信息
*/
@PostMapping(value = "/detail")
public AjaxResult getInfo(@RequestBody IDQO qo) {
return success(testCaseStepService.selectTestCaseStepById(qo.getId()));
}
/**
* 新增步骤
*/
@Log(title = "步骤", businessType = BusinessType.INSERT)
@PostMapping("/add")
public AjaxResult add(@RequestBody TestCaseStep testCaseStep) {
testCaseStep.setCreateBy(getLoginUser().getUsername());
testCaseStep.setCreateTime(DateUtils.getNowDate());
return toAjax(testCaseStepService.insertTestCaseStep(testCaseStep));
}
/**
* 修改步骤
*/
@Log(title = "步骤", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
public AjaxResult edit(@RequestBody TestCaseStep testCaseStep) {
return toAjax(testCaseStepService.updateTestCaseStep(testCaseStep));
}
/**
* 删除步骤
*/
@Log(title = "步骤", businessType = BusinessType.DELETE)
@PostMapping("/del")
public AjaxResult remove(@RequestBody IDQO qo) {
return toAjax(testCaseStepService.deleteTestCaseStepById(qo.getId()));
}
}

View File

@@ -56,8 +56,8 @@
datasource_id,
sql_command,
count,
async,
interval,
`async`,
`interval`,
break_error,
pre_script,
post_script,

View File

@@ -0,0 +1,46 @@
import request from '@/utils/request'
// 查询步骤列表
export function listCaseStep(query) {
return request({
url: '/test/caseStep/list',
method: 'get',
params: query
})
}
// 查询步骤详细
export function getCaseStep(id) {
return request({
url: '/test/caseStep/detail',
method: 'post',
data: {id}
})
}
// 新增步骤
export function addCaseStep(data) {
return request({
url: '/test/caseStep/add',
method: 'post',
data: data
})
}
// 修改步骤
export function updateCaseStep(data) {
return request({
url: '/test/caseStep/edit',
method: 'post',
data: data
})
}
// 删除步骤
export function delCaseStep(id) {
return request({
url: '/test/caseStep/del',
method: 'post',
data: {id}
})
}

View File

@@ -23,7 +23,7 @@
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.header.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope)">删除</el-button>
<el-button v-if="form.header.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('header',scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -42,15 +42,12 @@
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.header.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope)">删除</el-button>
<el-button v-if="form.header.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('param', scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Body" name="Body">
<el-select v-model="form.contentType" style="width: 200px; margin-bottom: 16px">
<el-option v-for="dict in dict.type.content_type" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
<JsonEditorVue mode="text" v-model="form.body" class="jse-theme-dark"/>
</el-tab-pane>
</el-tabs>
@@ -89,7 +86,6 @@ export default {
key: "",
value: "",
}],
contentType: "application/json",
body: "",
},
}
@@ -139,8 +135,13 @@ export default {
}
}
},
handleDelete(scope) {
handleDelete(flag, scope) {
if (flag === "header") {
this.form.header.splice(scope.$index, 1)
}
if (flag === "param") {
this.form.param.splice(scope.$index, 1)
}
},
cancel() {
this.$tab.closeOpenPage({path: "/api"});

View File

@@ -1,16 +0,0 @@
<template>
</template>
<script>
export default {
name: "test",
created() {
console.log(this.$route.query.id);
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div class="app-container" v-loading="loading">
<el-form :model="form" ref="form">
<el-form-item label="用例名称" prop="name">
<el-input v-model="form.name" placeholder="请输入用例名称"/>
</el-form-item>
</el-form>
<el-tabs>
<el-tab-pane label="用例编排">
<step @submit="submit"/>
</el-tab-pane>
<!-- <el-tab-pane label="操作日志">-->
<!-- </el-tab-pane>-->
</el-tabs>
</div>
</template>
<script>
import {getCase, updateCase} from "@/api/test/case";
import Step from "@/views/test/case/detail/step.vue";
export default {
name: "test",
components: {Step},
created() {
getCase(this.$route.query.id).then(res => {
this.form = res.data;
this.name = res.data.name
})
},
data(){
return {
form: {},
name: "",
loading: false
}
},
methods: {
submit(list) {
this.loading = true;
if (this.form.name !== this.name) {
updateCase(this.form).then(res => {
this.saveStep(list)
})
} else {
this.saveStep(list)
}
},
saveStep(list) {
let p = []
list.forEach((item) => {
p.push({
...item,
caseId: this.$route.query.id,
});
})
console.log(p)
this.loading = false;
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,327 @@
<template>
<div class="app-container">
<el-form :model="form" ref="form">
<el-form-item label="步骤名称" prop="name">
<el-input v-model="form.name" placeholder="请输入步骤名称"/>
</el-form-item>
<el-form-item label="接口" prop="uri">
<template type="label">
<el-button type="text" size="mini" @click="() => this.open = true">引用接口</el-button>
</template>
<el-input placeholder="请输入接口路径" v-model="form.requestUrl" class="input-with-select">
<el-select v-model="form.requestMethod" slot="prepend">
<el-option v-for="dict in dict.type.http_method" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-input>
</el-form-item>
<el-form-item label="主机" prop="apiHttpId">
<el-select v-model="form.apiHttpId" style="width: 100%;" clearable @change="apiHttpIdHandleChange">
<el-option v-for="item in this.hosts" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
<el-row :gutter="12">
<el-col :span="4">
<el-form-item label="协议" prop="apiProtocol">
<el-select v-model="form.apiProtocol" style="width: 100%;" :disabled="!!form.apiHttpId">
<el-option v-for="dict in dict.type.http_protocol" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="14">
<el-form-item label="主机地址" prop="apiHost">
<el-input v-model="form.apiHost" placeholder="请输入主机地址" :disabled="!!form.apiHttpId"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="端口号" prop="apiPort">
<el-input v-model="form.apiPort" placeholder="请输入端口号" :disabled="!!form.apiHttpId"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-tabs v-model="activeName">
<el-tab-pane label="Headers" name="Headers">
<el-table :data="form.requestHeader">
<el-table-column label="参数名">
<template slot-scope="scope">
<el-input placeholder="请输入参数名" v-model="form.requestHeader[scope.$index].key" @input="e => handleTableEdit(e, 'header', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="示例值">
<template slot-scope="scope">
<el-input placeholder="请输入参数名" v-model="form.requestHeader[scope.$index].value" @input="e => handleTableEdit(e, 'header', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.requestHeader.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('header', scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Params" name="Params">
<el-table :data="form.requestParams">
<el-table-column label="参数名">
<template slot-scope="scope">
<el-input placeholder="请输入参数名" v-model="form.requestParams[scope.$index].key" @input="e => handleTableEdit(e, 'param', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="示例值">
<template slot-scope="scope">
<el-input placeholder="请输入示例值" v-model="form.requestParams[scope.$index].value" @input="e => handleTableEdit(e, 'param', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.requestParams.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('param', scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Body" name="Body">
<JsonEditorVue mode="text" v-model="form.requestBody" class="jse-theme-dark"/>
</el-tab-pane>
<el-tab-pane label="提取" name="assignment">
<el-table :data="form.assignment">
<el-table-column label="变量名">
<template slot-scope="scope">
<el-input placeholder="请输入变量名" v-model="form.assignment[scope.$index].name" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="提取方式">
<template slot-scope="scope">
<el-input placeholder="请输入提取方式" v-model="form.assignment[scope.$index].type" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="提取对象">
<template slot-scope="scope">
<el-input placeholder="请输入提取对象" v-model="form.assignment[scope.$index].content" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="提取表达式">
<template slot-scope="scope">
<el-input placeholder="请输入提取表达式" v-model="form.assignment[scope.$index].path" @input="e => handleTableEdit(e, 'assignment', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.assignment.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('assignment', scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="校验" name="assertion">
<el-table :data="form.assertion">
<el-table-column label="描述">
<template slot-scope="scope">
<el-input placeholder="请输入描述" v-model="form.assertion[scope.$index].name" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="对象">
<template slot-scope="scope">
<el-input placeholder="请输入对象" v-model="form.assertion[scope.$index].source" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="条件">
<template slot-scope="scope">
<el-input placeholder="请输入条件" v-model="form.assertion[scope.$index].fn" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="内容">
<template slot-scope="scope">
<el-input placeholder="请输入内容" v-model="form.assertion[scope.$index].target" @input="e => handleTableEdit(e, 'assertion', scope)" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template slot-scope="scope">
<el-button v-if="form.assertion.length > scope.$index+1" size="mini" type="text" icon="el-icon-delete" @click="handleDelete('assertion', scope)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<el-dialog title="选择接口" :visible.sync="open" width="80%" append-to-body>
<folder-page type="api" @click="folderHandleSelected" ref="folder">
<div v-if="queryParams.groupId && queryParams.groupId !== 0">
<el-table v-loading="loading" :data="apiList">
<el-table-column label="接口名称" align="center" prop="name"/>
<el-table-column label="接口请求类型" align="center" prop="method"/>
<el-table-column label="接口路径" align="center" prop="uri"/>
<el-table-column label="创建时间" align="center" prop="createTime"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="linkApi(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
</div>
<el-empty v-else />
</folder-page>
</el-dialog>
</div>
</template>
<script>
import JsonEditorVue from "json-editor-vue";
import {listHttp} from "@/api/test/http";
import FolderPage from "@/components/FolderPage/index.vue";
import {listApi} from "@/api/test/api";
export default {
components: {FolderPage, JsonEditorVue},
dicts: ['http_method', 'http_protocol'],
props: {
form: {
type: Object
}
},
data() {
return {
activeName: "Headers",
hosts: null,
open: false,
loading: false,
total: 0,
apiList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
groupId: null,
},
}
},
created() {
listHttp().then(res => {
this.hosts = res.data;
})
},
methods: {
folderHandleSelected(id) {
if (id) {
this.queryParams.groupId = id;
this.getList();
} else {
this.apiList = [];
}
},
getList() {
this.loading = true;
listApi(this.queryParams).then(response => {
this.apiList = response.rows;
this.total = response.total;
this.loading = false;
});
},
linkApi(row) {
console.log(row)
this.open = false
if (row) {
this.form.requestMethod = row.method
this.form.name = row.name
this.form.requestUrl = row.uri
this.form.requestBody = row.body
if (row.header && JSON.parse(row.header).length) {
this.form.requestHeader = JSON.parse(row.header);
this.form.requestHeader.push([{
key: "",
value: "",
}])
} else {
this.form.requestHeader = [{
key: "",
value: "",
}]
}
if (row.param && JSON.parse(row.param).length) {
this.form.requestParam = JSON.parse(row.param);
this.form.requestParam.push([{}])
} else {
this.form.requestParam = [{
key: "",
value: "",
}]
}
}
},
apiHttpIdHandleChange(e) {
let p = this.hosts.findLast(item => item.id === e)
if (p) {
this.form.apiHost = p.host;
this.form.apiPort = p.port;
this.form.apiProtocol = p.protocol;
if (p.header) {
this.form.requestHeader = JSON.parse(p.header);
this.form.requestHeader.push([{
key: "",
value: "",
}])
} else {
this.form.requestHeader = [{
key: "",
value: "",
}]
}
} else {
this.form.apiHttpId = undefined;
this.form.apiHost = undefined;
this.form.apiPort = undefined;
this.form.apiProtocol = undefined;
this.form.requestHeader = [{
key: "",
value: "",
}]
}
},
handleTableEdit(e, flag, scope) {
if (flag === "header") {
if (e && this.form.header.length === scope.$index + 1) {
this.form.header.push({
key: "",
value: ""
})
}
}
if (flag === "param") {
if (e && this.form.param.length === scope.$index + 1) {
this.form.param.push({
key: "",
value: ""
})
}
}
},
handleDelete(flag, scope) {
if (flag === "header") {
this.form.header.splice(scope.$index, 1)
}
if (flag === "param") {
this.form.param.splice(scope.$index, 1)
}
if (flag === "assignment") {
this.form.assignment.splice(scope.$index, 1)
}
if (flag === "assertion") {
this.form.assertion.splice(scope.$index, 1)
}
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-collapse-item__wrap {
padding: 0 16px;
}
::v-deep.el-select {
width: 130px;
}
.input-with-select ::v-deep .el-input-group__prepend {
background-color: #fff;
}
::v-deep .el-container {
height: unset;
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div class="app-container">
2
</div>
</template>
<script>
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
3
</div>
</template>
<script>
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
4
</div>
</template>
<script>
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div>
<div style="margin-bottom:20px;">
<el-dropdown trigger="click" @command="handleAdd">
<el-button type="primary" plain icon="el-icon-plus" size="mini">添加步骤</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="dict in dict.type.step_type" :command="dict.value" :key="dict.value">{{ dict.label }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<el-collapse v-model="activeName" accordion v-if="list && list.length">
<el-collapse-item :name="index" v-for="(item, index) in list" :key="index">
<template slot="title">
<el-button size="mini" plain type="warning" icon="el-icon-rank" circle style="margin-right: 12px"/>
<el-tag effect="plain" style="width: 70px;text-align: center;margin-right: 12px;">{{ dict.type.step_type.findLast(t => t.value === item.type).label }}</el-tag>
<el-tag v-if="item.name" effect="plain" style="width: 240px;text-align: center;margin-right: 12px;" type="info">{{ item.name }}</el-tag>
<el-tag v-if="item.requestUrl" effect="plain" style="text-align: center;margin-right: 12px;" type="success">{{ item.requestUrl }}</el-tag>
</template>
<el-button size="mini" type="text" @click="handleDel(index)" style="float: right">删除</el-button>
<page1 v-if="item.type == 1" :form="item"/>
</el-collapse-item>
</el-collapse>
<el-empty v-else/>
<div class="footer">
<el-button type="primary" @click="submit"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</div>
</template>
<script>
import {listCaseStep} from "@/api/test/caseStep";
import page1 from "./page1.vue"
import page2 from "./page2.vue"
import page3 from "./page3.vue"
import page4 from "./page4.vue"
export default {
dicts: ['step_type'],
components: {page1, page2, page3, page4},
data() {
return {
activeName: null,
list: [],
};
},
created() {
listCaseStep({caseId: this.$route.query.id}).then(res => {
this.list = res.data;
});
},
methods: {
handleAdd(type) {
this.list.push({
name: "",
type: type,
requestMethod: 'POST',
requestUrl: "",
apiHttpId: null,
requestHeader: [{
key: "",
value: "",
}],
requestParams: [{
key: "",
value: "",
}],
assignment:[{
name: "",
type: "",
content: "",
path: "",
}],
assertion:[{
name: "",
source: "",
fn: "",
target: ""
}],
requestBody: "",
});
},
submit() {
this.$emit("submit", this.list);
},
cancel() {
this.$tab.closeOpenPage({path: "/case"});
},
handleDel(index) {
this.list.splice(index, 1)
}
}
}
</script>
<style scoped lang="scss">
.footer {
margin-top: 16px;
text-align: right;
}
</style>