diff --git a/dvadmin-backend/application/settings.py b/dvadmin-backend/application/settings.py index 1b1ba17..60e33c0 100644 --- a/dvadmin-backend/application/settings.py +++ b/dvadmin-backend/application/settings.py @@ -45,10 +45,12 @@ INSTALLED_APPS = [ 'rest_framework', 'corsheaders', 'captcha', + 'djcelery', # 自定义app 'apps.vadmin.permission', 'apps.vadmin.op_drf', 'apps.vadmin.system', + 'apps.vadmin.celery', ] MIDDLEWARE = [ diff --git a/dvadmin-backend/apps/vadmin/celery/__init__.py b/dvadmin-backend/apps/vadmin/celery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dvadmin-backend/apps/vadmin/celery/admin.py b/dvadmin-backend/apps/vadmin/celery/admin.py new file mode 100644 index 0000000..694323f --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/admin.py @@ -0,0 +1 @@ +from django.contrib import admin diff --git a/dvadmin-backend/apps/vadmin/celery/apps.py b/dvadmin-backend/apps/vadmin/celery/apps.py new file mode 100644 index 0000000..efafaf1 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class DpCmdbConfig(AppConfig): + name = 'vadmin.celery' + + diff --git a/dvadmin-backend/apps/vadmin/celery/filters.py b/dvadmin-backend/apps/vadmin/celery/filters.py new file mode 100644 index 0000000..8d5b806 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/filters.py @@ -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__' + diff --git a/dvadmin-backend/apps/vadmin/celery/models/__init__.py b/dvadmin-backend/apps/vadmin/celery/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dvadmin-backend/apps/vadmin/celery/serializers.py b/dvadmin-backend/apps/vadmin/celery/serializers.py new file mode 100644 index 0000000..e029cb4 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/serializers.py @@ -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__' diff --git a/dvadmin-backend/apps/vadmin/celery/tasks.py b/dvadmin-backend/apps/vadmin/celery/tasks.py new file mode 100644 index 0000000..e69de29 diff --git a/dvadmin-backend/apps/vadmin/celery/tests.py b/dvadmin-backend/apps/vadmin/celery/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/dvadmin-backend/apps/vadmin/celery/urls.py b/dvadmin-backend/apps/vadmin/celery/urls.py new file mode 100644 index 0000000..40d523a --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/urls.py @@ -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 diff --git a/dvadmin-backend/apps/vadmin/celery/views.py b/dvadmin-backend/apps/vadmin/celery/views.py new file mode 100644 index 0000000..df26138 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/celery/views.py @@ -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}) diff --git a/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py b/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py index f60f3ee..3e03558 100644 --- a/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py +++ b/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py @@ -24,7 +24,7 @@ class ViewLogger(object): 'model'): self.model: Model = self.view.get_serializer().Meta.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): pass @@ -38,6 +38,7 @@ class ViewLogger(object): self.request.session['request_msg'] = msg return logger + class APIViewLogger(ViewLogger): """ (1)仅在op_drf.views.CustomAPIView的子类中生效 diff --git a/dvadmin-backend/apps/vadmin/urls.py b/dvadmin-backend/apps/vadmin/urls.py index 341e6fb..093e65d 100644 --- a/dvadmin-backend/apps/vadmin/urls.py +++ b/dvadmin-backend/apps/vadmin/urls.py @@ -53,5 +53,6 @@ urlpatterns = [ re_path('captcha/', include('captcha.urls')), # 图片验证码 路由 re_path(r'^permission/', include('apps.vadmin.permission.urls')), re_path(r'^system/', include('apps.vadmin.system.urls')), + re_path(r'^celery/', include('apps.vadmin.celery.urls')), ] diff --git a/dvadmin-backend/apps/vadmin/utils/exceptions.py b/dvadmin-backend/apps/vadmin/utils/exceptions.py index b07262b..cbd065e 100644 --- a/dvadmin-backend/apps/vadmin/utils/exceptions.py +++ b/dvadmin-backend/apps/vadmin/utils/exceptions.py @@ -1,7 +1,7 @@ import logging import traceback -from rest_framework import serializers, exceptions +from rest_framework import exceptions from rest_framework.views import set_rollback from .request_util import get_verbose_name @@ -18,9 +18,9 @@ class APIException(Exception): """ def __init__(self, code=201, message='API异常', args=('API异常',)): - args = args - code = code - message = message + self.args = args + self.code = code + self.message = message def __str__(self): return self.message @@ -36,8 +36,8 @@ class FrameworkException(Exception): """ def __init__(self, message='框架异常', *args: object, **kwargs: object) -> None: - super().__init__(*args, **kwargs) - message = message + super().__init__(*args, ) + self.message = message def __str__(self) -> str: return f"{self.message}" @@ -66,7 +66,7 @@ def op_exception_handler(ex, context): msg = '' code = '201' 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): code = 401 @@ -80,4 +80,4 @@ def op_exception_handler(ex, context): elif isinstance(ex, Exception): logger.error(traceback.format_exc()) msg = str(ex) - return ErrorResponse(msg=msg,code=code) + return ErrorResponse(msg=msg, code=code) diff --git a/dvadmin-backend/requirements.txt b/dvadmin-backend/requirements.txt index 3c2399d..b99e992 100644 --- a/dvadmin-backend/requirements.txt +++ b/dvadmin-backend/requirements.txt @@ -1,6 +1,7 @@ asgiref==3.3.1 Django==2.2.16 django-cors-headers==3.7.0 +django-celery==3.2.2 django-filter==2.4.0 django-ranged-response==0.2.0 django-redis==4.12.1 diff --git a/dvadmin-doc/docs/document/hjbs.md b/dvadmin-doc/docs/document/hjbs.md index 52653ef..5e81cba 100644 --- a/dvadmin-doc/docs/document/hjbs.md +++ b/dvadmin-doc/docs/document/hjbs.md @@ -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' + diff --git a/dvadmin-ui/package.json b/dvadmin-ui/package.json index 7a917c8..2637669 100755 --- a/dvadmin-ui/package.json +++ b/dvadmin-ui/package.json @@ -49,6 +49,7 @@ "js-beautify": "1.13.0", "js-cookie": "2.2.1", "jsencrypt": "3.0.0-rc.1", + "moment": "^2.29.1", "nprogress": "0.2.0", "quill": "1.3.7", "screenfull": "5.0.2", diff --git a/dvadmin-ui/src/api/vadmin/monitor/celery.js b/dvadmin-ui/src/api/vadmin/monitor/celery.js new file mode 100644 index 0000000..d21c8a1 --- /dev/null +++ b/dvadmin-ui/src/api/vadmin/monitor/celery.js @@ -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 + }); +} diff --git a/dvadmin-ui/src/assets/icons/svg/icon-excel.svg b/dvadmin-ui/src/assets/icons/svg/icon-excel.svg new file mode 100644 index 0000000..bb00722 --- /dev/null +++ b/dvadmin-ui/src/assets/icons/svg/icon-excel.svg @@ -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> \ No newline at end of file diff --git a/dvadmin-ui/src/assets/icons/svg/icon-filter.svg b/dvadmin-ui/src/assets/icons/svg/icon-filter.svg new file mode 100644 index 0000000..869117a --- /dev/null +++ b/dvadmin-ui/src/assets/icons/svg/icon-filter.svg @@ -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> \ No newline at end of file diff --git a/dvadmin-ui/src/components/CommonIcon/index.vue b/dvadmin-ui/src/components/CommonIcon/index.vue new file mode 100644 index 0000000..2526060 --- /dev/null +++ b/dvadmin-ui/src/components/CommonIcon/index.vue @@ -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> diff --git a/dvadmin-ui/src/components/CommonStaticTable/index.vue b/dvadmin-ui/src/components/CommonStaticTable/index.vue new file mode 100644 index 0000000..fe92660 --- /dev/null +++ b/dvadmin-ui/src/components/CommonStaticTable/index.vue @@ -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> diff --git a/dvadmin-ui/src/components/SmallDialog/index.vue b/dvadmin-ui/src/components/SmallDialog/index.vue new file mode 100644 index 0000000..a5ced32 --- /dev/null +++ b/dvadmin-ui/src/components/SmallDialog/index.vue @@ -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> + diff --git a/dvadmin-ui/src/main.js b/dvadmin-ui/src/main.js index 8f90052..9037bb0 100755 --- a/dvadmin-ui/src/main.js +++ b/dvadmin-ui/src/main.js @@ -14,12 +14,24 @@ import permission from './directive/permission' import './assets/icons' // icon import './permission' // permission control -import { getDicts } from "@/api/vadmin/system/dict/data"; -import { getConfigKey } from "@/api/vadmin/system/config"; -import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, download, handleTree } from "@/utils/ruoyi"; +import {getDicts} from "@/api/vadmin/system/dict/data"; +import {getConfigKey} from "@/api/vadmin/system/config"; +import { + addDateRange, + download, + handleTree, + parseTime, + resetForm, + selectDictLabel, + selectDictLabels +} from "@/utils/ruoyi"; import Pagination from "@/components/Pagination"; // 自定义表格工具扩展 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 @@ -29,30 +41,35 @@ Vue.prototype.resetForm = resetForm Vue.prototype.addDateRange = addDateRange Vue.prototype.selectDictLabel = selectDictLabel Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.getCrontabData = getCrontabData +Vue.prototype.getIntervalData = getIntervalData Vue.prototype.download = download Vue.prototype.handleTree = handleTree Vue.prototype.hasPermi = function (values) { const permissions = store.getters && store.getters.permissions - return permissions.some(permission => { + return permissions.some(permission => { return "*:*:*" === permission || values.includes(permission) }) }; Vue.prototype.msgSuccess = function (msg) { - this.$message({ showClose: true, message: msg, type: "success" }); + this.$message({showClose: true, message: msg, type: "success"}); } Vue.prototype.msgError = function (msg) { - this.$message({ showClose: true, message: msg, type: "error" }); + this.$message({showClose: true, message: msg, type: "error"}); } Vue.prototype.msgInfo = function (msg) { this.$message.info(msg); } - +// 自定义组件 +Vue.component('small-dialog', SmallDialog); // 全局组件挂载 Vue.component('Pagination', Pagination) Vue.component('RightToolbar', RightToolbar) +Vue.component('common-icon', CommonIcon); +Vue.component('common-static-table', CommonStaticTable); Vue.use(permission) diff --git a/dvadmin-ui/src/router/index.js b/dvadmin-ui/src/router/index.js index e13fbab..9b9ad26 100755 --- a/dvadmin-ui/src/router/index.js +++ b/dvadmin-ui/src/router/index.js @@ -98,6 +98,18 @@ export const constantRoutes = [ 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: '' } + } + ] } ] diff --git a/dvadmin-ui/src/utils/index.js b/dvadmin-ui/src/utils/index.js index 918580f..60fa980 100755 --- a/dvadmin-ui/src/utils/index.js +++ b/dvadmin-ui/src/utils/index.js @@ -5,12 +5,12 @@ import { parseTime } from './ruoyi' */ export function formatDate(cellValue) { if (cellValue == null || cellValue == "") return ""; - var date = new Date(cellValue) + var date = new Date(cellValue) var year = date.getFullYear() var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 - var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() - var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() - var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds } @@ -330,7 +330,7 @@ export function makeMap(str, expectsLowerCase) { ? val => map[val.toLowerCase()] : val => map[val] } - + export const exportDefault = 'export default ' export const beautifierConf = { @@ -387,4 +387,18 @@ export function camelCase(str) { export function isNumberStr(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'; +}; diff --git a/dvadmin-ui/src/utils/validate.js b/dvadmin-ui/src/utils/validate.js index adfa254..55e35b8 100755 --- a/dvadmin-ui/src/utils/validate.js +++ b/dvadmin-ui/src/utils/validate.js @@ -81,3 +81,29 @@ export function 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]}`; +} diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/crontab-index.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/crontab-index.vue new file mode 100644 index 0000000..c49d01a --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/crontab-index.vue @@ -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> diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/edit-form-crontab-task.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/edit-form-crontab-task.vue new file mode 100644 index 0000000..de433dd --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/crontab-task/edit-form-crontab-task.vue @@ -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> + diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/index.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/index.vue new file mode 100644 index 0000000..f1a8a28 --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/index.vue @@ -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> diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/edit-form-Interval-task.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/edit-form-Interval-task.vue new file mode 100644 index 0000000..8805172 --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/edit-form-Interval-task.vue @@ -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> + diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/interval-index.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/interval-index.vue new file mode 100644 index 0000000..ff561b7 --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/interval-task/interval-index.vue @@ -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> diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue new file mode 100644 index 0000000..b402cda --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/edit-form-periodic-task.vue @@ -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> + diff --git a/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/periodic-index.vue b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/periodic-index.vue new file mode 100644 index 0000000..9946adf --- /dev/null +++ b/dvadmin-ui/src/views/vadmin/monitor/celery/periodic-task/periodic-index.vue @@ -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>