feature: 任务管理-80%

This commit is contained in:
charlesxie
2022-06-17 18:25:48 +08:00
parent 194c649172
commit 071b6c9b6a
34 changed files with 1925 additions and 26 deletions

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class TaskConfig(AppConfig):
name = 'task'

View File

@@ -0,0 +1,30 @@
# Generated by Django 2.2.6 on 2022-06-17 15:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('flow', '0006_auto_20220616_1616'),
]
operations = [
migrations.CreateModel(
name='Task',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='任务名称')),
('run_type', models.CharField(choices=[('hand', '手动'), ('now', '立即'), ('time', '定时'), ('cycle', '周期'), ('cron', 'cron表达式')], max_length=64, verbose_name='执行方式')),
('when_start', models.CharField(max_length=100, verbose_name='执行时间')),
('cycle_time', models.CharField(max_length=20, null=True, verbose_name='周期时间')),
('cycle_type', models.CharField(choices=[('min', '分钟'), ('hour', '小时'), ('day', '')], max_length=20, null=True, verbose_name='周期间隔(min,hour,day)')),
('cron_time', models.TextField(default='', verbose_name='cron表达式')),
('celery_task_id', models.CharField(max_length=64, null=True, verbose_name='celery的任务ID')),
('process_run', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='flow.ProcessRun')),
],
),
]

View File

View File

@@ -0,0 +1,29 @@
from django.db import models
from applications.flow.models import ProcessRun
class Task(models.Model):
TypeChoices = (
("hand", "手动"),
("now", "立即"),
("time", "定时"),
("cycle", "周期"),
("cron", "cron表达式"),
)
CycleChoices = (
("min", "分钟"),
("hour", "小时"),
("day", ""),
)
name = models.CharField("任务名称", max_length=255, blank=False, null=False)
process_run = models.ForeignKey(ProcessRun, on_delete=models.CASCADE, null=True, db_constraint=False,
related_name="tasks")
run_type = models.CharField("执行方式", choices=TypeChoices,max_length=64)
when_start = models.CharField(max_length=100, verbose_name="执行时间")
cycle_time = models.CharField(max_length=20, null=True, verbose_name="周期时间")
cycle_type = models.CharField(max_length=20, null=True, verbose_name="周期间隔(min,hour,day)", choices=CycleChoices)
cron_time = models.TextField(default="", verbose_name="cron表达式")
celery_task_id = models.CharField(max_length=64, null=True, verbose_name="celery的任务ID")

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from applications.task.models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = "__all__"

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,6 @@
from rest_framework.routers import DefaultRouter
from . import views
task_router = DefaultRouter()
task_router.register(r"task", viewset=views.TaskViewSets, base_name="task")

View File

@@ -0,0 +1,12 @@
from django.shortcuts import render
from applications.task.models import Task
from applications.task.serializers import TaskSerializer
from component.drf.viewsets import GenericViewSet
from rest_framework import mixins
class TaskViewSets(mixins.ListModelMixin,
GenericViewSet):
queryset = Task.objects.order_by("-id")
serializer_class = TaskSerializer

View File

@@ -34,7 +34,8 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
"custom_plugins",
"rest_framework",
"applications.flow"
"applications.flow",
"applications.task"
]

View File

@@ -17,6 +17,7 @@ from django.contrib import admin
from django.urls import path, include
from applications.flow.urls import flow_router, node_router
from applications.flow.views import flow
from applications.task.urls import task_router
from dj_flow.views import index
urlpatterns = [
@@ -24,6 +25,6 @@ urlpatterns = [
path('', index),
path("process/", include(flow_router.urls)),
path("node/", include(node_router.urls)),
path("task/", include(task_router.urls)),
path("tt/", flow),
]

View File

@@ -0,0 +1,7 @@
import {GET, reUrl} from '../../axiosconfig/axiosconfig'
export default {
list: function(params) {
return GET(reUrl + '/task/task/', params)
}
}

View File

@@ -22,6 +22,8 @@ import processReport from './apiUrl/report/process_report'
import nodeHistory from './apiUrl/history/node_history'
import processHistory from './apiUrl/history/process_history'
import Task from './apiUrl/task/task'
export default {
alarmCenter,
auditLog,
@@ -41,5 +43,6 @@ export default {
nodeHistory,
processHistory,
processReport,
showTable
showTable,
Task
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
'use strict';
export default (() => {
function convertAsterisk(expression, replecement){
if(expression.indexOf('*') !== -1){
return expression.replace('*', replecement);
}
return expression;
}
function convertAsterisksToRanges(expressions){
expressions[0] = convertAsterisk(expressions[0], '0-59');
expressions[1] = convertAsterisk(expressions[1], '0-59');
expressions[2] = convertAsterisk(expressions[2], '0-23');
expressions[3] = convertAsterisk(expressions[3], '1-31');
expressions[4] = convertAsterisk(expressions[4], '1-12');
expressions[5] = convertAsterisk(expressions[5], '0-6');
return expressions;
}
return convertAsterisksToRanges;
})();

View File

@@ -0,0 +1,64 @@
/* eslint-disable */
'use strict';
import monthNamesConversion from './month-names-conversion'
import weekDayNamesConversion from './week-day-names-conversion'
import convertAsterisksToRanges from './asterisk-to-range-conversion'
import convertRanges from './range-conversion'
import convertSteps from './step-values-conversion'
export default (() => {
function appendSeccondExpression(expressions){
if(expressions.length === 5){
return ['0'].concat(expressions);
}
return expressions;
}
function removeSpaces(str) {
return str.replace(/\s{2,}/g, ' ').trim();
}
// Function that takes care of normalization.
function normalizeIntegers(expressions) {
for (const i in expressions){
var numbers = expressions[i].split(',');
for (const j in numbers){
numbers[j] = parseInt(numbers[j]);
}
expressions[i] = numbers;
}
return expressions;
}
/*
* The node-cron core allows only numbers (including multiple numbers e.g 1,2).
* This module is going to translate the month names, week day names and ranges
* to integers relatives.
*
* Month names example:
* - expression 0 1 1 January,Sep *
* - Will be translated to 0 1 1 1,9 *
*
* Week day names example:
* - expression 0 1 1 2 Monday,Sat
* - Will be translated to 0 1 1 1,5 *
*
* Ranges example:
* - expression 1-5 * * * *
* - Will be translated to 1,2,3,4,5 * * * *
*/
function interprete(expression){
var expressions = removeSpaces(expression).split(' ');
expressions = appendSeccondExpression(expressions);
expressions[4] = monthNamesConversion(expressions[4]);
expressions[5] = weekDayNamesConversion(expressions[5]);
expressions = convertAsterisksToRanges(expressions);
expressions = convertRanges(expressions);
expressions = convertSteps(expressions);
expressions = normalizeIntegers(expressions);
return expressions.join(' ');
}
return interprete;
})();

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
'use strict';
export default (() => {
var months = ['january','february','march','april','may','june','july',
'august','september','october','november','december'];
var shortMonths = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
'sep', 'oct', 'nov', 'dec'];
function convertMonthName(expression, items){
for(const i in expression){
expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10) + 1);
}
return expression;
}
function interprete(monthExpression){
monthExpression = convertMonthName(monthExpression, months);
monthExpression = convertMonthName(monthExpression, shortMonths);
return monthExpression;
}
return interprete;
})();

