diff --git a/src/components/CronModal/components/Day.vue b/src/components/CronModal/components/Day.vue new file mode 100644 index 0000000..dfd853a --- /dev/null +++ b/src/components/CronModal/components/Day.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/components/CronModal/components/Hour.vue b/src/components/CronModal/components/Hour.vue new file mode 100644 index 0000000..ba7e65f --- /dev/null +++ b/src/components/CronModal/components/Hour.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/components/CronModal/components/Minute.vue b/src/components/CronModal/components/Minute.vue new file mode 100644 index 0000000..57c1ed0 --- /dev/null +++ b/src/components/CronModal/components/Minute.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/components/CronModal/components/Month.vue b/src/components/CronModal/components/Month.vue new file mode 100644 index 0000000..82ccf0c --- /dev/null +++ b/src/components/CronModal/components/Month.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/components/CronModal/components/Second.vue b/src/components/CronModal/components/Second.vue new file mode 100644 index 0000000..543e563 --- /dev/null +++ b/src/components/CronModal/components/Second.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/components/CronModal/index.vue b/src/components/CronModal/index.vue new file mode 100644 index 0000000..434d12b --- /dev/null +++ b/src/components/CronModal/index.vue @@ -0,0 +1,114 @@ + + + + diff --git a/src/components/advanced/table-header-operation.vue b/src/components/advanced/table-header-operation.vue index 8c97d26..f91519e 100644 --- a/src/components/advanced/table-header-operation.vue +++ b/src/components/advanced/table-header-operation.vue @@ -50,7 +50,7 @@ function handleExport() {
- +
{{ $t('common.add') }} diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 99f8aa4..877eaad 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -24,8 +24,13 @@ const local: any = { config: 'Config', confirm: 'Confirm', delete: 'Delete', + view: 'View', + exportOk: "Export Completed", + exportTip: "Confirm exporting xlsx table files based on search criteria?", deleteSuccess: 'Delete Success', confirmDelete: 'Are you sure you want to delete?', + confirmClean: 'Are you sure you want to Clean?', + clearText: "Clean", edit: 'Edit', index: 'Index', keywordSearch: 'Please enter keyword', @@ -55,7 +60,75 @@ const local: any = { abnormal: 'Abnormal', export: 'Export', loading: 'Loading', + selectPlease: 'please select', + disable: 'Disable', + default: 'Default', + system:'System', + record: 'Record', + noRecord: 'No Record', + msgSuccess: 'Success {msg}', + errorFields: 'Please fill in the required information in {num} correctly!', + tipTitle: 'Prompt', + units: { + second: 'Second', + minute: 'Minute', + hour: 'Hour', + day: 'Day', + week: 'Week', + month: 'Month', + year: 'Year', + core: 'Core', + } }, + // 组件 + components: { + CronModal: { + title: "Cron Expression Generator", + addon: "Expression Preview:", + day1: "Every day", + day21: "Execute every", + day22: "days, starting from the", + day23: "day", + day31: "Cycle from", + day32: "to", + day33: "days", + day4: "Designated day (optional)", + day5: "Last day of the month", + hour1: "Hourly", + hour21: "Execute every", + hour22: "hours, starting from the", + hour23: "hour", + hour31: "Cycle time from", + hour32: "to", + hour33: "hours", + hour4: "Specified hours (multiple options available)", + minute1: "Every minute", + minute21: "Execute every", + minute22: "minutes, starting from the", + minute23: "minute", + minute31: "Cycle time from", + minute32: "to", + minute33: "minutes", + minute4: "Specified minutes (multiple options available)", + month1: "Every month", + month21: "Execute every", + month22: "months, starting from the", + month23: "month", + month31: "Cycle time from", + month32: "to", + month33: "months", + month4: "Specified months (multiple options available)", + second1: "Every second", + second21: "Execute every", + second22: "seconds, starting from the", + second23: "second", + second31: "Cycle time from", + second32: "to", + second33: "seconds", + second4: "Specify the number of seconds (multiple selectable)", + }, + }, + request: { logout: 'Logout user after request failed', logoutMsg: 'User status is invalid, please log in again', @@ -517,7 +590,27 @@ const local: any = { viewJob:'View Job', addJob:'Add Job', editJob:'Edit Job', + viewInfoErr:'Failed to get job information', getInfoError:'Get Info Error', + jobNamePlease: 'Please enter the task name correctly, limited to 2-50 characters', + invokeTargetPlease: 'Please enter the call target correctly, limited to 2-50 characters.', + cronExpressionPlease: 'Please enter or generate a cron execution expression', + jobGroup: "Job Group", + saveLog: "Save Log", + invokeTargetTip: "Parameter description: support for preset incoming parameters, serialized processing parameters in the processor", + cronExpressionTip: "Example of an expression:0/20 * * * * ?", + cronExpressionTip1: "Illustrative example: Execute tasks every 20 seconds", + cronExpressionNew: "Generating Expression", + targetParamsPlease: 'Call target incoming parameters, only support json strings', + delTip: "Are you sure you want to delete task number [{num}]?", + delLogTip: "Are you sure you want to delete Log number [{num}]?", + runOneTip: "Are you sure you want to perform a [{num}] task right away?", + runOneOk: "{num} Executed successfully", + runOne:'execute once', + jobLog: "Task Log", + jobMessage: "Task Message", + clearTip: "Confirm that all dispatch log data items are cleared?", + clearOk: "Cleared Successfully", } } }, diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 58d4ec1..e7bf312 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -24,8 +24,13 @@ const local:any = { config: '配置', confirm: '确认', delete: '删除', + view:'查看', + exportOk: "已完成导出", + exportTip: "确认根据搜索条件导出xlsx表格文件吗?", deleteSuccess: '删除成功', confirmDelete: '确认删除吗?', + confirmClean: '确认清空吗?', + clearText: "清空", edit: '编辑', index: '序号', keywordSearch: '请输入关键词搜索', @@ -55,7 +60,75 @@ const local:any = { abnormal: '异常', export:'导出', loading: '加载中...', + selectPlease: '请选择', + disable: '停用', + default: '默认', + system:'系统', + record: '记录', + noRecord: '不记录', + msgSuccess: '{msg} 成功', + errorFields: '请正确填写 {num} 处必填信息!', + tipTitle: '提示', + units: { + second: '秒', + minute: '分钟', + hour: '小时', + day: '天', + week: '周', + month: '月', + year: '年', + core: '核', + } }, + // 组件 + components: { + CronModal: { + title: "Cron表达式生成", + addon: "表达式预览:", + day1: "每一天", + day21: "每隔", + day22: "天执行一次,从", + day23: "日开始", + day31: "周期从", + day32: "到", + day33: "日", + day4: "指定日(可多选)", + day5: "本月最后一天", + hour1: "每一小时", + hour21: "每隔", + hour22: "小时执行一次,从", + hour23: "时开始", + hour31: "周期从", + hour32: "到", + hour33: "小时", + hour4: "指定小时(可多选)", + minute1: "每一分钟", + minute21: "每隔", + minute22: "分钟执行一次,从", + minute23: "分钟开始", + minute31: "周期从", + minute32: "到", + minute33: "分钟", + minute4: "指定分钟(可多选)", + month1: "每一月", + month21: "每隔", + month22: "月执行,从", + month23: "月开始", + month31: "周期从", + month32: "到", + month33: "月之间的每个月", + month4: "指定月(可多选)", + second1: "每一秒钟", + second21: "每隔", + second22: "秒执行一次,从", + second23: "秒开始", + second31: "周期从", + second32: "到", + second33: "秒", + second4: "指定秒数(可多选)", + } + }, + request: { logout: '请求失败后登出用户', logoutMsg: '用户状态失效,请重新登录', @@ -504,7 +577,7 @@ const local:any = { other:'其他', }, task:{ - taskId:'ID', + taskId:'编号', taskName:'任务名称', group:'任务组名', invoke:'调用目标', @@ -519,6 +592,25 @@ const local:any = { editJob:'编辑任务', viewInfoErr:'查看异常信息', getInfoError:'获取信息失败', + jobNamePlease: '请正确输入任务名称,限2-50个字符', + invokeTargetPlease: '请正确输入调用目标,限2-50个字符', + cronExpressionPlease: '请输入或生成cron执行表达式', + jobGroup: "任务组名", + saveLog: "记录日志", + invokeTargetTip: "参数说明:支持预设传入参数,在处理器中进行序列化处理参数", + cronExpressionTip: "表达式示例:0/20 * * * * ?", + cronExpressionTip1: "示例说明:每20秒执行任务", + cronExpressionNew: "生成表达式", + targetParamsPlease: '调用目标传入参数,仅支持json字符串', + delTip: "确认删除定时任务编号为 【{num}】 任务吗?", + delLogTip: "确认删除日志编号为 【{num}】 任务日志吗?", + runOneTip: "确定要立即执行一次 【{num}】 任务吗?", + runOneOk: "{num} 执行成功", + runOne:'执行一次', + jobLog: "任务日志", + jobMessage: "执行信息", + clearTip: "确认清空所有调度日志数据项吗?", + clearOk: "清空成功", } } }, diff --git a/src/service/api/job.ts b/src/service/api/job.ts index 5ae21b0..b72d20a 100644 --- a/src/service/api/job.ts +++ b/src/service/api/job.ts @@ -16,3 +16,88 @@ export function doGetjobInfo(paramsId: any) { method: 'get', }); } + + +/** + * 新增定时任务调度 + * @param data 任务对象 + * @returns object + */ +export function addJob(data: Record) { + return request({ + url: '/schedule/job', + method: 'post', + data: data, + }); +} + +/** + * 修改定时任务调度 + * @param data 任务对象 + * @returns object + */ +export function updateJob(data: Record) { + return request({ + url: '/schedule/job', + method: 'put', + data: data, + }); +} + +/** + * 删除调度日志 + * @param jobIds 任务日志Id + * @returns object + */ +export function delJobLog(jobIds: string) { + return request({ + url: `/schedule/job/${jobIds}`, + method: 'delete', + }); +} + +/** + * 定时任务立即执行一次 + * @param jobId 任务ID + * @returns object + */ +export function runJob(jobId: string) { + return request({ + url: `/schedule/job/run`, + method: 'put', + data: {jobId}, + }); +} + +/** + * 任务状态修改 + * @param jobId 任务ID + * @param status 变更状态值 + * @returns + */ +export function changeJobStatus( + jobId: string | number, + status: string | number +) { + return request({ + url: '/schedule/job/changeStatus', + method: 'put', + data: { + jobId, + status, + }, + }); +} + +/** + * 定时任务调度列表导出 + * @param query 查询参数 + * @returns bolb + */ +export function exportJob() { + return request({ + url: '/schedule/job/export', + method: 'post', + responseType: 'blob', + }); +} diff --git a/src/views/manage/task/index.vue b/src/views/manage/task/index.vue index f15eac6..421b2cc 100644 --- a/src/views/manage/task/index.vue +++ b/src/views/manage/task/index.vue @@ -3,19 +3,18 @@ import { message, Tag } from 'ant-design-vue'; import type { Key } from 'ant-design-vue/es/_util/type'; import { useTable, useTableOperate } from '@/hooks/common/table'; import { SimpleScrollbar } from '~/packages/materials/src'; -import taskOperateDrawer from './modules/task-operate-drawer.vue'; import { useI18n } from "vue-i18n"; -import { SyncOutlined, SearchOutlined, ProfileOutlined, FormOutlined, DeleteOutlined, RocketOutlined, ContainerOutlined } from '@ant-design/icons-vue'; +import { SyncOutlined, SearchOutlined, ProfileOutlined, FormOutlined, DeleteOutlined, RocketOutlined, ContainerOutlined, FieldTimeOutlined, ExportOutlined } from '@ant-design/icons-vue'; import { enableStatusOptions } from '@/constants/business'; +import { saveAs } from 'file-saver'; +import Form from 'ant-design-vue/es/form/Form'; +import Modal from 'ant-design-vue/es/modal/Modal'; +import { useRouter, useRoute } from 'vue-router'; +const router = useRouter(); +const route = useRoute(); +const routePath = route.path; -import { useAntdForm, useFormRules } from '@/hooks/common/form'; -const { defaultRequiredRule, formRules } = useFormRules(); -const rules = { - jonName: defaultRequiredRule, - invokeTarget: defaultRequiredRule, - cronExpression: defaultRequiredRule, -}; const { t } = useI18n(); @@ -44,8 +43,50 @@ let modalState: any = reactive({ }, confirmLoading: false, openByCron: false, + statusOpt: [ + { label: t('common.normal'), value: '0' }, + { label: t('common.disable'), value: '1' } + ], + jobGroup: [ + { label: t('common.default'), value: "DEFAULT" }, + { label: t('common.system'), value: "SYSTEM" } + ], + sysJobSaveLog: [ + { label: t('common.record'), value: "0" }, + { label: t('common.noRecord'), value: "1" } + ] }); +/**对话框内表单属性和校验规则 */ +const modalStateFrom = Form.useForm( + modalState.from, + reactive({ + jobName: [ + { + required: true, + min: 2, + max: 50, + message: t('page.manage.task.jobNamePlease'), + }, + ], + invokeTarget: [ + { + required: true, + min: 2, + max: 50, + message: t('page.manage.task.invokeTargetPlease'), + }, + ], + cronExpression: [ + { + required: true, + min: 6, + message: t('page.manage.task.cronExpressionPlease'), + }, + ], + }) +); + const scrollConfig = computed(() => { return { y: wrapperElHeight.value - 72, @@ -53,11 +94,14 @@ const scrollConfig = computed(() => { }; }); -const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({ +const { columns, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({ apiFn: doGetjobList, apiParams: { pageNum: 1, pageSize: 10, + jobName: '', + jobGroup: '', + status: '' }, rowKey: 'jobId', columns: () => [ @@ -78,22 +122,6 @@ const { columns, columnChecks, data, loading, getData, mobilePagination, searchP dataIndex: 'jobGroup', title: t('page.manage.task.group'), align: 'center', - // customRender: ({ record }: any) => { - // if (record.operatorType === null) { - // return null; - // } - // const tagMap: any = { - // 0: t('page.manage.log.other'), - // 1: t('page.manage.log.backUser'), - // 2: t('page.manage.log.phoneUser'), - // }; - // const tagColor: any = { - // '0': 'pink', - // '1': 'warning', - // '2': 'blue', - // }; - // return {tagMap[record.operatorType]}; - // } }, { key: 'invokeTarget', @@ -132,74 +160,42 @@ const { columns, columnChecks, data, loading, getData, mobilePagination, searchP title: t('common.operate'), align: 'center', width: 200, - // customRender: ({ record }: any) => - // !record.admin && ( - //
- // handleDelete(record.operId)} title={t('common.confirmDelete')}> - // - // - // - // - //
- // ) } ] }); const { - drawerVisible, operateType, - editingData, - handleAdd, checkedRowKeys, onBatchDeleted, onDeleted // closeDrawer } = useTableOperate(data, { getData, idKey: 'jobId' }); -const deptTreeData = ref([]); -onMounted(() => { -}); + async function handleBatchDelete() { - const { error } = await doDeleteLog(checkedRowKeys.value); - if (!error) { - onBatchDeleted(); - } + Modal.confirm({ + title: t('common.tipTitle'), + content: t('page.manage.task.delTip', { num: checkedRowKeys.value.join(',') }), + async onOk() { + + const { error } = await doDeleteLog(checkedRowKeys.value); + if (!error) { + onBatchDeleted(); + } + } + }) } -async function handleDelete(id: number) { - const { error } = await doDeleteLog([id]); - if (!error) { - onDeleted(); - } -} function handleUserSelectChange(selectedRowKeys: Key[], selectedRows: any[]) { checkedRowKeys.value = selectedRowKeys as number[]; } -function fnTest() { - searchParams.value = { - - }; - - resetSearchParams(); - // 使用 nextTick 确保视图更新后检查 - nextTick(() => { - console.log(operateType) - }); -} - async function fnRecordView(jobId: number) { - // const { error, data } = await doGetjobInfo(jobId); - // // if (!error) { - // // onDeleted(); - // // } - // console.log(data); + operateType.value = 'view'; if (modalState.confirmLoading) return; @@ -215,16 +211,44 @@ async function fnRecordView(jobId: number) { modalState.title = t('page.manage.task.viewJob'); console.log(modalState.openByView); } - // if (res.code === RESULT_CODE_SUCCESS && res.data) { - // modalState.from = Object.assign(modalState.from, res.data); - // modalState.title = t('views.manage.job.viewJob'); - // modalState.openByView = true; - // } else { - // message.error(t('views.manage.job.viewInfoErr'), 2); - // } + }); } +/** + * 对话框弹出确认执行函数 + * 进行表达规则校验 + */ +function fnModalOk() { + modalStateFrom + .validate() + .then(() => { + modalState.confirmLoading = true; + const from = toRaw(modalState.from); + const job = from.jobId ? updateJob(from) : addJob(from); + const key = 'job'; + message.loading({ content: t('common.loading'), key }); + job + .then((res: any) => { + if (!res.error) { + message.success({ + content: t('common.msgSuccess', { msg: modalState.title }), + key, + duration: 2, + }); + modalState.openByEdit = false; + modalStateFrom.resetFields(); + getData(); + } + }) + .finally(() => { + modalState.confirmLoading = false; + }); + }) + .catch(e => { + message.error(t('common.errorFields', { num: e.errorFields.length }), 3); + }); +} /** * 对话框弹出关闭执行函数 @@ -233,7 +257,7 @@ async function fnRecordView(jobId: number) { function fnModalCancel() { modalState.openByEdit = false; modalState.openByView = false; - // modalStateFrom.resetFields(); + modalStateFrom.resetFields(); } @@ -264,6 +288,123 @@ function fnModalVisibleByEdit(jobId?: string | number) { } } +/** + * 对话框弹出cron生成回调 + */ +function fnModalCron(opt: boolean, cronStr?: string) { + modalState.openByCron = opt; + if (cronStr) { + modalState.from.cronExpression = cronStr; + } +} + +function fnRecordDelete(recordId: any) { + Modal.confirm({ + title: t('common.tipTitle'), + content: t('page.manage.task.delTip', { num: recordId }), + async onOk() { + + const { error } = await delJobLog(recordId); + if (!error) { + onDeleted(); + } + } + }) +} + +function fnRunTask(jobId: any) { + Modal.confirm({ + title: t('common.tipTitle'), + content: t('page.manage.task.runOneTip', { num: jobId }), + async onOk() { + const { error } = await runJob(jobId); + if (!error) { + message.success(t('page.manage.task.runOneOk'), 2); + } + } + }) +} + +/**跳转任务日志页面 */ +function fnJobLogView(jobId: string | number = '0') { + console.log(`${routePath}/log/${jobId}`) + router.push(`${routePath}/log?jobId=${jobId}`); +} + +/** + * 任务状态修改 + * @param row 任务信息对象 + */ +function fnRecordStatus(row: Record) { + doGetType(row.jobId).then((res: any) => { + console.log(res); + }) + const sysJobStatus = [ + { + "label": t('common.normal'), + "value": "0", + "tagType": "", + "tagClass": "" + }, + { + "label": t('common.disable'), + "value": "1", + "tagType": "", + "tagClass": "" + } + ]; + const text = + row.status === '1' + ? sysJobStatus.find(s => s.value === '1')?.label + : sysJobStatus.find(s => s.value === '0')?.label; + Modal.confirm({ + title: t('common.tipTitle'), + content: t('views.monitor.job.statusChange', { text, num: row.jobName }), + onOk() { + const key = 'changeJobStatus'; + message.loading({ content: t('common.loading'), key }); + changeJobStatus(row.jobId, row.status).then(res => { + if (!res.error) { + message.success({ + content: t('common.msgSuccess', { msg: `${row.jobName} ${text}` }), + key, + duration: 2, + }); + } + getData(); + }); + }, + onCancel() { + const value = + row.status === '1' + ? sysJobStatus.find(s => s.value === '0')?.value + : sysJobStatus.find(s => s.value === '1')?.value; + row.status = value || '0'; + }, + }); +} + +function fnExportList() { + Modal.confirm({ + title: t('common.tipTitle'), + content: t('common.exportTip'), + onOk() { + const key = 'exportJob'; + message.loading({ content: t('common.loading'), key }); + exportJob().then(res => { + if (!res.error) { + message.success({ + content: t('common.exportOk'), + key, + duration: 2, + }); + saveAs(res.data, `job_${Date.now()}.xlsx`); + } + }); + }, + }); +} +