异步任务管理
parent
aae1c3429a
commit
0ce069a95d
|
@ -45,10 +45,12 @@ INSTALLED_APPS = [
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'captcha',
|
'captcha',
|
||||||
|
'djcelery',
|
||||||
# 自定义app
|
# 自定义app
|
||||||
'apps.vadmin.permission',
|
'apps.vadmin.permission',
|
||||||
'apps.vadmin.op_drf',
|
'apps.vadmin.op_drf',
|
||||||
'apps.vadmin.system',
|
'apps.vadmin.system',
|
||||||
|
'apps.vadmin.celery',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from django.contrib import admin
|
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DpCmdbConfig(AppConfig):
|
||||||
|
name = 'vadmin.celery'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import django_filters
|
||||||
|
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalScheduleFilter(django_filters.rest_framework.FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = IntervalSchedule
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class CrontabScheduleFilter(django_filters.rest_framework.FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = CrontabSchedule
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicTaskFilter(django_filters.rest_framework.FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = PeriodicTask
|
||||||
|
fields = '__all__'
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..op_drf.serializers import CustomModelSerializer
|
||||||
|
from ..utils.exceptions import APIException
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalScheduleSerializer(CustomModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = IntervalSchedule
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class CrontabScheduleSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
queryset = CrontabSchedule.objects.filter(**self.validated_data)
|
||||||
|
if queryset.count() and (
|
||||||
|
queryset.count() == 1 and self.initial_data.get('id', None) not in queryset.values_list('id',
|
||||||
|
flat=True)):
|
||||||
|
raise APIException(message='值已存在!!!')
|
||||||
|
super().save(**kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CrontabSchedule
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicTaskSerializer(CustomModelSerializer):
|
||||||
|
interval_list = serializers.SerializerMethodField(read_only=True)
|
||||||
|
crontab_list = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
|
def get_interval_list(self, obj):
|
||||||
|
return IntervalScheduleSerializer(obj.interval).data if obj.interval else {}
|
||||||
|
|
||||||
|
def get_crontab_list(self, obj):
|
||||||
|
return CrontabScheduleSerializer(obj.crontab).data if obj.crontab else {}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PeriodicTask
|
||||||
|
fields = '__all__'
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,20 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
|
|
||||||
|
from apps.vadmin.celery.views import IntervalScheduleModelViewSet, CrontabScheduleModelViewSet, PeriodicTaskModelViewSet, TasksAsChoices, \
|
||||||
|
OperateCeleryTask
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
# 调度间隔
|
||||||
|
router.register(r'intervalschedule', IntervalScheduleModelViewSet)
|
||||||
|
router.register(r'crontabschedule', CrontabScheduleModelViewSet)
|
||||||
|
router.register(r'periodictask', PeriodicTaskModelViewSet)
|
||||||
|
|
||||||
|
urlpatterns = format_suffix_patterns([
|
||||||
|
# 获取所有 tasks 名称
|
||||||
|
url(r'^tasks_as_choices/', TasksAsChoices.as_view()),
|
||||||
|
url(r'^operate_celery/', OperateCeleryTask.as_view()),
|
||||||
|
])
|
||||||
|
|
||||||
|
urlpatterns += router.urls
|
|
@ -0,0 +1,101 @@
|
||||||
|
from djcelery.admin import TaskSelectWidget
|
||||||
|
from djcelery.models import IntervalSchedule, CrontabSchedule, PeriodicTask
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from ..celery.filters import IntervalScheduleFilter, CrontabScheduleFilter, PeriodicTaskFilter
|
||||||
|
from ..celery.serializers import IntervalScheduleSerializer, CrontabScheduleSerializer, PeriodicTaskSerializer
|
||||||
|
from ..op_drf.views import CustomAPIView
|
||||||
|
from ..op_drf.viewsets import CustomModelViewSet
|
||||||
|
from ..system.models import DictData
|
||||||
|
from ..system.serializers import DictDataSerializer
|
||||||
|
from ..utils.response import SuccessResponse
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalScheduleModelViewSet(CustomModelViewSet):
|
||||||
|
"""
|
||||||
|
IntervalSchedule 调度间隔模型
|
||||||
|
every 次数
|
||||||
|
period 时间(天,小时,分钟,秒.毫秒)
|
||||||
|
"""
|
||||||
|
queryset = IntervalSchedule.objects.all()
|
||||||
|
serializer_class = IntervalScheduleSerializer
|
||||||
|
create_serializer_class = IntervalScheduleSerializer
|
||||||
|
update_serializer_class = IntervalScheduleSerializer
|
||||||
|
filter_class = IntervalScheduleFilter
|
||||||
|
search_fields = ('every', 'period')
|
||||||
|
ordering = 'every' # 默认排序
|
||||||
|
|
||||||
|
|
||||||
|
class CrontabScheduleModelViewSet(CustomModelViewSet):
|
||||||
|
"""
|
||||||
|
CrontabSchedule crontab调度模型
|
||||||
|
minute 分钟
|
||||||
|
hour 小时
|
||||||
|
day_of_week 每周的周几
|
||||||
|
day_of_month 每月的某一天
|
||||||
|
month_of_year 每年的某一个月
|
||||||
|
|
||||||
|
"""
|
||||||
|
queryset = CrontabSchedule.objects.all()
|
||||||
|
serializer_class = CrontabScheduleSerializer
|
||||||
|
filter_class = CrontabScheduleFilter
|
||||||
|
search_fields = ('minute', 'hour')
|
||||||
|
ordering = 'minute' # 默认排序
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicTaskModelViewSet(CustomModelViewSet):
|
||||||
|
"""
|
||||||
|
PeriodicTask celery 任务数据模型
|
||||||
|
name 名称
|
||||||
|
task celery任务名称
|
||||||
|
interval 频率
|
||||||
|
crontab 任务编排
|
||||||
|
args 形式参数
|
||||||
|
kwargs 位置参数
|
||||||
|
queue 队列名称
|
||||||
|
exchange 交换
|
||||||
|
routing_key 路由密钥
|
||||||
|
expires 过期时间
|
||||||
|
enabled 是否开启
|
||||||
|
|
||||||
|
"""
|
||||||
|
queryset = PeriodicTask.objects.all()
|
||||||
|
serializer_class = PeriodicTaskSerializer
|
||||||
|
filter_class = PeriodicTaskFilter
|
||||||
|
search_fields = ('name', 'task', 'date_changed')
|
||||||
|
ordering = 'date_changed' # 默认排序
|
||||||
|
|
||||||
|
|
||||||
|
class TasksAsChoices(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
"""
|
||||||
|
获取所有 tasks 名称
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
lis = []
|
||||||
|
|
||||||
|
def get_data(datas):
|
||||||
|
for item in datas:
|
||||||
|
if isinstance(item, (str, int)) and item:
|
||||||
|
lis.append(item)
|
||||||
|
else:
|
||||||
|
get_data(item)
|
||||||
|
|
||||||
|
get_data(TaskSelectWidget().tasks_as_choices())
|
||||||
|
return SuccessResponse(list(set(lis)))
|
||||||
|
|
||||||
|
|
||||||
|
class OperateCeleryTask(CustomAPIView):
|
||||||
|
def post(self, request):
|
||||||
|
req_data = request.data
|
||||||
|
task = req_data.get('celery_name', '')
|
||||||
|
data = {
|
||||||
|
'task': ''
|
||||||
|
}
|
||||||
|
test = f"""
|
||||||
|
from {'.'.join(task.split('.')[:-1])} import {task.split('.')[-1]}
|
||||||
|
task = {task.split('.')[-1]}.delay()
|
||||||
|
"""
|
||||||
|
exec(test, data)
|
||||||
|
return SuccessResponse({'task_id': data.get('task').id})
|
|
@ -24,7 +24,7 @@ class ViewLogger(object):
|
||||||
'model'):
|
'model'):
|
||||||
self.model: Model = self.view.get_serializer().Meta.model
|
self.model: Model = self.view.get_serializer().Meta.model
|
||||||
if self.model:
|
if self.model:
|
||||||
request.session['model_name'] = getattr(self.model, '_meta').verbose_name
|
request.session['model_name'] = str(getattr(self.model, '_meta').verbose_name)
|
||||||
|
|
||||||
def handle(self, request: Request, *args, **kwargs):
|
def handle(self, request: Request, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
@ -38,6 +38,7 @@ class ViewLogger(object):
|
||||||
self.request.session['request_msg'] = msg
|
self.request.session['request_msg'] = msg
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
class APIViewLogger(ViewLogger):
|
class APIViewLogger(ViewLogger):
|
||||||
"""
|
"""
|
||||||
(1)仅在op_drf.views.CustomAPIView的子类中生效
|
(1)仅在op_drf.views.CustomAPIView的子类中生效
|
||||||
|
|
|
@ -53,5 +53,6 @@ urlpatterns = [
|
||||||
re_path('captcha/', include('captcha.urls')), # 图片验证码 路由
|
re_path('captcha/', include('captcha.urls')), # 图片验证码 路由
|
||||||
re_path(r'^permission/', include('apps.vadmin.permission.urls')),
|
re_path(r'^permission/', include('apps.vadmin.permission.urls')),
|
||||||
re_path(r'^system/', include('apps.vadmin.system.urls')),
|
re_path(r'^system/', include('apps.vadmin.system.urls')),
|
||||||
|
re_path(r'^celery/', include('apps.vadmin.celery.urls')),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from rest_framework import serializers, exceptions
|
from rest_framework import exceptions
|
||||||
from rest_framework.views import set_rollback
|
from rest_framework.views import set_rollback
|
||||||
|
|
||||||
from .request_util import get_verbose_name
|
from .request_util import get_verbose_name
|
||||||
|
@ -18,9 +18,9 @@ class APIException(Exception):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, code=201, message='API异常', args=('API异常',)):
|
def __init__(self, code=201, message='API异常', args=('API异常',)):
|
||||||
args = args
|
self.args = args
|
||||||
code = code
|
self.code = code
|
||||||
message = message
|
self.message = message
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.message
|
return self.message
|
||||||
|
@ -36,8 +36,8 @@ class FrameworkException(Exception):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message='框架异常', *args: object, **kwargs: object) -> None:
|
def __init__(self, message='框架异常', *args: object, **kwargs: object) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, )
|
||||||
message = message
|
self.message = message
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.message}"
|
return f"{self.message}"
|
||||||
|
@ -66,7 +66,7 @@ def op_exception_handler(ex, context):
|
||||||
msg = ''
|
msg = ''
|
||||||
code = '201'
|
code = '201'
|
||||||
request = context.get('request')
|
request = context.get('request')
|
||||||
request.session['model_name'] = get_verbose_name(view=context.get('view'))
|
request.session['model_name'] = str(get_verbose_name(view=context.get('view')))
|
||||||
|
|
||||||
if isinstance(ex, AuthenticationFailed):
|
if isinstance(ex, AuthenticationFailed):
|
||||||
code = 401
|
code = 401
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
asgiref==3.3.1
|
asgiref==3.3.1
|
||||||
Django==2.2.16
|
Django==2.2.16
|
||||||
django-cors-headers==3.7.0
|
django-cors-headers==3.7.0
|
||||||
|
django-celery==3.2.2
|
||||||
django-filter==2.4.0
|
django-filter==2.4.0
|
||||||
django-ranged-response==0.2.0
|
django-ranged-response==0.2.0
|
||||||
django-redis==4.12.1
|
django-redis==4.12.1
|
||||||
|
|
|
@ -1 +1,98 @@
|
||||||
# 环境部署
|
# 环境部署
|
||||||
|
## 1.前端搭建环境
|
||||||
|
|
||||||
|
### 1.1 安装node
|
||||||
|
|
||||||
|
## 1. 后端搭建环境
|
||||||
|
|
||||||
|
### 1.1 安装Python3.8
|
||||||
|
|
||||||
|
### 1.2 安装Reids
|
||||||
|
sudo apt-get install -y redis-server
|
||||||
|
|
||||||
|
### 1.3 安装nginx
|
||||||
|
sudo apt-get install -y nginx
|
||||||
|
|
||||||
|
### 1.1 安装其它软件
|
||||||
|
sudo apt-get install -y python3-venv pcre pcre-devel pcre-static zlib* gcc openssl openssl-devel libffi-devel
|
||||||
|
|
||||||
|
## 2. 创建虚拟环境
|
||||||
|
### 2.1 进入项目目录 cd gh-baohua-backend
|
||||||
|
在项目根目录中,复制./conf/env.example.py文件为一份新的到./conf文件夹下,并重命名为env.py,在env.py中配置数据库信息。
|
||||||
|
|
||||||
|
### 2.2 激活虚拟环境
|
||||||
|
|
||||||
|
#### 2.2.1 python(python3) -m venv xxxx-venv, (xxxx根据情况定义)
|
||||||
|
|
||||||
|
#### 2.2.2 \xxxx-venv\Scripts\activate (window OS)
|
||||||
|
|
||||||
|
#### 2.2.3 sudo chmod -R 777 xxxx-venv/* (Linux OS)
|
||||||
|
#### 2.2.4 source ./gh-baohua-venv/bin/activate (Linux OS)
|
||||||
|
|
||||||
|
## 3. 升级pip
|
||||||
|
|
||||||
|
sudo python(python3) -m pip install --upgrade pip
|
||||||
|
|
||||||
|
## 4. 安装依赖环境
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
## 5. 执行迁移命令:
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
## 6. 初始化数据
|
||||||
|
python manage.py init
|
||||||
|
|
||||||
|
## 7. 启动项目
|
||||||
|
python manage.py runserver 8888
|
||||||
|
|
||||||
|
## 8. 初始账号:admin 密码:123456
|
||||||
|
|
||||||
|
## 9. 搭建正式环境,完成上述步骤1-6
|
||||||
|
|
||||||
|
### 9.1 配置uwsgi.ini(主要配置项)
|
||||||
|
|
||||||
|
[uwsgi]
|
||||||
|
|
||||||
|
chdir = /mnt/dvadmin-backend
|
||||||
|
wsgi-file = /mnt/dvadmin-backend/application/wsgi.py
|
||||||
|
home = /mnt/dvadmin-backend/leo-baohua-venv
|
||||||
|
pidfile = /mnt/dvadmin-backend/uwsgi.pid
|
||||||
|
daemonize = /mnt/dvadmin-backend/uwsgi.log
|
||||||
|
master = true
|
||||||
|
processes = 8
|
||||||
|
socket = 0.0.0.0:7777
|
||||||
|
module = application.wsgi:application
|
||||||
|
vacuum = true
|
||||||
|
|
||||||
|
### 9.2 Nginx 配置
|
||||||
|
|
||||||
|
#### 9.2.1 配置uwsgi
|
||||||
|
server {
|
||||||
|
listen 7077;
|
||||||
|
server_name 192.168.xx.xxx;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass 127.0.0.1:7777;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#### 9.2.2 配置前端
|
||||||
|
server {
|
||||||
|
listen 7078;
|
||||||
|
server_name 192.168.xx.xxx;
|
||||||
|
|
||||||
|
root /mnt/dvadmin-ui/dist;
|
||||||
|
|
||||||
|
index index.html index.htm index.nginx-debian.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#### 9.2.3 配置前端接口-env.production
|
||||||
|
VUE_APP_BASE_API = 'http://192.168.xx.xxx:7077'
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"js-beautify": "1.13.0",
|
"js-beautify": "1.13.0",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"jsencrypt": "3.0.0-rc.1",
|
"jsencrypt": "3.0.0-rc.1",
|
||||||
|
"moment": "^2.29.1",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"quill": "1.3.7",
|
"quill": "1.3.7",
|
||||||
"screenfull": "5.0.2",
|
"screenfull": "5.0.2",
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
/**
|
||||||
|
* 封装celery任务信息接口请求
|
||||||
|
*/
|
||||||
|
// 获取
|
||||||
|
export const sync_data_prefix = '/admin/celery';
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function getIntervalschedulea(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/intervalschedule/${id}/`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function listIntervalschedule(params) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/intervalschedule/`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新
|
||||||
|
export function updateIntervalschedule(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/intervalschedule/${data.id}/`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
export function createIntervalschedule(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/intervalschedule/`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 删除
|
||||||
|
export function deleteIntervalschedule(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/intervalschedule/${id}/`,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function getCrontabSchedule(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/crontabschedule/${id}/`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function listCrontabSchedule(params) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/crontabschedule/`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新
|
||||||
|
export function updateCrontabSchedule(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/crontabschedule/${data.id}/`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
export function createCrontabSchedule(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/crontabschedule/`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 删除
|
||||||
|
export function deleteCrontabSchedule(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/crontabschedule/${id}/`,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function getPeriodicTask(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/periodictask/${id}/`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function listPeriodicTask(params) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/periodictask/`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取所有 tasks 名称
|
||||||
|
export function TasksAsChoices(params) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/tasks_as_choices/`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新
|
||||||
|
export function updatePeriodicTask(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/periodictask/${data.id}/`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
export function createPeriodicTask(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/periodictask/`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 删除
|
||||||
|
export function deletePeriodicTask(id) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/periodictask/${id}/`,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取
|
||||||
|
export function operatesyncdata(data) {
|
||||||
|
return request({
|
||||||
|
url: `${sync_data_prefix}/operate_celery/`,
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576580909902" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1356" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M625.664 132.608v67.072h309.76v43.008h-309.76v69.632h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.608h309.76v43.008h-309.76v68.096h309.76v43.008h-309.76v89.088H1024v-757.76H625.664zM0 914.944L577.024 1024V0L0 109.056v805.888z" p-id="1357"></path><path d="M229.376 660.48h-89.6l118.272-187.904-112.64-180.736h92.16l65.536 119.808 67.584-119.808h89.088l-112.64 177.664L466.944 660.48H373.248l-70.144-125.44z" p-id="1358"></path></svg>
|
After Width: | Height: | Size: 847 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1592549265947" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1995" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M595.716 972.68c-7.013 0-13.911-2.279-19.616-6.788l-178.877-142.27c-7.47-5.929-11.803-15.052-11.803-24.63v-301.706c0-1.023-0.458-1.938-1.141-2.619l-283.803-283.915c-1.14-1.14-2.222-2.341-3.137-3.591-16.023-16.822-24.521-38.031-24.521-60.446 0-47.612 38.775-86.387 86.33-86.387h703.654c47.558 0 86.331 38.775 86.331 86.387 0 22.183-8.434 43.284-23.718 59.362-0.57 0.801-1.765 2.222-2.566 2.907l-294.576 294.691c-0.682 0.686-1.14 1.657-1.14 2.684v434.962c0 12.090-6.899 23.041-17.846 28.282-4.336 2.052-9.010 3.079-13.573 3.079v0zM448.259 783.823l116.038 92.266v-369.791c0-17.846 6.899-34.494 19.386-47.098l293.664-293.664c0.912-1.085 1.77-1.997 2.684-2.967 3.877-4.109 6.211-9.863 6.211-15.853 0-12.943-10.606-23.49-23.491-23.49h-703.596c-12.941 0-23.491 10.606-23.491 23.49 0 6.047 2.341 11.865 6.557 16.196 1.537 1.597 2.909 3.308 4.22 4.903l282.318 282.373c12.372 12.372 19.504 29.535 19.504 47.098v286.539zM448.259 783.823z" p-id="1996"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,114 @@
|
||||||
|
<!--
|
||||||
|
@description: 封装组件, 通用图标组件(已注册全局组件)
|
||||||
|
用法:<common-icon value="el:el-icon-delete-solid"/>
|
||||||
|
用法:<common-icon value="svg:icon-folder"/>
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<!--<svg v-if="iconType && iconType.toLocaleLowerCase() === 'svg'" :class="commonClass" aria-hidden="true">
|
||||||
|
<use :xlink:href="commonName"/>
|
||||||
|
</svg>-->
|
||||||
|
<span>
|
||||||
|
<slot name="prepend"/>
|
||||||
|
<svg v-if="iconType && iconType.toLocaleLowerCase() === 'svg'" :class="commonClass" aria-hidden="true">
|
||||||
|
<use :xlink:href="commonName"/>
|
||||||
|
</svg>
|
||||||
|
<i v-if="iconType && iconType.toLocaleLowerCase() === 'el'" :class="commonClass"/>
|
||||||
|
<span v-if="showTitle">{{ iconTitle }}</span>
|
||||||
|
<slot name="append"/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CommonIcon',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
iconTitle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
showTitle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
iconType: '',
|
||||||
|
iconName: '',
|
||||||
|
commonName: '',
|
||||||
|
commonClass: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.change(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.change(this.value);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
change(val) {
|
||||||
|
const arr = val.split(':');
|
||||||
|
if (arr.length >= 2) {
|
||||||
|
this.iconType = arr[0];
|
||||||
|
this.iconName = arr[1];
|
||||||
|
} else {
|
||||||
|
this.iconType = '';
|
||||||
|
this.iconName = '';
|
||||||
|
}
|
||||||
|
this.commonName = this.getCommonName();
|
||||||
|
this.commonClass = this.getCommonClass();
|
||||||
|
},
|
||||||
|
getCommonName() {
|
||||||
|
if (this.iconType && this.iconType.toLocaleLowerCase() === 'svg') {
|
||||||
|
return `#icon-${this.iconName}`;
|
||||||
|
}
|
||||||
|
if (this.iconType && this.iconType.toLocaleLowerCase() === 'el') {
|
||||||
|
return `${this.iconName}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getCommonClass() {
|
||||||
|
if (this.iconType && this.iconType.toLocaleLowerCase() === 'svg') {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className;
|
||||||
|
} else {
|
||||||
|
return 'svg-icon';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.iconType && this.iconType.toLocaleLowerCase() === 'el') {
|
||||||
|
if (this.className) {
|
||||||
|
return this.iconName + ' ' + this.className;
|
||||||
|
} else {
|
||||||
|
return this.iconName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,465 @@
|
||||||
|
<!--
|
||||||
|
@description: 封装组件
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div style="padding-left: 10px;">
|
||||||
|
<el-row v-if="topLayout" style="margin-bottom: 20px">
|
||||||
|
<el-col v-if="topLayoutLeft" :span="18">
|
||||||
|
<div class="grid-content bg-purple">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.search"
|
||||||
|
:disabled="tableLoading"
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
:placeholder="filterPlaceholder"
|
||||||
|
clearable
|
||||||
|
style="width: 200px;"
|
||||||
|
@keyup.enter.native="handleSearchFormSubmit"/>
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="primary"
|
||||||
|
title="过滤"
|
||||||
|
@click="handleSearchFormSubmit">
|
||||||
|
<common-icon value="svg:icon-filter"/>
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-show="isFilter"
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="info"
|
||||||
|
title="取消过滤"
|
||||||
|
style="margin-left: 0;"
|
||||||
|
@click="handleCancelFilter">
|
||||||
|
<common-icon value="svg:icon-unfilter"/>
|
||||||
|
</el-button>
|
||||||
|
<slot name="button"/>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="topLayoutRight" :span="6">
|
||||||
|
<div class="grid-content bg-purple-light" style="text-align: right">
|
||||||
|
<slot name="tools"/>
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
name="refresh"
|
||||||
|
type="info"
|
||||||
|
title="导出数据"
|
||||||
|
@click="handleExportTableData">
|
||||||
|
<svg-icon icon-class="icon-excel" style="font-size: 1em"/>
|
||||||
|
</el-button>
|
||||||
|
<el-popover
|
||||||
|
placement="bottom"
|
||||||
|
width="200"
|
||||||
|
trigger="click">
|
||||||
|
<div style="width: 50px;">
|
||||||
|
<el-checkbox-group v-model="showFields">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="(field, index) in fields"
|
||||||
|
:key="index"
|
||||||
|
:label="field"
|
||||||
|
:checked="field.show"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleSelectField($event, field)">{{ field.label }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
slot="reference"
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
name="refresh"
|
||||||
|
type="info"
|
||||||
|
icon="el-icon-s-fold"
|
||||||
|
title="设置显示的字段"/>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-table
|
||||||
|
v-loading="tableLoading"
|
||||||
|
ref="table"
|
||||||
|
:data="filterData"
|
||||||
|
:span-method="spanMethod"
|
||||||
|
:max-height="maxHeight"
|
||||||
|
:row-key="getRowKeys"
|
||||||
|
:stripe="stripe"
|
||||||
|
:fit="fit"
|
||||||
|
:border="border"
|
||||||
|
:empty-text="emptyText"
|
||||||
|
:highlight-current-row="highlightCurrentRow"
|
||||||
|
:show-overflow-tooltip="showOverflowTooltip"
|
||||||
|
@cell-click="handleCellClick"
|
||||||
|
@cell-dblclick="handleCellDbClick"
|
||||||
|
@header-click="handleHeaderClick"
|
||||||
|
@row-click="handleRowClick"
|
||||||
|
@row-dblclick="handleRowDblClick"
|
||||||
|
@selection-change="handleSelectionChange">
|
||||||
|
<el-table-column
|
||||||
|
v-if="selection"
|
||||||
|
:reserve-selection="true"
|
||||||
|
type="selection"
|
||||||
|
width="50"/>
|
||||||
|
<template v-for="field in fields">
|
||||||
|
<el-table-column
|
||||||
|
v-if="field.show"
|
||||||
|
:key="field.prop"
|
||||||
|
:prop="field.prop"
|
||||||
|
:label="field.label"
|
||||||
|
:sortable="field.sortable"
|
||||||
|
:width="field.width || ''"
|
||||||
|
show-overflow-tooltip>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<slot :name="field.prop" :values="scope.row" :prop="field.prop" :field="field">
|
||||||
|
<span v-html="formatColumnData(scope.row, field)"/>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
<slot name="column"/>
|
||||||
|
</el-table>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="6" style="margin-top: 20px">
|
||||||
|
<span>已选择:<span style="color: #ff00ff;font-weight: bold;">{{ multipleSelection.length }}</span>条</span>
|
||||||
|
<el-button
|
||||||
|
v-show="multipleSelection.length"
|
||||||
|
type="info"
|
||||||
|
size="mini"
|
||||||
|
title="清空多选"
|
||||||
|
@click="clearMultipleSelection">清空</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="18" style="margin-top: 20px; text-align: right">
|
||||||
|
<span>总计:<span style="color: #ff00ff;font-weight: bold;">{{ filterData.length }}</span>条</span>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import * as Utils from '@/utils';
|
||||||
|
export default {
|
||||||
|
name: 'CommonStaticTable',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
spanMethod: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
initSelected: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
|
maxHeight: {
|
||||||
|
default: 700
|
||||||
|
},
|
||||||
|
stripe: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
fit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
highlightCurrentRow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showOverflowTooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据'
|
||||||
|
},
|
||||||
|
topLayout: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return ['left', 'right'];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomLayout: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return ['left', 'right'];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
// 后端返回的字段
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
// 开始开启多选(默认不开启, false)
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// api 对象
|
||||||
|
api: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableEditable: true,
|
||||||
|
showFields: [], // 显示的字段
|
||||||
|
filterFields: [], // 过滤的字段
|
||||||
|
filterPlaceholder: '过滤', // 过滤提示文字
|
||||||
|
buttonTagList: [], // 所有按钮标签
|
||||||
|
excelDialogVisible: false,
|
||||||
|
tableLoading: false,
|
||||||
|
advancedSearchForm: {},
|
||||||
|
advancedSearchFields: [],
|
||||||
|
rowKey: null,
|
||||||
|
multipleSelection: [],
|
||||||
|
excelHeader: [],
|
||||||
|
excelData: [],
|
||||||
|
searchForm: {
|
||||||
|
search: ''
|
||||||
|
},
|
||||||
|
getRowKeys: row => {
|
||||||
|
if (this.rowKey) {
|
||||||
|
return row[this.rowKey];
|
||||||
|
}
|
||||||
|
return row.id || row.uuid;
|
||||||
|
},
|
||||||
|
exportFields: [],
|
||||||
|
tableData: [],
|
||||||
|
filterData: [],
|
||||||
|
isFilter: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
topLayoutLeft() {
|
||||||
|
return this.topLayout.indexOf('left') >= 0;
|
||||||
|
},
|
||||||
|
topLayoutRight() {
|
||||||
|
return this.topLayout.indexOf('right') >= 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
data: {
|
||||||
|
handler: function(newData, oldData) {
|
||||||
|
this.handleChangeTableData(newData);
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initComponentData();
|
||||||
|
this.initData();
|
||||||
|
this.initSelect();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData() {
|
||||||
|
if (Utils.isFunction(this.api)) {
|
||||||
|
this.listInterfaceData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initSelect() {
|
||||||
|
for (const row of this.initSelected) {
|
||||||
|
this.$refs['table'].toggleRowSelection(row, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initComponentData() {
|
||||||
|
this.fields.forEach(field => {
|
||||||
|
field.show = (!!field.show);
|
||||||
|
field.type = (field.type || 'string').toLocaleLowerCase();
|
||||||
|
field.label = field.label || field.prop;
|
||||||
|
field.search = (!!field.search);
|
||||||
|
field.sortable = (!!field.sortable);
|
||||||
|
field.unique = (!!field.unique);
|
||||||
|
field.width = field.width || '';
|
||||||
|
if (field.type === 'choices') {
|
||||||
|
if (Utils.isArray(field.choices) && field.choices.length > 0) {
|
||||||
|
if (!Utils.isObj(field.choices[0])) {
|
||||||
|
field.choices = field.choices.map(value => {
|
||||||
|
return {
|
||||||
|
label: value,
|
||||||
|
value: value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.unique = (!!field.unique);
|
||||||
|
if (field.unique) {
|
||||||
|
this.rowKey = field.prop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.filterFields = this.fields.filter(field => field.search).map(field => field.prop);
|
||||||
|
if (this.filterFields.length) {
|
||||||
|
const text = this.fields.filter(field => field.search).map(field => field.label).join('、');
|
||||||
|
this.filterPlaceholder = `${text} 过滤`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listInterfaceData() {
|
||||||
|
this.tableLoading = true;
|
||||||
|
this.api(this.params).then(response => {
|
||||||
|
this.tableLoading = false;
|
||||||
|
this.handleChangeTableData(response.data);
|
||||||
|
}).catch(() => {
|
||||||
|
this.tableLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatColumnData(row, field) {
|
||||||
|
const type = field.type || 'string';
|
||||||
|
const prop = field.prop;
|
||||||
|
if (field.formatter && typeof field.formatter === 'function') {
|
||||||
|
return field.formatter(row, prop, type);
|
||||||
|
}
|
||||||
|
if (type === 'string') {
|
||||||
|
return row[prop];
|
||||||
|
} else if (type === 'datetime') {
|
||||||
|
return this.formatDatetime(row[prop]);
|
||||||
|
} else if (type === 'date') {
|
||||||
|
return this.formatDate(row[prop]);
|
||||||
|
} else if (type === 'time') {
|
||||||
|
return this.formatTime(row[prop]);
|
||||||
|
} else if (type.startsWith('bool')) {
|
||||||
|
return row[prop] ? '是' : '否';
|
||||||
|
} else if (type === 'choices') {
|
||||||
|
const choices = field.choices;
|
||||||
|
return this.formatChoices(choices, row[prop]);
|
||||||
|
} else {
|
||||||
|
return row[prop];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatChoices(choices, value) {
|
||||||
|
for (const choice of choices) {
|
||||||
|
if (choice.value === value) {
|
||||||
|
return choice.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
formatDatetime(datetime) {
|
||||||
|
return moment(datetime).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
},
|
||||||
|
formatDate(date) {
|
||||||
|
return moment(date).format('YYYY-MM-DD');
|
||||||
|
},
|
||||||
|
formatTime(time) {
|
||||||
|
return moment(time).format('HH:mm:ss');
|
||||||
|
},
|
||||||
|
getMultipleSelection() {
|
||||||
|
return this.multipleSelection || [];
|
||||||
|
},
|
||||||
|
clearMultipleSelection() {
|
||||||
|
this.$refs.table.clearSelection();
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.$refs.table.clearSelection();
|
||||||
|
},
|
||||||
|
clearFilter() {
|
||||||
|
// 重置过滤
|
||||||
|
this.searchForm.search = '';
|
||||||
|
this.filterData = Array.from(this.tableData);
|
||||||
|
},
|
||||||
|
handleSelectField(e, field) {
|
||||||
|
field.show = e;
|
||||||
|
},
|
||||||
|
handleChangeTableData(data) {
|
||||||
|
this.tableData = Array.from(data);
|
||||||
|
this.filterData = Array.from(this.filterHandler(this.tableData));
|
||||||
|
},
|
||||||
|
// 导出表格的数据, 当前数据、当前列
|
||||||
|
handleExportTableData() {
|
||||||
|
this.excelDialogVisible = true;
|
||||||
|
this.exportFields = this.fields.map(field => {
|
||||||
|
return { prop: field.prop, label: field.label, show: field.show };
|
||||||
|
});
|
||||||
|
this.excelHeader = this.showFields.map(field => field['prop']);
|
||||||
|
},
|
||||||
|
// 处理修改多选的值
|
||||||
|
handleSelectionChange(val) {
|
||||||
|
this.$emit('selection-change', val);
|
||||||
|
this.multipleSelection = val;
|
||||||
|
},
|
||||||
|
handleSortChange(val) {
|
||||||
|
this.sort.prop = val.prop;
|
||||||
|
this.sort.order = val.order;
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
filterHandler(data) {
|
||||||
|
if (!data) {
|
||||||
|
data = this.tableData || [];
|
||||||
|
}
|
||||||
|
const search = this.searchForm.search.trim();
|
||||||
|
if (!search.length || !this.filterFields.length) {
|
||||||
|
this.isFilter = false;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const filterData = data.filter(row => {
|
||||||
|
for (const field of this.filterFields) {
|
||||||
|
if (row[field] && row[field].indexOf(search) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
this.isFilter = true;
|
||||||
|
return filterData;
|
||||||
|
},
|
||||||
|
handleCellClick(row, column, cell, event) {
|
||||||
|
this.$emit('cell-click', row, column, cell, event);
|
||||||
|
},
|
||||||
|
handleCellDbClick(row, column, cell, event) {
|
||||||
|
this.$emit('cell-dblclick', row, column, cell, event);
|
||||||
|
},
|
||||||
|
handleRowClick(row, column, event) {
|
||||||
|
this.$emit('row-click', row, column, event);
|
||||||
|
},
|
||||||
|
handleRowDblClick(row, column, event) {
|
||||||
|
this.$emit('row-dblclick', row, column, event);
|
||||||
|
},
|
||||||
|
handleHeaderClick(column, event) {
|
||||||
|
this.$emit('header-click', column, event);
|
||||||
|
},
|
||||||
|
toggleRowSelection(row, selected = true) {
|
||||||
|
this.$refs.table.toggleRowSelection(row, selected);
|
||||||
|
},
|
||||||
|
toggleFilter() {
|
||||||
|
// 触发过滤
|
||||||
|
this.filterData = Array.from(this.filterHandler());
|
||||||
|
},
|
||||||
|
handleSearchFormSubmit() {
|
||||||
|
this.toggleFilter();
|
||||||
|
},
|
||||||
|
handleCancelFilter() {
|
||||||
|
this.isFilter = false;
|
||||||
|
this.clearFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.picker {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
.el-pagination {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.right_pagination {
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,119 @@
|
||||||
|
<!--
|
||||||
|
@description: 已封装组件、通用组件、基础组件、全局组件(已注册全局组件)
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
ref="elDialog"
|
||||||
|
:visible.sync="visible"
|
||||||
|
:loading="loading"
|
||||||
|
:append-to-body="appendToBody"
|
||||||
|
:width="width"
|
||||||
|
:show-close="false"
|
||||||
|
:destroy-on-close="destroyOnClose"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
class="small-dialog"
|
||||||
|
@open="open"
|
||||||
|
@opened="opened"
|
||||||
|
@close="close"
|
||||||
|
@closed="closed"
|
||||||
|
>
|
||||||
|
<el-row slot="title">
|
||||||
|
<el-col :span="18" style="text-align: left;">
|
||||||
|
<common-icon :icon-title="dialogTitle || 'Dialog组件'" :value="icon" style="font-size: 1.2em"/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" style="text-align: right">
|
||||||
|
<i class="el-icon-close" style="font-size: 30px; cursor: pointer" title="关闭" @click="dialogClose"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
<slot name="footer">
|
||||||
|
<div class="dialog-button">
|
||||||
|
<el-button v-if="buttons.indexOf('cancel') >= 0" :size="size" :disabled="loading" type="info" title="取消" @click="cancel">取消</el-button>
|
||||||
|
<el-button v-if="buttons.indexOf('confirm') >= 0" :size="size" :disabled="loading" type="success" title="确定" @click="confirm">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SmallDialog',
|
||||||
|
props: {
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
dialogTitle: { type: String, default: '' },
|
||||||
|
width: { type: String, default: '50%' },
|
||||||
|
icon: { type: String, default: 'el:el-icon-platform-eleme' },
|
||||||
|
buttons: { type: Array, default: () => ['cancel', 'confirm'] },
|
||||||
|
loading: { type: Boolean, default: false },
|
||||||
|
appendToBody: { type: Boolean, default: false },
|
||||||
|
destroyOnClose: { type: Boolean, default: false },
|
||||||
|
closeOnClickModal: { type: Boolean, default: true }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
size: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.visible = val;
|
||||||
|
},
|
||||||
|
visible(val) {
|
||||||
|
this.$emit('input', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.$emit('open');
|
||||||
|
},
|
||||||
|
opened() {
|
||||||
|
this.$emit('opened');
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
closed() {
|
||||||
|
this.$emit('closed');
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
this.$emit('confirm');
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
this.dialogClose();
|
||||||
|
},
|
||||||
|
dialogOpen() {
|
||||||
|
},
|
||||||
|
dialogClose() {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style rel="stylesheet/scss" lang="scss">
|
||||||
|
.small-dialog {
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
|
.small-dialog {
|
||||||
|
.dialog-body {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
.dialog-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -16,10 +16,22 @@ import './assets/icons' // icon
|
||||||
import './permission' // permission control
|
import './permission' // permission control
|
||||||
import {getDicts} from "@/api/vadmin/system/dict/data";
|
import {getDicts} from "@/api/vadmin/system/dict/data";
|
||||||
import {getConfigKey} from "@/api/vadmin/system/config";
|
import {getConfigKey} from "@/api/vadmin/system/config";
|
||||||
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, download, handleTree } from "@/utils/ruoyi";
|
import {
|
||||||
|
addDateRange,
|
||||||
|
download,
|
||||||
|
handleTree,
|
||||||
|
parseTime,
|
||||||
|
resetForm,
|
||||||
|
selectDictLabel,
|
||||||
|
selectDictLabels
|
||||||
|
} from "@/utils/ruoyi";
|
||||||
import Pagination from "@/components/Pagination";
|
import Pagination from "@/components/Pagination";
|
||||||
// 自定义表格工具扩展
|
// 自定义表格工具扩展
|
||||||
import RightToolbar from "@/components/RightToolbar"
|
import RightToolbar from "@/components/RightToolbar"
|
||||||
|
import SmallDialog from '@/components/SmallDialog';
|
||||||
|
import CommonIcon from '@/components/CommonIcon';
|
||||||
|
import CommonStaticTable from '@/components/CommonStaticTable';
|
||||||
|
import {getCrontabData, getIntervalData} from "./utils/validate"; // 通用图标组件
|
||||||
|
|
||||||
// 全局方法挂载
|
// 全局方法挂载
|
||||||
Vue.prototype.getDicts = getDicts
|
Vue.prototype.getDicts = getDicts
|
||||||
|
@ -29,6 +41,8 @@ Vue.prototype.resetForm = resetForm
|
||||||
Vue.prototype.addDateRange = addDateRange
|
Vue.prototype.addDateRange = addDateRange
|
||||||
Vue.prototype.selectDictLabel = selectDictLabel
|
Vue.prototype.selectDictLabel = selectDictLabel
|
||||||
Vue.prototype.selectDictLabels = selectDictLabels
|
Vue.prototype.selectDictLabels = selectDictLabels
|
||||||
|
Vue.prototype.getCrontabData = getCrontabData
|
||||||
|
Vue.prototype.getIntervalData = getIntervalData
|
||||||
Vue.prototype.download = download
|
Vue.prototype.download = download
|
||||||
Vue.prototype.handleTree = handleTree
|
Vue.prototype.handleTree = handleTree
|
||||||
Vue.prototype.hasPermi = function (values) {
|
Vue.prototype.hasPermi = function (values) {
|
||||||
|
@ -49,10 +63,13 @@ Vue.prototype.msgError = function (msg) {
|
||||||
Vue.prototype.msgInfo = function (msg) {
|
Vue.prototype.msgInfo = function (msg) {
|
||||||
this.$message.info(msg);
|
this.$message.info(msg);
|
||||||
}
|
}
|
||||||
|
// 自定义组件
|
||||||
|
Vue.component('small-dialog', SmallDialog);
|
||||||
// 全局组件挂载
|
// 全局组件挂载
|
||||||
Vue.component('Pagination', Pagination)
|
Vue.component('Pagination', Pagination)
|
||||||
Vue.component('RightToolbar', RightToolbar)
|
Vue.component('RightToolbar', RightToolbar)
|
||||||
|
Vue.component('common-icon', CommonIcon);
|
||||||
|
Vue.component('common-static-table', CommonStaticTable);
|
||||||
|
|
||||||
Vue.use(permission)
|
Vue.use(permission)
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,18 @@ export const constantRoutes = [
|
||||||
meta: { title: '字典数据', icon: '' }
|
meta: { title: '字典数据', icon: '' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},{
|
||||||
|
path: '/celerymanage',
|
||||||
|
component: Layout,
|
||||||
|
hidden: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'celerymanage',
|
||||||
|
component: (resolve) => require(['@/views/vadmin/monitor/celery/index'], resolve),
|
||||||
|
name: 'Data',
|
||||||
|
meta: { title: 'celery管理', icon: '' }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -388,3 +388,17 @@ export function isNumberStr(str) {
|
||||||
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
|
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否函数
|
||||||
|
export const isFunction = (o) => {
|
||||||
|
return Object.prototype.toString.call(o).slice(8, -1) === 'Function';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否数组
|
||||||
|
export const isArray = (o) => {
|
||||||
|
return Object.prototype.toString.call(o).slice(8, -1) === 'Array';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否对象
|
||||||
|
export const isObj = (o) => {
|
||||||
|
return Object.prototype.toString.call(o).slice(8, -1) === 'Object';
|
||||||
|
};
|
||||||
|
|
|
@ -81,3 +81,29 @@ export function isArray(arg) {
|
||||||
}
|
}
|
||||||
return Array.isArray(arg)
|
return Array.isArray(arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCrontabData(val) {
|
||||||
|
if (!val || Object.keys(val).length === 0) return '';
|
||||||
|
const week = {1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六', 7: '日'};
|
||||||
|
let res = '';
|
||||||
|
if (val.month_of_year !== '*') {
|
||||||
|
res = `${val.month_of_year} 月 ${val.day_of_month} 日 ${val.hour}点${val.minute}分`;
|
||||||
|
} else if (val.day_of_month !== '*') {
|
||||||
|
res = `每月 ${val.day_of_month} 日 ${val.hour}点${val.minute}分`;
|
||||||
|
} else if (val.day_of_week !== '*') {
|
||||||
|
res = `每周周${week[val.day_of_week] || val.day_of_week} ${val.hour}点${val.minute}分 `;
|
||||||
|
} else if (val.hour !== '*') {
|
||||||
|
res = `每天 ${val.hour}点${val.minute}分`;
|
||||||
|
} else if (val.minute !== '*') {
|
||||||
|
res = `每分钟 ${val.minute}秒`;
|
||||||
|
} else {
|
||||||
|
res = `${val.month_of_year} 月 ${val.day_of_month} 日 ${val.hour}点${val.minute}分`;
|
||||||
|
}
|
||||||
|
return res.replace(/\*/g, '00');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIntervalData(val) {
|
||||||
|
if (!val || Object.keys(val).length === 0) return '';
|
||||||
|
const lists = {days: '天', hours: '小时', seconds: '秒', minutes: '分钟'};
|
||||||
|
return `每${val.every !== 1 ? val.every : ''}${lists[val.period]}`;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 接口信息页面
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card class="box-card" shadow="never">
|
||||||
|
<div slot="header" class="clearfix">
|
||||||
|
<span>任务定时</span>
|
||||||
|
<el-button style="float: right; padding: 3px 0" type="text" @click="handleOpenEditCrontabForm(true)">
|
||||||
|
新增定时
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="height: 200px;">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div v-for="(val,index) in detail" :key="index">
|
||||||
|
<div class="text" style="display:inline-block;height: 10px;">
|
||||||
|
<span>{{ getCrontabData(val) }}</span>
|
||||||
|
</div>
|
||||||
|
<div style="float: right;padding-right: 10px;display:inline-block">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
size="mini"
|
||||||
|
circle
|
||||||
|
@click="handleOpenEditCrontabForm(false, val)"/>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
size="mini"
|
||||||
|
circle
|
||||||
|
@click="handleRemoveCrontabTable(val)"/>
|
||||||
|
</div>
|
||||||
|
<el-divider/>
|
||||||
|
</div>
|
||||||
|
<div v-if="detail.length===0" style="text-align: center">
|
||||||
|
暂无信息
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<edit-form-crontab-task
|
||||||
|
v-model="editCrontabFormVisible"
|
||||||
|
:entity="editCrontab"
|
||||||
|
:create="editCrontabCreate"
|
||||||
|
:periodic-data="periodicData"
|
||||||
|
:width="'30%'"
|
||||||
|
@success="handleSuccessEditCrontab"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
import EditFormCrontabTask from './edit-form-crontab-task';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { EditFormCrontabTask },
|
||||||
|
props: {
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
periodicData: [],
|
||||||
|
multipleSelection: [],
|
||||||
|
CrontabTagList: [],
|
||||||
|
editCrontab: {},
|
||||||
|
editCrontabFormVisible: false,
|
||||||
|
editCrontabCreate: false,
|
||||||
|
modelFormVisible: false,
|
||||||
|
modelSwaggerVisible: false,
|
||||||
|
batchEditFormVisible: false,
|
||||||
|
detail: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData() {
|
||||||
|
SyncDataApi.listCrontabSchedule({ page_size: 1000 }).then((response) => {
|
||||||
|
this.detail = response.data.results || [];
|
||||||
|
this.$store.state.Crontab = this.detail;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleRefresh(infos) {
|
||||||
|
this.$refs.table.clearSelection();
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handleOpenEditCrontabForm(create = false, info) {
|
||||||
|
if (create) {
|
||||||
|
this.editCrontab = { periodic: this.detail.id };
|
||||||
|
} else {
|
||||||
|
this.editCrontab = { ...info };
|
||||||
|
}
|
||||||
|
this.editCrontabCreate = create;
|
||||||
|
this.editCrontabFormVisible = true;
|
||||||
|
},
|
||||||
|
handleRemoveCrontabTable(info) {
|
||||||
|
this.$confirm('确认删除?', '确认信息', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then(() => {
|
||||||
|
SyncDataApi.deleteCrontabSchedule(info.id).then(response => {
|
||||||
|
const name = info.name ? info.name + ':' : '';
|
||||||
|
this.msgSuccess(name + '删除成功!');
|
||||||
|
this.initData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSuccessEditCrontab() {
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handleOpenModelForm() {
|
||||||
|
this.modelFormVisible = true;
|
||||||
|
},
|
||||||
|
handleOpenSwagger(model = false) {
|
||||||
|
this.modelSwaggerVisible = true;
|
||||||
|
},
|
||||||
|
handleBatchEdit() {
|
||||||
|
this.batchEditFormVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-table th {
|
||||||
|
display: table-cell !important;
|
||||||
|
}
|
||||||
|
.el-scrollbar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__wrap {
|
||||||
|
overflow: scroll;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__view {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-divider--horizontal {
|
||||||
|
margin: 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.el-button--mini.is-circle {
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,122 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 接口编辑组件
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<small-dialog
|
||||||
|
ref="dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:dialog-title="dialogTitle"
|
||||||
|
:width="width"
|
||||||
|
icon="svg:icon-interface"
|
||||||
|
@confirm="handleSubmit"
|
||||||
|
@closed="dialogClose"
|
||||||
|
@opened="dialogOpen">
|
||||||
|
<el-form v-loading="loading" ref="form" :model="form" :size="$ELEMENT.size" label-width="120px">
|
||||||
|
<el-form-item v-show="false" prop="instanceId" label="instanceId" style="width: 200px;">
|
||||||
|
<el-input v-model="form.instanceId" readonly/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="minute" label="分钟:">
|
||||||
|
<el-input v-model="form.minute" placeholder="默认: * "/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="hour" label="小时:">
|
||||||
|
<el-input v-model="form.hour" placeholder="默认: * "/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="day_of_week" label="每周的周几">
|
||||||
|
<el-input v-model="form.day_of_week" placeholder="默认: * "/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="day_of_month" label="每月的某天">
|
||||||
|
<el-input v-model="form.day_of_month" placeholder="默认: * "/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="month_of_year" label="每年的某月">
|
||||||
|
<el-input v-model="form.month_of_year" placeholder="默认: * "/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</small-dialog>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
entity: { type: Object, default: null },
|
||||||
|
value: { type: Boolean, default: null },
|
||||||
|
create: { type: Boolean, default: false },
|
||||||
|
width: { type: String, default: '50%' },
|
||||||
|
tags: { type: Array, default: () => [] }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
form: {
|
||||||
|
instanceId: '',
|
||||||
|
minute: '*',
|
||||||
|
hour: '*',
|
||||||
|
day_of_week: '*',
|
||||||
|
day_of_month: '*',
|
||||||
|
month_of_year: '*'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dialogTitle() {
|
||||||
|
return this.create ? '新增任务定时' : '编辑任务定时';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
},
|
||||||
|
dialogVisible(val) {
|
||||||
|
this.$emit('input', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dialogOpen() {
|
||||||
|
// 为True意味着是通过遍及方式打开对话框
|
||||||
|
if (!this.create) {
|
||||||
|
this.form = { ...this.entity };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.loading = true;
|
||||||
|
const data = { ...this.form };
|
||||||
|
console.log(this.form);
|
||||||
|
if (this.create) {
|
||||||
|
delete data['instanceId'];
|
||||||
|
SyncDataApi.createCrontabSchedule(data).then(response => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.dialogClose();
|
||||||
|
this.msgSuccess('新增成功!');
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
SyncDataApi.updateCrontabSchedule(data).then(response => {
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.loading = false;
|
||||||
|
this.msgSuccess('更新成功!');
|
||||||
|
this.dialogClose();
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialogClose() {
|
||||||
|
this.$refs['form'].resetFields();
|
||||||
|
this.$parent.initData();
|
||||||
|
this.dialogVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 应用列表页面
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<interval-index/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<crontabe-index/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<periodic-task/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import PeriodicTask from './periodic-task/periodic-index';
|
||||||
|
import IntervalIndex from './interval-task/interval-index';
|
||||||
|
import CrontabeIndex from './crontab-task/crontab-index';
|
||||||
|
export default {
|
||||||
|
components: { IntervalIndex, PeriodicTask, CrontabeIndex },
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-table th {
|
||||||
|
display: table-cell !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,124 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 接口编辑组件
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<small-dialog
|
||||||
|
ref="dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:dialog-title="dialogTitle"
|
||||||
|
:width="width"
|
||||||
|
icon="svg:icon-interface"
|
||||||
|
@confirm="handleSubmit"
|
||||||
|
@closed="dialogClose"
|
||||||
|
@opened="dialogOpen">
|
||||||
|
<el-form v-loading="loading" ref="form" :model="form" :size="$ELEMENT.size" label-width="120px">
|
||||||
|
<el-form-item v-show="false" prop="instanceId" label="instanceId" style="width: 200px;">
|
||||||
|
<el-input v-model="form.instanceId" readonly/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="every" label="频率:">
|
||||||
|
<el-input-number v-model="form.every" :min="1" label="频率"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="period" label="周期:">
|
||||||
|
<el-select v-model="form.period" placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="(item,index) in lists"
|
||||||
|
:key="index.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</small-dialog>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
entity: {type: Object, default: null},
|
||||||
|
value: {type: Boolean, default: null},
|
||||||
|
create: {type: Boolean, default: false},
|
||||||
|
width: {type: String, default: '50%'},
|
||||||
|
tags: {type: Array, default: () => []}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lists: [
|
||||||
|
{label: '天', value: 'days'},
|
||||||
|
{label: '小时', value: 'hours'},
|
||||||
|
{label: '分钟', value: 'minutes'},
|
||||||
|
{label: '秒', value: 'seconds'}
|
||||||
|
],
|
||||||
|
loading: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
form: {
|
||||||
|
instanceId: '',
|
||||||
|
every: 1,
|
||||||
|
period: '',
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dialogTitle() {
|
||||||
|
return this.create ? '新增任务频率' : '编辑任务频率';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
},
|
||||||
|
dialogVisible(val) {
|
||||||
|
this.$emit('input', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dialogOpen() {
|
||||||
|
// 为True意味着是通过遍及方式打开对话框
|
||||||
|
if (!this.create) {
|
||||||
|
this.form = {...this.entity};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.loading = true;
|
||||||
|
const data = {...this.form};
|
||||||
|
console.log(this.form);
|
||||||
|
if (this.create) {
|
||||||
|
delete data['instanceId'];
|
||||||
|
SyncDataApi.createIntervalschedule(data).then(response => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.dialogClose();
|
||||||
|
this.msgSuccess('新增成功!');
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
SyncDataApi.updateIntervalschedule(data).then(response => {
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.loading = false;
|
||||||
|
this.msgSuccess('更新成功!');
|
||||||
|
this.dialogClose();
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialogClose() {
|
||||||
|
this.$refs['form'].resetFields();
|
||||||
|
this.$parent.initData();
|
||||||
|
this.dialogVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 接口信息页面
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card class="box-card" shadow="never">
|
||||||
|
<div slot="header" class="clearfix">
|
||||||
|
<span>任务频率</span>
|
||||||
|
<el-button style="float: right; padding: 3px 0" type="text" @click="handleOpenEditIntervalForm(true)">
|
||||||
|
新增频率
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="height: 200px;">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div v-for="(val,index) in detail" :key="index">
|
||||||
|
<div class="text" style="display:inline-block;height: 10px;">
|
||||||
|
{{ getIntervalData(val) }}
|
||||||
|
</div>
|
||||||
|
<div style="float: right;padding-right: 10px;display:inline-block">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
size="mini"
|
||||||
|
circle
|
||||||
|
@click="handleOpenEditIntervalForm(false, val)"/>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
size="mini"
|
||||||
|
circle
|
||||||
|
@click="handleRemoveIntervalTable(val)"/>
|
||||||
|
</div>
|
||||||
|
<el-divider/>
|
||||||
|
</div>
|
||||||
|
<div v-if="detail.length===0" style="text-align: center">
|
||||||
|
暂无信息
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<edit-form-interval-task
|
||||||
|
v-model="editIntervalFormVisible"
|
||||||
|
:entity="editInterval"
|
||||||
|
:create="editIntervalCreate"
|
||||||
|
:periodic-data="periodicData"
|
||||||
|
:width="'30%'"
|
||||||
|
@success="handleSuccessEditInterval"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
import EditFormIntervalTask from './edit-form-Interval-task';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { EditFormIntervalTask },
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
periodicData: [],
|
||||||
|
multipleSelection: [],
|
||||||
|
IntervalTagList: [],
|
||||||
|
editInterval: {},
|
||||||
|
editIntervalFormVisible: false,
|
||||||
|
editIntervalCreate: false,
|
||||||
|
modelFormVisible: false,
|
||||||
|
modelSwaggerVisible: false,
|
||||||
|
batchEditFormVisible: false,
|
||||||
|
detail: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
watch: {},
|
||||||
|
created() {
|
||||||
|
this.initData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData() {
|
||||||
|
SyncDataApi.listIntervalschedule({ page_size: 1000 }).then((response) => {
|
||||||
|
this.detail = response.data.results || [];
|
||||||
|
this.$store.state.Interval = this.detail;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleRefresh(infos) {
|
||||||
|
this.$refs.table.clearSelection();
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handleIntervalSelectionChange(infos) {
|
||||||
|
this.multipleSelection = infos;
|
||||||
|
},
|
||||||
|
handleOpenEditIntervalForm(create = false, info) {
|
||||||
|
if (create) {
|
||||||
|
this.editInterval = { periodic: this.detail.id };
|
||||||
|
} else {
|
||||||
|
this.editInterval = { ...info };
|
||||||
|
}
|
||||||
|
this.editIntervalCreate = create;
|
||||||
|
this.editIntervalFormVisible = true;
|
||||||
|
},
|
||||||
|
handleRemoveIntervalTable(info) {
|
||||||
|
this.$confirm('确认删除?', '确认信息', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then(() => {
|
||||||
|
SyncDataApi.deleteIntervalschedule(info.id).then(response => {
|
||||||
|
const name = info.name ? info.name + ':' : '';
|
||||||
|
this.msgSuccess(name + '删除成功');
|
||||||
|
this.initData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSuccessEditInterval() {
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handleOpenModelForm() {
|
||||||
|
this.modelFormVisible = true;
|
||||||
|
},
|
||||||
|
handleOpenSwagger(model = false) {
|
||||||
|
this.modelSwaggerVisible = true;
|
||||||
|
},
|
||||||
|
handleBatchEdit() {
|
||||||
|
this.batchEditFormVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-table th {
|
||||||
|
display: table-cell !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__wrap {
|
||||||
|
overflow: scroll;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar__view {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-divider--horizontal {
|
||||||
|
margin: 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.el-button--mini.is-circle {
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,191 @@
|
||||||
|
<!--
|
||||||
|
@author: xuchi
|
||||||
|
@description: 接口编辑组件
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<small-dialog
|
||||||
|
ref="dialog"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:dialog-title="dialogTitle"
|
||||||
|
:width="width"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:append-to-body="true"
|
||||||
|
icon="svg:icon-interface"
|
||||||
|
@confirm="handleSubmit"
|
||||||
|
@closed="dialogClose"
|
||||||
|
@opened="dialogOpen">
|
||||||
|
<el-form v-loading="loading" ref="form" :model="form" :size="$ELEMENT.size" label-width="120px">
|
||||||
|
<el-form-item :rules="[{ required: true, message: '任务不能为空'}]" label="celery任务:" prop="task">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="form.task"
|
||||||
|
:fetch-suggestions="querySearch"
|
||||||
|
class="inline-input"
|
||||||
|
filterable
|
||||||
|
placeholder="celery任务"
|
||||||
|
style="width: 400px;"
|
||||||
|
@select="handleSelect"
|
||||||
|
>
|
||||||
|
<template slot-scope="{ item }">
|
||||||
|
<div class="name">{{ item }}</div>
|
||||||
|
</template>
|
||||||
|
</el-autocomplete>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :rules="[{ required: true, message: '名称不能为空'}]" prop="name" label="名称:">
|
||||||
|
<el-input v-model="form.name" placeholder="例如: 主机表同步任务" style="width: 400px;"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="interval" label="任务频率:">
|
||||||
|
<el-select v-model="form.interval" placeholder="请选择任务频率" style="width: 400px;" @change="form.crontab = ''">
|
||||||
|
<el-option
|
||||||
|
v-for="(item,index) in Interval"
|
||||||
|
:key="index"
|
||||||
|
:label="getIntervalData(item)"
|
||||||
|
:value="item.id"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="crontab" label="任务定时:">
|
||||||
|
<el-select v-model="form.crontab" placeholder="请选择任务定时" style="width: 400px;" @change="form.interval = ''">
|
||||||
|
<el-option
|
||||||
|
v-for="(item,index) in Crontab"
|
||||||
|
:key="index"
|
||||||
|
:label="getCrontabData(item)"
|
||||||
|
:value="item.id"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="enabled" label="是否开启:">
|
||||||
|
<template>
|
||||||
|
<el-radio-group v-model="form.enabled">
|
||||||
|
<el-radio :label="true">是</el-radio>
|
||||||
|
<el-radio :label="false">否</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</small-dialog>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
entity: { type: Object, default: null },
|
||||||
|
value: { type: Boolean, default: null },
|
||||||
|
create: { type: Boolean, default: false },
|
||||||
|
width: { type: String, default: '50%' },
|
||||||
|
tags: { type: Array, default: () => [] }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
form: {
|
||||||
|
task: '',
|
||||||
|
name: '',
|
||||||
|
interval: '',
|
||||||
|
crontab: '',
|
||||||
|
date: '',
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
tasks_as_choices: [],
|
||||||
|
Crontab: [],
|
||||||
|
Interval: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dialogTitle() {
|
||||||
|
return this.create ? '新增任务' : '编辑任务';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.dialogVisible = val;
|
||||||
|
},
|
||||||
|
dialogVisible(val) {
|
||||||
|
this.$emit('input', val);
|
||||||
|
if (!this.Crontab[0]) {
|
||||||
|
this.Crontab = this.$store.state.Crontab;
|
||||||
|
console.log(1, this.Crontab);
|
||||||
|
}
|
||||||
|
if (!this.Interval[0]) {
|
||||||
|
this.Interval = this.$store.state.Interval;
|
||||||
|
console.log(2, this.Interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 获取所有 tasks 名称
|
||||||
|
SyncDataApi.TasksAsChoices().then((response) => {
|
||||||
|
this.tasks_as_choices = response.data || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
querySearch(queryString, cb) {
|
||||||
|
var restaurants = this.tasks_as_choices;
|
||||||
|
var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants;
|
||||||
|
// 调用 callback 返回建议列表的数据
|
||||||
|
cb(results);
|
||||||
|
}, createFilter(queryString) {
|
||||||
|
return (restaurant) => {
|
||||||
|
return (restaurant.toLowerCase().indexOf(queryString.toLowerCase()) !== -1);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSelect(item) {
|
||||||
|
this.form.task = item;
|
||||||
|
},
|
||||||
|
dialogOpen() {
|
||||||
|
// 为True意味着是通过遍及方式打开对话框
|
||||||
|
if (!this.create) {
|
||||||
|
this.form = { ...this.entity };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
this.$refs['form'].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.loading = true;
|
||||||
|
const data = { ...this.form };
|
||||||
|
if (this.create) {
|
||||||
|
delete data['instanceId'];
|
||||||
|
SyncDataApi.createPeriodicTask(data).then(response => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.dialogClose();
|
||||||
|
this.msgSuccess('新增成功!');
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
SyncDataApi.updatePeriodicTask(data).then(response => {
|
||||||
|
this.$emit('success', response.data);
|
||||||
|
this.loading = false;
|
||||||
|
this.msgSuccess('更新成功!');
|
||||||
|
this.dialogClose();
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialogClose() {
|
||||||
|
this.$refs['form'].resetFields();
|
||||||
|
this.dialogVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.el-picker-panel {
|
||||||
|
color: #606266;
|
||||||
|
border: 1px solid #E4E7ED;
|
||||||
|
-webkit-box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||||
|
background: #FFF;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 5px!important;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.el-picker-panel__content {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
<!--
|
||||||
|
@description: 接口信息页面
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<common-static-table
|
||||||
|
ref="table"
|
||||||
|
:data="detail"
|
||||||
|
:fields="fields"
|
||||||
|
selection
|
||||||
|
@selection-change="handlePeriodicSelectionChange"
|
||||||
|
>
|
||||||
|
<template v-slot:enabled="scope">
|
||||||
|
<el-switch
|
||||||
|
:value="scope.values[scope.prop]"
|
||||||
|
active-color="#13ce66"
|
||||||
|
disabled
|
||||||
|
inactive-color="#ff4949"/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:interval="scope">
|
||||||
|
{{ getIntervalData(scope.values[`${scope.prop}_list`]) }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:crontab="scope">
|
||||||
|
{{ getCrontabData(scope.values[`${scope.prop}_list`]) }}
|
||||||
|
</template>
|
||||||
|
<template slot="button">
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="primary"
|
||||||
|
title="添加任务"
|
||||||
|
icon="el-icon-circle-plus"
|
||||||
|
@click="handleOpenEditPeriodicForm(true)">新增
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<!--以下是自定义新增的工具栏内容-->
|
||||||
|
<template slot="tools">
|
||||||
|
<el-popover placement="bottom" title="温馨提示" width="400" trigger="click" style="margin-left: 10px">
|
||||||
|
<li>待编写</li>
|
||||||
|
<el-button
|
||||||
|
slot="reference"
|
||||||
|
name="refresh"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
icon="el-icon-info"
|
||||||
|
title="温馨提示"/>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<!--以下是自定义新增的列的配置内容-->
|
||||||
|
<template slot="column">
|
||||||
|
<el-table-column fixed="right" label="操作" align="center" width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="primary"
|
||||||
|
title="立即执行"
|
||||||
|
icon="el-icon-caret-right"
|
||||||
|
circle
|
||||||
|
@click="test(scope.row)"/>
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="primary"
|
||||||
|
title="编辑"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
circle
|
||||||
|
@click="handleOpenEditPeriodicForm(false, scope.row)"/>
|
||||||
|
<el-button
|
||||||
|
:size="$ELEMENT.size"
|
||||||
|
type="danger"
|
||||||
|
title="移除"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
circle
|
||||||
|
@click="handleRemovePeriodicTable(scope.$index, scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
</common-static-table>
|
||||||
|
<el-dialog :visible.sync="dialogFormVisible" title="请确认" >
|
||||||
|
<span>
|
||||||
|
正在同步:{{ row.task }}
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="dialogFormVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="handleOperate">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
<edit-form-periodic-task
|
||||||
|
v-model="editPeriodicFormVisible"
|
||||||
|
:entity="editPeriodic"
|
||||||
|
:create="editPeriodicCreate"
|
||||||
|
:periodic-data="periodicData"
|
||||||
|
:width="'40%'"
|
||||||
|
@success="handleSuccessEditPeriodic"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as SyncDataApi from "@/api/vadmin/monitor/celery";
|
||||||
|
import EditFormPeriodicTask from './edit-form-periodic-task';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { EditFormPeriodicTask },
|
||||||
|
props: {
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fields: [
|
||||||
|
// prop,后端列名称, 必填; label,前端表头名称, 必填; 其他可有可无
|
||||||
|
{ prop: 'name', label: '名称', show: true, unique: true },
|
||||||
|
{ prop: 'task', label: 'celery任务', show: true, width: 400 },
|
||||||
|
{ prop: 'interval', label: '频率', show: true, search: true },
|
||||||
|
{ prop: 'crontab', label: '任务编排', show: true, search: true, sortable: true },
|
||||||
|
{ prop: 'args', label: '参数', show: false, search: true, sortable: true, width: 80 },
|
||||||
|
{ prop: 'kwargs', label: '位置参数', show: false, search: true, sortable: true },
|
||||||
|
{ prop: 'queue', label: '队列', show: false, search: true, sortable: true },
|
||||||
|
{ prop: 'exchange', label: '状态', show: false, search: true },
|
||||||
|
{ prop: 'routing_key', label: '路由密钥', show: false, search: true },
|
||||||
|
{ prop: 'expires', label: '过期时间', show: false, search: true, type: 'datetime' },
|
||||||
|
{ prop: 'enabled', label: '是否开启', show: true, search: true }
|
||||||
|
],
|
||||||
|
periodicData: [],
|
||||||
|
multipleSelection: [],
|
||||||
|
PeriodicTagList: [],
|
||||||
|
editPeriodic: {},
|
||||||
|
editPeriodicFormVisible: false,
|
||||||
|
editPeriodicCreate: false,
|
||||||
|
modelFormVisible: false,
|
||||||
|
modelSwaggerVisible: false,
|
||||||
|
batchEditFormVisible: false,
|
||||||
|
detail: [],
|
||||||
|
dialogFormVisible: false,
|
||||||
|
form: { name: '' },
|
||||||
|
formLabelWidth: '120px',
|
||||||
|
row: '',
|
||||||
|
reqloading: false,
|
||||||
|
task_id:''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData() {
|
||||||
|
SyncDataApi.listPeriodicTask({ page_size: 1000 }).then((response) => {
|
||||||
|
this.detail = response.data.results || [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleRefresh(infos) {
|
||||||
|
this.$refs.table.clearSelection();
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handlePeriodicSelectionChange(infos) {
|
||||||
|
this.multipleSelection = infos;
|
||||||
|
},
|
||||||
|
handleOpenEditPeriodicForm(create = false, info) {
|
||||||
|
if (create) {
|
||||||
|
this.editPeriodic = { periodic: this.detail.id };
|
||||||
|
} else {
|
||||||
|
this.editPeriodic = { ...info };
|
||||||
|
}
|
||||||
|
this.editPeriodicCreate = create;
|
||||||
|
this.editPeriodicFormVisible = true;
|
||||||
|
},
|
||||||
|
handleRemovePeriodicTable(index, info) {
|
||||||
|
this.$confirm('确认删除?', '确认信息', {
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
}).then(() => {
|
||||||
|
SyncDataApi.deletePeriodicTask(info.id).then(response => {
|
||||||
|
const name = info.name ? info.name + ':' : '';
|
||||||
|
this.msgSuccess(name + '删除成功');
|
||||||
|
this.initData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSuccessEditPeriodic() {
|
||||||
|
this.initData();
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
handleOpenModelForm() {
|
||||||
|
this.modelFormVisible = true;
|
||||||
|
},
|
||||||
|
handleOpenSwagger(model = false) {
|
||||||
|
this.modelSwaggerVisible = true;
|
||||||
|
},
|
||||||
|
handleBatchEdit() {
|
||||||
|
this.batchEditFormVisible = true;
|
||||||
|
},
|
||||||
|
test(row) {
|
||||||
|
this.dialogFormVisible = true;
|
||||||
|
this.row = row;
|
||||||
|
this.DetailMsg = ''
|
||||||
|
},
|
||||||
|
handleOperate() {
|
||||||
|
this.dialogFormVisible = false
|
||||||
|
this.reqloading = true;
|
||||||
|
SyncDataApi.operatesyncdata({ celery_name: this.row.task }).then(response => {
|
||||||
|
this.task_id = response.data.task_id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeView(){
|
||||||
|
this.reqloading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-table th {
|
||||||
|
display: table-cell !important;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue