mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-07-05 10:08:15 +08:00
feat: ✨ 计划作业 (#87)
This commit is contained in:
106
src/frontend/admin/src/api/sys/job.js
Normal file
106
src/frontend/admin/src/api/sys/job.js
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 计划作业服务
|
||||
* @module @/api/sys/job
|
||||
*/
|
||||
import config from '@/config'
|
||||
import http from '@/utils/request'
|
||||
export default {
|
||||
/**
|
||||
* 批量删除计划作业
|
||||
*/
|
||||
bulkDelete: {
|
||||
url: `${config.API_URL}/api/sys/job/bulk.delete`,
|
||||
name: `批量删除计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建计划作业
|
||||
*/
|
||||
create: {
|
||||
url: `${config.API_URL}/api/sys/job/create`,
|
||||
name: `创建计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除计划作业
|
||||
*/
|
||||
delete: {
|
||||
url: `${config.API_URL}/api/sys/job/delete`,
|
||||
name: `删除计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 计划作业是否存在
|
||||
*/
|
||||
exist: {
|
||||
url: `${config.API_URL}/api/sys/job/exist`,
|
||||
name: `计划作业是否存在`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单个计划作业
|
||||
*/
|
||||
get: {
|
||||
url: `${config.API_URL}/api/sys/job/get`,
|
||||
name: `获取单个计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询计划作业
|
||||
*/
|
||||
pagedQuery: {
|
||||
url: `${config.API_URL}/api/sys/job/paged.query`,
|
||||
name: `分页查询计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询计划作业
|
||||
*/
|
||||
query: {
|
||||
url: `${config.API_URL}/api/sys/job/query`,
|
||||
name: `查询计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 启用/禁用作业
|
||||
*/
|
||||
setEnabled: {
|
||||
url: `${config.API_URL}/api/sys/job/set.enabled`,
|
||||
name: `启用/禁用作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新计划作业
|
||||
*/
|
||||
update: {
|
||||
url: `${config.API_URL}/api/sys/job/update`,
|
||||
name: `更新计划作业`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
}
|
10
src/frontend/admin/src/assets/icons/ScheduledJob.vue
Normal file
10
src/frontend/admin/src/assets/icons/ScheduledJob.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg t="1706693297583" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4279" width="128" height="128">
|
||||
<path
|
||||
d="M553 186.4v-84h204.8c22.5 0 41-18.4 41-41 0-22.5-18.4-41-41-41H266.2c-22.5 0-41 18.4-41 41 0 22.5 18.4 41 41 41H471v84C264 207 102.4 381.5 102.4 593.9c0 226.2 183.4 409.6 409.6 409.6s409.6-183.4 409.6-409.6c0-212.4-161.7-387-368.6-407.5z m-41 735.2c-180.7 0-327.7-147-327.7-327.7s147-327.7 327.7-327.7 327.7 147 327.7 327.7-147 327.7-327.7 327.7z"
|
||||
p-id="4280"></path>
|
||||
<path
|
||||
d="M532.5 556.5V368.6c0-22.5-18.4-41-41-41s-41 18.4-41 41v204.8c0 0.3 0.1 0.5 0.1 0.7 0 2.4 0.3 4.9 0.7 7.3 0.2 0.9 0.5 1.7 0.8 2.6 0.5 1.7 0.9 3.5 1.6 5.2 0.2 0.5 0.5 1 0.8 1.5 2 4.2 4.5 8.2 8 11.6l173.8 173.8c15.9 15.9 42 15.9 57.9 0 15.9-15.9 15.9-42 0-57.9L532.5 556.5z"
|
||||
p-id="4281"></path>
|
||||
</svg>
|
||||
</template>
|
@ -52,4 +52,5 @@ export { default as LoginLog } from './LoginLog.vue'
|
||||
export { default as ExLog } from './ExLog.vue'
|
||||
export { default as Key } from './Key.vue'
|
||||
export { default as OpenDoor } from './OpenDoor.vue'
|
||||
export { default as Alert } from './Alert.vue'
|
||||
export { default as Alert } from './Alert.vue'
|
||||
export { default as ScheduledJob } from './ScheduledJob.vue'
|
File diff suppressed because one or more lines are too long
@ -6,6 +6,7 @@ html {
|
||||
height: 100%;
|
||||
background-color: #f6f8f9;
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
a {
|
||||
|
229
src/frontend/admin/src/views/sys/job/index.vue
Normal file
229
src/frontend/admin/src/views/sys/job/index.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<na-search
|
||||
:controls="[
|
||||
{
|
||||
type: 'input',
|
||||
field: ['root', 'keywords'],
|
||||
placeholder: $t('作业编号 / 作业名称'),
|
||||
style: 'width:20rem',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
field: ['dy', 'status'],
|
||||
options: Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
|
||||
return { value: x[0], label: x[1][1] }
|
||||
}),
|
||||
placeholder: $t('作业状态'),
|
||||
style: 'width:15rem',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
field: ['dy', 'httpMethod'],
|
||||
options: Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
|
||||
return { value: x[0], label: x[1][1] }
|
||||
}),
|
||||
placeholder: $t('请求方式'),
|
||||
style: 'width:15rem',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
field: ['dy', 'enabled'],
|
||||
options: [
|
||||
{ label: '启用', value: true },
|
||||
{ label: '禁用', value: false },
|
||||
],
|
||||
placeholder: '状态',
|
||||
style: 'width:15rem',
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
@search="onSearch" />
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<na-button-add :vue="this" />
|
||||
<el-button :disabled="selection.length === 0" icon="el-icon-delete" plain type="danger" @click="batchDel"></el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<sc-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
:apiObj="$API.sys_job.pagedQuery"
|
||||
:default-sort="{ prop: 'createdTime', order: 'descending' }"
|
||||
:params="query"
|
||||
remote-filter
|
||||
remote-sort
|
||||
stripe
|
||||
@selection-change="
|
||||
(items) => {
|
||||
selection = items
|
||||
}
|
||||
">
|
||||
<el-table-column type="selection"></el-table-column>
|
||||
<el-table-column :label="$t('作业编号')" prop="id" width="150" />
|
||||
<el-table-column :label="$t('作业名称')" prop="jobName" />
|
||||
<na-col-indicator
|
||||
:label="$t('作业状态')"
|
||||
:options="[
|
||||
{ text: '空闲', type: 'success', value: 'idle' },
|
||||
{ text: '运行', type: 'warning', value: 'running' },
|
||||
]"
|
||||
prop="status"
|
||||
width="100"></na-col-indicator>
|
||||
<na-col-indicator
|
||||
:label="$t('请求方式')"
|
||||
:options="
|
||||
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
|
||||
return { value: x[0], text: x[1][1] }
|
||||
})
|
||||
"
|
||||
prop="httpMethod" />
|
||||
<el-table-column :label="$t('上次执行时间')" prop="lastExecTime" sortable="custom" />
|
||||
<el-table-column :label="$t('上次执行状态')" prop="lastStatusCode" sortable="custom" />
|
||||
<el-table-column :label="$t('下次执行时间')" prop="nextExecTime" sortable="custom" />
|
||||
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" />
|
||||
<el-table-column :label="$t('启用')" prop="enabled">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<na-col-operation
|
||||
:buttons="
|
||||
naColOperation.buttons.concat({
|
||||
icon: 'el-icon-delete',
|
||||
confirm: true,
|
||||
title: $t('删除作业'),
|
||||
click: rowDel,
|
||||
})
|
||||
"
|
||||
:vue="this" />
|
||||
</sc-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<save-dialog
|
||||
v-if="dialog.save"
|
||||
ref="saveDialog"
|
||||
@closed="dialog.save = false"
|
||||
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"></save-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import saveDialog from './save'
|
||||
import table from '@/config/table'
|
||||
import naColOperation from '@/config/naColOperation'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
saveDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
query: {
|
||||
dynamicFilter: {
|
||||
filters: [],
|
||||
},
|
||||
filter: {},
|
||||
},
|
||||
dialog: {
|
||||
save: false,
|
||||
},
|
||||
selection: [],
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
computed: {
|
||||
naColOperation() {
|
||||
return naColOperation
|
||||
},
|
||||
table() {
|
||||
return table
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
created() {},
|
||||
methods: {
|
||||
//表格内开关事件
|
||||
async changeSwitch(event, row) {
|
||||
try {
|
||||
await this.$API.sys_job.setEnabled.post(row)
|
||||
this.$message.success(`操作成功`)
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
this.$refs.table.refresh()
|
||||
},
|
||||
//删除
|
||||
async rowDel(row) {
|
||||
try {
|
||||
const res = await this.$API.sys_job.delete.post({ id: row.id })
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success(`删除 ${res.data} 项`)
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
},
|
||||
//批量删除
|
||||
async batchDel() {
|
||||
let loading
|
||||
try {
|
||||
await this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
|
||||
type: 'warning',
|
||||
})
|
||||
loading = this.$loading()
|
||||
const res = await this.$API.sys_job.bulkDelete.post({
|
||||
items: this.selection,
|
||||
})
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success(`删除 ${res.data} 项`)
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
loading?.close()
|
||||
},
|
||||
|
||||
//搜索
|
||||
onSearch(form) {
|
||||
if (Array.isArray(form.dy.createdTime)) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'createdTime',
|
||||
operator: 'dateRange',
|
||||
value: form.dy.createdTime,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.status === 'string' && form.dy.status.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'status',
|
||||
operator: 'eq',
|
||||
value: form.dy.status,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.enabled === 'boolean') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'enabled',
|
||||
operator: 'eq',
|
||||
value: form.dy.enabled,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'httpMethod',
|
||||
operator: 'eq',
|
||||
value: form.dy.httpMethod,
|
||||
})
|
||||
}
|
||||
|
||||
this.$refs.table.upData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
193
src/frontend/admin/src/views/sys/job/save.vue
Normal file
193
src/frontend/admin/src/views/sys/job/save.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<sc-dialog v-model="visible" :title="titleMap[mode]" :width="800" destroy-on-close fullscreen @closed="$emit('closed')">
|
||||
<el-form
|
||||
ref="dialogForm"
|
||||
v-loading="loading"
|
||||
:disabled="mode === 'view'"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="150px">
|
||||
<el-tabs tab-position="top">
|
||||
<el-tab-pane :label="$t('基本信息')">
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('作业编号')" prop="id">
|
||||
<el-input v-model="form.id" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('执行计划')" prop="executionCron">
|
||||
<el-input v-model="form.executionCron" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求方法')" prop="httpMethod">
|
||||
<el-select v-model="form.httpMethod" clearable filterable>
|
||||
<el-option v-for="(item, i) in $GLOBAL.enums.httpMethods" :key="i" :label="item[1]" :value="i" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('作业名称')" prop="jobName">
|
||||
<el-input v-model="form.jobName" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('最近一次执行时间')" prop="lastExecTime">
|
||||
<el-input v-model="form.lastExecTime" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('下一次执行时间')" prop="nextExecTime">
|
||||
<el-input v-model="form.nextExecTime" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('下一次执行时间编号')" prop="nextTimeId">
|
||||
<el-input v-model="form.nextTimeId" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求头')" prop="requestHeader">
|
||||
<el-input v-model="form.requestHeader" clearable rows="5" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求体')" prop="requestBody">
|
||||
<el-input v-model="form.requestBody" clearable rows="5" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求的网络地址')" prop="requestUrl">
|
||||
<el-input v-model="form.requestUrl" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('作业状态')" prop="status">
|
||||
<el-input v-model="form.status" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('执行用户编号')" prop="userId">
|
||||
<el-input v-model="form.userId" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-else :label="$t('执行用户')" prop="userId">
|
||||
<na-user v-model="form.userId"></na-user>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('创建时间')" prop="createdTime">
|
||||
<el-input v-model="form.createdTime" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('修改时间')" prop="modifiedTime">
|
||||
<el-input v-model="form.modifiedTime" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('数据版本')" prop="version">
|
||||
<el-input v-model="form.version" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('创建者编号')" prop="createdUserId">
|
||||
<el-input v-model="form.createdUserId" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('创建者用户名')" prop="createdUserName">
|
||||
<el-input v-model="form.createdUserName" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('修改者编号')" prop="modifiedUserId">
|
||||
<el-input v-model="form.modifiedUserId" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('修改者用户名')" prop="modifiedUserName">
|
||||
<el-input v-model="form.modifiedUserName" clearable />
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
|
||||
<json-viewer
|
||||
:expand-depth="5"
|
||||
:expanded="true"
|
||||
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
|
||||
:value="form"
|
||||
copyable
|
||||
sort></json-viewer>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
<el-button v-if="mode !== 'view'" :loading="loading" type="primary" @click="submit">保 存</el-button>
|
||||
</template>
|
||||
</sc-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import scEditor from '@/components/scEditor/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
scEditor,
|
||||
},
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: 'add',
|
||||
titleMap: {
|
||||
view: this.$t('查看作业'),
|
||||
add: this.$t('新增作业'),
|
||||
edit: this.$t('编辑作业'),
|
||||
},
|
||||
visible: false,
|
||||
loading: false,
|
||||
//表单数据
|
||||
form: {},
|
||||
//验证规则
|
||||
rules: {
|
||||
executionCron: [
|
||||
{
|
||||
required: true,
|
||||
pattern: this.$GLOBAL.chars.RGX_CRON,
|
||||
message: this.$t('执行计划不正确'),
|
||||
},
|
||||
],
|
||||
httpMethod: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('请求方不能为空'),
|
||||
},
|
||||
],
|
||||
jobName: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('作业名称不能为空'),
|
||||
},
|
||||
],
|
||||
requestUrl: [
|
||||
{
|
||||
required: true,
|
||||
pattern: this.$GLOBAL.chars.RGX_URL,
|
||||
message: this.$t('请求的网络地址不正确'),
|
||||
},
|
||||
],
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: this.$t('执行用户不能为空'),
|
||||
validator: (rule, value, callback) => {
|
||||
if (value.id) callback()
|
||||
else callback(new Error())
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
//显示
|
||||
async open(mode = 'add', data) {
|
||||
this.visible = true
|
||||
this.loading = true
|
||||
this.mode = mode
|
||||
if (data) {
|
||||
const res = await this.$API.sys_job.get.post({ id: data.id })
|
||||
Object.assign(this.form, res.data)
|
||||
}
|
||||
this.loading = false
|
||||
return this
|
||||
},
|
||||
|
||||
//表单提交方法
|
||||
async submit() {
|
||||
const valid = await this.$refs.dialogForm.validate().catch(() => {})
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const method = this.mode === 'add' ? this.$API.sys_job.create : this.$API.sys_job.update
|
||||
this.loading = true
|
||||
const res = await method.post(Object.assign({}, this.form, { userId: this.form.userId.id }))
|
||||
this.loading = false
|
||||
this.$emit('success', res.data, this.mode)
|
||||
this.visible = false
|
||||
this.$message.success('操作成功')
|
||||
} catch {
|
||||
//
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
Reference in New Issue
Block a user