feat: 计划作业执行记录 (#89)

顶部通栏黑夜模式开关
计划作业快捷预览面板
This commit is contained in:
2024-02-18 14:43:22 +08:00
committed by GitHub
parent 6f32acaacf
commit 6f89015198
71 changed files with 844 additions and 229 deletions

View File

@ -3,7 +3,8 @@
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "vite build --mode production",
"build:test": "vite build --mode test",
"preview": "vite preview",
"prettier": "prettier --write ."
},
@ -12,34 +13,34 @@
"@tinymce/tinymce-vue": "^5.1.1",
"axios": "^1.6.7",
"clipboard": "^2.0.11",
"core-js": "^3.35.1",
"core-js": "^3.36.0",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"echarts": "^5.4.3",
"element-plus": "^2.5.3",
"element-plus": "^2.5.5",
"json-bigint": "^1.0.0",
"json5-to-table": "^0.1.8",
"nprogress": "^0.2.0",
"pinyin-match": "^1.2.5",
"qrcodejs2": "^0.0.2",
"sortablejs": "^1.15.2",
"tinymce": "^6.8.2",
"vue": "^3.4.15",
"vue-i18n": "^9.9.0",
"tinymce": "^6.8.3",
"vue": "^3.4.19",
"vue-i18n": "^9.9.1",
"vue-router": "^4.2.5",
"vue3-json-viewer": "^2.2.2",
"vuedraggable": "^4.0.3",
"vuex": "^4.1.0",
"xgplayer": "^3.0.11",
"xgplayer-hls": "^3.0.11"
"xgplayer": "^3.0.12",
"xgplayer-hls": "^3.0.12"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.3",
"prettier": "^3.2.4",
"@vitejs/plugin-vue": "^5.0.4",
"prettier": "^3.2.5",
"prettier-plugin-organize-attributes": "^1.0.0",
"sass": "^1.70.0",
"terser": "^5.27.0",
"vite": "^5.0.12"
"sass": "^1.71.0",
"terser": "^5.27.1",
"vite": "^5.1.3"
},
"browserslist": [
"> 1%",

View File

@ -82,6 +82,28 @@ export default {
},
},
/**
* 获取单个作业记录
*/
recordGet: {
url: `${config.API_URL}/api/sys/job/record.get`,
name: `获取单个作业记录`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询作业记录
*/
recordPagedQuery: {
url: `${config.API_URL}/api/sys/job/record.paged.query`,
name: `分页查询作业记录`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 启用/禁用作业
*/

View File

@ -0,0 +1,10 @@
<template>
<svg class="icon" height="128" p-id="4304" t="1708217899083" version="1.1" viewBox="0 0 1024 1024" width="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M497.3 409.6c-1.6 3.7-4.8 7-9.6 10s-9.3 4.8-13.6 5.5c-4.2 0.7-9 1-14.3 0.8h-25.7v57.4h59.3V740h64.4V400.2L502 400l-4.7 9.6z"
p-id="4305"></path>
<path
d="M880 160H738v-56c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v56H350v-56c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v56H144c-17.7 0-32 14.3-32 32v688c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-32 680c0 4.4-3.6 8-8 8H184c-4.4 0-8-3.6-8-8V232c0-4.4 3.6-8 8-8h102v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48h324v48c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-48h110v616z"
p-id="4306"></path>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg class="icon" height="128" p-id="5158" t="1708217788265" version="1.1" viewBox="0 0 1024 1024" width="128" xmlns="http://www.w3.org/2000/svg">
<path
d="M228.254587 913.306922 66.10251 913.306922l0-240.783948 162.152077 0L228.254587 913.306922zM471.464795 913.306922 309.318858 913.306922 309.318858 351.483166l162.145937 0L471.464795 913.306922zM714.681142 913.306922 552.540322 913.306922 552.540322 592.267115 714.681142 592.267115 714.681142 913.306922zM957.896466 913.306922 795.744389 913.306922 795.744389 110.693078l162.152077 0L957.896466 913.306922z"
p-id="5159"></path>
</svg>
</template>

View File

@ -53,4 +53,6 @@ export { default as Unlink } from './Unlink.vue'
export { default as Upload } from './Upload.vue'
export { default as Vue } from './Vue.vue'
export { default as Warning } from './Warning.vue'
export { default as Wechat } from './Wechat.vue'
export { default as Wechat } from './Wechat.vue'
export { default as Report } from './Report.vue'
export { default as Daily } from './Daily.vue'

View File

@ -3,7 +3,7 @@
<template #default="scope">
<template v-for="(item, i) in Array.isArray(scope.row[prop]) ? scope.row[prop] : [scope.row[prop]]" :key="i">
<el-tag v-if="item" @click="$emit('click', item)">
{{ item ? item[field] : console.error(scope.row) }}
{{ item ? item[field] : '' }}
</el-tag>
</template>
</template>

View File

@ -0,0 +1,50 @@
<template>
<el-table-column v-bind="$attrs">
<template #default="scope">
<div @click="click(tool.getNestedProperty(scope.row, $attrs.prop))" class="avatar" style="cursor: pointer">
<el-avatar :src="getAvatar(scope)" size="small"></el-avatar>
<el-text tag="ins">{{ tool.getNestedProperty(scope.row, $attrs.nestProp) }}</el-text>
</div>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" ref="saveDialog"></save-dialog>
</template>
</el-table-column>
</template>
<style scoped>
.avatar {
display: flex;
gap: 0.5rem;
}
</style>
<script>
import saveDialog from '@/views/sys/user/save.vue'
import tool from '@/utils/tool'
export default {
emits: ['click'],
props: {},
data() {
return {
dialog: { save: false },
}
},
mounted() {},
created() {},
components: { saveDialog },
computed: {
tool() {
return tool
},
},
methods: {
async click(id) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', { id: id })
},
//获取头像
getAvatar(scope) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR
},
},
}
</script>

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,7 @@ Object.assign(DEFAULT_CONFIG, MY_CONFIG)
// 如果生产模式就合并动态的APP_CONFIG
// public/config.js
if (import.meta.env.PROD) {
if (import.meta.env.MODE === 'production' || import.meta.env.MODE === 'test') {
Object.assign(DEFAULT_CONFIG, APP_CONFIG)
}

View File

@ -1,59 +1,60 @@
<template>
<el-container v-loading="loading">
<el-main>
<el-empty v-if="tasks.length === 0" :image-size="120">
<el-empty v-if="jobs.length === 0" :image-size="120">
<template #description>
<h2>没有正在执行的任务</h2>
</template>
<p style="font-size: 14px; color: #999; line-height: 1.5; margin: 0 40px">
<p style="color: #999; line-height: 1.5; margin: 0 3rem">
在处理耗时过久的任务时为了不阻碍正在处理的工作可在任务中心进行异步执行
</p>
</el-empty>
<el-card v-for="task in tasks" :key="task.id" class="user-bar-tasks-item" shadow="hover">
<div class="user-bar-tasks-item-body">
<div class="taskIcon">
<el-icon v-if="task.type === 'export'" :size="20">
<el-icon-paperclip />
</el-icon>
<el-icon v-if="task.type === 'report'" :size="20">
<el-icon-dataAnalysis />
</el-icon>
<el-card v-for="job in jobs" :key="job.id" class="user-bar-jobs-item" shadow="hover">
<div class="user-bar-jobs-item-body">
<div class="jobIcon">
{{ job.lastStatusCode }}
</div>
<div class="taskMain">
<div class="jobMain">
<div class="title">
<h2>{{ task.taskName }}</h2>
<p><span v-time.tip="task.createDate"></span> 创建</p>
<h2>{{ job.jobName }}</h2>
<p>上次执行<span v-time.tip="job.lastExecTime"></span></p>
<p>
下次执行<span>{{ job.nextExecTime }}</span>
</p>
</div>
<div class="bottom">
<div class="state">
<el-tag v-if="task.state === '0'" type="info">执行中</el-tag>
<el-tag v-if="task.state === '1'">完成</el-tag>
<div class="status">
<el-tag v-if="job.status === 'running'" type="info">执行中</el-tag>
<el-tag v-if="job.status === 'idle'">空闲</el-tag>
</div>
<div class="handler">
<el-button
v-if="task.state === '1'"
@click="download(task)"
circle
icon="el-icon-download"
type="primary"></el-button>
<el-button v-if="job.status === 'idle'" @click="view(job)" circle icon="el-icon-view" type="primary"></el-button>
</div>
</div>
</div>
</div>
</el-card>
</el-main>
<el-footer style="padding: 10px; text-align: right">
<el-footer style="text-align: right">
<el-button @click="refresh" circle icon="el-icon-refresh"></el-button>
</el-footer>
</el-container>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from '@/views/sys/job/save.vue'
export default {
components: {
saveDialog,
},
data() {
return {
dialog: {
save: false,
},
loading: false,
tasks: [],
jobs: [],
}
},
mounted() {
@ -62,69 +63,64 @@ export default {
methods: {
async getData() {
this.loading = true
var res = await this.$API.system.tasks.list.get()
this.tasks = res.data
const res = await this.$API.sys_job.query.post({ prop: 'lastExecTime', order: 'descending' })
this.jobs = res.data
this.loading = false
},
refresh() {
this.getData()
},
download(row) {
let a = document.createElement('a')
a.style = 'display: none'
a.target = '_blank'
a.href = row.result
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
async view(job) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', { id: job.id })
},
},
}
</script>
<style scoped>
.user-bar-tasks-item {
margin-bottom: 10px;
.user-bar-jobs-item {
margin-bottom: 0.5rem;
}
.user-bar-tasks-item:hover {
.user-bar-jobs-item:hover {
border-color: var(--el-color-primary);
}
.user-bar-tasks-item-body {
.user-bar-jobs-item-body {
display: flex;
}
.user-bar-tasks-item-body .taskIcon {
width: 45px;
height: 45px;
.user-bar-jobs-item-body .jobIcon {
width: 3rem;
height: 3rem;
background: var(--el-color-primary-light-9);
margin-right: 20px;
margin-right: 2rem;
display: flex;
justify-content: center;
align-items: center;
color: var(--el-color-primary);
border-radius: 20px;
border-radius: 1.5rem;
}
.user-bar-tasks-item-body .taskMain {
.user-bar-jobs-item-body .jobMain {
flex: 1;
}
.user-bar-tasks-item-body .title h2 {
.user-bar-jobs-item-body .title h2 {
font-size: 1rem;
}
.user-bar-tasks-item-body .title p {
font-size: 12px;
.user-bar-jobs-item-body .title p {
font-size: 1rem;
color: #999;
margin-top: 5px;
margin-top: 0.5rem;
}
.user-bar-tasks-item-body .bottom {
.user-bar-jobs-item-body .bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20px;
}
</style>

View File

@ -1,5 +1,10 @@
<template>
<div class="user-bar">
<div @click="configDark" class="tasks panel-item">
<el-icon>
<component :is="config.dark ? 'el-icon-sunny' : 'el-icon-moon'" />
</el-icon>
</div>
<div @click="search" class="panel-item hidden-sm-and-down">
<el-icon>
<el-icon-search />
@ -12,7 +17,7 @@
</div>
<div @click="tasks" class="tasks panel-item">
<el-icon>
<el-icon-sort />
<sc-icon-ScheduledJob />
</el-icon>
</div>
<div @click="showMsg" class="msg panel-item">
@ -69,8 +74,22 @@ export default {
tasks,
message,
},
watch: {
'config.dark'(val) {
if (val) {
document.documentElement.classList.add('dark')
this.$TOOL.data.set('APP_DARK', val)
} else {
document.documentElement.classList.remove('dark')
this.$TOOL.data.remove('APP_DARK')
}
},
},
data() {
return {
config: {
dark: this.$TOOL.data.get('APP_DARK') || false,
},
user: {},
userName: '',
userNameF: '',
@ -86,6 +105,9 @@ export default {
this.unreadCnt = res.data
},
methods: {
configDark() {
this.config.dark = !this.config.dark
},
gotoMsgCenter() {
this.$router.push({ path: '/profile/message' })
this.msg = false

View File

@ -17,7 +17,7 @@
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('作业状态'),
style: 'width:15rem',
style: 'width:10rem',
},
{
type: 'select',
@ -26,7 +26,7 @@
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('请求方式'),
style: 'width:15rem',
style: 'width:10rem',
},
{
type: 'select',
@ -36,7 +36,7 @@
{ label: '禁用', value: false },
],
placeholder: '状态',
style: 'width:15rem',
style: 'width:10rem',
},
]"
:vue="this"
@ -51,7 +51,7 @@
<sc-table
v-loading="loading"
:apiObj="$API.sys_job.pagedQuery"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:default-sort="{ prop: 'lastExecTime', order: 'descending' }"
:params="query"
@selection-change="
(items) => {
@ -61,6 +61,7 @@
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection"></el-table-column>
<el-table-column :label="$t('作业编号')" prop="id" width="150" />
@ -80,16 +81,17 @@
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">
prop="httpMethod"
width="100" />
<el-table-column :label="$t('上次执行时间')" prop="lastExecTime" sortable="custom" width="170" />
<el-table-column :label="$t('次执行状态')" prop="lastStatusCode" sortable="custom" width="150" />
<el-table-column :label="$t('下次执行时间')" prop="nextExecTime" sortable="custom" width="170" />
<el-table-column :label="$t('启用')" prop="enabled" sortable="custom" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -128,6 +130,7 @@ export default {
filters: [],
},
filter: {},
prop: 'lastExecTime',
},
dialog: {
save: false,

View File

@ -0,0 +1,200 @@
<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', 'httpMethod'],
options: Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('请求方式'),
style: 'width:10rem',
},
{
multiple: true,
type: 'select',
field: ['dy', 'httpStatusCode'],
options: [
{ label: '20x', value: '200,299' },
{ label: '30x', value: '300,399' },
{ label: '40x', value: '400,499' },
{ label: '50x', value: '500,599' },
{ label: '90x', value: '900,999' },
],
placeholder: '状态码',
style: 'width:20rem',
},
]"
:vue="this"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel"></div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_job.recordPagedQuery"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
@selection-change="
(items) => {
selection = items
}
"
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column :label="$t('唯一编码')" prop="id" sortable="custom" width="150" />
<el-table-column :label="$t('执行耗时(毫秒)')" align="right" prop="duration" sortable="custom" width="150" />
<el-table-column :label="$t('请求方法')" prop="httpMethod" sortable="custom" width="100" />
<el-table-column :label="$t('HTTP 状态码')" align="right" prop="httpStatusCode" sortable="custom" width="150" />
<el-table-column :label="$t('请求的网络地址')" prop="requestUrl" sortable="custom" />
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<na-col-operation :buttons="[naColOperation.buttons[0]]" :vue="this" width="100" />
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
export default {
props: ['keywords'],
components: {
saveDialog,
},
data() {
return {
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
},
dialog: {
save: false,
},
selection: [],
}
},
watch: {},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
}
},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
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.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'httpMethod',
operator: 'eq',
value: form.dy.httpMethod,
})
}
if (Array.isArray(form.dy.httpStatusCode) && form.dy.httpStatusCode.length !== 0) {
const filters = []
for (const code of form.dy.httpStatusCode) {
filters.push({
field: 'httpStatusCode',
operator: 'range',
value: code,
})
}
this.query.dynamicFilter.filters.push({
logic: 'or',
filters: filters,
})
}
this.$refs.table.upData()
},
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,87 @@
<template>
<sc-dialog v-model="visible" :title="titleMap[mode]" :width="800" @closed="$emit('closed')" destroy-on-close full-screen>
<el-form
v-loading="loading"
:disabled="mode === 'view'"
:model="form"
:rules="rules"
label-position="right"
label-width="150px"
ref="dialogForm">
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form-item :label="$t('唯一编码')" prop="id"><el-input v-model="form.id" clearable /></el-form-item
><el-form-item :label="$t('执行耗时(毫秒)')" prop="duration"><el-input v-model="form.duration" clearable /></el-form-item
><el-form-item :label="$t('请求方法')" prop="httpMethod"><el-input v-model="form.httpMethod" clearable /></el-form-item
><el-form-item :label="$t('HTTP 状态码')" prop="httpStatusCode"><el-input v-model="form.httpStatusCode" clearable /></el-form-item
><el-form-item :label="$t('作业编号')" prop="jobId"><el-input v-model="form.jobId" clearable /></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="requestHeader">
<el-input v-model="form.requestHeader" 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 :label="$t('响应体')" prop="responseBody"
><el-input v-model="form.responseBody" clearable rows="5" type="textarea" /></el-form-item
><el-form-item :label="$t('响应头')" prop="responseHeader">
<el-input v-model="form.responseHeader" clearable rows="5" type="textarea" /></el-form-item
><el-form-item :label="$t('执行时间编号')" prop="timeId"><el-input v-model="form.timeId" clearable /></el-form-item
><el-form-item :label="$t('创建时间')" prop="createdTime"><el-input v-model="form.createdTime" 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>
</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: {},
}
},
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.recordGet.post({ id: data.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
},
},
}
</script>

View File

@ -1,15 +1,15 @@
<template>
<sc-dialog v-model="visible" :title="titleMap[mode]" :width="800" @closed="$emit('closed')" destroy-on-close full-screen>
<el-form
v-loading="loading"
:disabled="mode === 'view'"
:model="form"
:rules="rules"
label-position="right"
label-width="150px"
ref="dialogForm">
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-tabs v-model="tabIndex" tab-position="top">
<el-tab-pane :label="$t('基本信息')" :name="0">
<el-form
v-loading="loading"
:disabled="mode === 'view'"
:model="form"
:rules="rules"
label-position="right"
label-width="150px"
ref="dialogForm">
<el-form-item v-if="mode === 'view'" :label="$t('作业编号')" prop="id">
<el-input v-model="form.id" clearable />
</el-form-item>
@ -48,8 +48,8 @@
<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-select v-model="form.userId"></na-user-select>
<el-form-item v-else :label="$t('执行用户')" prop="user">
<na-user-select v-model="form.user"></na-user-select>
</el-form-item>
<el-form-item v-if="mode === 'view'" :label="$t('创建时间')" prop="createdTime">
<el-input v-model="form.createdTime" clearable />
@ -72,18 +72,22 @@
<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>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('执行记录')" :name="1">
<record v-if="tabIndex === 1" :keywords="form.id" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')" :name="2">
<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>
<template #footer>
<el-button @click="visible = false"> </el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary"> </el-button>
@ -93,14 +97,17 @@
<script>
import scEditor from '@/components/scEditor/index.vue'
import Record from '@/views/sys/job/record/index.vue'
export default {
components: {
Record,
scEditor,
},
emits: ['success', 'closed'],
data() {
return {
tabIndex: 0,
mode: 'add',
titleMap: {
view: this.$t('查看作业'),
@ -110,7 +117,12 @@ export default {
visible: false,
loading: false,
//表单数据
form: {},
form: {
executionCron: '* * * * *',
httpMethod: 'Post',
requestHeader: `{ "Content-Type": "application/json" }`,
requestBody: '{}',
},
//验证规则
rules: {
executionCron: [
@ -132,6 +144,19 @@ export default {
message: this.$t('作业名称不能为空'),
},
],
requestHeader: [
{
validator: (rule, value, callback) => {
if (!value) return callback()
try {
JSON.parse(value)
} catch {
return callback(this.$t('请求头不正确'))
}
return callback()
},
},
],
requestUrl: [
{
required: true,
@ -139,7 +164,7 @@ export default {
message: this.$t('请求的网络地址不正确'),
},
],
userId: [
user: [
{
required: true,
trigger: 'blur',
@ -178,7 +203,9 @@ export default {
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 }))
const res = await method.post(
Object.assign({}, this.form, { userId: this.form.user.id, requestHeaders: JSON.parse(this.form.requestHeader) }),
)
this.loading = false
this.$emit('success', res.data, this.mode)
this.visible = false

View File

@ -44,6 +44,7 @@
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom"></el-table-column>
<el-table-column :label="$t('日志时间')" prop="createdTime" sortable="custom"></el-table-column>

View File

@ -61,6 +61,7 @@
ref="table"
remoteFilter
remoteSort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150"></el-table-column>
<el-table-column :label="$t('日志时间')" prop="createdTime" sortable="custom" width="170"></el-table-column>

View File

@ -42,6 +42,7 @@
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection"></el-table-column>
<el-table-column :label="$t('消息编号')" prop="id" width="150" />

View File

@ -58,6 +58,7 @@
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection"></el-table-column>
<el-table-column :label="$t('用户编号')" prop="id" sortable="custom" width="150"></el-table-column>