体验地址:
http://1.12.224.200/
账号:test
密码:test
后端数据库+接口实现
目录与接口绑定数据库呈现:
接口信息模型:
# 接口信息
class Testcase(models.Model):
t_name = models.CharField("用例名称", null=True, max_length=50)
t_method = models.IntegerField("请求方法", null=True) # 1:get, 2:post, 3:put, 4:delete
t_url = models.TextField("url请求地址", null=True)
t_params = models.CharField("请求参数", null=True, default=[], max_length=255)
t_type = models.IntegerField("参数类型", null=True, default=2) # 1:form-data, 2:Json, 3:x-www-form-urlencoded, 4:None
api_type = models.IntegerField("接口类型", null=True, default=1) # 1:普通接口,可做定制化接口
t_header = models.TextField("请求头", null=True, default={})
t_body = models.TextField("请求体", null=True, default={})
t_extract = models.CharField("参数提取", null=True, default=[], max_length=255)
update_time = models.DateTimeField("更新时间", auto_now=True)
create_time = models.DateTimeField("创建时间", auto_now_add=True)
t_environment = models.ForeignKey(Environment, on_delete=models.CASCADE, default=1)
t_user = models.ForeignKey(Userinfo, on_delete=models.CASCADE)
def __str__(self):
return self.t_name
# 本地变量
class Variable_local(models.Model):
local_name = models.CharField("变量名称", null=False, max_length=255)
local_data = models.CharField("变量路径", null=True, max_length=255)
local_result = models.CharField("变量结果", null=False, max_length=255)
local_description = models.CharField("变量描述", null=True, max_length=255)
update_time = models.DateTimeField("更新时间", auto_now=True)
create_time = models.DateTimeField("创建时间", auto_now_add=True)
local_user = models.ForeignKey(Userinfo, on_delete=models.CASCADE)
def __str__(self):
return self.local_name
# 断言
class Asserts(models.Model):
assert_key = models.CharField("断言字段名", null=False, max_length=255)
assert_value = models.TextField("断言结果", null=False)
assert_status = models.CharField("断言状态", null=True, max_length=255)
assert_testcase = models.ForeignKey(Testcase, on_delete=models.CASCADE)
def __str__(self):
return self.assert_key
获取接口信息接口实现:
class Singlecase(View):
# 获取单个测试用例
def post(self, request, *args, **kwargs):
data = json.loads(request.body)
case_id = data["menu_id"] # 通过目录id帮绑定接口id
menu = Menu.objects.get(id=case_id)
c_id = menu.testcase_id
asserts = Asserts.objects.filter(assert_testcase_id=c_id)
assert_list = []
for a in asserts:
assert_dict = {
"assert_key": a.assert_key,
"assert_value": a.assert_value
}
assert_list.append(assert_dict)
case = Testcase.objects.get(id=c_id)
case_dict = {
"id": menu.testcase_id,
"name": case.t_name,
"method": case.t_method,
"api_type": case.api_type,
"url": case.t_url,
"body_type": case.t_type,
"params": json.loads(case.t_params.replace("\'", "\"")),
"headers": case.t_header,
"extract": json.loads(case.t_extract.replace("\'", "\"")),
"body": json.loads(case.t_body.replace("\'", "\"")),
"environment_id": case.t_environment_id,
"assert_form": assert_list
}
return JsonResponse(case_dict)
环境模型:
class Environment(models.Model):
"""
API环境设置
"""
e_name = models.CharField("API环境名称", max_length=200, null=False)
e_address = models.CharField("API环境标题名称", max_length=200, null=False)
e_describe = models.TextField("API环境描述", default="", null=True)
create_time = models.DateTimeField("创建时间", auto_now_add=True)
update_time = models.DateTimeField("更新时间", auto_now_add=True)
e_user = models.ForeignKey(Userinfo, on_delete=models.CASCADE)
def __str__(self):
return self.e_name
获取环境接口:
class Env_list(View):
def post(self, request, *args, **kwargs):
envs = Environment.objects.all()
env_list = []
for env in envs:
env_dict = {
"environment_id": env.id,
"environment_name": env.e_name
}
env_list.append(env_dict)
return JsonResponse({
"code": 200,
"message": "获取环境列表成功",
"content": env_list
})
前面处理好了接下来就是url的配置啦:
# 在app文件夹下面新建urls文件夹
# urls文件下面根据不同模块新建底部文件
# 新增xxx_urls.py文件,格式跟下方保持一致即可,不做详细说明,举个例子而已
urlpatterns = [
path("api/enviroment/all", Env_view.as_view()), # 环境列表
path("api/enviroment/list", Env_list.as_view()), # 接口信息中的环境列表
path("api/enviroment/get_environment", get_environment), # 获取单个环境信息
path("api/enviroment/add_env", add_environment), # 新增环境
path("api/enviroment/edit_env", edit_environment), # 编辑环境
]
发送接口请求:
import json
import logging
import re
import urllib
from datetime import datetime
import hashlib
from urllib.parse import urlencode
import requests
from django.http import JsonResponse
from django.views import View
from lapi_app.models.environment_model.environment import Environment
from lapi_app.models.testcase_model.testcase import Variable_local, Testcase, Asserts
logger = logging.getLogger('django')
class Send_request(View):
def post(self, request, *args, **kwargs):
body_data = request.body
if not body_data:
return JsonResponse({
"code": 100,
"message": "参数异常"
})
data = json.loads(body_data)
t_id = data["testcase_id"]
t_url = data["url"]
t_type = data["body_type"]
t_body = data["body"]
t_method = data["method"]
t_params = data["params"]
t_headers = data["headers"]
t_assert = data["assert"]
api_type = data["api_type"]
t_env_id = data["environment_id"]
t_extract = data["variable_extract"]
# 每次发送请求相当于保存接口
if t_id != '' or t_id is not None:
cases = Testcase.objects.filter(id=t_id)
cases.update(t_url=t_url, t_extract=t_extract, t_type=t_type, t_body=t_body, t_params=t_params,
t_method=t_method, t_header=t_headers, t_environment_id=t_env_id, api_type=api_type,
update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# 请求的域名
req_api = Environment.objects.get(id=data["environment_id"])
# 请求地址
req_url = req_api.e_address + data["url"]
# url处理
if "${" in req_url and "}" in req_url:
key = re.findall(r"\${(.+?)}", req_url)
for a in range(len(key)):
v_key = "${" + key[a] + "}"
value_variable = Variable_local.objects.filter(local_name=str(v_key))
if value_variable.count() > 0:
variable = value_variable[0].local_result
req_url = req_url.replace(v_key, variable)
else:
return JsonResponse({
"code": 101,
"message": str(v_key) + "变量数据库未找到"
})
logger.info("请求url=" + req_url)
# body处理
if data["body"] == '' or data["body"] == {}:
payload = data["body"]
else:
try:
body = data["body"]
if "${" in body and "}" in body:
key = re.findall(r"\${(.+?)}", body)
for b in range(len(key)):
b_key = "${" + key[b] + "}"
value_variable = Variable_local.objects.filter(local_name=str(b_key))
if value_variable.count() > 0:
variable = value_variable[0].local_result
body = body.replace(b_key, variable)
else:
return JsonResponse({
"code": 101,
"message": str(b_key) + "变量数据库未找到"
})
payload = body
except Exception as e:
return JsonResponse({
"code": 101,
"message": "body参数类型错误",
"system_log": e
})
logger.info("请求体=" + str(payload))
# headers处理
if data["headers"] == '':
headers = data["headers"]
else:
try:
head = data["headers"]
if "${" in head and "}" in head:
key = re.findall(r"\${(.+?)}", head)
for b in range(len(key)):
h_key = "${" + key[b] + "}"
value_variable = Variable_local.objects.filter(local_name=str(h_key))
if value_variable.count() > 0:
variable = value_variable[0].local_result
head = head.replace(h_key, variable)
else:
return JsonResponse({
"code": 101,
"message": str(h_key) + "变量数据库未找到"
})
headers = head
except Exception as e:
return JsonResponse({
"code": 101,
"message": "header参数类型错误",
"system_log": e
})
logger.info("请求头=" + str(headers))
# 请求方法:1:get, 2:post, 3:put, 4:delete
# 参数类型:1:form-data, 2:Json, 3:x-www-form-urlencoded, 4:None
# method处理
# params处理
p = data["params"]
params = {}
if p != "":
for item in p:
params[item['key']] = item['value']
logger.info("params =" + str(params))
method = str(data['method'])
body_type = str(data['body_type'])
res = None
# Get
if method == "1":
if body_type == "1":
res = requests.get(req_url, params=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "2":
res = requests.get(req_url, json=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "3":
res = requests.get(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "4":
res = requests.get(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
# Post
if method == "2":
if body_type == "1":
res = requests.post(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "2":
res = requests.post(req_url, json=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "3":
res = requests.post(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "4":
res = requests.post(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
# Put
if method == "3":
if body_type == "1":
res = requests.put(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "2":
res = requests.put(req_url, json=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "3":
res = requests.put(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "4":
res = requests.post(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
# Delete
if method == "4":
if body_type == "1":
res = requests.delete(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "2":
res = requests.delete(req_url, json=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "3":
res = requests.delete(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if body_type == "4":
res = requests.delete(req_url, data=payload, headers=headers)
logger.info("响应结果 =" + res.text)
if res is None or res == "":
assert_res = res.text
else:
assert_res = json.loads(res.text)
# assert处理
assert_result = []
assert_a = data["assert"]
assert_data = {}
result = []
if assert_a != "":
for item in assert_a:
assert_data[item['assert_key']] = item['assert_value']
if assert_data[item['assert_key']] == '' or assert_data[item['assert_key']] is None:
assert_result.extend(result)
else:
for key in assert_data:
if (key in assert_res) and (assert_data[key] == str(assert_res[key])):
print(str(assert_res[key]))
assert_status = {
"status": True,
"assert_key": key,
"assert_value": assert_data[key]
}
else:
assert_status = {
"status": False,
"assert_key": key,
"assert_value": str(assert_data[key])
}
result.append(assert_status)
assert_result.extend(result)
logger.info("assert=" + str(assert_result))
if assert_result:
for ats in assert_result:
if Asserts.objects.filter(assert_testcase_id=t_id,
assert_key=str(ats["assert_key"])).exists():
Asserts.objects.filter(assert_testcase_id=t_id, assert_key=str(ats["assert_key"])). \
update(assert_status=str(ats["status"]), assert_value=ats["assert_value"])
else:
Asserts.objects.create(assert_testcase_id=t_id, assert_key=str(ats["assert_key"]),
assert_value=ats["assert_value"],
assert_status=str(ats["status"]))
elif not assert_result:
print("删除")
Asserts.objects.filter(assert_testcase_id=t_id).delete()
# variable提取处理
extract_result = []
extract = data["variable_extract"]
if extract is None or extract == "":
extract_result = []
else:
for v in extract:
if v["variable_name"] == "" or v["variable_data"] == "":
extract_dict = {
"message": "无参数提取"
}
extract_result.append(extract_dict)
else:
# 提取变量
try:
res_result = json.loads(res.text)
except Exception as e:
logger.info(str(e))
pass
v_data = v["variable_data"].split(".")
try:
for a in v_data:
if "[" in a and "]" in a:
variable_1 = a.split('[')[0]
variable_2 = a.split('[')[1].split(']')[0]
res_result = res_result[variable_1][variable_2]
else:
res_result = res_result[a]
extract_dict = {
"variable_name": v["variable_name"],
"variable_data": v["variable_data"],
"variable_result": str(res_result),
"status": 1
}
extract_result.append(extract_dict)
except Exception as e:
extract_dict = {
"variable_name": v["variable_name"],
"variable_data": v["variable_data"],
"variable_result": "参数提取失败" + str(e),
"status": 0
}
extract_result.append(extract_dict)
for e in extract_result:
if e["status"] == 1:
if Variable_local.objects.filter(local_name=e["variable_name"]).count() > 0:
Variable_local.objects.filter(local_name=e["variable_name"]). \
update(local_data=e["variable_data"], local_result=e["variable_result"])
elif Variable_local.objects.filter(local_name=e["variable_name"]).count() == 0:
Variable_local.objects.filter(local_name=e["variable_name"]). \
create(local_name=e["variable_name"], local_data=e["variable_data"],
local_result=e["variable_result"],
local_user_id=data["user_id"])
logger.info("参数提取结果 =" + str(extract_result))
logger.info("接口响应时间 =" + str(res.elapsed.total_seconds()) + 's')
# 返回接口请求结果,给到前端渲染
return JsonResponse({
"code": 200,
"host": req_url,
"params": params,
"headers": headers,
"body": payload,
"assert_data": assert_result,
"res_data": res.text,
"response_time": str(res.elapsed.total_seconds()) + '秒',
"variable": extract_result
})
实际效果:
请求结果:
断言:
参数提取:
接口请求详情:
Vue 页面展示
配置JS文件读取接口:
case_index.vue
<template>
<div class="app-container">
<div>
<div style="border-radius: 2px; float: left; width: 15%; padding-right:10px">
<el-input v-model="filterText" placeholder="请输入要搜索的节点" style="margin-bottom:30px;" />
<el-tree
ref="tree"
:data="tree_data"
:props="defaultProps"
:highlight-current= "true"
class="filter-tree"
:default-expanded-keys="[2]"
:filter-node-method="filterNode"
node-key="id"
@node-click="getcase"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span v-if="data.type===0"><i :class="icon0" style="padding-right: 10px"></i>{{ node.label }}</span>
<span v-if="data.type===1"><i :class="icon1" style="padding-right: 10px"></i>{{ node.label }}</span>
<span v-if="data.type===2"><i :class="icon2" style="padding-right: 10px"></i>{{ node.label }}</span>
<span v-if="data.type === 0" class="right" :class="{newStyle: 1 === number}">
<el-dropdown trigger="click" placement="bottom">
<span class="el-dropdown-link" style="font-size: 20px">···</span>
<el-dropdown-menu slot="dropdown" class="header-new-drop">
<el-dropdown-item @click.native="new_menu">新建子菜单</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
<span v-if="data.type === 1" class="right" :class="{newStyle: 1 === number}">
<el-dropdown trigger="click" placement="bottom">
<span class="el-dropdown-link" style="font-size: 20px">···</span>
<el-dropdown-menu slot="dropdown" class="header-new-drop">
<el-dropdown-item @click.native="new_menu">新增</el-dropdown-item>
<el-dropdown-item @click.native="edit">重命名</el-dropdown-item>
<el-dropdown-item @click.native="input">*postman.json文件导入</el-dropdown-item>
<el-dropdown-item @click.native="deletepro">删除文件夹</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
<span v-if="data.type ===2" class="right" :class="{newStyle: 2 === number}">
<el-dropdown trigger="click" placement="bottom">
<span class="el-dropdown-link" style="font-size: 20px">···</span>
<el-dropdown-menu slot="dropdown" class="header-new-drop">
<el-dropdown-item @click.native="edit">重命名</el-dropdown-item>
<el-dropdown-item @click.native="copy">复制接口</el-dropdown-item>
<el-dropdown-item @click.native="delete_case">删除接口</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</span>
</el-tree>
</div>
<div>
<el-dialog
title="新增子菜单"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<el-form :model="menu_pid">
<el-form-item label="Type:">
<el-select v-model="type_value">
<el-option
v-for="item in type_form"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="菜单名称">
<el-input v-model="menu_form.label"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="add_menu">确 定</el-button>
</span>
</el-dialog>
</div>
<div>
<el-dialog
title="重命名"
:visible.sync="editdialogVisible"
width="30%"
:before-close="handleClose">
<el-form :model="menu_form">
<el-form-item label="menu_id">
<el-input readonly v-model="menu_form.id"></el-input>
</el-form-item>
<el-form-item label="菜单名称">
<el-input v-model="tree_name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editdialogVisible = false">取 消</el-button>
<el-button type="primary" @click="edit_menu">确 定</el-button>
</span>
</el-dialog>
</div>
<el-dialog
:visible.sync="dialogImport"
title="导入"
v-if="dialogImport"
width="30%"
append-to-body>
<div style="text-align:center">
<!-- 此处action需为有效的任意接口——当前为官网范例接口 -->
<el-upload
drag
:limit="1"
action="https://jsonplaceholder.typicode.com/posts/"
ref="upload"
accept=".json"
:file-list="fileList"
:on-success="onSuccess"
:on-remove="onRemove"
:on-exceed="handleExceed"
:headers="{ 'Content-Type': 'application/x-www-form-urlencoded' }">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
可上传 postman 导出的 .json 文件,且只能上传 1 个文件
</div>
</el-upload>
</div>
<span slot="footer">
<el-button @click="dialogImport = false" size="mini" type="danger">取 消</el-button>
<el-button @click="inputcase" size="mini" type="primary">确 定</el-button>
</span>
</el-dialog>
<div v-show="1 === number">
<el-container>
<Case_list :table="table_data" v-if="update" style="float:left; width:100%; padding-top: 0"></Case_list>
</el-container>
</div>
<div v-show="2 === number">
<el-container>
// 数据传递给接口信息组件(:form="form_case")
<Case_info :form="form_case" v-if="update" style="float:left; width:100%; padding-top: 0"></Case_info>
</el-container>
</div>
</div>
</div>
</template>
<script>
import {
addmenu, copy_case, deletecase,
deletepeoject,
getallTestcase,
getCase_tree,
getsinglecase,
getsingleTestcaselist, uploadcase
} from "@/api/testcase";
import case_info from "./case_info"; //调用组件
import case_list from "./case_list";
export default {
name: 'case_index',
data() {
return {
uploadData: [],
fileList: [],
dialogImport: false,
editableTabsValue: '根目录',
tree_name: '',
editableTabs:[],
tabIndex: 2,
list: null,
number: 1,
filterText: '',
expaAndList:'',
listLoading: true,
input_type: false,
inputValue: '',
menu_pid: null,
update: true,
icon0: 'el-icon-s-home',
icon1: 'el-icon-folder-opened',
icon2: 'el-icon-tickets',
//列表
table_data: [], //目录
defaultProps: {
children: 'children',
label: 'label',
id:"id"
},
tree_data: [],
type_value: '',
type_form: [
{
label: "文件夹",
value: 1
},
{
label: "接口",
value: 2
},
],
//菜单
m_id: null,
menu_form:{
label: '',
pid: '',
type: null,
user_id: null,
},
// 用例id
case_form: {
menu_id: ''
},
// 用例详情
form_case: [
{
assert_form: [
{
assert_key: '',
assert_value: ''
}
],
params: [{key: '', value: ''}],
extract: [{variable_name: '', variable_data: ''}]
}
],
params_form:{},
editdialogVisible: false,
dialogVisible: false,
pidFrom: {
id: ''
},
}
},
components: {
"Case_info": case_info,
"Case_list": case_list
}
,
created() {
this.get_tree()
this.getall()
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
methods: {
filterNode(value, tree_data) {
if (!value) return true;
return tree_data.label.indexOf(value) !== -1;
},
// 导入文件
input(){
this.dialogImport =true
},
// 移除文件
onRemove(file) {
this.fileList = [];
},
// 文件上传成功
onSuccess(res, file, fileList) {
let reader = new FileReader()
reader.readAsText(file.raw)
reader.onload = (e) => {
this.uploadData = []
this.uploadData = JSON.parse(e.target.result)
}
},
// 导入确认
inputcase() {
this.$confirm("导入后原数据会被覆盖,确定导入吗?", "温馨提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",}).then(() => {
// 使用目标数据变量接收上传后的文件数据
this.dialogImport = false
this.uploadData.user_id = Number(localStorage.getItem("user_id"))
this.uploadData.pid = this.pidFrom.id
uploadcase(this.uploadData).then(res => {
if (200 === res.data.code) {
this.$message({
type: "success",
message: "导入成功!",
})
this.get_tree()
this.getsinglelist()
} else if (100 === res.data.code) {
this.$message({
type: "danger",
message: "导入失败!",
})
}
})
})
},
//刷新组件
reload() {
this.update = false
this.$nextTick(() => (this.update = true))
},
// 获取接口目录
get_tree(){
getCase_tree().then(res => {
this.tree_data = res.data.content
})
},
// 获取所有测试用例
getall(){
getallTestcase().then(res => {
this.table_data = res.data.content
})
},
// 获取单个文件夹列表
getsinglelist(){
getsingleTestcaselist(this.pidFrom).then(res => {
this.table_data = res.data.content
})
},
//获取单个接口详情
getsinglecase(){
getsinglecase(this.case_form).then(res => {
// 详细内容
this.form_case = res.data
let head = res.data.headers
this.form_case.headers = eval(`(${head})`)
})
},
// 复制接口
copy(){
let req = this.menu_form
req.id = this.case_form.menu_id
req.label = this.tree_name
req.pid = this.m_id
req.type = 2
req.user_id = Number(localStorage.getItem("user_id"))
copy_case(this.menu_form).then(res => {
if (res.data.code === 200){
this.$message.success("复制接口成功")
this.get_tree()
}else if (res.data.code === 100) {
this.$message.error("必要参数不可为空")
}
})
},
//删除测试用例
delete_case(){
let data = this.case_form
deletecase(data).then(res => {
if (res.data.code === 200){
this.$message({
message: "删除接口成功",
type: "success"
})
this.get_tree()
} else if (res.data.code === 100){
this.$message({
message: "删除接口失败,无此接口",
type: 'error'
})
}
})
},
//删除文件夹
deletepro(){
let data = this.pidFrom
deletepeoject(data).then(res => {
if (res.data.code === 200){
this.get_tree()
this.$message({
message: "删除文件夹成功",
type: "success"
})
} else if (res.data.code === 100){
this.$message({
message: "必要参数不能为空",
type: 'error'
})
} else if (res.data.code === 101){
this.$message({
message: "目录下存在文件或用例,不可删除",
type: 'error'
})
} else if (res.data.code === 102){
this.$message({
message: "目标id不存在,无法删除",
type: 'error'
})
}
})
},
// 选中节点,渲染对应数据
getcase(data){
if (data.type === 1){
this.number = data.type
this.getsinglelist()
this.reload()
this.menu_pid = data.id
this.pidFrom.id = data.id
this.m_id = data.pid
this.tree_name = data.label
this.expaAndList = data.id
}
else if (data.type === 2){
this.number = data.type
this.reload()
this.case_form.menu_id = data.id
this.pidFrom.id = data.id
this.tree_name = data.label
this.m_id = data.pid
this.getsinglecase()
this.expaAndList = data.id
}
else if (data.type === 0){
this.pidFrom.id = data.id
this.number = 1
this.reload()
this.getall()
}
},
new_menu() {
this.dialogVisible = true
},
add_menu(){
this.menu_form.type = this.type_value
this.menu_form.pid = this.pidFrom.id
this.menu_form.user_id = Number(localStorage.getItem("user_id"))
addmenu(this.menu_form).then(res => {
if (res.data.code === 200){
this.$message({
message: '创建子菜单成功',
type: 'success'
})
this.dialogVisible = false
this.get_tree()
} else if (res.data.code === 100){
this.dialogVisible = true
this.$message.error("菜单名称不可为空")
}else if (res.data.code === 101){
this.dialogVisible = true
this.$message.error("名称已存在,请重新输入")
}
})
},
edit(){
this.menu_form.id = this.pidFrom.id
this.editdialogVisible = true
},
edit_menu(){
let req = this.menu_form
req.id = this.pidFrom.id
req.label = this.tree_name
req.pid = this.m_id
req.user_id = Number(localStorage.getItem("user_id"))
addmenu(req).then(res => {
if (res.data.code === 200){
this.$message({
message: '重命名成功',
type: 'success'
})
this.editdialogVisible = false
this.get_tree()
} else if (res.data.code === 100){
this.editdialogVisible = true
this.$message.error("菜单名称不可为空")
}else if (res.data.code === 101){
this.editdialogVisible = true
this.$message.error("名称已存在,请重新输入")
}
})
},
handleClose(done) {
done()
},
}
}
</script>
<style scoped>
.el-button{
padding: 4px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
case_info.vue
<template>
<div class="form">
<div>
<div>
<el-form v-model="form">
<div class="global_box" style="padding: 8px; height: 100%; width: 100%">
<div style="padding-left: 10px; font-size: 20px">{{form.name}}</div>
<el-select v-model="form.api_type" placeholder="接口类型" style="padding: 10px">
<el-option
v-for="item in api_type"
:key="item.api_type_value"
:label="item.label"
:value="item.api_type_value">
</el-option>
</el-select>
<el-select v-model="form.method" placeholder="method" style="padding: 10px">
<el-option
v-for="item in method_form"
:key="item.method_value"
:label="item.label"
:value="item.method_value">
</el-option>
</el-select>
<el-select v-model="form.environment_id" placeholder="environment">
<el-option
v-for="env in env_form"
:key="env.environment_id"
:label="env.environment_name"
:value="env.environment_id">
</el-option>
</el-select>
<el-input v-model="form.url" id="url" placeholder="接口URL" style="width: 500px; padding: 10px"></el-input>
<el-button type="primary" style="padding: 10px" icon="el-icon-s-promotion" @click="send">Send</el-button>
<el-button type="success" style="padding: 10px" icon="el-icon-s-tools" @click="get_env_all">环境变量</el-button>
<el-button type="primary" style="padding: 12px; float: right" icon="el-icon-s-tools" @click.native="save_testcase">保存</el-button>
</div>
<div style="height: 100%; width: 100%" class="global_box">
<el-tabs v-model="req_activeName" value="params" type="border-card">
<el-tab-pane style="font-weight:bolder" label="Params" name="Params">
<div>
<el-table :data="form.params">
<template slot="empty">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_params">Add</el-button>
</template>
<el-table-column label="key">
<template slot-scope="scope">
<el-input id="params_key" v-model="scope.row.key"></el-input>
</template>
</el-table-column>
<el-table-column label="value">
<template slot-scope="scope">
<el-input id="params_value" v-model="scope.row.value"></el-input>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_params"></el-button>
<el-button type="danger" icon="el-icon-remove-outline" @click.native.prevent="del_params(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="Headers" name="Headers">
<vue-json-editor
v-model="form.headers"
:showBtns="false"
:mode="'code'"
style="height: 100%"
/>
</el-tab-pane>
<el-tab-pane label="Body" name="Body">
<el-radio-group v-model="form.body_type" style="padding-block-end: 10px">
<el-radio :label="4" value="4">None</el-radio>
<el-radio :label="1" value="1">form-data</el-radio>
<el-radio :label="2" value="2">JSON</el-radio>
<el-radio :label="3" value="3">x-www-form-urlencoded</el-radio>
</el-radio-group>
<vue-json-editor
v-model="form.body"
:showBtns="false"
:mode="'code'"
style="height: 100%"
/>
</el-tab-pane>
<el-tab-pane label="Assert" name="Assert">
<div>
<el-table :data="form.assert_form">
<template slot="empty">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_assert">Add</el-button>
</template>
<el-table-column label="字段名">
<template slot-scope="scope">
<el-input v-model="scope.row.assert_key"></el-input>
</template>
</el-table-column>
<el-table-column label="结果">
<template slot-scope="scope">
<el-input v-model="scope.row.assert_value"></el-input>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_assert"></el-button>
<el-button type="danger" icon="el-icon-remove-outline" @click.native.prevent="del_assert(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="参数提取" name="loacl_Variable">
<div>
<el-table :data="form.extract">
<template slot="empty">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_extract_form">Add</el-button>
</template>
<el-table-column label="变量名称">
<template slot-scope="scope">
<el-input v-model="scope.row.variable_name"></el-input>
</template>
</el-table-column>
<el-table-column label="变量路径">
<template slot-scope="scope">
<el-input v-model="scope.row.variable_data" placeholder="res.content"></el-input>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="success" icon="el-icon-circle-plus-outline" @click="add_extract_form"></el-button>
<el-button type="danger" icon="el-icon-remove-outline" @click.native.prevent="del_extract_form(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
</el-tabs>
</div>
<div style="height: 100%; width: 100%" class="global_box">
<!-- <el-title>响应结果</el-title>-->
<el-tabs v-model="res_activeName" type="border-card">
<el-tab-pane label="Response body" name="body">
<vue-json-editor
v-model="res_form.res_data"
:showBtns="false"
:mode="'code'"
style="height: 100%"
>
</vue-json-editor>
</el-tab-pane>
<el-tab-pane label="Assert-result" name="third">
<div>
<el-table :data="res_form.assert_data">
<el-table-column label="字段名">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.assert_key"></el-input>
</template>
</el-table-column>
<el-table-column label="值">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.assert_value"></el-input>
</template>
</el-table-column>
<el-table-column label="结果">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.status"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="RES-参数提取" name="fourth">
<div>
<el-table :data="res_form.variable">
<el-table-column label="变量名称">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.variable_name"></el-input>
</template>
</el-table-column>
<el-table-column label="变量路径">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.variable_data" placeholder=""></el-input>
</template>
</el-table-column>
<el-table-column label="变量结果">
<template slot-scope="scope">
<el-input readonly v-model="scope.row.variable_result" placeholder=""></el-input>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="接口请求详情" name="fifth">
<div style="height: 282px; overflow: scroll">
<p>接口响应时间:{{res_form.response_time}}</p>
<p>[ url ] ==> {{res_form.host}}</p>
<p>[ params ] ==> {{res_form.params}}</p>
<p>[ headers ] ==> {{res_form.headers}}</p>
<p>[ body ] ==> {{res_form.body}}</p>
<p>[ response ] ==> {{res_form.res_data}}</p>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-form>
<div>
<el-dialog
title="环境"
:visible.sync="dialogVisible"
width="70%"
>
<div>
<div style="padding-block-end: 10px">
<el-input v-model="env_page.env_name" id="task_name" placeholder="请输入环境名称" style="width: 800px; padding-right: 10px;"></el-input>
<el-button type="primary" @click="get_env_all" size="medium" style="padding: 10px;">搜索</el-button>
</div>
<el-table
:data="table_data"
element-loading-text="Loading"
border
fit
highlight-current-row
>
<el-table-column align="center" label="ID">
<template slot-scope="scope">
{{ scope.row.environment_id }}
</template>
</el-table-column>
<el-table-column label="环境名称" width="280px">
<template slot-scope="scope">
{{ scope.row.environment_name }}
</template>
</el-table-column>
<el-table-column label="环境地址" width="280px">
<template slot-scope="scope">
{{ scope.row.environment_address }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="描述" align="center">
<template slot-scope="scope">
{{ scope.row.environment_e_describe }}
</template>
</el-table-column>
<el-table-column label="更新人" align="center">
<template slot-scope="scope">
{{ scope.row.username }}
</template>
</el-table-column>
</el-table>
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="env_page.current_page"
:page-sizes="[6]"
:page-size="env_page.size_page"
layout="total, sizes, prev, pager, next, jumper"
:total="env_case_total"
style="float: left; padding: 10px"
>
</el-pagination>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</template>
<script>
import vueJsonEditor from 'vue-json-editor'
import {get_envall, getenvlist} from "@/api/environment";
import {saverequest, sendrequest} from "@/api/testcase";
import Link from "../../layout/components/Sidebar/Link";
export default {
name: "case_info",
props: {
form: [Array, Object] // 接收case_index.vue传来的数据
},
data() {
return {
table_data: [],
dialogVisible: false,
req_activeName: 'Params',
res_activeName: 'body',
url_input: '',
method: '',
local_variable_form: [
{
local_variable_name: '',
local_variable_data: '',
local_variable_result: '',
local_variable_description: ''
}
],
method_form: [
{
label: "GET",
method_value: 1
},
{
label: "POST",
method_value: 2
},
{
label: "PUT",
method_value: 3
},
{
label: "DELETE",
method_value: 4
}
],
api_type: [
{
label: "普通接口",
api_type_value: 1
}
],
env_value: '',
env_form: [
{
environment_id: '',
environment_name: '',
}
],
// 接口请求
req_form: {
url: '',
environment_id: '',
headers: {},
body: {},
params: [],
method: '',
body_type: '',
assert: [],
variable_extract: []
},
// 接口返回
res_form: [
{
res_data: {},
variable_extract: [
{
variable_name: '',
variable_data: ''
}
],
assert_data: [
{
assert_key: '',
assert_value: '',
status: ''
}
],
variable: [{
variable_name: '',
variable_data: '',
variable_result: ''
}]
}
],
env_page: {
env_name: "",
current_page: 1,
size_page: 6
},
env_case_total: null
}
},
components: {
Link,
vueJsonEditor
},
created() {
this.get_env()
},
methods: {
// 页签-条/页 跳转
handleSizeChange(val) {
this.env_page.size_page = val
this.get_env_all()
},
// 底部页签跳转
handleCurrentChange(val) {
this.env_page.current_page = val
this.get_env_all()
},
get_env_all(){
this.dialogVisible = true
get_envall(this.env_page).then(res => {
this.table_data = res.data.content
this.env_case_total = res.data.case_total
this.env_page.current_page = res.data.current_page
this.env_page.size_page = res.data.size_page
})
},
// 发送请求
send(){
let req = this.req_form
req.testcase_id = this.form.id
req.user_id = Number(localStorage.getItem("user_id"))
req.url = this.form.url
req.environment_id = this.form.environment_id
req.headers = this.form.headers
req.body = this.form.body
req.api_type = this.form.api_type
req.params = this.form.params
req.method = this.form.method
req.body_type = this.form.body_type
req.assert = this.form.assert_form
req.variable_extract = this.form.extract
sendrequest(this.req_form).then(res => {
if (res.data.code === 200){
this.res_form = res.data
let res_body = res.data.res_data
this.res_form.res_data = eval(`(${res_body})`)
}
})
},
add_params(){
let list = {
key: '',
value: ''
}
this.form.params.push(list)
},
// 删除Params
del_params(index, row){
this.form.params.splice(index,1)
},
// 新增Assert
add_assert(){
let list = {
assert_key: '',
assert_value: ''
}
this.form.assert_form.push(list)
},
// 删除Assert
del_assert(index, row){
this.form.assert_form.splice(index,1)
},
// 新增本地变量
add_extract_form(){
let list = {
variable_name: '',
variable_value: ''
}
this.form.extract.push(list)
},
// 删除本地变量
del_extract_form(index, row){
this.form.extract.splice(index,1)
},
get_env(){
getenvlist().then(res =>{
this.env_form = res.data.content
})
},
save_testcase(){
let req = this.req_form
req.testcase_id = this.form.id
req.url = this.form.url
req.environment_id = this.form.environment_id
req.headers = JSON.parse(JSON.stringify(this.form.headers))
req.body = JSON.parse(JSON.stringify(this.form.body))
req.params = JSON.parse(JSON.stringify(this.form.params))
req.api_type = this.form.api_type
req.method = this.form.method
req.body_type = this.form.body_type
req.assert = JSON.parse(JSON.stringify(this.form.assert_form))
req.variable_extract = JSON.parse(JSON.stringify(this.form.extract))
saverequest(this.req_form).then(res => {
if (res.data.code === 200){
this.$message.success("保存接口成功")
}
})
}
}
}
</script>
<style lang="scss">
.global_box {
max-width: 100%;
max-height: 100%;
box-shadow: 0 0 0.01vw #000000 inset;
padding:0;
}
.jsoneditor-outer{
height: 280px !important;
}
.el-dialog__body{
height: 100%;
padding: 50px 20px
}
</style>
具体效果:
疑问可在评论区评论,有空就会回复