mirror of
https://github.com/xhongc/music-tag-web.git
synced 2026-02-02 17:59:07 +08:00
feature: 任务管理-80%
This commit is contained in:
0
applications/task/__init__.py
Normal file
0
applications/task/__init__.py
Normal file
3
applications/task/admin.py
Normal file
3
applications/task/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
applications/task/apps.py
Normal file
5
applications/task/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TaskConfig(AppConfig):
|
||||
name = 'task'
|
||||
30
applications/task/migrations/0001_initial.py
Normal file
30
applications/task/migrations/0001_initial.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
applications/task/migrations/__init__.py
Normal file
0
applications/task/migrations/__init__.py
Normal file
29
applications/task/models.py
Normal file
29
applications/task/models.py
Normal 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")
|
||||
9
applications/task/serializers.py
Normal file
9
applications/task/serializers.py
Normal 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__"
|
||||
3
applications/task/tests.py
Normal file
3
applications/task/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
6
applications/task/urls.py
Normal file
6
applications/task/urls.py
Normal 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")
|
||||
12
applications/task/views.py
Normal file
12
applications/task/views.py
Normal 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
|
||||
@@ -34,7 +34,8 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
"custom_plugins",
|
||||
"rest_framework",
|
||||
"applications.flow"
|
||||
"applications.flow",
|
||||
"applications.task"
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
]
|
||||
|
||||
7
web/src/api/apiUrl/task/task.js
Normal file
7
web/src/api/apiUrl/task/task.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import {GET, reUrl} from '../../axiosconfig/axiosconfig'
|
||||
|
||||
export default {
|
||||
list: function(params) {
|
||||
return GET(reUrl + '/task/task/', params)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
BIN
web/src/assets/base/img/task-example.png
Normal file
BIN
web/src/assets/base/img/task-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -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;
|
||||
})();
|
||||
64
web/src/assets/js/convert-expression/index.js
Normal file
64
web/src/assets/js/convert-expression/index.js
Normal 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;
|
||||
})();
|
||||
@@ -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;
|
||||
})();
|
||||
42
web/src/assets/js/convert-expression/range-conversion.js
Normal file
42
web/src/assets/js/convert-expression/range-conversion.js
Normal 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;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
31
web/src/assets/js/cron-validator.js
Normal file
31
web/src/assets/js/cron-validator.js
Normal 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
|
||||
};
|
||||
})();
|
||||
162
web/src/assets/js/pattern-validation.js
Normal file
162
web/src/assets/js/pattern-validation.js
Normal 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;
|
||||
})();
|
||||
@@ -5,6 +5,7 @@
|
||||
</keep-alive>
|
||||
<router-view v-if="$route.path !== '/largescreen'"></router-view>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
<leftMenu ref="leftMenu"></leftMenu>
|
||||
</template>
|
||||
<!-- 内容区域 -->
|
||||
<container></container>
|
||||
<container>
|
||||
</container>
|
||||
</bk-navigation>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
635
web/src/components/time_crontab/crontab.vue
Normal file
635
web/src/components/time_crontab/crontab.vue
Normal 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>
|
||||
5
web/src/constants/index.js
Normal file
5
web/src/constants/index.js
Normal 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
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
229
web/src/views/task_mgmt/task_create.vue
Normal file
229
web/src/views/task_mgmt/task_create.vue
Normal 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>
|
||||
471
web/src/views/task_mgmt/task_list.vue
Normal file
471
web/src/views/task_mgmt/task_list.vue
Normal 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>
|
||||
Reference in New Issue
Block a user