@jiangood/open-admin 3.1.8 → 3.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/framework/GlobalData.ts +4 -0
- package/src/framework/biz/OrgTree.tsx +4 -0
- package/src/framework/biz/RoleTree.tsx +4 -0
- package/src/framework/components/DownloadModal/index.tsx +7 -0
- package/src/framework/components/LogViewer/index.tsx +249 -0
- package/src/framework/components/Page/index.less +2 -0
- package/src/framework/components/Page/index.tsx +3 -9
- package/src/framework/components/index.ts +1 -0
- package/src/framework/config/themeConfig.ts +30 -6
- package/src/framework/fields/FieldTable/styles.less +3 -3
- package/src/framework/utils/HttpClient.ts +63 -25
- package/src/layouts/admin/AdminTabs.jsx +139 -31
- package/src/layouts/admin/AdminTabs.less +93 -14
- package/src/layouts/admin/index.jsx +4 -1
- package/src/layouts/admin/index.less +2 -2
- package/src/layouts/index.tsx +47 -10
- package/src/pages/system/codegen/index.jsx +83 -44
- package/src/pages/system/job/index.jsx +165 -48
- package/src/pages/userCenter/index.jsx +1 -1
- package/src/style/global.less +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Button, Card, Collapse, Drawer, Form, Input, Modal, Select,
|
|
1
|
+
import {Button, Card, Checkbox, Collapse, Drawer, Form, Input, Modal, Select, Tag, Typography} from 'antd'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import {HttpClient, getMessageApi, getToken, Page, Perm} from '../../../framework'
|
|
4
4
|
|
|
@@ -13,9 +13,9 @@ export default class CodegenPage extends React.Component {
|
|
|
13
13
|
module: '',
|
|
14
14
|
label: '',
|
|
15
15
|
parentMenu: 'sys',
|
|
16
|
-
overwrite: false,
|
|
17
16
|
|
|
18
17
|
files: [],
|
|
18
|
+
selectedPaths: [],
|
|
19
19
|
previewOpen: false,
|
|
20
20
|
confirmOpen: false,
|
|
21
21
|
|
|
@@ -47,7 +47,6 @@ export default class CodegenPage extends React.Component {
|
|
|
47
47
|
module: this.state.module,
|
|
48
48
|
label: this.state.label,
|
|
49
49
|
parentMenu: this.state.parentMenu,
|
|
50
|
-
overwrite: this.state.overwrite,
|
|
51
50
|
})
|
|
52
51
|
|
|
53
52
|
handlePreview = () => {
|
|
@@ -57,7 +56,13 @@ export default class CodegenPage extends React.Component {
|
|
|
57
56
|
}
|
|
58
57
|
this.setState({loading: true})
|
|
59
58
|
HttpClient.post('admin/codegen/preview', this.buildReq(), null, {toastError: false}).then(rs => {
|
|
60
|
-
|
|
59
|
+
const files = rs.data || []
|
|
60
|
+
this.setState({
|
|
61
|
+
files,
|
|
62
|
+
// 默认只勾选新增文件,已存在文件需手动勾选后才会覆盖
|
|
63
|
+
selectedPaths: files.filter(file => !file.exists).map(file => file.path),
|
|
64
|
+
previewOpen: true,
|
|
65
|
+
})
|
|
61
66
|
}).catch(e => {
|
|
62
67
|
console.error('[Codegen] 预览失败:', e)
|
|
63
68
|
getMessageApi().error(e?.message || '预览失败')
|
|
@@ -74,7 +79,8 @@ export default class CodegenPage extends React.Component {
|
|
|
74
79
|
|
|
75
80
|
doGenerate = () => {
|
|
76
81
|
this.setState({confirmOpen: false, loading: true})
|
|
77
|
-
|
|
82
|
+
const params = {...this.buildReq(), paths: this.state.selectedPaths}
|
|
83
|
+
HttpClient.post('admin/codegen/generate', params, null, {toastError: false}).then(rs => {
|
|
78
84
|
this.setState({result: rs.data, resultOpen: true, previewOpen: false})
|
|
79
85
|
}).catch(e => {
|
|
80
86
|
console.error('[Codegen] 生成失败:', e)
|
|
@@ -82,6 +88,20 @@ export default class CodegenPage extends React.Component {
|
|
|
82
88
|
}).finally(() => this.setState({loading: false}))
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
togglePath = (path, checked) => {
|
|
92
|
+
this.setState(state => ({
|
|
93
|
+
selectedPaths: checked
|
|
94
|
+
? [...state.selectedPaths, path]
|
|
95
|
+
: state.selectedPaths.filter(item => item !== path),
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
toggleAll = checked => {
|
|
100
|
+
this.setState(state => ({
|
|
101
|
+
selectedPaths: checked ? state.files.map(file => file.path) : [],
|
|
102
|
+
}))
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
copy = (text) => {
|
|
86
106
|
if (navigator.clipboard) {
|
|
87
107
|
navigator.clipboard.writeText(text)
|
|
@@ -96,14 +116,10 @@ export default class CodegenPage extends React.Component {
|
|
|
96
116
|
|
|
97
117
|
render() {
|
|
98
118
|
const selected = this.selectedOption()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</Perm>
|
|
104
|
-
<Button onClick={this.loadEntities}>刷新实体</Button>
|
|
105
|
-
</Space>
|
|
106
|
-
}>
|
|
119
|
+
const {files, selectedPaths} = this.state
|
|
120
|
+
const allSelected = files.length > 0 && selectedPaths.length === files.length
|
|
121
|
+
const overwriteCount = files.filter(file => file.exists && selectedPaths.includes(file.path)).length
|
|
122
|
+
return <Page>
|
|
107
123
|
<Card size='small'>
|
|
108
124
|
<Form layout='vertical' style={{maxWidth: 720}}>
|
|
109
125
|
<Form.Item label='实体' required>
|
|
@@ -134,11 +150,10 @@ export default class CodegenPage extends React.Component {
|
|
|
134
150
|
<Input value={this.state.parentMenu}
|
|
135
151
|
onChange={e => this.setState({parentMenu: e.target.value})}/>
|
|
136
152
|
</Form.Item>
|
|
137
|
-
<Form.Item label='覆盖已存在文件'>
|
|
138
|
-
<Switch checked={this.state.overwrite}
|
|
139
|
-
onChange={checked => this.setState({overwrite: checked})}/>
|
|
140
|
-
</Form.Item>
|
|
141
153
|
</Form>
|
|
154
|
+
<Perm code='sys-codegen:generate'>
|
|
155
|
+
<Button type='primary' loading={this.state.loading} onClick={this.handlePreview}>预览</Button>
|
|
156
|
+
</Perm>
|
|
142
157
|
</Card>
|
|
143
158
|
|
|
144
159
|
<Drawer title='生成预览' width={960}
|
|
@@ -146,34 +161,58 @@ export default class CodegenPage extends React.Component {
|
|
|
146
161
|
onClose={() => this.setState({previewOpen: false})}
|
|
147
162
|
extra={
|
|
148
163
|
<Perm code='sys-codegen:generate'>
|
|
149
|
-
<Button type='primary'
|
|
164
|
+
<Button type='primary'
|
|
165
|
+
disabled={selectedPaths.length === 0}
|
|
166
|
+
loading={this.state.loading}
|
|
167
|
+
onClick={this.handleGenerate}>
|
|
168
|
+
生成并写入({selectedPaths.length})
|
|
169
|
+
</Button>
|
|
150
170
|
</Perm>
|
|
151
171
|
}>
|
|
172
|
+
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12}}>
|
|
173
|
+
<Checkbox checked={allSelected}
|
|
174
|
+
indeterminate={selectedPaths.length > 0 && !allSelected}
|
|
175
|
+
onChange={e => this.toggleAll(e.target.checked)}>
|
|
176
|
+
全选
|
|
177
|
+
</Checkbox>
|
|
178
|
+
<Typography.Text type='secondary'>
|
|
179
|
+
已选 {selectedPaths.length} / 共 {files.length} 个文件,默认勾选新增文件
|
|
180
|
+
</Typography.Text>
|
|
181
|
+
</div>
|
|
152
182
|
<Collapse
|
|
153
|
-
items={
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
items={files.map((file, index) => {
|
|
184
|
+
const checked = selectedPaths.includes(file.path)
|
|
185
|
+
return {
|
|
186
|
+
key: String(index),
|
|
187
|
+
label: (
|
|
188
|
+
<span onClick={e => e.stopPropagation()}
|
|
189
|
+
style={{display: 'flex', alignItems: 'center', gap: 8, minWidth: 0}}>
|
|
190
|
+
<Checkbox checked={checked}
|
|
191
|
+
onChange={e => this.togglePath(file.path, e.target.checked)}/>
|
|
192
|
+
<span title={file.path}
|
|
193
|
+
style={{flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
|
|
194
|
+
{file.path}
|
|
195
|
+
</span>
|
|
196
|
+
{file.exists
|
|
197
|
+
? <Tag color='orange' style={{marginInlineEnd: 0}}>{checked ? '将覆盖' : '已存在'}</Tag>
|
|
198
|
+
: <Tag color='green' style={{marginInlineEnd: 0}}>新增</Tag>}
|
|
199
|
+
</span>
|
|
200
|
+
),
|
|
201
|
+
children: (
|
|
202
|
+
<>
|
|
203
|
+
<Button size='small' style={{marginBottom: 8}}
|
|
204
|
+
onClick={() => this.copy(file.content)}>复制</Button>
|
|
205
|
+
<pre style={{
|
|
206
|
+
maxHeight: 420,
|
|
207
|
+
overflow: 'auto',
|
|
208
|
+
background: String(getToken().colorFillTertiary ?? '#f6f8fa'),
|
|
209
|
+
padding: 12,
|
|
210
|
+
borderRadius: 4,
|
|
211
|
+
}}>{file.content}</pre>
|
|
212
|
+
</>
|
|
213
|
+
),
|
|
214
|
+
}
|
|
215
|
+
})}
|
|
177
216
|
/>
|
|
178
217
|
</Drawer>
|
|
179
218
|
|
|
@@ -184,7 +223,7 @@ export default class CodegenPage extends React.Component {
|
|
|
184
223
|
onCancel={() => this.setState({confirmOpen: false})}>
|
|
185
224
|
<p>将按以下配置生成代码到项目源码目录:</p>
|
|
186
225
|
<p>模块名:{this.state.module}</p>
|
|
187
|
-
<p
|
|
226
|
+
<p>写入 {selectedPaths.length} 个文件{overwriteCount > 0 ? `,其中覆盖 ${overwriteCount} 个已存在文件` : ''}。</p>
|
|
188
227
|
</Modal>
|
|
189
228
|
|
|
190
229
|
<Modal title='生成结果'
|
|
@@ -194,7 +233,7 @@ export default class CodegenPage extends React.Component {
|
|
|
194
233
|
<p>已写入 {this.state.result?.written?.length || 0} 个文件,跳过 {this.state.result?.skipped?.length || 0} 个。</p>
|
|
195
234
|
{this.state.result?.skipped?.length > 0 &&
|
|
196
235
|
<>
|
|
197
|
-
<Typography.Text type='warning'
|
|
236
|
+
<Typography.Text type='warning'>以下文件未勾选,已跳过:</Typography.Text>
|
|
198
237
|
<ul>
|
|
199
238
|
{this.state.result.skipped.map(path => <li key={path}>{path}</li>)}
|
|
200
239
|
</ul>
|
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
Form,
|
|
7
7
|
Input,
|
|
8
8
|
Modal,
|
|
9
|
+
Progress,
|
|
9
10
|
Select,
|
|
10
|
-
Spin,
|
|
11
11
|
Switch,
|
|
12
|
-
Tag
|
|
12
|
+
Tag,
|
|
13
|
+
Tooltip
|
|
13
14
|
} from 'antd'
|
|
14
15
|
import {PlusOutlined, SearchOutlined} from "@ant-design/icons";
|
|
15
|
-
import {FormModal, getMessageApi, HttpClient, Page, PermActions, ProTable, UrlUtils, ValueType} from "../../../framework";
|
|
16
|
+
import {FormModal, DictUtils, FieldDictSelect, getMessageApi, HttpClient, LogViewer, Page, PermActions, ProTable, UrlUtils, ValueType} from "../../../framework";
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
const cronOptions = [
|
|
@@ -54,11 +55,12 @@ export default class JobPage extends React.Component {
|
|
|
54
55
|
executeRecordOpen: false,
|
|
55
56
|
|
|
56
57
|
logOpen: false,
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
progressMap: {},
|
|
59
60
|
}
|
|
60
61
|
tableRef = React.createRef()
|
|
61
62
|
modalRef = React.createRef()
|
|
63
|
+
progressTimer = null
|
|
62
64
|
|
|
63
65
|
componentDidMount() {
|
|
64
66
|
HttpClient.get('admin/job/job-class-options', null, {toastError: false}).then(rs => {
|
|
@@ -66,6 +68,39 @@ export default class JobPage extends React.Component {
|
|
|
66
68
|
}).catch(e => {
|
|
67
69
|
console.error('[Job] 加载任务类选项失败:', e);
|
|
68
70
|
})
|
|
71
|
+
this.startProgressPolling()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
componentWillUnmount() {
|
|
75
|
+
this.stopProgressPolling()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
startProgressPolling = () => {
|
|
79
|
+
this.loadProgress()
|
|
80
|
+
this.progressTimer = setInterval(() => {
|
|
81
|
+
if (!document.hidden) {
|
|
82
|
+
this.loadProgress()
|
|
83
|
+
}
|
|
84
|
+
}, 3000)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
stopProgressPolling = () => {
|
|
88
|
+
if (this.progressTimer) {
|
|
89
|
+
clearInterval(this.progressTimer)
|
|
90
|
+
this.progressTimer = null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
loadProgress = () => {
|
|
95
|
+
HttpClient.get('admin/job/progress', null, {toastError: false}).then(rs => {
|
|
96
|
+
const progressMap = {}
|
|
97
|
+
;(rs.data || []).forEach(p => {
|
|
98
|
+
progressMap[p.jobId] = p
|
|
99
|
+
})
|
|
100
|
+
this.setState({progressMap})
|
|
101
|
+
}).catch(e => {
|
|
102
|
+
console.error('[Job] 加载任务进度失败:', e);
|
|
103
|
+
})
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
handleAdd = () => {
|
|
@@ -107,6 +142,7 @@ export default class JobPage extends React.Component {
|
|
|
107
142
|
handleTriggerJob = row => {
|
|
108
143
|
HttpClient.post('admin/job/trigger-job', {id: row.id}, null, {toastError: false}).then(() => {
|
|
109
144
|
this.tableRef.current.reload();
|
|
145
|
+
this.loadProgress()
|
|
110
146
|
}).catch(e => {
|
|
111
147
|
console.error('[Job] 触发任务失败:', e);
|
|
112
148
|
})
|
|
@@ -147,6 +183,38 @@ export default class JobPage extends React.Component {
|
|
|
147
183
|
},
|
|
148
184
|
},
|
|
149
185
|
|
|
186
|
+
{
|
|
187
|
+
title: '日志聚合',
|
|
188
|
+
dataIndex: 'logAggregation',
|
|
189
|
+
render: v => DictUtils.dictTag('jobLogAggregation', v) || '每次执行',
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
{
|
|
193
|
+
title: '运行状态',
|
|
194
|
+
dataIndex: 'progress',
|
|
195
|
+
width: 220,
|
|
196
|
+
render: (_, record) => {
|
|
197
|
+
const p = this.state.progressMap[record.id]
|
|
198
|
+
if (!p) {
|
|
199
|
+
return <Tag>未运行</Tag>
|
|
200
|
+
}
|
|
201
|
+
return <div>
|
|
202
|
+
<Tag color={p.stateColor || 'default'}>{p.stateLabel}</Tag>
|
|
203
|
+
{p.percent != null && <Progress percent={p.percent} size='small'/>}
|
|
204
|
+
{p.message && <Tooltip title={p.message}>
|
|
205
|
+
<div style={{
|
|
206
|
+
fontSize: 12,
|
|
207
|
+
color: 'var(--oa-color-text-secondary, rgba(0, 0, 0, 0.65))',
|
|
208
|
+
overflow: 'hidden',
|
|
209
|
+
textOverflow: 'ellipsis',
|
|
210
|
+
whiteSpace: 'nowrap',
|
|
211
|
+
maxWidth: 200
|
|
212
|
+
}}>{p.message}</div>
|
|
213
|
+
</Tooltip>}
|
|
214
|
+
</div>
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
|
|
150
218
|
{
|
|
151
219
|
title: '操作',
|
|
152
220
|
dataIndex: 'option',
|
|
@@ -157,7 +225,7 @@ export default class JobPage extends React.Component {
|
|
|
157
225
|
more
|
|
158
226
|
size="small"
|
|
159
227
|
actions={[
|
|
160
|
-
{label: '
|
|
228
|
+
{label: '执行情况', perm: 'job:read', onClick: () => this.showExecuteRecord(record)},
|
|
161
229
|
{label: '执行一次', perm: 'job:trigger', onClick: () => this.handleTriggerJob(record)},
|
|
162
230
|
{label: '编辑', perm: 'job:update', onClick: () => this.handleEdit(record)},
|
|
163
231
|
{label: '删除', perm: 'job:delete', confirm: '是否确定删除?', onClick: () => this.handleDelete(record)},
|
|
@@ -179,19 +247,51 @@ export default class JobPage extends React.Component {
|
|
|
179
247
|
|
|
180
248
|
showExecuteRecord(record) {
|
|
181
249
|
this.setState({executeRecordOpen: true, selectedRecord: record})
|
|
250
|
+
this.loadProgress()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 执行记录弹窗顶部:当前任务的运行中进度(内存态)
|
|
255
|
+
*/
|
|
256
|
+
renderRunningProgress() {
|
|
257
|
+
const {selectedRecord, progressMap} = this.state
|
|
258
|
+
const p = selectedRecord ? progressMap[selectedRecord.id] : null
|
|
259
|
+
if (!p || p.state !== 'RUNNING') {
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
return <Alert
|
|
263
|
+
type='info'
|
|
264
|
+
showIcon
|
|
265
|
+
style={{marginBottom: 12}}
|
|
266
|
+
title={<div>
|
|
267
|
+
<div style={{marginBottom: 6}}>{p.message || '任务运行中'}</div>
|
|
268
|
+
{p.percent != null && <Progress percent={p.percent} size='small'/>}
|
|
269
|
+
</div>}
|
|
270
|
+
/>
|
|
182
271
|
}
|
|
183
272
|
|
|
184
273
|
showLog = (record) => {
|
|
185
|
-
this.setState({logOpen: true,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
274
|
+
this.setState({logOpen: true, selectedLogRecord: record})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 该执行记录对应的日志是否仍在写入:用进度快照里的 logId 精确匹配
|
|
279
|
+
*/
|
|
280
|
+
isLogRunning = () => {
|
|
281
|
+
const {selectedLogRecord, progressMap} = this.state
|
|
282
|
+
if (!selectedLogRecord) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
return Object.values(progressMap).some(p =>
|
|
286
|
+
p.logId === selectedLogRecord.id && p.state === 'RUNNING')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** 结束时间与开始时间为同一天时只显示时分秒,避免重复日期 */
|
|
290
|
+
formatRecordEndTime = (beginTime, endTime) => {
|
|
291
|
+
if (!beginTime || !endTime) {
|
|
292
|
+
return endTime || '-'
|
|
293
|
+
}
|
|
294
|
+
return endTime.slice(0, 10) === beginTime.slice(0, 10) ? endTime.slice(11) : endTime
|
|
195
295
|
}
|
|
196
296
|
|
|
197
297
|
|
|
@@ -241,6 +341,11 @@ export default class JobPage extends React.Component {
|
|
|
241
341
|
<Switch/>
|
|
242
342
|
</Form.Item>
|
|
243
343
|
|
|
344
|
+
<Form.Item label='日志聚合' name='logAggregation' initialValue='PER_RUN'
|
|
345
|
+
help='高频任务建议按天聚合:一天只保留一条执行记录,日志文件按天合并'>
|
|
346
|
+
<FieldDictSelect typeCode='jobLogAggregation'/>
|
|
347
|
+
</Form.Item>
|
|
348
|
+
|
|
244
349
|
{this.state.paramList?.map(p => (
|
|
245
350
|
<div key={p.name}>
|
|
246
351
|
<Divider>任务参数</Divider>
|
|
@@ -276,38 +381,55 @@ export default class JobPage extends React.Component {
|
|
|
276
381
|
width={1024}
|
|
277
382
|
destroyOnHidden
|
|
278
383
|
>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
dataIndex: 'beginTime',
|
|
282
|
-
},
|
|
384
|
+
{this.renderRunningProgress()}
|
|
385
|
+
<ProTable columns={[
|
|
283
386
|
{
|
|
284
|
-
title: '结束时间',
|
|
285
|
-
dataIndex: '
|
|
387
|
+
title: '开始 / 结束时间',
|
|
388
|
+
dataIndex: 'beginTime',
|
|
389
|
+
width: 170,
|
|
390
|
+
render: (v, record) => <div style={{lineHeight: 1.45}}>
|
|
391
|
+
<div>{v || '-'}</div>
|
|
392
|
+
<div style={{
|
|
393
|
+
fontSize: 12,
|
|
394
|
+
color: 'var(--oa-color-text-tertiary, rgba(0, 0, 0, 0.45))',
|
|
395
|
+
whiteSpace: 'nowrap'
|
|
396
|
+
}}>{record.endTime
|
|
397
|
+
? '至 ' + this.formatRecordEndTime(v, record.endTime)
|
|
398
|
+
: '运行中…'}</div>
|
|
399
|
+
</div>
|
|
286
400
|
},
|
|
287
401
|
{
|
|
288
|
-
title: '
|
|
289
|
-
dataIndex: '
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
402
|
+
title: '成功/失败',
|
|
403
|
+
dataIndex: 'executeCount',
|
|
404
|
+
width: 150,
|
|
405
|
+
render: (v, record) => {
|
|
406
|
+
const count = v == null ? 1 : v
|
|
407
|
+
// 旧版本升级上来的记录没有 failCount,用 success 兜底
|
|
408
|
+
const failCount = record.failCount == null
|
|
409
|
+
? (record.success === false ? 1 : 0)
|
|
410
|
+
: record.failCount
|
|
411
|
+
const successCount = Math.max(count - failCount, 0)
|
|
412
|
+
if (successCount === 0 && failCount === 0) {
|
|
413
|
+
return '-'
|
|
299
414
|
}
|
|
300
|
-
|
|
415
|
+
return <span>
|
|
416
|
+
{successCount > 0 &&
|
|
417
|
+
<Tag color='success' style={{marginRight: 4}}>成功 {successCount}</Tag>}
|
|
418
|
+
{failCount > 0 &&
|
|
419
|
+
<Tag color='error' style={{marginRight: 0}}>失败 {failCount}</Tag>}
|
|
420
|
+
</span>
|
|
421
|
+
},
|
|
301
422
|
},
|
|
302
423
|
{
|
|
303
424
|
title: '返回结果',
|
|
304
425
|
dataIndex: 'result',
|
|
305
|
-
|
|
426
|
+
render: v => (v == null || v === '') ? '-' : v,
|
|
306
427
|
},
|
|
307
|
-
|
|
308
428
|
{
|
|
309
429
|
title: '操作',
|
|
310
430
|
dataIndex: 'option',
|
|
431
|
+
width: 64,
|
|
432
|
+
fixed: 'right',
|
|
311
433
|
render: (_, record) => <a onClick={() => this.showLog(record)}>日志</a>,
|
|
312
434
|
}
|
|
313
435
|
]} request={(params) => {
|
|
@@ -323,21 +445,16 @@ export default class JobPage extends React.Component {
|
|
|
323
445
|
width={1024}
|
|
324
446
|
destroyOnHidden
|
|
325
447
|
footer={this.state.selectedLogRecord ? (
|
|
326
|
-
<a href={UrlUtils.contextPath('/admin/sys/log/job/' + this.state.selectedLogRecord.id)}
|
|
448
|
+
<a href={UrlUtils.contextPath('/admin/sys/log/raw/job/' + this.state.selectedLogRecord.id)}
|
|
327
449
|
target='_blank' rel='noreferrer'>在新窗口打开</a>
|
|
328
450
|
) : null}
|
|
329
451
|
>
|
|
330
|
-
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
wordBreak: 'break-all',
|
|
337
|
-
fontSize: 12,
|
|
338
|
-
lineHeight: 1.6
|
|
339
|
-
}}>{this.state.logContent}</pre>
|
|
340
|
-
</Spin>
|
|
452
|
+
{this.state.selectedLogRecord && (
|
|
453
|
+
<LogViewer key={this.state.selectedLogRecord.id}
|
|
454
|
+
logKey={'job/' + this.state.selectedLogRecord.id}
|
|
455
|
+
running={this.isLogRunning()}
|
|
456
|
+
height={480}/>
|
|
457
|
+
)}
|
|
341
458
|
</Modal>
|
|
342
459
|
</Page>
|
|
343
460
|
}
|
package/src/style/global.less
CHANGED
|
@@ -26,9 +26,9 @@ table.oa-table {
|
|
|
26
26
|
background-color: var(--oa-color-fill, #f5f5f5); /* 悬停背景色 */
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/* 斑马纹效果 -
|
|
29
|
+
/* 斑马纹效果 - 用最浅一档填充色;若用页面底色,暗色下会在容器上形成纯黑条 */
|
|
30
30
|
tr:nth-child(even) {
|
|
31
|
-
background-color: var(--oa-color-
|
|
31
|
+
background-color: var(--oa-color-fill-quaternary, #f9f9f9);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/* 响应式表格 - 在小屏幕上添加水平滚动 */
|