@jiangood/open-admin 3.1.6 → 3.1.7
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 +2 -5
- package/src/framework/biz/UserPicker.less +17 -0
- package/src/framework/biz/UserPicker.tsx +296 -0
- package/src/framework/biz/index.tsx +2 -0
- package/src/framework/biz/pickerUser.ts +19 -0
- package/src/framework/components/ContextMenu/index.tsx +11 -8
- package/src/framework/components/DownloadModal/index.tsx +4 -4
- package/src/framework/components/ErrorBoundary/index.tsx +1 -1
- package/src/framework/components/OrgSwitcher/index.tsx +1 -1
- package/src/framework/components/ProTable/index.less +26 -8
- package/src/framework/components/ProTable/index.tsx +9 -3
- package/src/framework/components/ThemeSettings/index.tsx +173 -0
- package/src/framework/components/index.ts +1 -0
- package/src/framework/config/index.ts +44 -2
- package/src/framework/config/layoutConfig.ts +52 -0
- package/src/framework/config/themeConfig.ts +449 -14
- package/src/framework/fields/FieldEditor/index.tsx +2 -2
- package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
- package/src/framework/fields/FieldUploadImage/index.tsx +15 -15
- package/src/framework/fields/FieldUserPicker/index.tsx +124 -0
- package/src/framework/fields/index.ts +1 -0
- package/src/framework/utils/HttpClient.ts +15 -4
- package/src/framework/utils/MessageApi.ts +16 -3
- package/src/framework/views/ViewText/index.tsx +1 -1
- package/src/layouts/admin/AdminTabs.less +11 -11
- package/src/layouts/admin/HeaderRight.jsx +23 -12
- package/src/layouts/admin/SinglePageLayout.jsx +36 -0
- package/src/layouts/admin/TabLayout.jsx +21 -1
- package/src/layouts/admin/index.jsx +45 -25
- package/src/layouts/admin/index.less +43 -23
- package/src/layouts/index.tsx +29 -4
- package/src/pages/article/article.less +13 -13
- package/src/pages/public/login.jsx +29 -15
- package/src/pages/public/login.less +243 -22
- package/src/pages/standalone/forceUpdatePwd.jsx +8 -7
- package/src/pages/system/codegen/index.jsx +56 -51
- package/src/pages/system/job/index.jsx +44 -7
- package/src/pages/system/role/index.jsx +17 -43
- package/src/pages/system/user/index.jsx +0 -4
- package/src/pages/system/user/userPerm.jsx +3 -3
- package/src/style/global.less +6 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {Button, Collapse, Drawer, Form, Input,
|
|
1
|
+
import {Button, Card, Collapse, Drawer, Form, Input, Modal, Select, Space, Switch, Tag, Typography} from 'antd'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import {HttpClient, Page, Perm} from '../../../framework'
|
|
3
|
+
import {HttpClient, getMessageApi, getToken, Page, Perm} from '../../../framework'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 代码生成:动态扫描实体,按实体生成常见 CRUD 代码并写入项目源码目录。
|
|
@@ -52,7 +52,7 @@ export default class CodegenPage extends React.Component {
|
|
|
52
52
|
|
|
53
53
|
handlePreview = () => {
|
|
54
54
|
if (!this.state.className) {
|
|
55
|
-
|
|
55
|
+
getMessageApi().warning('请先选择实体')
|
|
56
56
|
return
|
|
57
57
|
}
|
|
58
58
|
this.setState({loading: true})
|
|
@@ -60,13 +60,13 @@ export default class CodegenPage extends React.Component {
|
|
|
60
60
|
this.setState({files: rs.data || [], previewOpen: true})
|
|
61
61
|
}).catch(e => {
|
|
62
62
|
console.error('[Codegen] 预览失败:', e)
|
|
63
|
-
|
|
63
|
+
getMessageApi().error(e?.message || '预览失败')
|
|
64
64
|
}).finally(() => this.setState({loading: false}))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
handleGenerate = () => {
|
|
68
68
|
if (!this.state.className) {
|
|
69
|
-
|
|
69
|
+
getMessageApi().warning('请先选择实体')
|
|
70
70
|
return
|
|
71
71
|
}
|
|
72
72
|
this.setState({confirmOpen: true})
|
|
@@ -78,17 +78,17 @@ export default class CodegenPage extends React.Component {
|
|
|
78
78
|
this.setState({result: rs.data, resultOpen: true, previewOpen: false})
|
|
79
79
|
}).catch(e => {
|
|
80
80
|
console.error('[Codegen] 生成失败:', e)
|
|
81
|
-
|
|
81
|
+
getMessageApi().error(e?.message || '生成失败')
|
|
82
82
|
}).finally(() => this.setState({loading: false}))
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
copy = (text) => {
|
|
86
86
|
if (navigator.clipboard) {
|
|
87
87
|
navigator.clipboard.writeText(text)
|
|
88
|
-
.then(() =>
|
|
89
|
-
.catch(() =>
|
|
88
|
+
.then(() => getMessageApi().success('已复制'))
|
|
89
|
+
.catch(() => getMessageApi().error('复制失败'))
|
|
90
90
|
} else {
|
|
91
|
-
|
|
91
|
+
getMessageApi().warning('当前浏览器不支持剪贴板')
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -96,47 +96,52 @@ export default class CodegenPage extends React.Component {
|
|
|
96
96
|
|
|
97
97
|
render() {
|
|
98
98
|
const selected = this.selectedOption()
|
|
99
|
-
return <Page title='代码生成'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
99
|
+
return <Page title='代码生成'
|
|
100
|
+
description='扫描实体,生成 Repository / Service / Controller / 前端页面 / 菜单 代码并写入项目源码目录'
|
|
101
|
+
actions={
|
|
102
|
+
<Space>
|
|
103
|
+
<Perm code='sys-codegen:generate'>
|
|
104
|
+
<Button type='primary' loading={this.state.loading} onClick={this.handlePreview}>预览</Button>
|
|
105
|
+
</Perm>
|
|
106
|
+
<Button onClick={this.loadEntities}>刷新实体</Button>
|
|
107
|
+
</Space>
|
|
108
|
+
}>
|
|
109
|
+
<Card size='small'>
|
|
110
|
+
<Form layout='vertical' style={{maxWidth: 720}}>
|
|
111
|
+
<Form.Item label='实体' required>
|
|
112
|
+
<Select
|
|
113
|
+
showSearch
|
|
114
|
+
optionFilterProp='label'
|
|
115
|
+
placeholder='请选择实体'
|
|
116
|
+
options={this.state.entities}
|
|
117
|
+
value={this.state.className}
|
|
118
|
+
onChange={this.handleEntityChange}
|
|
119
|
+
/>
|
|
120
|
+
</Form.Item>
|
|
121
|
+
{selected?.data?.frameworkEntity &&
|
|
122
|
+
<Typography.Text type='warning'>
|
|
123
|
+
该实体属于框架内置包({selected.data.packageName}),生成的文件会写入同一包路径,请确认输出位置后再生成。
|
|
124
|
+
</Typography.Text>
|
|
125
|
+
}
|
|
126
|
+
<Form.Item label='模块名(请求前缀 / 前端目录)'>
|
|
127
|
+
<Input placeholder='如 customer'
|
|
128
|
+
value={this.state.module}
|
|
129
|
+
onChange={e => this.setState({module: e.target.value})}/>
|
|
130
|
+
</Form.Item>
|
|
131
|
+
<Form.Item label='中文名'>
|
|
132
|
+
<Input value={this.state.label}
|
|
133
|
+
onChange={e => this.setState({label: e.target.value})}/>
|
|
134
|
+
</Form.Item>
|
|
135
|
+
<Form.Item label='父菜单 id' tooltip='默认挂在系统管理(sys)下'>
|
|
136
|
+
<Input value={this.state.parentMenu}
|
|
137
|
+
onChange={e => this.setState({parentMenu: e.target.value})}/>
|
|
138
|
+
</Form.Item>
|
|
139
|
+
<Form.Item label='覆盖已存在文件'>
|
|
140
|
+
<Switch checked={this.state.overwrite}
|
|
141
|
+
onChange={checked => this.setState({overwrite: checked})}/>
|
|
142
|
+
</Form.Item>
|
|
143
|
+
</Form>
|
|
144
|
+
</Card>
|
|
140
145
|
|
|
141
146
|
<Drawer title='生成预览' width={960}
|
|
142
147
|
open={this.state.previewOpen}
|
|
@@ -164,7 +169,7 @@ export default class CodegenPage extends React.Component {
|
|
|
164
169
|
<pre style={{
|
|
165
170
|
maxHeight: 420,
|
|
166
171
|
overflow: 'auto',
|
|
167
|
-
background: '#f6f8fa',
|
|
172
|
+
background: String(getToken().colorFillTertiary ?? '#f6f8fa'),
|
|
168
173
|
padding: 12,
|
|
169
174
|
borderRadius: 4,
|
|
170
175
|
}}>{file.content}</pre>
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
Divider,
|
|
7
7
|
Form,
|
|
8
8
|
Input,
|
|
9
|
-
message,
|
|
10
9
|
Modal,
|
|
11
10
|
Select,
|
|
11
|
+
Spin,
|
|
12
12
|
Switch,
|
|
13
13
|
Tag
|
|
14
14
|
} from 'antd'
|
|
15
15
|
import {PlusOutlined} from "@ant-design/icons";
|
|
16
|
-
import {FormModal, HttpClient, Page, PermActions, ProTable, UrlUtils, ValueType} from "../../../framework";
|
|
16
|
+
import {FormModal, getMessageApi, HttpClient, Page, PermActions, ProTable, UrlUtils, ValueType} from "../../../framework";
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
const cronOptions = [
|
|
@@ -53,6 +53,10 @@ export default class JobPage extends React.Component {
|
|
|
53
53
|
status: null,
|
|
54
54
|
|
|
55
55
|
executeRecordOpen: false,
|
|
56
|
+
|
|
57
|
+
logOpen: false,
|
|
58
|
+
logLoading: false,
|
|
59
|
+
logContent: '',
|
|
56
60
|
}
|
|
57
61
|
tableRef = React.createRef()
|
|
58
62
|
modalRef = React.createRef()
|
|
@@ -91,7 +95,7 @@ export default class JobPage extends React.Component {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
handleDelete = row => {
|
|
94
|
-
const hide =
|
|
98
|
+
const hide = getMessageApi().loading("删除任务中...")
|
|
95
99
|
HttpClient.post('admin/job/delete', {id: row.id}, null, {toastError: false}).then(() => {
|
|
96
100
|
hide();
|
|
97
101
|
this.tableRef.current.reload();
|
|
@@ -178,6 +182,19 @@ export default class JobPage extends React.Component {
|
|
|
178
182
|
this.setState({executeRecordOpen: true, selectedRecord: record})
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
showLog = (record) => {
|
|
186
|
+
this.setState({logOpen: true, logLoading: true, logContent: '', selectedLogRecord: record})
|
|
187
|
+
HttpClient.get('admin/sys/log/job/' + record.id, null, {toastError: false}).then(content => {
|
|
188
|
+
this.setState({
|
|
189
|
+
logContent: typeof content === 'string' ? content : JSON.stringify(content),
|
|
190
|
+
logLoading: false
|
|
191
|
+
})
|
|
192
|
+
}).catch(e => {
|
|
193
|
+
console.error('[Job] 加载任务日志失败:', e)
|
|
194
|
+
this.setState({logContent: '日志加载失败:' + (e?.message || ''), logLoading: false})
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
181
198
|
|
|
182
199
|
render() {
|
|
183
200
|
return <Page>
|
|
@@ -291,10 +308,7 @@ export default class JobPage extends React.Component {
|
|
|
291
308
|
{
|
|
292
309
|
title: '操作',
|
|
293
310
|
dataIndex: 'option',
|
|
294
|
-
render: (_, record) => {
|
|
295
|
-
const url = UrlUtils.contextPath('/admin/sys/log/job/' + record.id);
|
|
296
|
-
return <a href={url} target='_blank'>日志</a>;
|
|
297
|
-
},
|
|
311
|
+
render: (_, record) => <a onClick={() => this.showLog(record)}>日志</a>,
|
|
298
312
|
}
|
|
299
313
|
]} request={(params) => {
|
|
300
314
|
params.jobId = this.state.selectedRecord.id
|
|
@@ -302,6 +316,29 @@ export default class JobPage extends React.Component {
|
|
|
302
316
|
}}></ProTable>
|
|
303
317
|
|
|
304
318
|
</Modal>
|
|
319
|
+
|
|
320
|
+
<Modal title='任务日志'
|
|
321
|
+
open={this.state.logOpen}
|
|
322
|
+
onCancel={() => this.setState({logOpen: false})}
|
|
323
|
+
width={1024}
|
|
324
|
+
destroyOnHidden
|
|
325
|
+
footer={this.state.selectedLogRecord ? (
|
|
326
|
+
<a href={UrlUtils.contextPath('/admin/sys/log/job/' + this.state.selectedLogRecord.id)}
|
|
327
|
+
target='_blank' rel='noreferrer'>在新窗口打开</a>
|
|
328
|
+
) : null}
|
|
329
|
+
>
|
|
330
|
+
<Spin spinning={this.state.logLoading}>
|
|
331
|
+
<pre style={{
|
|
332
|
+
margin: 0,
|
|
333
|
+
maxHeight: '60vh',
|
|
334
|
+
overflow: 'auto',
|
|
335
|
+
whiteSpace: 'pre-wrap',
|
|
336
|
+
wordBreak: 'break-all',
|
|
337
|
+
fontSize: 12,
|
|
338
|
+
lineHeight: 1.6
|
|
339
|
+
}}>{this.state.logContent}</pre>
|
|
340
|
+
</Spin>
|
|
341
|
+
</Modal>
|
|
305
342
|
</Page>
|
|
306
343
|
}
|
|
307
344
|
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import {PlusOutlined} from '@ant-design/icons'
|
|
2
|
-
import {Button, Form, Input, InputNumber
|
|
2
|
+
import {Button, Form, Input, InputNumber} from 'antd'
|
|
3
3
|
import React from 'react'
|
|
4
|
-
import {FieldBoolean, FormModal, HttpClient, Page, PageUtils, PermActions, ProTable, ViewText} from "../../../framework";
|
|
4
|
+
import {FieldBoolean, FormModal, HttpClient, Page, PageUtils, PermActions, ProTable, UserPicker, ViewText} from "../../../framework";
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export default class RolePage extends React.Component {
|
|
8
8
|
|
|
9
9
|
state = {
|
|
10
|
-
|
|
10
|
+
pickerOpen: false,
|
|
11
11
|
usersModalLoading: false,
|
|
12
12
|
selectedRecord: null,
|
|
13
13
|
|
|
14
|
-
userList: [],
|
|
15
14
|
targetKeys: [],
|
|
16
|
-
selectedKeys: [],
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
modalRef = React.createRef()
|
|
@@ -28,9 +26,8 @@ export default class RolePage extends React.Component {
|
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
handleEditUser = record => {
|
|
31
|
-
this.setState({usersModalOpen: true, selectedRecord: record})
|
|
32
29
|
HttpClient.get('admin/sysRole/user-list', {id: record.id}).then(rs => {
|
|
33
|
-
this.setState({
|
|
30
|
+
this.setState({pickerOpen: true, selectedRecord: record, targetKeys: rs.data?.selectedKeys || []})
|
|
34
31
|
})
|
|
35
32
|
}
|
|
36
33
|
|
|
@@ -121,14 +118,14 @@ export default class RolePage extends React.Component {
|
|
|
121
118
|
},
|
|
122
119
|
]
|
|
123
120
|
|
|
124
|
-
handleSaveUsers =
|
|
121
|
+
handleSaveUsers = userIdList => {
|
|
125
122
|
this.setState({usersModalLoading: true})
|
|
126
123
|
const params = {
|
|
127
124
|
id: this.state.selectedRecord.id,
|
|
128
|
-
userIdList
|
|
125
|
+
userIdList
|
|
129
126
|
}
|
|
130
127
|
HttpClient.post('admin/sysRole/grant-users', params, null, {toastError: false}).then(() => {
|
|
131
|
-
this.setState({
|
|
128
|
+
this.setState({pickerOpen: false, usersModalLoading: false})
|
|
132
129
|
}).catch(() => {
|
|
133
130
|
this.setState({usersModalLoading: false})
|
|
134
131
|
})
|
|
@@ -185,39 +182,16 @@ export default class RolePage extends React.Component {
|
|
|
185
182
|
</FormModal>
|
|
186
183
|
|
|
187
184
|
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<Transfer
|
|
200
|
-
styles={{ section: { height: '60vh', width: 300 } }}
|
|
201
|
-
|
|
202
|
-
dataSource={this.state.userList} titles={["未选择", "已选择"]}
|
|
203
|
-
targetKeys={this.state.targetKeys}
|
|
204
|
-
selectedKeys={this.state.selectedKeys}
|
|
205
|
-
render={item => item.title}
|
|
206
|
-
onChange={(nextTargetKeys, _direction, _moveKeys) => {
|
|
207
|
-
this.setState({
|
|
208
|
-
targetKeys: nextTargetKeys
|
|
209
|
-
})
|
|
210
|
-
}}
|
|
211
|
-
onSelectChange={(sourceSelectedKeys, targetSelectedKeys) => {
|
|
212
|
-
this.setState({
|
|
213
|
-
selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys]
|
|
214
|
-
})
|
|
215
|
-
}}
|
|
216
|
-
showSearch
|
|
217
|
-
/>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
</Modal>
|
|
185
|
+
<UserPicker
|
|
186
|
+
open={this.state.pickerOpen}
|
|
187
|
+
title={'为「' + (this.state.selectedRecord?.name || '') + '」角色分配用户'}
|
|
188
|
+
value={this.state.targetKeys}
|
|
189
|
+
confirmLoading={this.state.usersModalLoading}
|
|
190
|
+
requestUrl='admin/sysRole/user-page'
|
|
191
|
+
usersUrl='admin/sysRole/user-by-ids'
|
|
192
|
+
onOk={this.handleSaveUsers}
|
|
193
|
+
onCancel={() => this.setState({pickerOpen: false})}
|
|
194
|
+
/>
|
|
221
195
|
</Page>
|
|
222
196
|
|
|
223
197
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {Form, Modal
|
|
1
|
+
import {Form, Modal} from 'antd';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {FieldDictSelect, FieldRemoteSelect, FieldSysOrgTree, HttpClient} from "../../../framework";
|
|
3
|
+
import {FieldDictSelect, FieldRemoteSelect, FieldSysOrgTree, getMessageApi, HttpClient} from "../../../framework";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export default class UserPerm extends React.Component {
|
|
@@ -41,7 +41,7 @@ export default class UserPerm extends React.Component {
|
|
|
41
41
|
})
|
|
42
42
|
this.props.onOk()
|
|
43
43
|
}).catch(e => {
|
|
44
|
-
|
|
44
|
+
getMessageApi().error(HttpClient.errToMsg(e))
|
|
45
45
|
this.setState({
|
|
46
46
|
confirmLoading: false
|
|
47
47
|
})
|
package/src/style/global.less
CHANGED
|
@@ -6,29 +6,29 @@ table.oa-table {
|
|
|
6
6
|
table-layout: fixed;
|
|
7
7
|
|
|
8
8
|
th {
|
|
9
|
-
background-color: #
|
|
10
|
-
color: #333; /* 文字颜色 */
|
|
9
|
+
background-color: var(--oa-color-fill, #fafafa); /* 背景色(跟随明暗主题) */
|
|
10
|
+
color: var(--oa-color-text, #333); /* 文字颜色 */
|
|
11
11
|
font-weight: bold; /* 加粗 */
|
|
12
12
|
padding: 12px 15px; /* 内边距 */
|
|
13
13
|
text-align: left; /* 文字对齐 */
|
|
14
|
-
border-bottom: 2px solid #ddd; /* 底部边框 */
|
|
14
|
+
border-bottom: 2px solid var(--oa-color-border, #ddd); /* 底部边框 */
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
td {
|
|
18
18
|
padding: 10px 15px; /* 内边距 */
|
|
19
|
-
border-bottom: 1px solid #ddd; /* 底部边框 */
|
|
19
|
+
border-bottom: 1px solid var(--oa-color-border, #ddd); /* 底部边框 */
|
|
20
20
|
text-align: left; /* 文字对齐 */
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/* 表格行悬停效果 */
|
|
24
24
|
|
|
25
25
|
tr:hover {
|
|
26
|
-
background-color: #f5f5f5; /* 悬停背景色 */
|
|
26
|
+
background-color: var(--oa-color-fill, #f5f5f5); /* 悬停背景色 */
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/* 斑马纹效果 - 交替行颜色 */
|
|
30
30
|
tr:nth-child(even) {
|
|
31
|
-
background-color: #f9f9f9;
|
|
31
|
+
background-color: var(--oa-color-bg-layout, #f9f9f9);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/* 响应式表格 - 在小屏幕上添加水平滚动 */
|