<template>
|
<el-container>
|
<template v-if="!next">
|
<el-main style="padding-top: 0">
|
<div style="margin-top: 10px">
|
<el-col :span="12">
|
<el-form
|
:model="ruleForm"
|
:rules="rules"
|
ref="ruleForm"
|
label-width="100px"
|
>
|
<el-form-item label="任务名称" prop="name">
|
<el-input
|
clearable
|
v-model.trim="ruleForm.name"
|
placeholder="请输入任务名称"
|
></el-input>
|
</el-form-item>
|
|
<el-form-item label="时间配置" prop="crontab">
|
<el-input
|
readonly
|
v-model="ruleForm.crontab"
|
placeholder="请配置cron表达式"
|
>
|
<el-button
|
slot="append"
|
icon="el-icon-edit"
|
type="success"
|
plain
|
@click="showDialog"
|
>生成</el-button
|
>
|
</el-input>
|
</el-form-item>
|
|
<el-dialog
|
title="生成 cron"
|
:visible.sync="showCron"
|
:close-on-click-modal="false"
|
>
|
<vcrontab
|
@hide="showCron = false"
|
@fill="crontabFill"
|
:expression="expression"
|
></vcrontab>
|
</el-dialog>
|
|
<el-form-item label="运行配置" prop="config">
|
<el-select
|
clearable
|
v-model="ruleForm.config"
|
value-key="id"
|
placeholder="请选择"
|
size="small"
|
>
|
<el-option
|
v-for="item in configOptions"
|
:key="item.id"
|
:label="item.name"
|
:value="item.name"
|
></el-option>
|
</el-select>
|
<el-tooltip placement="top">
|
<div slot="content">
|
指定任务运行时的配置<br />请选择:使用用例中的配置
|
<br />选择其他配置:当前选择配置覆盖用例的配置<br />
|
</div>
|
<span class="el-icon-question"></span>
|
</el-tooltip>
|
</el-form-item>
|
|
<el-form-item label="任务状态" prop="switch">
|
<el-switch
|
v-model="ruleForm.switch"
|
></el-switch>
|
</el-form-item>
|
|
<el-form-item label="运行模式" prop="is_parallel">
|
<template>
|
<el-radio
|
v-model="ruleForm.is_parallel"
|
:label="false"
|
>串行</el-radio
|
>
|
<el-radio
|
v-model="ruleForm.is_parallel"
|
:label="true"
|
>并行</el-radio
|
>
|
<el-tooltip placement="top">
|
<div slot="content">
|
用例运行模式<br />任务中的用例默认是一个接一个运行;并行时,同时执行用例,不分先后
|
</div>
|
<span class="el-icon-question"></span>
|
</el-tooltip>
|
</template>
|
</el-form-item>
|
|
<el-form-item label="通知策略" prop="strategy">
|
<el-radio-group v-model="ruleForm.strategy">
|
<el-radio label="始终发送"></el-radio>
|
<el-radio label="仅失败发送"></el-radio>
|
<el-radio label="从不发送"></el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
<el-form-item label="邮件接收人" prop="receiver">
|
<el-input
|
type="textarea"
|
v-model="ruleForm.receiver"
|
placeholder="多个接收人以;分隔"
|
clearable
|
:autosize="{
|
minRows: 1,
|
maxRows: 3
|
}"
|
></el-input>
|
</el-form-item>
|
|
<el-form-item label="邮件抄送人" prop="mail_cc">
|
<el-input
|
type="textarea"
|
v-model="ruleForm.mail_cc"
|
placeholder="多个抄送人以;分隔"
|
clearable
|
:autosize="{
|
minRows: 1,
|
maxRows: 3
|
}"
|
></el-input>
|
</el-form-item>
|
|
<el-form-item label="webhook" prop="webhook">
|
<el-input
|
clearable
|
placeholder="企微机器人webhook地址, 多个时换行即可"
|
type="textarea"
|
v-model="ruleForm.webhook"
|
:autosize="{
|
minRows: 1,
|
maxRows: 3
|
}"
|
></el-input>
|
</el-form-item>
|
|
<el-form-item>
|
<div style="display: flex;">
|
<el-button
|
type="primary"
|
@click="submitForm('ruleForm')"
|
>下一步</el-button
|
>
|
<el-button @click="resetForm('ruleForm')"
|
>重置</el-button
|
>
|
</div>
|
</el-form-item>
|
</el-form>
|
</el-col>
|
</div>
|
</el-main>
|
</template>
|
|
<template v-if="next">
|
<el-aside style="width: 250px; margin-top: 10px">
|
<div class="nav-api-side">
|
<div class="api-tree">
|
<el-input
|
clearable
|
placeholder="请输入关键字进行过滤"
|
v-model="filterText"
|
size="medium"
|
prefix-icon="el-icon-search"
|
></el-input>
|
<el-tree
|
@node-click="handleNodeClick"
|
:data="dataTree"
|
node-key="id"
|
:default-expand-all="false"
|
:expand-on-click-node="false"
|
highlight-current
|
:filter-node-method="filterNode"
|
ref="tree2"
|
>
|
<span
|
class="custom-tree-node"
|
slot-scope="{ node, data }"
|
>
|
<span class="custom-tree-node-span">
|
<i
|
v-if="node.childNodes.length > 0"
|
class="el-icon-folder-opened"
|
></i>
|
<i v-else class="el-icon-folder"></i
|
> {{ node.label }}
|
</span>
|
|
<span style="flex-shrink: 0; margin-left: 5px;">
|
<el-badge
|
:value="data.data_count"
|
:max="99"
|
type="primary"
|
></el-badge>
|
</span>
|
</span>
|
</el-tree>
|
</div>
|
</div>
|
</el-aside>
|
|
<el-main style="padding-top: 0">
|
<div>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div style="display: flex">
|
<el-input
|
style="width: 300px"
|
size="medium"
|
placeholder="请输入用例名称"
|
v-model="search"
|
clearable
|
>
|
</el-input>
|
<div style="margin-left: 10px">
|
<el-button
|
plain
|
size="medium"
|
icon="el-icon-refresh"
|
@click="resetSearch"
|
>重置</el-button
|
>
|
</div>
|
<div style="margin-left: 10px">
|
<el-select
|
v-model="creator"
|
placeholder="请选择创建人"
|
size="medium"
|
style="width: 100px"
|
>
|
<el-option
|
v-for="item in creatorOptions"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value"
|
>
|
</el-option>
|
</el-select>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div style="display: flex;">
|
<el-button
|
size="medium"
|
v-if="testData.length > 0"
|
@click="next = false"
|
>上一步</el-button
|
>
|
<el-button
|
size="medium"
|
type="primary"
|
v-if="testData.length > 0"
|
@click="saveTask"
|
>保存</el-button
|
>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
|
<div style="position: relative; height: 680px;">
|
<div
|
style="height: calc(100% - 50px); overflow-y: auto; overflow-x: hidden;"
|
>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div
|
v-for="(item, index) in suiteData.results"
|
style="cursor: pointer; margin-top: 10px; overflow: auto"
|
draggable="true"
|
@dragstart="
|
currentSuite = JSON.parse(
|
JSON.stringify(item)
|
)
|
"
|
:key="index"
|
>
|
<div class="block block_options">
|
<span
|
class="block-method block_method_options block_method_color"
|
>Case</span
|
>
|
<span class="block_name">{{
|
item.name
|
}}</span>
|
<i
|
class="el-icon-success"
|
style="color: green; padding: 15px"
|
v-if="item.tasks.length > 0"
|
:title="
|
'已加入定时任务:' +
|
item.tasks
|
.map(task => task.name)
|
.join(', ')
|
"
|
></i>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div
|
@drop="drop($event)"
|
@dragover="allowDrop($event)"
|
class="drag-drop-zone"
|
>
|
<span
|
v-if="testData.length === 0"
|
style="color: red"
|
>温馨提示:<br />从左边拖拽用例至此区域组成任务列表<br />上下拖动此区域任务调整监控调用顺序</span
|
>
|
<div>
|
<draggable
|
v-model="testData"
|
@end="dragEnd"
|
@start="length = testData.length"
|
:animation="200"
|
>
|
<div
|
v-for="(test,
|
index) in testData"
|
:key="index"
|
class="block block_test"
|
@mousemove="currentTest = index"
|
>
|
<span
|
class="block-method block_method_test block_method_color"
|
>Task</span
|
>
|
<span class="block-test-name">{{
|
test.name
|
}}</span>
|
<el-button
|
style="position: absolute; right: 12px; top: 8px"
|
v-show="
|
currentTest === index
|
"
|
type="danger"
|
icon="el-icon-delete"
|
title="删除"
|
circle
|
size="mini"
|
@click="
|
testData.splice(
|
index,
|
1
|
)
|
"
|
></el-button>
|
</div>
|
</draggable>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
<div class="pagination-container">
|
<el-pagination
|
v-show="suiteData.count !== 0"
|
background
|
@current-change="handlePageChange"
|
@size-change="handleSizeChange"
|
:current-page.sync="currentPage"
|
:page-sizes="[10, 15, 20]"
|
:page-size="pageSize"
|
:pager-count="5"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="suiteData.count"
|
style="text-align: center"
|
></el-pagination>
|
</div>
|
</div>
|
</el-main>
|
</template>
|
</el-container>
|
</template>
|
|
<script>
|
import draggable from "vuedraggable";
|
import vcrontab from "@/pages/vcrontab/Crontab";
|
import { isNumArray } from "@/validator";
|
|
export default {
|
name: "AddTasks",
|
components: {
|
draggable,
|
vcrontab
|
},
|
props: {
|
ruleForm: {
|
required: false
|
},
|
args: {
|
required: true
|
},
|
scheduleId: {
|
required: true
|
},
|
configOptions: {
|
required: true,
|
type: Array
|
}
|
},
|
data() {
|
return {
|
creator: this.$store.state.name,
|
creatorOptions: [],
|
searchType: "1", // 1:根据用例名称搜索
|
currentTest: "",
|
length: 0,
|
testData: [],
|
currentSuite: "",
|
search: "",
|
next: false,
|
expression: "",
|
showCron: false,
|
node: "",
|
currentPage: 1,
|
pageSize: 10,
|
filterText: "",
|
expand: "",
|
dataTree: [],
|
suiteData: {
|
count: 0,
|
results: []
|
},
|
rules: {
|
name: [
|
{
|
required: true,
|
message: "请输入任务名称",
|
trigger: "blur"
|
},
|
{
|
min: 1,
|
max: 50,
|
message: "长度在 1 到 50 个字符",
|
trigger: "blur"
|
}
|
],
|
crontab: [
|
{
|
required: true,
|
message: "请配置cron表达式",
|
trigger: "blur"
|
}
|
],
|
ci_project_ids: [
|
{
|
required: false,
|
message: "请选择正确的ci_project_ids",
|
trigger: "blur"
|
},
|
{
|
validator: isNumArray,
|
trigger: "blur"
|
}
|
]
|
},
|
editTestCaseActivate: false
|
};
|
},
|
methods: {
|
crontabFill(value) {
|
//确定后回传的值
|
this.ruleForm.crontab = value;
|
},
|
showDialog() {
|
//传入的 cron 表达式,可以反解析到 UI 上
|
this.expression = this.ruleForm.crontab;
|
this.showCron = true;
|
},
|
saveTask() {
|
let task = [];
|
for (let value of this.testData) {
|
task.push(value.id);
|
}
|
let form = this.ruleForm;
|
form["data"] = task;
|
form["project"] = this.$route.params.id;
|
// 新增
|
if (this.scheduleId === "") {
|
this.$api.addTask(form).then(resp => {
|
if (resp.success) {
|
this.$message.success(resp.msg);
|
this.$emit("changeStatus", false);
|
} else {
|
this.$message.error(resp.msg);
|
}
|
});
|
// 修改
|
} else {
|
this.$api.updateTask(this.scheduleId, form).then(resp => {
|
if (resp.success) {
|
this.$message.success(resp.msg);
|
this.$emit("changeStatus", false);
|
} else {
|
this.$message.error(resp.msg);
|
}
|
});
|
}
|
},
|
dragEnd() {
|
if (this.testData.length > this.length) {
|
this.testData.splice(this.length, 1);
|
}
|
},
|
drop(event) {
|
event.preventDefault();
|
this.testData.push(this.currentSuite);
|
},
|
allowDrop(event) {
|
event.preventDefault();
|
},
|
handlePageChange() {
|
this.$api
|
.getTestPaginationByPage({
|
params: {
|
page: this.currentPage,
|
size: this.pageSize,
|
node: this.node,
|
project: this.$route.params.id,
|
search: this.search
|
}
|
})
|
.then(resp => {
|
this.suiteData = resp;
|
});
|
},
|
handleSizeChange(newSize) {
|
this.pageSize = newSize;
|
// 计算新的最大页码
|
let maxPage = Math.ceil(this.suiteData.count / newSize);
|
if (this.currentPage > maxPage) {
|
// 如果当前页码超出了范围,请将其设置为最大页面
|
this.currentPage = maxPage;
|
}
|
this.$api
|
.getTestPaginationByPage({
|
params: {
|
page: this.currentPage,
|
size: newSize,
|
node: this.node,
|
project: this.$route.params.id,
|
search: this.search,
|
creator: this.creator,
|
searchType: this.searchType
|
}
|
})
|
.then(resp => {
|
this.suiteData = resp;
|
});
|
},
|
handleCurrentChange() {
|
this.$api
|
.getTestPaginationByPage({
|
params: {
|
page: this.currentPage,
|
size: this.pageSize,
|
project: this.$route.params.id,
|
node: this.node,
|
search: this.search,
|
creator: this.creator,
|
searchType: this.searchType
|
}
|
})
|
.then(resp => {
|
this.suiteData = resp;
|
});
|
},
|
submitForm(formName) {
|
this.$refs[formName].validate(valid => {
|
if (valid) {
|
this.next = true;
|
} else {
|
return false;
|
}
|
});
|
this.testData = this.args;
|
// 用map遍历args的所有caseId, 如果和用例中的id相等, 就返回该用例的全部信息
|
// 用map, filter过滤, case的数据在第二页时, 会导致name=undefined
|
// this.testData = this.args.map(caseId=> this.suiteData.results.filter(testCase=> testCase.id === caseId)[0]);
|
},
|
resetForm(formName) {
|
this.$refs[formName].resetFields();
|
},
|
filterNode(value, data) {
|
if (!value) return true;
|
return data.label.indexOf(value) !== -1;
|
},
|
getTree() {
|
this.$api
|
.getTree(this.$route.params.id, { params: { type: 2 } })
|
.then(resp => {
|
this.dataTree = resp.tree;
|
});
|
},
|
handleNodeClick(node) {
|
this.node = node.id;
|
this.getTestList();
|
},
|
getTestList() {
|
this.$api
|
.testList({
|
params: {
|
page: this.currentPage,
|
size: this.pageSize,
|
project: this.$route.params.id,
|
node: this.node,
|
search: this.search,
|
creator: this.creator,
|
searchType: this.searchType
|
}
|
})
|
.then(resp => {
|
this.suiteData = resp;
|
});
|
},
|
debouncedGetTestList() {
|
clearTimeout(this.searchDebounce);
|
this.searchDebounce = setTimeout(() => {
|
this.currentPage = 1;
|
this.getTestList();
|
}, 300);
|
},
|
resetSearch() {
|
this.currentPage = 1;
|
this.pageSize = 10;
|
this.search = "";
|
this.node = "";
|
this.creator = this.$store.state.name;
|
},
|
getUserList() {
|
this.$api.getUserList().then(resp => {
|
for (let i = 0; i < resp.length; i++) {
|
this.creatorOptions.push({
|
label: resp[i].name,
|
value: resp[i].name
|
});
|
}
|
this.creatorOptions.unshift({ label: "所有人", value: "" });
|
});
|
}
|
},
|
watch: {
|
search() {
|
this.debouncedGetTestList();
|
},
|
creator() {
|
this.debouncedGetTestList();
|
},
|
filterText(val) {
|
this.$refs.tree2.filter(val);
|
}
|
},
|
mounted() {
|
this.getTree();
|
this.getTestList();
|
this.getUserList();
|
}
|
};
|
</script>
|
|
<style scoped>
|
.block_test {
|
margin-top: 10px;
|
border: 1px solid #49cc90;
|
background-color: rgba(236, 248, 238, 0.4);
|
}
|
|
.block_method_test {
|
background-color: #304056;
|
}
|
|
.block-test-name {
|
width: 700px;
|
padding-left: 10px;
|
font-family: Open Sans, sans-serif;
|
color: #3b4151;
|
border: none;
|
outline: none;
|
background: rgba(236, 248, 238, 0.4);
|
}
|
|
.pagination-container {
|
display: flex;
|
justify-content: left;
|
align-items: center;
|
position: absolute;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
}
|
|
.drag-drop-zone {
|
min-height: 600px;
|
}
|
</style>
|