View File

@@ -0,0 +1,42 @@
/* eslint-disable */
'use strict';
export default ( () => {
function replaceWithRange(expression, text, init, end) {
var numbers = [];
var last = parseInt(end);
var first = parseInt(init);
if(first > last){
last = parseInt(init);
first = parseInt(end);
}
for(let i = first; i <= last; i++) {
numbers.push(i);
}
return expression.replace(new RegExp(text, 'gi'), numbers.join());
}
function convertRange(expression){
var rangeRegEx = /(\d+)\-(\d+)/;
var match = rangeRegEx.exec(expression);
while(match !== null && match.length > 0){
expression = replaceWithRange(expression, match[0], match[1], match[2]);
match = rangeRegEx.exec(expression);
}
return expression;
}
function convertAllRanges(expressions){
for(let i in expressions){
expressions[i] = convertRange(expressions[i]);
}
return expressions;
}
return convertAllRanges;
})();

View File

@@ -0,0 +1,27 @@
/* eslint-disable */
'use strict';
export default (() => {
function convertSteps(expressions){
var stepValuePattern = /^(.+)\/(\d+)$/;
for(const i in expressions){
var match = stepValuePattern.exec(expressions[i]);
var isStepValue = match !== null && match.length > 0;
if(isStepValue){
var values = match[1].split(',');
var setpValues = [];
var divider = parseInt(match[2], 10);
for(const j in values){
var value = parseInt(values[j], 10);
if(value % divider === 0){
setpValues.push(value);
}
}
expressions[i] = setpValues.join(',');
}
}
return expressions;
}
return convertSteps;
})();

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
'use strict';
export default (() => {
var weekDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
'friday', 'saturday'];
var shortWeekDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
function convertWeekDayName(expression, items){
for(const i in items){
expression = expression.replace(new RegExp(items[i], 'gi'), parseInt(i, 10));
}
return expression;
}
function convertWeekDays(expression){
expression = expression.replace('6', '0');
expression = convertWeekDayName(expression, weekDays);
return convertWeekDayName(expression, shortWeekDays);
}
return convertWeekDays;
})();

View File

@@ -0,0 +1,31 @@
/* eslint-disable */
'use strict';
import validation from './pattern-validation'
function ErrorException (value) {
this.value = value;
this.message = "是一个非法表达式,请校验";
this.toString = function() {
return this.value + this.message;
};
}
export default (() => {
function validate(expression, config) {
var common_config = Object.assign({ language: 'en'}, config || {})
try {
validation(expression, common_config, ErrorException);
} catch(e) {
if (e instanceof ErrorException) {
return {status: false, msg: e.toString()};
}
return { status: false, msg: common_config.language === 'en' ? 'this is a invalid expression' : '非法表达式,请校验'}
}
return {status: true, msg: ''};
}
return {
validate: validate
};
})();

View File

@@ -0,0 +1,162 @@
/* eslint-disable */
'use strict';
import convertExpression from './convert-expression'
var commonConfig = {}
var KEYSMAP = [
{
key: 'second',
error_en: 'is a invalid expression for second',
error_ch: '在 “秒” '
},
{
key: 'minute',
error_en: 'is a invalid expression for minute',
error_ch: '在 “分” '
},
{
key: 'hour',
error_en: 'is a invalid expression for hour',
error_ch: '在 “小时” '
},
{
key: 'day',
error_en: 'is a invalid expression for day of month',
error_ch: '在 “天” '
},
{
key: 'month',
error_en: 'is a invalid expression for month',
error_ch: '在 “月” '
},
{
key: 'week',
error_en: 'is a invalid expression for second',
error_ch: '在 “周” '
}
]
export default ( () => {
function isValidExpression(expression, min, max){
var options = expression.split(',');
var regexValidation = /^\d+$|^\*$|^\*\/\d+$/;
for(const i in options){
var option = options[i];
var optionAsInt = parseInt(options[i], 10);
if(optionAsInt < min || optionAsInt > max || !regexValidation.test(option)) {
return false;
}
}
return true;
}
function isIncludeDecimals(patterns) {
for(var i = 0; i < patterns.length; i++){
if(patterns[i].indexOf('.') > 0) {
return {valid: true, index: i};
}
}
return {valid: false};
}
function isInvalidSecond(expression){
return !isValidExpression(expression, 0, 59);
}
function isInvalidMinute(expression){
return !isValidExpression(expression, 0, 59);
}
function isInvalidHour(expression){
return !isValidExpression(expression, 0, 23);
}
function isInvalidDayOfMonth(expression){
return !isValidExpression(expression, 1, 31);
}
function isInvalidMonth(expression){
return !isValidExpression(expression, 1, 12);
}
function isInvalidWeekDay(expression){
return !isValidExpression(expression, 0, 6);
}
function validateFields(patterns, executablePatterns, ErrorException){
var errorKey = 'error_ch'
if (isIncludeDecimals(patterns).valid) {
var currIndex = isIncludeDecimals(patterns).index
throw new ErrorException(patterns[currIndex] + KEYSMAP[currIndex][errorKey]);
}
if (isInvalidSecond(executablePatterns[0])) {
throw new ErrorException(patterns[0] + KEYSMAP[0][errorKey]);
}
if (isInvalidMinute(executablePatterns[1])) {
throw new ErrorException(patterns[1] + KEYSMAP[1][errorKey]);
}
if (isInvalidHour(executablePatterns[2])) {
throw new ErrorException(patterns[2] + KEYSMAP[2][errorKey]);
}
if (isInvalidDayOfMonth(executablePatterns[3])) {
throw new ErrorException(patterns[3] + KEYSMAP[3][errorKey]);
}
if (isInvalidMonth(executablePatterns[4])) {
throw new ErrorException(patterns[4] + KEYSMAP[4][errorKey]);
}
if (isInvalidWeekDay(executablePatterns[5])) {
throw new ErrorException(patterns[5] + KEYSMAP[5][errorKey]);
}
}
/**
* 接受:
* [a-z] , - * / \d
* 排除:
* [A-Z]
* \d[a-z]
* *[^\/]
*/
function basicCheck (patterns) {
var allowValue = /[^\,|\-|\*|\/|\w]|\d[a-z]|[A-Z]|\*[^\/]/
for (const pattern in patterns) {
if (allowValue.test(patterns[pattern])) {
throw '表达式非法,请校验'
}
}
}
function WeekExchangeDay (pattern) {
var patterns = pattern.split(' ');
var week = patterns[2]
var day = patterns[3]
var moth = patterns[4]
patterns[2] = day
patterns[3] = moth
patterns[4] = week
return patterns.join(' ')
}
function validate(pattern, common_config, ErrorException){
commonConfig = common_config
if (typeof pattern !== 'string'){
throw new ErrorException('pattern must be a string!');
}
if (pattern.split(' ').length !== 5) {
throw '表达式非法,请校验'
}
pattern = WeekExchangeDay(pattern);
var patterns = pattern.split(' ');
// 先基础验证下
basicCheck(patterns, ErrorException)
// 对应的表达式解析成数字
var executablePattern = convertExpression(pattern);
var executablePatterns = executablePattern.split(' ');
if(patterns.length === 5){
patterns = ['0'].concat(patterns);
}
validateFields(patterns, executablePatterns, ErrorException);
}
return validate;
})();

