mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-09 08:57:14 +08:00
feat: 新增任务调度模块
SnailJob(灵活,可靠和快速的分布式任务重试和分布式任务调度平台)
This commit is contained in:
BIN
.idea/icon.png
generated
BIN
.idea/icon.png
generated
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
2736
pnpm-lock.yaml
generated
2736
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ export * from './common'
|
||||
export * from './monitor'
|
||||
export * from './system'
|
||||
export * from './tool'
|
||||
export * from './schedule'
|
||||
|
||||
export * from './area/type'
|
||||
export * from './auth/type'
|
||||
@@ -11,3 +12,4 @@ export * from './common/type'
|
||||
export * from './monitor/type'
|
||||
export * from './system/type'
|
||||
export * from './tool/type'
|
||||
export * from './schedule/type'
|
||||
|
2
src/apis/schedule/index.ts
Normal file
2
src/apis/schedule/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from '../schedule/job'
|
||||
export * from '../schedule/log'
|
39
src/apis/schedule/job.ts
Normal file
39
src/apis/schedule/job.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type * as Schedule from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/schedule/job'
|
||||
|
||||
/** @desc 查询任务组列表 */
|
||||
export function listGroup() {
|
||||
return http.get(`${BASE_URL}/group`)
|
||||
}
|
||||
|
||||
/** @desc 查询任务列表 */
|
||||
export function listJob(query: Schedule.JobPageQuery) {
|
||||
return http.get<PageRes<Schedule.JobResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 新增任务 */
|
||||
export function addJob(data: any) {
|
||||
return http.post(`${BASE_URL}`, data)
|
||||
}
|
||||
|
||||
/** @desc 修改任务 */
|
||||
export function updateJob(data: any, id: number) {
|
||||
return http.put(`${BASE_URL}/${id}`, data)
|
||||
}
|
||||
|
||||
/** @desc 修改任务状态 */
|
||||
export function updateJobStatus(data: any, id: number) {
|
||||
return http.patch(`${BASE_URL}/${id}/status`, data)
|
||||
}
|
||||
|
||||
/** @desc 删除任务 */
|
||||
export function deleteJob(id: number) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 执行任务 */
|
||||
export function triggerJob(id: number) {
|
||||
return http.post(`${BASE_URL}/trigger/${id}`)
|
||||
}
|
34
src/apis/schedule/log.ts
Normal file
34
src/apis/schedule/log.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type * as Schedule from './type'
|
||||
import http from '@/utils/http'
|
||||
|
||||
const BASE_URL = '/schedule/log'
|
||||
|
||||
/** @desc 查询任务日志列表 */
|
||||
export function listJobLog(query: Schedule.JobLogPageQuery) {
|
||||
return http.get<PageRes<Schedule.JobLogResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询任务日志详情 */
|
||||
export function getJobLogDetail(id: number) {
|
||||
return http.get<boolean>(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 停止任务 */
|
||||
export function stopJob(id: number) {
|
||||
return http.post(`${BASE_URL}/stop/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 重试任务 */
|
||||
export function retryJob(id: number) {
|
||||
return http.post(`${BASE_URL}/retry/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 查询任务实例列表 */
|
||||
export function listJobInstance(query: Schedule.JobInstanceQuery) {
|
||||
return http.get<Schedule.JobInstanceResp[]>(`${BASE_URL}/instance`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询任务实例日志列表 */
|
||||
export function listJobInstanceLog(query: Schedule.JobInstanceLogQuery) {
|
||||
return http.get<Schedule.JobInstanceLogResp>(`${BASE_URL}/instance/log`, query)
|
||||
}
|
85
src/apis/schedule/type.ts
Normal file
85
src/apis/schedule/type.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/** 任务类型 */
|
||||
export interface JobResp {
|
||||
id: number
|
||||
groupName: string
|
||||
jobName: string
|
||||
description?: string
|
||||
triggerType: number
|
||||
triggerInterval: string | number
|
||||
executorType: number
|
||||
taskType: number
|
||||
executorInfo: string
|
||||
argsStr?: string
|
||||
argsType?: string
|
||||
routeKey: number
|
||||
blockStrategy: number
|
||||
executorTimeout: number
|
||||
maxRetryTimes: number
|
||||
retryInterval: number
|
||||
parallelNum: number
|
||||
jobStatus: number
|
||||
nextTriggerAt?: Date
|
||||
createDt?: Date
|
||||
updateDt?: Date
|
||||
}
|
||||
export interface JobQuery {
|
||||
groupName: string
|
||||
jobName?: string
|
||||
jobStatus?: number
|
||||
}
|
||||
export interface JobPageQuery extends JobQuery, PageQuery {}
|
||||
|
||||
/** 任务日志类型 */
|
||||
export interface JobLogResp {
|
||||
id: number
|
||||
groupName: string
|
||||
jobName: string
|
||||
jobId: number
|
||||
taskBatchStatus: number
|
||||
operationReason: number
|
||||
executorType: number
|
||||
executorInfo: string
|
||||
executionAt: string
|
||||
createDt: string
|
||||
}
|
||||
export interface JobLogQuery {
|
||||
jobId?: number
|
||||
groupName?: string
|
||||
jobName?: string
|
||||
taskBatchStatus?: number
|
||||
datetimeRange?: Array<string>
|
||||
}
|
||||
export interface JobLogPageQuery extends JobLogQuery, PageQuery {}
|
||||
|
||||
/** 任务实例类型 */
|
||||
export interface JobInstanceResp {
|
||||
id: number
|
||||
groupName: string
|
||||
jobId: number
|
||||
taskBatchId: number
|
||||
taskStatus: number
|
||||
retryCount: number
|
||||
resultMessage: string
|
||||
clientInfo: string
|
||||
}
|
||||
export interface JobInstanceQuery {
|
||||
jobId?: string | number
|
||||
taskBatchId?: number | string
|
||||
}
|
||||
|
||||
/** 任务实例日志类型 */
|
||||
export interface JobInstanceLogResp {
|
||||
id: number
|
||||
message: any[]
|
||||
isFinished: number
|
||||
fromIndex: number
|
||||
nextStartId: number
|
||||
}
|
||||
export interface JobInstanceLogQuery {
|
||||
taskBatchId: number
|
||||
jobId: number
|
||||
taskId: number
|
||||
startId: number
|
||||
fromIndex: number
|
||||
size: number
|
||||
}
|
@@ -12,6 +12,7 @@ export interface TableQuery {
|
||||
tableName?: string
|
||||
}
|
||||
export interface TablePageQuery extends PageQuery, TableQuery {}
|
||||
|
||||
export interface FieldConfigResp {
|
||||
tableName: string
|
||||
columnName: string
|
||||
|
151
src/components/CornTab/CrontabDay.vue
Normal file
151
src/components/CornTab/CrontabDay.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="1">
|
||||
日,允许的通配符[, - * ? / L W]
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="2">
|
||||
不指定
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="3">
|
||||
周期从
|
||||
<a-input-number v-model="cycle01" :min="1" :max="30" /> -
|
||||
<a-input-number v-model="cycle02" :min="cycle01 ? cycle01 + 1 : 2" :max="31" /> 日
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="4">
|
||||
从
|
||||
<a-input-number v-model="average01" :min="1" :max="30" /> 号开始,每
|
||||
<a-input-number v-model="average02" :min="1" :max="31 - average01 || 1" /> 日执行一次
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="5">
|
||||
每月
|
||||
<a-input-number v-model="workday" :min="1" :max="31" /> 号最近的那个工作日
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="6">
|
||||
本月最后一天
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="7">
|
||||
指定
|
||||
<a-select v-model="checkboxList" clearable placeholder="可多选" multiple style="width:100%">
|
||||
<a-option v-for="item in 31" :key="item" :value="item">{{ item }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
check: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
cron: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const radioValue = ref(1)
|
||||
const workday = ref(1)
|
||||
const cycle01 = ref(1)
|
||||
const cycle02 = ref(2)
|
||||
const average01 = ref(1)
|
||||
const average02 = ref(1)
|
||||
const checkboxList = ref([])
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
const cycle01Val = props.check(cycle01.value, 1, 30)
|
||||
const cycle02Val = props.check(cycle02.value, cycle01Val ? cycle01Val + 1 : 2, 31, 31)
|
||||
return `${cycle01Val}-${cycle02Val}`
|
||||
})
|
||||
|
||||
const averageTotal = computed(() => {
|
||||
const average01Val = props.check(average01.value, 1, 30)
|
||||
const average02Val = props.check(average02.value, 1, 31 - average01Val || 0)
|
||||
return `${average01Val}/${average02Val}`
|
||||
})
|
||||
|
||||
const workdayCheck = computed(() => {
|
||||
const workdayVal = props.check(workday.value, 1, 31)
|
||||
return workdayVal
|
||||
})
|
||||
|
||||
const checkboxString = computed(() => {
|
||||
const str = checkboxList.value.join()
|
||||
return str === '' ? '*' : str
|
||||
})
|
||||
|
||||
const emitUpdate = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
watch(radioValue, () => {
|
||||
if (radioValue.value !== 2 && props.cron.week !== '?') {
|
||||
props.cron.week = '?'
|
||||
}
|
||||
switch (radioValue.value) {
|
||||
case 1:
|
||||
emitUpdate('*')
|
||||
break
|
||||
case 2:
|
||||
emitUpdate('?')
|
||||
break
|
||||
case 3:
|
||||
emitUpdate(cycleTotal.value)
|
||||
break
|
||||
case 4:
|
||||
emitUpdate(averageTotal.value)
|
||||
break
|
||||
case 5:
|
||||
emitUpdate(`${workdayCheck.value}W`)
|
||||
break
|
||||
case 6:
|
||||
emitUpdate('L')
|
||||
break
|
||||
case 7:
|
||||
emitUpdate(checkboxString.value)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch(cycleTotal, () => {
|
||||
emitUpdate(cycleTotal.value)
|
||||
})
|
||||
|
||||
watch(averageTotal, () => {
|
||||
emitUpdate(averageTotal.value)
|
||||
})
|
||||
|
||||
watch(workdayCheck, () => {
|
||||
emitUpdate(`${workdayCheck.value}W`)
|
||||
})
|
||||
|
||||
watch(checkboxString, () => {
|
||||
emitUpdate(checkboxString.value)
|
||||
})
|
||||
</script>
|
109
src/components/CornTab/CrontabField.vue
Normal file
109
src/components/CornTab/CrontabField.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="1">
|
||||
*
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<a-input-number v-model="cycle01" :min="0" :max="58" />
|
||||
-
|
||||
<a-input-number v-model="cycle02" :min="cycle01 ? cycle01 + 1 : 1" :max="59" />
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<a-input-number v-model="average01" :min="0" :max="58" />
|
||||
开始,每
|
||||
<a-input-number v-model="average02" :min="1" :max="59 - average01 || 0" />
|
||||
执行一次
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<a-select v-model="checkboxList" clearable placeholder="可多选" multiple style="width:100%">
|
||||
<a-option v-for="item in 60" :key="item" :value="item - 1">{{ item - 1 }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
check: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const radioValue = ref(1)
|
||||
const cycle01 = ref(0)
|
||||
const cycle02 = ref(1)
|
||||
const average01 = ref(0)
|
||||
const average02 = ref(1)
|
||||
const checkboxList = ref([])
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
const cycle01Val = props.check(cycle01.value, 0, 58)
|
||||
const cycle02Val = props.check(cycle02.value, cycle01Val ? cycle01Val + 1 : 1, 59)
|
||||
return `${cycle01Val}-${cycle02Val}`
|
||||
})
|
||||
|
||||
const averageTotal = computed(() => {
|
||||
const average01Val = props.check(average01.value, 0, 58)
|
||||
const average02Val = props.check(average02.value, 1, 59 - average01Val || 0)
|
||||
return `${average01Val}/${average02Val}`
|
||||
})
|
||||
|
||||
const checkboxString = computed(() => {
|
||||
const str = checkboxList.value.join()
|
||||
return str === '' ? '*' : str
|
||||
})
|
||||
|
||||
const emitUpdate = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
watch(radioValue, () => {
|
||||
switch (radioValue.value) {
|
||||
case 1:
|
||||
emitUpdate('*')
|
||||
break
|
||||
case 2:
|
||||
emitUpdate(cycleTotal.value)
|
||||
break
|
||||
case 3:
|
||||
emitUpdate(averageTotal.value)
|
||||
break
|
||||
case 4:
|
||||
emitUpdate(checkboxString.value)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch(cycleTotal, () => {
|
||||
emitUpdate(cycleTotal.value)
|
||||
})
|
||||
|
||||
watch(averageTotal, () => {
|
||||
emitUpdate(averageTotal.value)
|
||||
})
|
||||
|
||||
watch(checkboxString, () => {
|
||||
emitUpdate(checkboxString.value)
|
||||
})
|
||||
</script>
|
145
src/components/CornTab/CrontabGenerator.vue
Normal file
145
src/components/CornTab/CrontabGenerator.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<a-tabs default-active-key="1" type="card">
|
||||
<a-tab-pane key="1" tab="秒">
|
||||
<CrontabField v-model="cron.second" :check="checkNumber" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="分">
|
||||
<CrontabField v-model="cron.minute" :check="checkNumber" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="时">
|
||||
<CrontabField v-model="cron.hour" :check="checkNumber" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="日">
|
||||
<CrontabDay v-model="cron.day" :check="checkNumber" :cron="cron" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab="月">
|
||||
<CrontabMonth v-model="cron.month" :check="checkNumber" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6" tab="星期">
|
||||
<CrontabWeek v-model="cron.week" :check="checkNumber" :cron="cron" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="7" tab="年">
|
||||
<CrontabYear v-model="cron.year" :check="checkNumber" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<div class="cron-result">
|
||||
<p>Cron 表达式:</p>
|
||||
<code class="cron-expression">{{ cronExpression }}</code>
|
||||
</div>
|
||||
|
||||
<div v-if="cronExpression">
|
||||
<!-- <button @click="parseCronExpression">解析表达式</button> -->
|
||||
<button @click="generateCronExpression">生成表达式</button>
|
||||
</div>
|
||||
<div v-if="parsedCron">
|
||||
<p>解析结果:</p>
|
||||
<pre>{{ parsedCron }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import CrontabField from './CrontabField.vue' // Import the CrontabField component
|
||||
import CrontabDay from './CrontabDay.vue' // Import the CrontabDay component
|
||||
import CrontabMonth from './CrontabMonth.vue' // Import the CrontabMonth component
|
||||
import CrontabWeek from './CrontabWeek.vue' // Import the CrontabWeek component
|
||||
import CrontabYear from './CrontabYear.vue' // Import the CrontabYear component
|
||||
|
||||
const cron = ref({
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '*'
|
||||
})
|
||||
|
||||
const cronExpression = computed(() => {
|
||||
return `${cron.value.second} ${cron.value.minute} ${cron.value.hour} ${cron.value.day} ${cron.value.month} ${cron.value.week} ${cron.value.year}`
|
||||
})
|
||||
|
||||
const parsedCron = ref(null)
|
||||
|
||||
const checkNumber = (value, min, max, defaultValue = null) => {
|
||||
if (value === '' || value === undefined || value === null || Number.isNaN(value)) {
|
||||
return defaultValue
|
||||
}
|
||||
if (value < min) {
|
||||
return min
|
||||
}
|
||||
if (value > max) {
|
||||
return max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const parseCronExpression = () => {
|
||||
// 使用第三方库或自定义函数解析 Cron 表达式
|
||||
// 例如:
|
||||
const parsed = parseCron(cronExpression.value) // 替换为实际解析函数
|
||||
parsedCron.value = parsed
|
||||
}
|
||||
|
||||
const generateCronExpression = () => {
|
||||
// 使用自定义函数根据选项生成 Cron 表达式
|
||||
// 例如:
|
||||
const generatedExpression = generateCronFromOptions(cron.value) // 替换为实际生成函数
|
||||
cron.value = generatedExpression
|
||||
}
|
||||
|
||||
// // 解析 Cron 表达式的函数
|
||||
// function parseCron(expression) {
|
||||
// // ... 解析逻辑
|
||||
// // 返回解析后的结果,例如一个包含各个字段信息的数组或对象
|
||||
// return {
|
||||
// second: '0-59',
|
||||
// minute: '0-59',
|
||||
// hour: '0-23',
|
||||
// day: '1-31',
|
||||
// month: '1-12',
|
||||
// week: '0-6',
|
||||
// year: '*'
|
||||
// }
|
||||
// }
|
||||
|
||||
// 根据选项生成 Cron 表达式的函数
|
||||
function generateCronFromOptions(options) {
|
||||
// ... 生成逻辑
|
||||
// 返回生成的 Cron 表达式字符串
|
||||
return `${options.second} ${options.minute} ${options.hour} ${options.day} ${options.month} ${options.week} ${options.year}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cron-generator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 0; /* Remove default margin */
|
||||
padding: 0; /* Remove default padding */
|
||||
}
|
||||
|
||||
.cron-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.cron-result {
|
||||
margin-top: 2rem;
|
||||
border: 1px solid #ccc;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cron-expression {
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
18
src/components/CornTab/CrontabModel.vue
Normal file
18
src/components/CornTab/CrontabModel.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<a-modal :visible="visible" width="600">
|
||||
<CrontabGenerator />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// const props = defineProps<CrontabType>()
|
||||
const visible = ref<boolean>(true)
|
||||
// interface CrontabType {
|
||||
// visible: boolean
|
||||
//
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
106
src/components/CornTab/CrontabMonth.vue
Normal file
106
src/components/CornTab/CrontabMonth.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="1">
|
||||
月,允许的通配符[, - * /]
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<a-input-number v-model="cycle01" :min="1" :max="11" /> -
|
||||
<a-input-number v-model="cycle02" :min="cycle01 ? cycle01 + 1 : 2" :max="12" /> 月
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<a-input-number v-model="average01" :min="1" :max="11" /> 月开始,每
|
||||
<a-input-number v-model="average02" :min="1" :max="12 - average01 || 0" /> 月执行一次
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<a-select v-model="checkboxList" clearable placeholder="可多选" multiple style="width:100%">
|
||||
<a-option v-for="item in 12" :key="item" :value="item">{{ item }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
check: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const radioValue = ref(1)
|
||||
const cycle01 = ref(1)
|
||||
const cycle02 = ref(2)
|
||||
const average01 = ref(1)
|
||||
const average02 = ref(1)
|
||||
const checkboxList = ref([])
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
const cycle01Val = props.check(cycle01.value, 1, 11)
|
||||
const cycle02Val = props.check(cycle02.value, cycle01Val ? cycle01Val + 1 : 2, 12)
|
||||
return `${cycle01Val}-${cycle02Val}`
|
||||
})
|
||||
|
||||
const averageTotal = computed(() => {
|
||||
const average01Val = props.check(average01.value, 1, 11)
|
||||
const average02Val = props.check(average02.value, 1, 12 - average01Val || 0)
|
||||
return `${average01Val}/${average02Val}`
|
||||
})
|
||||
|
||||
const checkboxString = computed(() => {
|
||||
const str = checkboxList.value.join()
|
||||
return str === '' ? '*' : str
|
||||
})
|
||||
|
||||
const emitUpdate = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
watch(radioValue, () => {
|
||||
switch (radioValue.value) {
|
||||
case 1:
|
||||
emitUpdate('*')
|
||||
break
|
||||
case 2:
|
||||
emitUpdate(cycleTotal.value)
|
||||
break
|
||||
case 3:
|
||||
emitUpdate(averageTotal.value)
|
||||
break
|
||||
case 4:
|
||||
emitUpdate(checkboxString.value)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch(cycleTotal, () => {
|
||||
emitUpdate(cycleTotal.value)
|
||||
})
|
||||
|
||||
watch(averageTotal, () => {
|
||||
emitUpdate(averageTotal.value)
|
||||
})
|
||||
|
||||
watch(checkboxString, () => {
|
||||
emitUpdate(checkboxString.value)
|
||||
})
|
||||
</script>
|
121
src/components/CornTab/CrontabWeek.vue
Normal file
121
src/components/CornTab/CrontabWeek.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="1">
|
||||
?
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="2">
|
||||
指定
|
||||
<a-select v-model="checkboxList" clearable placeholder="可多选" multiple style="width:100%">
|
||||
<a-option v-for="item in 7" :key="item" :value="item - 1">{{ item === 0 ? '日' : item }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="3">
|
||||
周期从
|
||||
<a-select v-model="cycle01" clearable placeholder="起始星期" style="width:100%">
|
||||
<a-option v-for="item in 7" :key="item" :value="item - 1">{{ item === 0 ? '日' : item }}</a-option>
|
||||
</a-select>
|
||||
-
|
||||
<a-select v-model="cycle02" clearable placeholder="结束星期" style="width:100%">
|
||||
<a-option v-for="item in 7" :key="item" :value="item - 1">{{ item === 0 ? '日' : item }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="4">
|
||||
每月
|
||||
<a-select v-model="weekDay" clearable placeholder="星期" style="width:100%">
|
||||
<a-option v-for="item in 7" :key="item" :value="item - 1">{{ item === 0 ? '日' : item }}</a-option>
|
||||
</a-select>
|
||||
的第
|
||||
<a-input-number v-model="weekNum" :min="1" :max="5" />
|
||||
个工作日
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
check: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
cron: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const radioValue = ref(1)
|
||||
const checkboxList = ref([])
|
||||
const cycle01 = ref(0)
|
||||
const cycle02 = ref(0)
|
||||
const weekDay = ref(0)
|
||||
const weekNum = ref(1)
|
||||
|
||||
const checkboxString = computed(() => {
|
||||
const str = checkboxList.value.join()
|
||||
return str === '' ? '?' : str
|
||||
})
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
const cycle01Val = props.check(cycle01.value, 0, 6)
|
||||
const cycle02Val = props.check(cycle02.value, cycle01Val ? cycle01Val + 1 : 1, 6)
|
||||
return `${cycle01Val}-${cycle02Val}`
|
||||
})
|
||||
|
||||
const weekDayCheck = computed(() => {
|
||||
const weekDayVal = props.check(weekDay.value, 0, 6)
|
||||
return weekDayVal
|
||||
})
|
||||
|
||||
const emitUpdate = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
watch(radioValue, () => {
|
||||
if (radioValue.value !== 1 && props.cron.day !== '?') {
|
||||
props.cron.day = '?'
|
||||
}
|
||||
switch (radioValue.value) {
|
||||
case 1:
|
||||
emitUpdate('?')
|
||||
break
|
||||
case 2:
|
||||
emitUpdate(checkboxString.value)
|
||||
break
|
||||
case 3:
|
||||
emitUpdate(cycleTotal.value)
|
||||
break
|
||||
case 4:
|
||||
emitUpdate(`${weekDayCheck.value}#${weekNum.value}`)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch(checkboxString, () => {
|
||||
emitUpdate(checkboxString.value)
|
||||
})
|
||||
|
||||
watch(cycleTotal, () => {
|
||||
emitUpdate(cycleTotal.value)
|
||||
})
|
||||
|
||||
watch(weekDayCheck, () => {
|
||||
emitUpdate(`${weekDayCheck.value}#${weekNum.value}`)
|
||||
})
|
||||
</script>
|
106
src/components/CornTab/CrontabYear.vue
Normal file
106
src/components/CornTab/CrontabYear.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="1">
|
||||
*
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="2">
|
||||
周期从
|
||||
<a-input-number v-model="cycle01" :min="1970" :max="2099" /> -
|
||||
<a-input-number v-model="cycle02" :min="cycle01 ? cycle01 + 1 : 1971" :max="2099" /> 年
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<a-input-number v-model="average01" :min="1970" :max="2098" /> 年开始,每
|
||||
<a-input-number v-model="average02" :min="1" :max="2099 - average01 || 0" /> 年执行一次
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<a-select v-model="checkboxList" clearable placeholder="可多选" multiple style="width:100%">
|
||||
<a-option v-for="item in 130" :key="item" :value="item + 1970">{{ item + 1970 }}</a-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
check: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const radioValue = ref(1)
|
||||
const cycle01 = ref(1970)
|
||||
const cycle02 = ref(1971)
|
||||
const average01 = ref(1970)
|
||||
const average02 = ref(1)
|
||||
const checkboxList = ref([])
|
||||
|
||||
const cycleTotal = computed(() => {
|
||||
const cycle01Val = props.check(cycle01.value, 1970, 2099)
|
||||
const cycle02Val = props.check(cycle02.value, cycle01Val ? cycle01Val + 1 : 1971, 2099)
|
||||
return `${cycle01Val}-${cycle02Val}`
|
||||
})
|
||||
|
||||
const averageTotal = computed(() => {
|
||||
const average01Val = props.check(average01.value, 1970, 2098)
|
||||
const average02Val = props.check(average02.value, 1, 2099 - average01Val || 0)
|
||||
return `${average01Val}/${average02Val}`
|
||||
})
|
||||
|
||||
const checkboxString = computed(() => {
|
||||
const str = checkboxList.value.join()
|
||||
return str === '' ? '*' : str
|
||||
})
|
||||
|
||||
const emitUpdate = (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
watch(radioValue, () => {
|
||||
switch (radioValue.value) {
|
||||
case 1:
|
||||
emitUpdate('*')
|
||||
break
|
||||
case 2:
|
||||
emitUpdate(cycleTotal.value)
|
||||
break
|
||||
case 3:
|
||||
emitUpdate(averageTotal.value)
|
||||
break
|
||||
case 4:
|
||||
emitUpdate(checkboxString.value)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch(cycleTotal, () => {
|
||||
emitUpdate(cycleTotal.value)
|
||||
})
|
||||
|
||||
watch(averageTotal, () => {
|
||||
emitUpdate(averageTotal.value)
|
||||
})
|
||||
|
||||
watch(checkboxString, () => {
|
||||
emitUpdate(checkboxString.value)
|
||||
})
|
||||
</script>
|
7
src/types/components.d.ts
vendored
7
src/types/components.d.ts
vendored
@@ -8,6 +8,13 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
|
||||
CrontabDay: typeof import('./../components/CornTab/CrontabDay.vue')['default']
|
||||
CrontabField: typeof import('./../components/CornTab/CrontabField.vue')['default']
|
||||
CrontabGenerator: typeof import('./../components/CornTab/CrontabGenerator.vue')['default']
|
||||
CrontabModel: typeof import('./../components/CornTab/CrontabModel.vue')['default']
|
||||
CrontabMonth: typeof import('./../components/CornTab/CrontabMonth.vue')['default']
|
||||
CrontabWeek: typeof import('./../components/CornTab/CrontabWeek.vue')['default']
|
||||
CrontabYear: typeof import('./../components/CornTab/CrontabYear.vue')['default']
|
||||
DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default']
|
||||
GiCellAvatar: typeof import('./../components/GiCell/GiCellAvatar.vue')['default']
|
||||
GiCellGender: typeof import('./../components/GiCell/GiCellGender.vue')['default']
|
||||
|
344
src/views/schedule/job/JobAddModal.vue
Normal file
344
src/views/schedule/job/JobAddModal.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:modal-style="{ maxWidth: '700px' }"
|
||||
:body-style="{ maxHeight: width >= 700 ? '76vh' : '100vh' }"
|
||||
:width="width >= 700 ? '90%' : '100%'"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" size="large" auto-label-width :layout="width >= 700 ? 'horizontal' : 'vertical'">
|
||||
<fieldset>
|
||||
<legend>基础配置</legend>
|
||||
<a-row>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="任务组" field="groupName">
|
||||
<a-select v-model="form.groupName" placeholder="请选择任务组" :options="groupList" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="任务名称" field="jobName">
|
||||
<a-input v-model.trim="form.jobName" placeholder="请输入任务名称" :max-length="64" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="描述" field="description">
|
||||
<a-textarea
|
||||
v-model.trim="form.description"
|
||||
placeholder="请输入描述"
|
||||
show-word-limit
|
||||
:max-length="200"
|
||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>调度配置</legend>
|
||||
<a-row>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="触发类型" field="triggerType">
|
||||
<a-select
|
||||
v-model="form.triggerType"
|
||||
placeholder="请选择触发类型"
|
||||
:options="job_trigger_type_enum"
|
||||
@change="triggerTypeChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item :label="form.triggerType === 2 ? '间隔时长' : 'CRON表达式'" field="triggerInterval">
|
||||
<a-input-number
|
||||
v-if="form.triggerType === 2"
|
||||
v-model="triggerIntervalNumber"
|
||||
placeholder="请输入间隔时长"
|
||||
:min="1"
|
||||
>
|
||||
<template #suffix>秒</template>
|
||||
</a-input-number>
|
||||
<a-input
|
||||
v-else
|
||||
v-model="form.triggerInterval"
|
||||
placeholder="请输入CRON表达式"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>任务配置</legend>
|
||||
<a-row>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="任务类型" field="taskType">
|
||||
<a-select v-model="form.taskType" :options="job_task_type_enum" placeholder="请选择任务类型" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="执行器名称" field="executorInfo">
|
||||
<a-input v-model.trim="form.executorInfo" placeholder="请输入执行器名称" :max-length="255" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="任务参数" field="argsStr">
|
||||
<a-textarea
|
||||
v-if="form.taskType !== 3"
|
||||
v-model.trim="form.argsStr"
|
||||
placeholder="请输入任务参数"
|
||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
||||
/>
|
||||
<div v-else class="args-container">
|
||||
<div v-for="(item, index) in args" :key="index" class="args-item">
|
||||
<a-form-item hide-label :rules="[{ required: true, message: '请输入分片参数' }]">
|
||||
<a-input v-model="item.value" :placeholder="`请输入分片参数 ${index + 1}`" />
|
||||
</a-form-item>
|
||||
<a-button status="danger" class="args-delete-button" @click="onDeleteArgs(index)">
|
||||
<template #icon>
|
||||
<icon-delete />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="outline" class="add-button" style="width: 100%;" @click="onAddArgs">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>高级配置</legend>
|
||||
<a-row>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="路由策略" field="routeKey">
|
||||
<a-select v-model.trim="form.routeKey" placeholder="请选择路由策略" :options="job_route_strategy_enum" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="阻塞策略" field="blockStrategy">
|
||||
<a-select v-model.trim="form.blockStrategy" placeholder="请选择阻塞策略" :options="job_block_strategy_enum" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="超时时间" field="executorTimeout">
|
||||
<a-input-number v-model.trim="form.executorTimeout" placeholder="请输入超时时间" :min="1">
|
||||
<template #suffix>秒</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="最大重试次数" field="maxRetryTimes">
|
||||
<a-input-number v-model="form.maxRetryTimes" placeholder="请输入最大重试次数" :min="0">
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="重试间隔" field="retryInterval">
|
||||
<a-input-number v-model.trim="form.retryInterval" placeholder="请输入重试间隔" :min="1">
|
||||
<template #suffix>
|
||||
秒
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-bind="colProps">
|
||||
<a-form-item label="并行数" field="parallelNum">
|
||||
<a-input-number v-model="form.parallelNum" placeholder="请输入并行数" :min="1" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</fieldset>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ColProps, type FormInstance, Message } from '@arco-design/web-vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { addJob, listGroup, updateJob } from '@/apis'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
const colProps: ColProps = { xs: 24, sm: 24, md: 12, lg: 12, xl: 12, xxl: 12 }
|
||||
const { width } = useWindowSize()
|
||||
const { job_trigger_type_enum, job_task_type_enum, job_route_strategy_enum, job_block_strategy_enum } = useDict(
|
||||
'job_trigger_type_enum',
|
||||
'job_task_type_enum',
|
||||
'job_route_strategy_enum',
|
||||
'job_block_strategy_enum'
|
||||
)
|
||||
|
||||
const dataId = ref()
|
||||
const isUpdate = computed(() => !!dataId.value)
|
||||
const title = computed(() => (isUpdate.value ? '修改任务' : '新增任务'))
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
groupName: [{ required: true, message: '请选择任务组' }],
|
||||
jobName: [{ required: true, message: '请输入任务名称' }],
|
||||
triggerType: [{ required: true, message: '请选择触发类型' }],
|
||||
triggerInterval: [{ required: true, message: '请输入间隔时长' }],
|
||||
taskType: [{ required: true, message: '请选择任务类型' }],
|
||||
executorInfo: [{ required: true, message: '请输入执行器名称' }],
|
||||
routeKey: [{ required: true, message: '请选择路由策略' }],
|
||||
blockStrategy: [{ required: true, message: '请选择阻塞策略' }],
|
||||
executorTimeout: [{ required: true, message: '请输入超时时间' }],
|
||||
maxRetryTimes: [{ required: true, message: '请输入最大重试次数' }],
|
||||
retryInterval: [{ required: true, message: '请输入重试间隔' }],
|
||||
parallelNum: [{ required: true, message: '请输入并行数' }]
|
||||
}
|
||||
|
||||
const { form, resetForm } = useForm({
|
||||
triggerType: 2,
|
||||
triggerInterval: 60,
|
||||
taskType: 1,
|
||||
routeKey: 4,
|
||||
blockStrategy: 1,
|
||||
executorTimeout: 60,
|
||||
maxRetryTimes: 3,
|
||||
retryInterval: 1,
|
||||
parallelNum: 1
|
||||
})
|
||||
|
||||
const args = ref<any[]>([])
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
args.value = [{ value: '' }]
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const groupList = ref()
|
||||
// 查询任务组列表
|
||||
const getGroupList = async () => {
|
||||
const { data } = await listGroup()
|
||||
groupList.value = data?.map((item: string) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}))
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
reset()
|
||||
getGroupList()
|
||||
dataId.value = undefined
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = async (record: any) => {
|
||||
await getGroupList()
|
||||
reset()
|
||||
dataId.value = record.id
|
||||
Object.assign(form, record)
|
||||
// 切片任务,解析 argsStr 并赋值给 args
|
||||
if (form.taskType === 3 && form.argsStr) {
|
||||
try {
|
||||
const parsedArgs = JSON.parse(form.argsStr)
|
||||
args.value = parsedArgs.map((arg: any) => ({ value: arg }))
|
||||
} catch (error: any) {
|
||||
Message.error(error)
|
||||
}
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
try {
|
||||
// 切片任务,将参数转换为 JSON 数组
|
||||
if (form.taskType === 3) {
|
||||
form.argsStr = JSON.stringify(args.value.map((arg) => arg.value))
|
||||
}
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return false
|
||||
if (isUpdate.value) {
|
||||
await updateJob(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
} else {
|
||||
await addJob(form)
|
||||
Message.success('新增成功')
|
||||
}
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 触发类型切换
|
||||
const triggerTypeChange = () => {
|
||||
switch (form.triggerType) {
|
||||
case 2:
|
||||
form.triggerInterval = 60
|
||||
break
|
||||
case 3:
|
||||
form.triggerInterval = ''
|
||||
break
|
||||
}
|
||||
}
|
||||
// 间隔时长
|
||||
const triggerIntervalNumber = computed({
|
||||
get() {
|
||||
return Number(form.triggerInterval)
|
||||
},
|
||||
set(newValue) {
|
||||
form.triggerInterval = newValue.toString()
|
||||
}
|
||||
})
|
||||
|
||||
// 新增切片参数
|
||||
const onAddArgs = () => {
|
||||
args.value.push({ value: '' })
|
||||
}
|
||||
// 删除切片参数
|
||||
const onDeleteArgs = (index) => {
|
||||
args.value.splice(index, 1)
|
||||
}
|
||||
|
||||
defineExpose({ onAdd, onUpdate })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
fieldset {
|
||||
padding: 15px 15px 0 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
fieldset legend {
|
||||
color: rgb(var(--gray-10));
|
||||
padding: 2px 5px 2px 5px;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.args-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.args-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
button {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.args-item > *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
align-self: flex-start;
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
60
src/views/schedule/job/JobDetailDrawer.vue
Normal file
60
src/views/schedule/job/JobDetailDrawer.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-drawer v-model:visible="visible" title="任务详情" :width="width >= 600 ? 600 : '100%'" :footer="false">
|
||||
<a-descriptions :column="2" size="large" class="general-description">
|
||||
<a-descriptions-item label="ID" :span="2">
|
||||
<a-typography-paragraph copyable>{{ dataDetail?.id }}</a-typography-paragraph>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务组">{{ dataDetail?.groupName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务名称">{{ dataDetail?.jobName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="触发类型">
|
||||
<GiCellTag :value="dataDetail?.triggerType" :dict="job_trigger_type_enum" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item v-if="dataDetail?.triggerType === 1" label="CRON">{{ dataDetail?.triggerInterval }}</a-descriptions-item>
|
||||
<a-descriptions-item v-else-if="dataDetail?.triggerType === 2" label="间隔时长">{{ dataDetail?.triggerInterval }} 秒</a-descriptions-item>
|
||||
<a-descriptions-item label="任务类型">
|
||||
<GiCellTag :value="dataDetail?.taskType" :dict="job_task_type_enum" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="执行器名称">{{ dataDetail?.executorInfo }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务参数">{{ dataDetail?.argsStr }}</a-descriptions-item>
|
||||
<a-descriptions-item label="路由策略">
|
||||
<GiCellTag :value="dataDetail?.routeKey" :dict="job_route_strategy_enum" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="阻塞策略">
|
||||
<GiCellTag :value="dataDetail?.blockStrategy" :dict="job_block_strategy_enum" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="超时时间">{{ dataDetail?.executorTimeout }} 秒</a-descriptions-item>
|
||||
<a-descriptions-item label="最大重试次数">{{ dataDetail?.maxRetryTimes }}</a-descriptions-item>
|
||||
<a-descriptions-item label="重试间隔">{{ dataDetail?.retryInterval }} 秒</a-descriptions-item>
|
||||
<a-descriptions-item label="并行数">{{ dataDetail?.parallelNum }}</a-descriptions-item>
|
||||
<a-descriptions-item label="任务状态">
|
||||
<GiCellTag :value="dataDetail?.jobStatus" :dict="job_status_enum" />
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">{{ dataDetail?.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import type { JobResp } from '@/apis'
|
||||
import { useDict } from '@/hooks/app'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const { job_status_enum, job_trigger_type_enum, job_task_type_enum, job_route_strategy_enum, job_block_strategy_enum } = useDict(
|
||||
'job_status_enum',
|
||||
'job_trigger_type_enum',
|
||||
'job_task_type_enum',
|
||||
'job_route_strategy_enum',
|
||||
'job_block_strategy_enum'
|
||||
)
|
||||
|
||||
const visible = ref(false)
|
||||
const dataDetail = ref<JobResp>()
|
||||
// 详情
|
||||
const onDetail = (record: JobResp) => {
|
||||
dataDetail.value = record
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ onDetail })
|
||||
</script>
|
195
src/views/schedule/job/index.vue
Normal file
195
src/views/schedule/job/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="table-page">
|
||||
<GiTable
|
||||
title="任务管理"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
:disabled-column-keys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #custom-left>
|
||||
<a-select
|
||||
v-model="queryForm.groupName"
|
||||
placeholder="请选择任务组"
|
||||
:options="groupList"
|
||||
style="width: 200px"
|
||||
@change="search"
|
||||
/>
|
||||
<a-input v-model="queryForm.jobName" placeholder="请输入任务名称" allow-clear @change="search" />
|
||||
<a-select v-model="queryForm.jobStatus" placeholder="请选择任务状态" :options="job_status_enum" allow-clear style="width: 150px" @change="search" />
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</template>
|
||||
<template #custom-right>
|
||||
<a-button v-permission="['schedule:job:add']" type="primary" @click="onAdd">
|
||||
<template #icon><icon-plus /></template>
|
||||
<span>新增</span>
|
||||
</a-button>
|
||||
</template>
|
||||
<template #jobName="{ record }">
|
||||
<a-link @click="onDetail(record)">{{ record.jobName }}</a-link>
|
||||
</template>
|
||||
<template #triggerType="{ record }">
|
||||
<GiCellTag :value="record.triggerType" :dict="job_trigger_type_enum" />:
|
||||
<span v-if="record.triggerType === 2">{{ record.triggerInterval }} 秒</span>
|
||||
<span v-else>{{ record.triggerInterval }}</span>
|
||||
</template>
|
||||
<template #taskType="{ record }">
|
||||
<GiCellTag :value="record.taskType" :dict="job_task_type_enum" />
|
||||
{{ record.executorInfo }}
|
||||
</template>
|
||||
<template #jobStatus="{ record }">
|
||||
<a-switch
|
||||
v-model="record.jobStatus"
|
||||
:checked-value="1"
|
||||
:unchecked-value="0"
|
||||
:disabled="!has.hasPerm('tool:job:update')"
|
||||
@change="onUpdateStatus(record)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click="onLog(record)">日志</a-link>
|
||||
<a-popconfirm content="是否确定立即执行一次任务?" type="warning" @ok="onTrigger(record)">
|
||||
<a-link v-permission="['schedule:job:trigger']">执行</a-link>
|
||||
</a-popconfirm>
|
||||
<a-link v-permission="['schedule:job:update']" @click="onUpdate(record)">修改</a-link>
|
||||
<a-link v-permission="['schedule:job:delete']" status="danger" @click="onDelete(record)">删除</a-link>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<JobAddModal ref="JobAddModalRef" @save-success="reset" />
|
||||
<JobDetailDrawer ref="JobDetailDrawerRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import JobAddModal from './JobAddModal.vue'
|
||||
import JobDetailDrawer from './JobDetailDrawer.vue'
|
||||
import { type JobQuery, type JobResp, deleteJob, listGroup, listJob, triggerJob, updateJobStatus } from '@/apis'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useTable } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
|
||||
defineOptions({ name: 'ScheduleJob' })
|
||||
|
||||
const { job_status_enum, job_trigger_type_enum, job_task_type_enum } = useDict('job_status_enum', 'job_trigger_type_enum', 'job_task_type_enum')
|
||||
|
||||
const queryForm = reactive<JobQuery>({
|
||||
groupName: ''
|
||||
})
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
search,
|
||||
handleDelete
|
||||
} = useTable((page) => listJob({ ...queryForm, ...page }), { immediate: false })
|
||||
|
||||
const columns: TableInstanceColumns[] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 66,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
|
||||
},
|
||||
{ title: '任务名称', dataIndex: 'jobName', slotName: 'jobName', width: 100, ellipsis: true, tooltip: true },
|
||||
{ title: '调度类型', dataIndex: 'triggerType', slotName: 'triggerType', width: 130 },
|
||||
{ title: '任务类型', dataIndex: 'taskType', slotName: 'taskType', width: 130, ellipsis: true, tooltip: true },
|
||||
{ title: '状态', dataIndex: 'jobStatus', width: 60, align: 'center', slotName: 'jobStatus' },
|
||||
{ title: '描述', dataIndex: 'description', width: 130, ellipsis: true, tooltip: true },
|
||||
{ title: '创建时间', dataIndex: 'createDt', width: 180 },
|
||||
{ title: '修改时间', dataIndex: 'updateDt', width: 180, show: false },
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'action',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['schedule:job:trigger', 'schedule:job:update', 'schedule:job:delete'])
|
||||
}
|
||||
]
|
||||
|
||||
const groupList = ref()
|
||||
// 查询任务组列表
|
||||
const getGroupList = async () => {
|
||||
const { data } = await listGroup()
|
||||
groupList.value = data?.map((item: string) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}))
|
||||
queryForm.groupName = groupList.value[0].label
|
||||
search()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.jobName = undefined
|
||||
queryForm.jobStatus = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onDelete = (record: JobResp) => {
|
||||
return handleDelete(() => deleteJob(record.id), {
|
||||
content: `是否确定删除任务 [${record.jobName}]?`,
|
||||
showModal: true
|
||||
})
|
||||
}
|
||||
|
||||
// 修改状态
|
||||
const onUpdateStatus = (record: JobResp) => {
|
||||
const msg = record.jobStatus === 1 ? '启用成功' : '禁用成功'
|
||||
updateJobStatus({ jobStatus: record.jobStatus }, record.id)
|
||||
.then(() => {
|
||||
Message.success(msg)
|
||||
}).catch(() => {
|
||||
record.jobStatus = record.jobStatus === 1 ? 0 : 1
|
||||
})
|
||||
}
|
||||
|
||||
// 执行
|
||||
const onTrigger = (record: JobResp) => {
|
||||
triggerJob(record.id).then(() => {
|
||||
Message.success('执行请求已下发')
|
||||
})
|
||||
}
|
||||
|
||||
const JobAddModalRef = ref<InstanceType<typeof JobAddModal>>()
|
||||
// 新增
|
||||
const onAdd = () => {
|
||||
JobAddModalRef.value?.onAdd()
|
||||
}
|
||||
|
||||
// 修改
|
||||
const onUpdate = (record: JobResp) => {
|
||||
JobAddModalRef.value?.onUpdate(record)
|
||||
}
|
||||
|
||||
const JobDetailDrawerRef = ref<InstanceType<typeof JobDetailDrawer>>()
|
||||
// 详情
|
||||
const onDetail = (record: JobResp) => {
|
||||
JobDetailDrawerRef.value?.onDetail(record)
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
// 日志
|
||||
const onLog = (record: JobResp) => {
|
||||
router.push({ path: '/schedule/log', query: { jobId: record.id, jobName: record.jobName, groupName: record.groupName } })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getGroupList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
79
src/views/schedule/log/LogDetailModal.vue
Normal file
79
src/views/schedule/log/LogDetailModal.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="任务日志详情"
|
||||
:width="width >= 1500 ? 1500 : '100%'"
|
||||
:footer="false"
|
||||
>
|
||||
<a-layout style="height: 500px">
|
||||
<a-layout-sider :resize-directions="['right']">
|
||||
<a-tabs size="large" position="left">
|
||||
<a-tab-pane v-for="item in dataList" :key="item.id">
|
||||
<template #title>
|
||||
<span @click="onLogDetail(item)">{{ item.clientInfo.split('@')[1] }}</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-layout-sider>
|
||||
<a-layout-content>
|
||||
<GiCodeView :code-json="content" />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { type JobInstanceQuery, type JobInstanceResp, type JobLogResp, listJobInstance, listJobInstanceLog } from '@/apis'
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const queryForm = reactive<JobInstanceQuery>({})
|
||||
const dataList = ref<JobInstanceResp[]>([])
|
||||
const loading = ref(false)
|
||||
// 查询列表数据
|
||||
const getInstanceList = async (query: JobInstanceQuery = { ...queryForm }) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listJobInstance(query)
|
||||
dataList.value = res.data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
// 详情
|
||||
const onDetail = (record: JobLogResp) => {
|
||||
visible.value = true
|
||||
// 更新 queryForm
|
||||
queryForm.jobId = record.jobId
|
||||
queryForm.taskBatchId = record.id
|
||||
getInstanceList()
|
||||
}
|
||||
|
||||
// 格式化日志
|
||||
const formatLog = (log: any) => {
|
||||
const date = new Date(Number.parseInt(log.time_stamp))
|
||||
return `${dayjs(date).format('YYYY-MM-DD HH:mm:ss')} ${log.level} [${log.thread}] ${log.location} - ${log.message}`
|
||||
}
|
||||
|
||||
const content = ref('')
|
||||
// 日志输出
|
||||
const onLogDetail = async (record: JobInstanceResp) => {
|
||||
// todo startId根据第一次查询 如果有返回!=0则需要在查一次
|
||||
const res = await listJobInstanceLog({
|
||||
taskBatchId: record.taskBatchId,
|
||||
jobId: record.jobId,
|
||||
taskId: record.id,
|
||||
startId: 0,
|
||||
fromIndex: 0,
|
||||
size: 50
|
||||
})
|
||||
content.value = res.data.message.map(formatLog).join('\n')
|
||||
}
|
||||
|
||||
defineExpose({ onDetail })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
164
src/views/schedule/log/index.vue
Normal file
164
src/views/schedule/log/index.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="table-page">
|
||||
<GiTable
|
||||
title="任务日志"
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabled-tools="['size']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #custom-left>
|
||||
<a-select
|
||||
v-model="queryForm.groupName"
|
||||
placeholder="请选择任务组"
|
||||
:options="groupList"
|
||||
style="width: 200px"
|
||||
@change="search"
|
||||
/>
|
||||
<a-input v-model="queryForm.jobName" placeholder="请输入任务名称" allow-clear @change="search" />
|
||||
<a-select
|
||||
v-model="queryForm.taskBatchStatus"
|
||||
placeholder="请选择状态"
|
||||
:options="job_execute_status_enum"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
@change="search"
|
||||
/>
|
||||
<DateRangePicker v-model="queryForm.datetimeRange" @change="search" />
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</template>
|
||||
<template #taskBatchStatus="{ record }">
|
||||
<GiCellTag :value="record.taskBatchStatus" :dict="job_execute_status_enum" />
|
||||
</template>
|
||||
<template #operationReason="{ record }">
|
||||
<GiCellTag :value="record.operationReason" :dict="job_execute_reason_enum" />
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click="onDetail(record)">详情</a-link>
|
||||
<a-popconfirm content="是否确定停止本次执行?" type="warning" @ok="onStop(record)">
|
||||
<a-link v-if="record.taskBatchStatus === 2" v-permission="['schedule:log:stop']" status="danger">停止</a-link>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm content="是否确定重试本次执行?" type="warning" @ok="onRetry(record)">
|
||||
<a-link
|
||||
v-if="record.taskBatchStatus === 4 || record.taskBatchStatus === 5 || record.taskBatchStatus === 6"
|
||||
v-permission="['schedule:log:retry']"
|
||||
status="danger"
|
||||
>
|
||||
重试
|
||||
</a-link>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
|
||||
<JobLogDetailModal ref="JobLogDetailModalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
import JobLogDetailModal from './LogDetailModal.vue'
|
||||
import { type JobLogQuery, type JobLogResp, listGroup, listJobLog, retryJob, stopJob } from '@/apis'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useTable } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import { isMobile } from '@/utils'
|
||||
import has from '@/utils/has'
|
||||
|
||||
defineOptions({ name: 'ScheduleLog' })
|
||||
|
||||
const { job_execute_reason_enum, job_execute_status_enum } = useDict('job_execute_reason_enum', 'job_execute_status_enum')
|
||||
|
||||
const queryForm = reactive<JobLogQuery>({
|
||||
datetimeRange: [
|
||||
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
]
|
||||
})
|
||||
const {
|
||||
tableData: dataList,
|
||||
pagination,
|
||||
loading,
|
||||
search
|
||||
} = useTable((page) => listJobLog({ ...queryForm, ...page }), { immediate: false })
|
||||
|
||||
const columns: TableInstanceColumns[] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 66,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
|
||||
},
|
||||
{ title: '任务组', dataIndex: 'groupName', width: 80, ellipsis: true, tooltip: true },
|
||||
{ title: '任务名称', dataIndex: 'jobName', width: 80, ellipsis: true, tooltip: true },
|
||||
{ title: '调度时间', dataIndex: 'createDt', width: 80 },
|
||||
{ title: '执行状态', dataIndex: 'taskBatchStatus', slotName: 'taskBatchStatus', width: 50, align: 'center' },
|
||||
{ title: '执行备注', dataIndex: 'operationReason', slotName: 'operationReason', width: 80, ellipsis: true, tooltip: true },
|
||||
{ title: '执行时间', dataIndex: 'executionAt', width: 80 },
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'action',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
fixed: !isMobile() ? 'right' : undefined,
|
||||
show: has.hasPermOr(['schedule:log:stop', 'schedule:log:retry'])
|
||||
}
|
||||
]
|
||||
|
||||
const groupList = ref()
|
||||
// 查询任务组列表
|
||||
const getGroupList = async () => {
|
||||
const { data } = await listGroup()
|
||||
groupList.value = data?.map((item: string) => ({
|
||||
label: item,
|
||||
value: item
|
||||
}))
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.taskBatchStatus = undefined
|
||||
queryForm.datetimeRange = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
// 停止
|
||||
const onStop = (record: JobLogResp) => {
|
||||
stopJob(record.id).then(() => {
|
||||
Message.success('停止成功')
|
||||
})
|
||||
}
|
||||
|
||||
// 重试
|
||||
const onRetry = (record: JobLogResp) => {
|
||||
retryJob(record.id).then(() => {
|
||||
Message.success('重试成功')
|
||||
})
|
||||
}
|
||||
|
||||
const JobLogDetailModalRef = ref<InstanceType<typeof JobLogDetailModal>>()
|
||||
// 查看日志详情
|
||||
const onDetail = (record: JobLogResp) => {
|
||||
JobLogDetailModalRef.value?.onDetail(record)
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
onMounted(() => {
|
||||
if (route.query) {
|
||||
queryForm.jobId = route.query.jobId ? Number.parseInt(route.query.jobId as string, 10) : undefined
|
||||
queryForm.groupName = route.query.groupName ? route.query.groupName : undefined
|
||||
queryForm.jobName = route.query.jobName ? route.query.jobName : undefined
|
||||
}
|
||||
getGroupList()
|
||||
search()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
Reference in New Issue
Block a user