View File

@@ -5,6 +5,7 @@
</keep-alive>
<router-view v-if="$route.path !== '/largescreen'"></router-view>
</div>
</template>
<script>

View File

@@ -13,7 +13,8 @@
<leftMenu ref="leftMenu"></leftMenu>
</template>
<!-- 内容区域 -->
<container></container>
<container>
</container>
</bk-navigation>
</template>

View File

@@ -60,6 +60,13 @@ export default {
'hasChild': false
}]
},
{
"name": "TaskList",
"cnName": "任务管理",
"to": "/taskList",
"icon": "iconfont icon-mianxingtubiao-zuoyejiankong",
"hasChild": false
},
{
"name": "JobMonitor",
"cnName": "作业监视",
@@ -75,6 +82,7 @@ export default {
watch: {
$route(val) {
this.nav.id = val.meta.hasOwnProperty('fatherName') ? val.meta.fatherName : val.name
console.log(this.nav.id)
}
},
mounted() {

View File

@@ -0,0 +1,635 @@
<template>
<div class="loop-rule-select">
<div class="loop-rule-title bk-button-group">
<bk-button
:theme="currentWay === 'selectGeneration' ? 'primary' : 'default'"
class="rule-btn"
@click="onSwitchWay('selectGeneration')">
选择生成
</bk-button>
<bk-button
:theme="currentWay === 'manualInput' ? 'primary' : 'default'"
class="rule-btn"
@click="onSwitchWay('manualInput')">
手动输入
</bk-button>
</div>
<div class="content-wrapper">
<div class="selected-input" v-show="currentWay === 'selectGeneration'">
<bk-input :value="expressionShowText" :disabled="true"></bk-input>
<span class="clear-selected" @click.stop="clearRule">
清空
</span>
</div>
<!-- 自动生成 -->
<bk-tab
style="background: #fff"
v-show="currentWay === 'selectGeneration'"
:type="'border-card'"
:active="tabName"
@tab-changed="tabChanged">
<bk-tab-panel
v-for="(item, index) in autoRuleList"
:key="index"
:name="item.key"
:label="item.title">
<div class="tabpanel-container">
<bk-radio-group v-model="item.radio" @change="renderRule">
<bk-radio :value="0">{{ autoWay.loop.name }}</bk-radio>
<bk-radio :value="1">{{ autoWay.appoint.name }}</bk-radio>
</bk-radio-group>
<!-- 循环生成 -->
<div v-if="item.radio === 0" class="loop-select-bd">
{{ item.key !== 'week' ? autoWay.loop.start : autoWay.loop.startWeek }}
<bk-input
v-model.number="item.loop.start"
v-validate="item.loop.reg"
:name="item.key + 'Rule'"
class="loop-time"
@blur="renderRule()">
</bk-input>
{{ item.key !== 'week' ? item.title : ''}}{{ autoWay.loop.center }}
<bk-input
v-model.number="item.loop.inter"
v-validate="{ required: true, integer: true }"
name="interval"
class="loop-time"
@blur="renderRule()">
</bk-input>
{{ item.key !== 'week' ? item.title : '天' }}{{ autoWay.loop.end }}
<!-- 星期说明 -->
<i v-if="item.key === 'week'" v-bk-tooltips="'0 表示星期天6 表示星期六'" class="common-icon-info month-tips top-start"></i>
<!-- startInput 错误提示 -->
<div v-show="errors.has(item.key + 'Rule') || errors.has('interval')"
class="local-error-tip error-msg">
{{ errors.first(item.key + 'Rule') || errors.first('interval') }}
</div>
</div>
<!-- 指定 -->
<div v-else class="appoint-select-bd">
<bk-checkbox
v-for="(box, i) in item.checkboxList"
:key="i"
v-model="box.checked"
@change="renderRule">
{{ box.value | addZero(item.key) }}
</bk-checkbox>
</div>
</div>
</bk-tab-panel>
</bk-tab>
<!-- 手动输入 -->
<div v-show="currentWay === 'manualInput'" class="hand-input">
<bk-input
:clearable="true"
name="periodicCron"
class="step-form-content-size"
v-model="periodicCron"
v-validate="{ required: true, cronRlue: true }">
</bk-input>
</div>
</div>
<bk-icon class="common-icon-info rule-tips" type="question-circle" v-bk-tooltips="ruleTipsHtmlConfig"></bk-icon>
<!-- corn 规则 tips -->
<div id="periodic-cron-tips-html">
<img style="width:100%" class="ui-img" :src="periodicCronImg">
</div>
<!-- 手动输入错误提示 -->
<span v-show="errors.has('periodicCron') && currentWay === 'manualInput'"
class="common-error-tip error-msg">
{{ errors.first('periodicCron') }}
</span>
</div>
</template>
<script>
import { PERIODIC_REG } from '@/constants/index.js'
const autoRuleList = [
{
key: 'min',
title: '分钟',
radio: 0,
long: 60,
max: 59,
loop: {
start: 0,
inter: 1,
reg: {
required: true,
regex: /^([0-9]|0[1-9]|[0-5][0-9])$/
}
}
},
{
key: 'hour',
title: '小时',
radio: 0,
long: 24,
max: 23,
loop: {
start: 0,
inter: 1,
reg: {
required: true,
regex: /^([0-9]|0[1-9]|[0-1][0-9]|20|21|23)$/
}
}
},
{
key: 'week',
title: '星期',
radio: 0,
long: 7,
max: 6,
loop: {
start: 0,
inter: 1,
reg: {
required: true,
regex: /^[0-6]$/
}
}
},
{
key: 'day',
title: '日期',
radio: 0,
long: 31,
max: 31,
loop: {
start: 1,
inter: 1,
reg: {
required: true,
regex: /^([1-9]|0[1-9]|1[0-9]|2[0-9]|30|31)$/
}
}
},
{
key: 'month',
title: '月份',
radio: 0,
long: 12,
max: 12,
loop: {
start: 1,
inter: 1,
reg: {
required: true,
regex: /^([1-9]|0[1-9]|10|11|12)$/
}
}
}
]
const loopStarZeroList = ['min', 'hour', 'week']
const loopStarOneList = ['day', 'month']
const autoWay = {
'loop': {
name: '循环',
start: '从第',
startWeek: '从星期',
center: '开始,每隔',
end: '执行一次'
},
'appoint': {
name: '指定'
}
}
const numberMap = {
1: '星期一',
2: '星期二',
3: '星期三',
4: '星期四',
5: '星期五',
6: '星期六',
0: '星期天'
}
export default {
name: 'loop-rule-select',
filters: {
addZero(v, k) {
return k === 'week' ? v : (v < 10 ? '0' + v : v)
}
},
props: {
manualInputValue: {
type: String,
default: '*/5 * * * *'
}
},
data() {
return {
periodicRule: {
required: true,
regex: PERIODIC_REG
},
expressionList: ['*', '*', '*', '*', '*'],
periodicCronImg: require('@/assets/base/img/task-example.png'),
// 规则列表
autoRuleList: [],
// 循环选择方式
autoWay: autoWay,
// manualInput 手动 / selectGeneration 选择生成
currentWay: 'selectGeneration',
currentRadio: 'loop',
tabName: 'min',
tName: '',
periodicCron: '',
templateNameRule: '',
ruleTipsHtmlConfig: {
allowHtml: true,
width: 560,
trigger: 'mouseenter',
theme: 'light',
content: '#periodic-cron-tips-html',
placement: 'top'
}
}
},
computed: {
expressionShowText() {
return this.expressionList.join('^').replace(/\^/g, ' ')
}
},
watch: {
manualInputValue: {
handler(v) {
this.periodicCron = v
this.setValue(v)
},
immediate: true
}
},
created() {
this.autoRuleList = this.cloneDeep(autoRuleList)
this.initializeAutoRuleListData()
this.renderRule()
},
methods: {
onSwitchWay(way) {
this.currentWay = way
},
/**
* 周期选择方式切换触发
* @param {String} name -tab name
*/
tabChanged(name) {
this.tabName = name
},
/**
* 周期循环方式切换,循环/指定
* @param {Number} index - 下标
* @param {Number} value - 改变的值
*/
onAutoWaySwitch(index, value) {
this.$set(this.autoRuleList[index], 'radio', Number(value))
this.renderRule()
},
/**
* 初始化数据
* @description 根据 autoRuleList 动态插入 radio 项
*/
initializeAutoRuleListData() {
this.autoRuleList.forEach((item, index) => {
const pushArr = []
for (let i = 0; i < item.long; i++) {
const realityIndex = loopStarOneList.includes(item.key) ? i + 1 : i
pushArr.push({
name: `${item.key}${i}`,
checked: true,
v: i,
value: item.key !== 'week' ? realityIndex : numberMap[realityIndex]
})
}
this.$set(this.autoRuleList[index], 'checkboxList', pushArr)
})
},
// 清空已选
clearRule() {
this.autoRuleList.forEach((item, index) => {
item.checkboxList.forEach((m, i) => {
m.checked = false
})
item.loop.start = loopStarZeroList.includes(item.key) ? 0 : 1
item.loop.inter = 1
})
this.renderRule()
},
/**
* 渲染规则
* @description
* 1. min-max/1 <=> *
* 2. min-max/n <=> 星/d
* @param {String} key --tab key
* @param {String} way --自动/手动
* @param {Number} index --下标
*/
renderRule() {
this.autoRuleList.forEach((m, i) => {
const { radio, loop, checkboxList, max } = m
let loopRule = ''
if (loop.start === (loopStarZeroList.includes(m.key) ? 0 : 1)) {
loopRule = `*/${loop.inter}`
if (loop.inter === 1) {
loopRule = '*'
}
} else {
loopRule = `${loop.start}-${max}/${loop.inter}`
}
const pointRule = checkboxList
.filter(res => res.checked)
.map(res => {
// satrt 1 时 显示 i + 1
return loopStarOneList.includes(m.key) ? res.v + 1 : res.v
})
.join(',') || '*'
const data = radio === 0 ? loopRule : pointRule
this.$set(this.expressionList, i, data)
})
},
/**
* 合并相近数字 1,2,3 => 1-3
* @param {Object} arr 数字数组
*/
mergeCloseNumber(arr) {
if (Array.isArray(arr)) {
let hasMergeList = []
const exportList = []
for (let i = 0; i < arr.length; i++) {
if (hasMergeList.some(t => t === arr[i])) continue
const mergeItem = []
let nowValue = arr[i]
mergeItem.push(arr[i])
for (let j = i + 1; j < arr.length; j++) {
if (nowValue + 1 === arr[j]) {
mergeItem.push(arr[j])
nowValue = arr[j]
continue
}
break
}
exportList.push(mergeItem)
hasMergeList = [...hasMergeList, ...mergeItem]
}
return exportList.map(m => m.length > 1 ? `${m[0]}-${m[m.length - 1]}/1` : `${m[0]}`)
} else {
return arr
}
},
/**
* 提交时验证表达式
* @returns {Boolean} true/false
*/
validationExpression() {
let flag = true
this.autoRuleList.forEach(m => {
if (this.$validator.errors.has(m.key + 'Rule') && this.currentWay === 'selectGeneration') {
this.tabName = m.key
flag = false
}
})
if (this.currentWay === 'manualInput' && this.$validator.errors.has('periodicCron')) {
this.currentWay = 'manualInput'
flag = false
}
return {
check: flag,
rule: this.currentWay === 'manualInput' ? this.periodicCron : this.expressionShowText
}
},
/**
* 根据表达式设置选中状态
* @param {String} v
* 目前传入值仅支持 4 中形式
* 1. *
* 2. min-max/d
* 3. d,d,d,d
* 4. ※/d <===> min-max/d
*/
setValue(setValue) {
this.$nextTick(() => {
const periodicList = setValue.split(' ')
periodicList.forEach((m, i) => {
const item = this.autoRuleList[i]
if (m === '*') {
item.radio = 0
item.checkboxList.forEach(t => {
t.checked = true
})
} else if (m.indexOf('/') !== -1 && m.split('/')[0].split('-')[1] * 1 === item.max) {
// min-max/d
item.radio = 0
item.loop.start = m.split('/')[0].split('-')[0] * 1
item.loop.inter = m.split('/')[1] * 1
} else if (m.indexOf('*/') !== -1) {
// */d
item.radio = 0
item.loop.start = loopStarZeroList.includes(item.key) ? 0 : 1
item.loop.inter = m.split('/')[1] * 1
} else if (!/[^(\d{1,2},)]|[^(\d{1,2})]/g.test(m)) {
// d,d,d,d
item.radio = 1
item.checkboxList.forEach((box, boxIndex) => {
box.checked = m.split(',').some(s => {
return loopStarOneList.includes(item.key) ? s * 1 - 1 === box.v * 1 : s * 1 === box.v * 1
})
})
} else {
// 匹配不到
this.currentWay = 'manualInput'
}
})
this.renderRule()
})
}
}
}
</script>
<style lang="scss" scoped>
$commonBorderColor: #dddddd;
$blueBtnBg: #c7dcff;
$colorBalck: #313238;
$colorBlue: #3a84ff;
$colorGrey: #63656e;
$bgBlue: #3a84ff;
.ui-checkbox-group {
margin-top: 20px;
margin-right: 18px;
display: inline-block;
.ui-checkbox-input {
display: none;
}
.ui-checkbox-icon {
box-sizing: border-box;
display: inline-block;
position: relative;
width: 16px;
height: 16px;
border: 1px solid #979BA5;
text-align: center;
padding-left: 12px;
line-height: 1;
}
.ui-checkbox-label {
user-select: none;
cursor: pointer;
color: $colorGrey;
.ui-checkbox-tex,
.ui-checkbox-icon {
vertical-align: middle;
}
}
.ui-checkbox-input:checked + .ui-checkbox-label > .ui-checkbox-icon::after {
content: "";
position: absolute;
left: 2px;
top: 2px;
height: 4px;
width: 8px;
border-left: 2px solid;
border-bottom: 2px solid;
border-color: #ffffff;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.ui-checkbox-input:checked + .ui-checkbox-label > .ui-checkbox-icon {
background: $bgBlue;
border-color: $bgBlue;
}
}
.bk-form-radio {
margin-right: 30px;
}
.bk-form-checkbox {
margin-top: 20px;
margin-right: 22px;
min-width: 40px;
}
.rule-tips {
position: absolute;
top: 0;
right: 0;
margin-right: -26px;
margin-top: 8px;
color: #c4c6cc;
font-size: 14px !important;
&:hover {
color: #f4aa1a;
}
}
.local-error-tip {
margin-top: 10px;
font-size: 14px;
line-height: 1;
color: #ff5757;
}
.loop-rule-select {
position: relative;
width: 500px;
.loop-rule-title {
width: 100%;
white-space: nowrap;
.rule-btn {
width: 50%;
border-radius: 0;
}
.bk-button.bk-primary {
position: relative;
z-index: 4;
color: #3a84ff;
background-color: #c7dcff;
border-radius: 2px;
border: 1px solid #3a84ff;
}
}
// content
.content-wrapper {
margin-top: 18px;
// background-color: $whiteDefault;
.selected-input {
margin-bottom: 18px;
& > .bk-form-control {
width: 450px;
}
.clear-selected {
float: right;
margin-top: 11px;
font-size: 12px;
color: #3a84ff;
cursor: pointer;
}
/deep/.bk-form-input {
color: #333333;
cursor: text;
}
}
/deep/ .tab2-nav-item {
width: 20%;
border-bottom: 1px solid $commonBorderColor;
line-height: 40px !important;
&:not(:first-child) {
border-left: 1px solid $commonBorderColor !important;
}
}
/deep/.bk-tab2-nav .active {
border-bottom: none;
border-right: none !important;
}
/deep/ .bk-tab2 {
border: 1px solid $commonBorderColor;
}
/deep/ .bk-tab-label-list {
width: 100%;
.bk-tab-label-item {
width: 20.05%;
}
}
.tabpanel-container {
padding: 20px;
.loop-select-bd {
margin-top: 18px;
font-size: 14px;
color: $colorBalck;
.loop-time {
display: inline-block;
margin: 0 10px;
width: 46px;
}
.month-tips {
margin-left: 6px;
color: #c4c6cc;color: #c4c6cc;
font-size: 14px;
&:hover {
color: #f4aa1a;
}
}
}
.appoint-select-bd {
margin-top: 18px;
padding: 0 20px 20px 20px;
border: 1px solid $commonBorderColor;
}
}
}
.periodic-img-tooltip {
position: absolute;
right: -18px;
top: 10px;
color: #c4c6cc;
font-size: 14px;
z-index: 4;
&:hover {
color: #f4aa1a;
}
/deep/ .bk-tooltip-arrow {
display: none;
}
/deep/ .bk-tooltip-inner {
max-width: 520px;
padding: 0px;
border: none;
background-color: transparent;
}
}
}
</style>

View File

@@ -0,0 +1,5 @@
const PERIODIC_REG = /^((\*\/)?(([0-5]?\d[,-/])*([0-5]?\d))|\*)[ ]((\*\/)?(([0]?[0-9]|1\d|2[0-3])[,-/])*(([0]?[0-9]|1\d|2[0-3]))|\*)[ ]((\*\/)?((([0-6][,-/])*[0-6])|((mon|tue|wed|thu|fri|sat|sun)[,-/])*(mon|tue|wed|thu|fri|sat|sun))|\*)[ ]((\*\/)?((0?[1-9]|[12]\d|3[01])[,-/])*((0?[1-9]|[12]\d|3[01]))|\*)[ ]((\*\/)?((0?[1-9]|1[0-2])[,-/])*(0?[1-9]|1[0-2])|\*)$/
export {
PERIODIC_REG
}

View File

@@ -42,9 +42,14 @@ import lodash from 'lodash'
import './assets/custom_icon/iconfont.css'
// import '../static/cw-icon/iconfont.css'
import 'echarts/dist/extension/dataTool'
import VeeValidate, {Validator} from 'vee-validate'
import cron from '@/assets/js/cron-validator.js'
// Vue.use(ElementUI)
// Vue.use(ViewUI);
const config = {
errorBagName: 'veeErrors',
fieldsBagName: 'veeFields'
}
Vue.use(VeeValidate, config)
Vue.use(bkMagic)
Vue.use(Echarts)
Vue.use(G6)
@@ -61,9 +66,18 @@ const headTheme = 'light' // 选择 light 或 blue
Vue.prototype.headTheme = headTheme
Vue.prototype.$lodash = lodash
Vue.prototype.hasPerm = hasPermission
Vue.config.productionTip = false
Vue.prototype.cloneDeep = function(data) {
return lodash.cloneDeep(data)
}
Validator.extend('cronRlue', {
getMessage: (field, args) => args + '输入定时表达式非法,请校验',
validate: value => cron.validate(value).status
})
Validator.extend('integer', {
getMessage: (field, args) => args + '间隔时间必须是正整数',
validate: value => Number(value) >= 1 && Number(value) % 1 === 0
})
/* eslint-disable no-new */
new Vue({
el: '#app',

View File

@@ -35,6 +35,8 @@ import JobViewDetail from '@/views/job_monitor/history/job_view_detail'
import variableChange from '@/views/job_flow_mgmt/variable_change'
import AddCalendarMgmt from '@/views/job_flow_mgmt/add_calendar_mgmt'
import LargeScreen from '@/views/job_monitor_large_screen/large_screen'
import TaskList from '@/views/task_mgmt/task_list'
import TaskCreate from '@/views/task_mgmt/task_create'
// const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法
@@ -375,6 +377,24 @@ router.beforeEach((to, from, next) => {
'meta': {
'title': '告警中心'
}
},
{
'path': '/taskList',
'name': 'TaskList',
'component': 'TaskList',
'meta': {
'title': '任务管理'
}
},
{
'path': '/taskCreate',
'name': 'taskCreate',
'component': 'TaskCreate',
'meta': {
'title': '新建任务',
'fatherName': 'TaskList',
'back': 'true'
}
}
]
getButton = [
@@ -508,6 +528,20 @@ router.beforeEach((to, from, next) => {
'search': true,
'operate': true
}
},
{
'url': '/taskList',
'auth': {
'search': true,
'operate': true
}
},
{
'url': '/taskCreate',
'auth': {
'search': true,
'operate': true
}
}
]
saveObjArr('router', getRouter) // 存储路由到localStorage
@@ -578,13 +612,14 @@ const ROUTER_MAP = {
'SystemClassManage': SystemClassManage,
'JobFlowDetail': JobFlowDetail,
'JobViewDetail': JobViewDetail,
'LargeScreen': LargeScreen
'LargeScreen': LargeScreen,
'TaskList': TaskList,
'TaskCreate': TaskCreate
}
function filterAsyncRouter(asyncRouterMap) { // 遍历后台传来的路由字符串,转换为组件对象
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
// console.log(route.component, 'oooo')
route.component = ROUTER_MAP[route.component]
// if (route.component === 'Layout') { //Layout组件特殊处理
// route.component = Layout

View File

@@ -77,7 +77,7 @@
<template slot-scope="props">
<div style="display: flex;align-items: center;">
<bk-button class="mr10" theme="primary" text @click="handleImplement(props.row)"
v-if="auth.operate">执行</bk-button>
v-if="auth.operate">新建任务</bk-button>
<bk-button class="mr10" theme="primary" text @click="handleOpenUpdate(props.row)"
v-if="auth.modify">修改</bk-button>
<bk-button class="mr10" theme="primary" text @click="handleDelete(props.row)"
@@ -252,21 +252,13 @@
type: 'primary',
title: '确认要执行吗?',
confirmLoading: false,
confirmFn: async() => {
this.tableLoading = true
this.$api.process.execute({
process_id: row.id
}).then(res => {
if (res.result) {
this.$cwMessage('执行成功!', 'success')
this.$store.commit('changeTabActive', 'jobflowview')
this.$router.push({
path: '/jobflowview'
})
} else {
this.$cwMessage('启动失败', 'error')
confirmFn: () => {
this.$router.push({
path: '/taskCreate',
query: {
job_flow_data: row.id,
type: 'detail'
}
this.tableLoading = false
})
}
})

View File

@@ -96,6 +96,12 @@
addModeDialog, // 前置作业流连线模式选择弹窗
preFlowCanvas // 前置作业流详情画布
},
props: {
pid: {
type: String,
default: ''
}
},
data() {
return {
formLoading: false,
@@ -672,7 +678,7 @@
this.formLoading = true
}
// 在操作接口未调用结束的情况下不做轮询
this.$api.processRun.retrieve(parseInt(this.$route.query.id)).then(res => {
this.$api.processRun.retrieve(parseInt(this.$route.query.id) || parseInt(this.pid)).then(res => {
if (res.result) {
this.form = res.data
if (this.form.hasOwnProperty('pre_commands')) {

View File

@@ -0,0 +1,229 @@
<template>
<div style="height: 100%;">
<bk-steps ext-cls="custom-icon"
:controllable="controllableSteps.controllable"
:steps="controllableSteps.steps"
:cur-step.sync="controllableSteps.curStep"
@step-changed="stepChanged">
</bk-steps>
<div class="step-1" v-if="controllableSteps.curStep === 1">
<single-job-flow></single-job-flow>
</div>
<div class="step-2" v-if="controllableSteps.curStep === 2">
<div style="padding-top: 26px;padding-left: 30px;">
<div style="font-size: 14px;color: #2b2929;">基础信息</div>
</div>
<bk-divider></bk-divider>
<div style="padding: 30px;">
<bk-form :label-width="100" :model="formData">
<bk-form-item label="任务名称" :required="true" :property="'name'">
<bk-input v-model="formData.name" style="width: 350px;"></bk-input>
</bk-form-item>
</bk-form>
</div>
<div style="padding-top: 26px;padding-left: 30px;">
<div style="font-size: 14px;color: #2b2929;">参数信息</div>
</div>
<bk-divider></bk-divider>
<div style="padding: 30px;">
<bk-form :label-width="100" :model="formData">
<bk-form-item label="测试1" :required="true" :property="'name'">
<bk-input v-model="formData.name1" style="width: 350px;"></bk-input>
</bk-form-item>
<bk-form-item label="测试2" :required="true" :property="'name'">
<bk-input v-model="formData.name2" style="width: 350px;"></bk-input>
</bk-form-item>
<bk-form-item label="测试3" :required="true" :property="'name'">
<bk-input v-model="formData.name3" style="width: 350px;"></bk-input>
</bk-form-item>
</bk-form>
</div>
</div>
<div class="step-2" v-if="controllableSteps.curStep === 3">
<div style="padding-top: 26px;padding-left: 30px;">
<div style="font-size: 14px;color: #2b2929;">执行方式</div>
</div>
<bk-divider></bk-divider>
<div style="padding: 30px;">
<bk-form :label-width="100" :model="formData">
<bk-form-item label="执行时间">
<bk-radio-group v-model="formItem.runtimeType">
<div style="margin-top: 8px;">
<bk-radio :value="'now'">立即</bk-radio>
</div>
<div style="margin-top: 16px;">
<bk-radio :value="'time'">定时</bk-radio>
<div style="display: inline-block;">
<bk-date-picker
:disabled="formItem.runtimeType !== 'time'"
v-model="formItem.beginTime"
style="width: 240px;margin-left: 20px;"
:type="'datetime'"
:options="disableTime"
@change="changeTime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择开始的日期时间">
</bk-date-picker>
</div>
</div>
<div style="color: #EA3636;font-size: 12px;margin: 8px 0 0 68px" v-show="displayImmediate">请填写定时时间</div>
<div style="margin-top: 16px;height: 32px">
<bk-radio :value="'cycle'">周期</bk-radio>
<bk-date-picker
:disabled="formItem.runtimeType !== 'cycle'"
v-model="formItem.cyclebeginTime"
style="width: 240px;margin-left: 20px;"
:type="'datetime'"
:options="disableTime"
@change="changeTime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择开始的日期时间">
</bk-date-picker>
<div style="margin-top: -32px;margin-left: 310px;">
<bk-button style="float: left;border: none;background: transparent;width: 54px;padding: 0 8px;">每隔</bk-button>
<bk-input
style="width:80px;float: left;"
type="number"
v-model="formItem.cycleDat"
:min="1"
:precision="0"
ext-cls="interval-wrap"
:disabled="formItem.runtimeType !== 'cycle'">
</bk-input>
<bk-select
v-model="formItem.cycleType"
:clearable="false"
style="width:80px;float: left;margin-left: 10px;background-color: #fff;"
:disabled="formItem.runtimeType !== 'cycle'">
<bk-option name="分钟" key="min" id="min"></bk-option>
<bk-option name="小时" key="hour" id="hour"></bk-option>
<bk-option name="天" key="day" id="day"></bk-option>
</bk-select>
<span style="margin-left: 10px;">执行一次</span>
</div>
<div style="color: #EA3636;font-size: 12px;margin: 28px 0 0 68px;position: absolute;line-height: 32px;" v-show="displayCycle">请填写周期时间及间隔</div>
</div>
<div style="margin-top: 16px;">
<bk-radio :value="'cron'">自定义</bk-radio>
<LoopRuleSelect
v-if="formItem.runtimeType === 'cron'"
ref="loopRuleSelect"
style="margin-left: 70px;margin-top: -20px;"
:manual-input-value="periodicCron">
</LoopRuleSelect>
</div>
</bk-radio-group>
</bk-form-item>
</bk-form>
</div>
</div>
<div class="step-2" v-if="controllableSteps.curStep === 4">
<view-detail :pid="'32'"></view-detail>
</div>
<div class="step-footer">
<bk-button :theme="'default'" :title="'主要按钮'" class="mr10" style="margin: 20px;width: 100px;">
上一步
</bk-button>
<bk-button :theme="'primary'" :title="'主要按钮'" class="mr10" style="margin: 20px;width: 100px;">
下一步
</bk-button>
</div>
</div>
</template>
<script>
import singleJobFlow from '../job_flow_mgmt/single_job_flow'
import viewDetail from '../job_monitor/monitor/view_detail'
import LoopRuleSelect from '@/components/time_crontab/crontab'
export default {
name: 'task-create',
components: {
'single-job-flow': singleJobFlow,
'view-detail': viewDetail,
'LoopRuleSelect': LoopRuleSelect
},
data() {
return {
controllableSteps: {
controllable: true,
steps: [
{ title: '节点选择', icon: 1 },
{ title: '参数填写', icon: 2 },
{ title: '执行方式', icon: 3 },
{ title: '任务执行', icon: 4 }
],
curStep: 1
},
formData: {
name: '',
name1: '',
name2: '',
name3: ''
},
formItem: {
taskName: '', // 任务名称
notifier: [], // 当前通知人
runtimeType: 'now', // 立即:now/定时:time/周期:cycle
beginTime: '', // 定时任务
cyclebeginTime: '', // 周期任务
cycleDat: '', // 每隔多少cycleType执行一次
cycleType: 'day',
modelList: [], // 当前选中模块集合
disabledGroup: [], // 当前选中业务的集合 *
nowModalData: [],
single: false
},
disableTime: {
disabledDate: function(val) {
const nowTime = new Date(new Date().toLocaleDateString()).getTime()
const calendarTime = new Date(val.toLocaleDateString()).getTime()
return calendarTime < nowTime
}
},
displayImmediate: false,
displayCycle: false,
periodicCron: '*/5 * * * *'
}
},
methods: {
stepChanged(index) {
console.log(index)
this.controllableSteps.curStep = index
},
changeTime(val) {
if (this.formItem.runtimeType === 'now' || this.formItem.runtimeType === 'cron') return
if (this.formItem.runtimeType === 'time') {
this.formItem.beginTime = val
}
if (this.formItem.runtimeType === 'cycle') {
this.formItem.cyclebeginTime = val
}
}
}
}
</script>
<style scoped>
.custom-icon {
margin: 20px 30px 30px 20px;
width: 90%;
}
.step-1 {
margin: 20px 30px 30px 20px;
height: 80%;
}
.step-2 {
margin: 20px 30px 30px 20px;
height: 80%;
background: #ffffff;
}
.step-footer {
background: #ffffff;
margin-bottom: -50px;
height: 100px;
border-top: 1px solid #cacedb;
}
</style>

View File

@@ -0,0 +1,471 @@
<template>
<div id="jobList">
<div class="header">
<div style="float: left;">
<bk-button theme="primary" @click="handleExportFiles">导出</bk-button>
</div>
<div style="float: right;" v-if="auth.search">
<bk-input clearable width="240px" style="width: 240px;margin-right: 8px;" :placeholder="'请输入作业名称'"
:right-icon="'bk-icon icon-search'" v-model="searchFrom.name" @right-icon-click="handleSearch"
@enter="handleSearch">
</bk-input>
<bk-button slot="dropdown-trigger" :theme="isDropdownShow === true ? 'primary' : 'default'"
@click="handleOpenSeniorSearch"
:icon-right="isDropdownShow === true ? 'angle-double-up' : 'angle-double-down'">高级搜索</bk-button>
</div>
<div class="senior-search-box" v-if="isDropdownShow">
<bk-container :margin="0">
<bk-form :label-width="100">
<bk-row>
<bk-col :span="6">
<bk-form-item label="作业名称:">
<bk-input :placeholder="'请输入作业名称'" v-model="searchFrom.name" clearable></bk-input>
</bk-form-item>
</bk-col>
<bk-col :span="6">
<bk-form-item label="Agent:">
<bk-input :placeholder="'请输入Agent名称'" v-model="searchFrom.station_name" clearable>
</bk-input>
</bk-form-item>
</bk-col>
<bk-col :span="6">
<bk-form-item label="跑批系统:">
<bk-select class="header-select" :clearable="true" style="background-color: #fff;"
v-model="searchFrom.category">
<bk-option v-for="(item, index) in runSysList" :key="index" :id="item.id"
:name="item.name">
</bk-option>
</bk-select>
</bk-form-item>
</bk-col>
<bk-col :span="6">
<bk-form-item label="IP:">
<bk-input :placeholder="'请输入IP'" v-model="searchFrom.ip" clearable></bk-input>
</bk-form-item>
</bk-col>
</bk-row>
<bk-row style="margin-top: 20px;">
<bk-col :span="6">
<bk-form-item label="作业流名称:">
<bk-input :placeholder="'请输入作业流名称'" v-model="searchFrom.process_name" clearable>
</bk-input>
</bk-form-item>
</bk-col>
<bk-col :span="6">
<bk-form-item label="创建人:">
<bk-input :placeholder="'请输入创建人'" v-model="searchFrom.creator" clearable></bk-input>
</bk-form-item>
</bk-col>
</bk-row>
<bk-row style="display: flex;justify-content: center;margin-top: 16px;">
<bk-button theme="primary" @click="handleSearch">查询</bk-button>
<bk-button style="margin-left: 8px;" @click="handleReset">重置</bk-button>
<bk-button style="margin-left: 8px;" @click="handleOpenSeniorSearch">取消</bk-button>
</bk-row>
</bk-form>
</bk-container>
</div>
</div>
<div style="clear: both;"></div>
<div class="content">
<bk-table ref="table" :data="tableList" :pagination="pagination" @page-change="handlePageChange"
@page-limit-change="handlePageLimitChange" v-bkloading="{ isLoading: tableLoading, zIndex: 10 }"
ext-cls="customTable" @select-all="handleSelectAll" @select="handleSelect" :size="setting.size" :max-height="maxTableHeight">
<bk-table-column type="selection" width="60"></bk-table-column>
<bk-table-column :label="item.label" :prop="item.id" v-for="(item, index) in setting.selectedFields"
:key="index" :show-overflow-tooltip="item.overflowTooltip" :sortable="item.sortable">
<template slot-scope="props">
<span
v-if="item.id !== 'name'">{{(props.row[item.id] === '' || props.row[item.id] === null) ? '- -' : props.row[item.id]}}</span>
<span v-else style="color: #3a84ff;cursor: pointer;"
@click="handleOpenDetail(props.row)">{{props.row[item.id]}}</span>
</template>
</bk-table-column>
<bk-table-column label="操作" width="180">
<template slot-scope="props">
<div style="display: flex;align-items: center;">
<bk-button class="mr10" theme="primary" text @click="handleOpenUpdate(props.row)"
v-if="auth.modify">修改</bk-button>
<bk-button class="mr10" theme="primary" text @click="handleClone(props.row)" v-if="auth.modify">克隆
</bk-button>
<bk-button class="mr10" theme="primary" text @click="handleDelete(props.row)" v-if="auth.del">删除
</bk-button>
</div>
</template>
</bk-table-column>
<bk-table-column type="setting">
<bk-table-setting-content :fields="setting.fields" :selected="setting.selectedFields"
@setting-change="handleSettingChange" :size="setting.size">
</bk-table-setting-content>
</bk-table-column>
</bk-table>
</div>
<div>
<bk-sideslider :is-show.sync="dialogShow" :quick-close="true" title="作业详情" :width="500" ext-cls="custom-sidelider">
<div slot="content" style="height: 100%;">
<job-dialog :job-from="jobFrom" :key="dialogKey">
</job-dialog>
</div>
</bk-sideslider>
</div>
</div>
</template>
<script>
export default {
components: {
},
data() {
const fields = [{
id: 'name',
label: '作业名',
overflowTooltip: true,
sortable: false
}, {
id: 'template_type',
label: '模板类型',
overflowTooltip: true,
sortable: false
}, {
id: 'description',
label: '作业描述',
overflowTooltip: true,
sortable: false
}]
return {
maxTableHeight: '',
auth: {},
dialogKey: 0,
jobFrom: {},
setting: {
size: 'small', // 表格大小
fields: fields, // 表格所有列
selectedFields: fields.slice(0, 8) // 表格当前显示列
},
tableLoading: false,
tableList: [],
runSysList: [], // 跑批系统下拉列表
isDropdownShow: false,
searchFrom: {
name: '', // 作业名称
station_name: '', // agent
category: '', // 跑批系统
ip: '', // ip
process_name: '', // 作业流名称
creator: '' // 创建人
},
pagination: {
current: 1,
count: 1,
limit: 10
},
selectionList: [], // 表格多选
dialogShow: false
}
},
created() {
this.handleLoad()
this.auth = this.hasPerm(this.$route.path)
this.maxTableHeight = this.$store.state.common.defaultTableHeight - 52
},
methods: {
handleJumpHistory(row) {
this.$store.commit('changeTabActive', 'jobviewhistory')
this.$router.push({
path: '/jobviewhistory',
query: {
job_id: row.id
}
})
},
// 处理克隆作业
handleClone(row) {
this.$router.push({
path: '/singlejob',
query: {
type: 'clone',
job_id: row.id
}
})
},
// 处理表格字段显隐
handleSettingChange({
fields,
size
}) {
this.setting.size = size
this.setting.selectedFields = fields
},
// 处理执行
handleImplement(row) {
this.$bkInfo({
title: '确认要执行吗?',
confirmLoading: false,
confirmFn: async() => {
this.tableLoading = true
this.$api.content.execute({
id: row.id
}).then(res => {
if (res.result) {
this.$cwMessage('执行成功!', 'success')
this.$store.commit('changeTabActive', 'jobview')
this.$router.push({
path: '/jobview'
})
} else {
this.$cwMessage(res.message, 'error')
}
this.tableLoading = false
})
}
})
},
// 处理打开详情
handleOpenDetail(row) {
this.dialogKey += 1
this.jobFrom = row
this.dialogShow = true
},
// 处理全选
handleSelectAll(selection) {
if (selection.length > 0) {
this.selectionList = this.selectionList.concat(selection)
} else {
this.tableList.forEach(ms => {
this.selectionList = this.selectionList.filter(item => item.id !== ms.id)
})
}
},
// 处理单选
handleSelect(selection, row) {
const isHaveItem = this.selectionList.find(item => item.id === row.id)
if (isHaveItem) {
this.selectionList = this.selectionList.filter(item => item.id !== isHaveItem.id)
} else {
this.selectionList.push(row)
}
},
// 处理导出
handleExportFiles() {
if (this.selectionList.length === 0) {
return this.$cwMessage('至少选择一条数据!', 'warning')
}
const ids = []
// 数组去重
this.selectionList.forEach(item => {
if (ids.indexOf(item.id) < 0) {
ids.push(item.id)
}
})
window.open(window.siteUrl + '/export/content/?id=' + ids.join(','))
},
// 处理跳转修改
handleOpenUpdate(row) {
this.$router.push({
path: '/singlejob',
query: {
type: 'update',
job_id: row.id
}
})
},
// 处理删除
handleDelete(row) {
this.$bkInfo({
type: 'primary',
title: '确认要删除吗?',
confirmLoading: false,
confirmFn: async() => {
this.tableLoading = true
this.$api.content.delete(row.id).then(res => {
if (res.result) {
this.$cwMessage('删除成功!', 'success')
if (this.tableList.length === 1 && this.pagination.current !== 1) {
this.pagination.current -= 1
}
this.handleLoad()
} else {
this.$cwMessage(res.message, 'error')
}
this.tableLoading = false
})
}
})
},
// 获取跑批系统
getRunSysList() {
this.$api.category.list().then(res => {
if (res.result) {
this.runSysList = res.data.items
} else {
this.$cwMessage(res.message, 'error')
}
})
},
// 处理搜索重置
handleReset() {
this.searchFrom = {
name: '', // 作业名称
station_name: '', // agent
category: '', // 跑批系统
ip: '', // ip
process_name: '', // 作业流名称
creator: '' // 创建人
}
},
// 处理打开高级搜索
handleOpenSeniorSearch() {
this.isDropdownShow = !this.isDropdownShow
// this.handleReset()
},
// 处理表格size切换
handlePageLimitChange(val) {
this.pagination.current = 1
this.pagination.limit = val
this.handleLoad()
},
// 处理查找
handleSearch() {
this.pagination.current = 1
this.handleLoad()
},
// 处理页面跳转
handlePageChange(page) {
this.pagination.current = page
this.handleLoad()
},
// 处理表格默认选择
defaultCheck() {
this.$nextTick(() => {
this.selectionList.forEach(item1 => {
this.tableList.forEach(item2 => {
if (item1.id === item2.id) {
this.$refs.table.toggleRowSelection(item2, true)
}
})
})
})
},
handleLoad() {
this.tableLoading = true
this.$api.Task.list({
...this.searchFrom,
page: this.pagination.current,
page_size: this.pagination.limit
}).then(res => {
if (res.result) {
this.pagination.count = res.data.count
this.tableList = res.data.items
if (this.selectionList.length > 0) {
this.defaultCheck()
}
} else {
this.$cwMessage(res.message, 'error')
}
this.tableLoading = false
})
}
}
}
</script>
<style lang="scss">
.dot-menu {
display: inline-block;
vertical-align: middle;
.tippy-tooltip.dot-menu-theme {
padding: 0;
}
}
.dot-menu-list {
margin: 0;
padding: 5px 0;
min-width: 50px;
list-style: none;
}
.dot-menu-list .dot-menu-item {
padding: 0 10px;
font-size: 12px;
line-height: 26px;
cursor: pointer;
text-align: center;
&:hover {
background-color: #eaf3ff;
color: #3a84ff;
}
}
</style>
<style lang="scss" scoped>
#jobList {
padding: 20px;
height: 100%;
overflow: auto;
.header {
width: 100%;
font-size: 0;
margin-bottom: 20px;
float: left;
// position: relative;
.senior-search-box {
background-color: #fff;
padding: 20px;
width: 100%;
margin-top: 20px;
float: left;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, .1);
border: 1px solid rgba(0, 0, 0, .2);
}
}
.content {
.customTable {
/deep/ .bk-table-pagination-wrapper {
background-color: #fff;
}
/deep/ .bk-table-empty-block {
background-color: #fff;
}
.dot-menu-trigger {
display: block;
width: 20px;
height: 20px;
line-height: 20px;
border-radius: 50%;
text-align: center;
font-size: 0;
cursor: pointer;
}
.dot-menu-trigger:hover {
color: #3A84FF;
background-color: #DCDEE5;
}
.dot-menu-trigger:before {
content: "";
display: inline-block;
width: 3px;
height: 3px;
border-radius: 50%;
background-color: currentColor;
box-shadow: 0 -4px 0 currentColor, 0 4px 0 currentColor;
}
}
}
.custom-sidelider {
/deep/ .bk-sideslider-wrapper {
overflow-y: hidden;
}
}
}
</style>