@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.
Files changed (41) hide show
  1. package/package.json +2 -5
  2. package/src/framework/biz/UserPicker.less +17 -0
  3. package/src/framework/biz/UserPicker.tsx +296 -0
  4. package/src/framework/biz/index.tsx +2 -0
  5. package/src/framework/biz/pickerUser.ts +19 -0
  6. package/src/framework/components/ContextMenu/index.tsx +11 -8
  7. package/src/framework/components/DownloadModal/index.tsx +4 -4
  8. package/src/framework/components/ErrorBoundary/index.tsx +1 -1
  9. package/src/framework/components/OrgSwitcher/index.tsx +1 -1
  10. package/src/framework/components/ProTable/index.less +26 -8
  11. package/src/framework/components/ProTable/index.tsx +9 -3
  12. package/src/framework/components/ThemeSettings/index.tsx +173 -0
  13. package/src/framework/components/index.ts +1 -0
  14. package/src/framework/config/index.ts +44 -2
  15. package/src/framework/config/layoutConfig.ts +52 -0
  16. package/src/framework/config/themeConfig.ts +449 -14
  17. package/src/framework/fields/FieldEditor/index.tsx +2 -2
  18. package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
  19. package/src/framework/fields/FieldUploadImage/index.tsx +15 -15
  20. package/src/framework/fields/FieldUserPicker/index.tsx +124 -0
  21. package/src/framework/fields/index.ts +1 -0
  22. package/src/framework/utils/HttpClient.ts +15 -4
  23. package/src/framework/utils/MessageApi.ts +16 -3
  24. package/src/framework/views/ViewText/index.tsx +1 -1
  25. package/src/layouts/admin/AdminTabs.less +11 -11
  26. package/src/layouts/admin/HeaderRight.jsx +23 -12
  27. package/src/layouts/admin/SinglePageLayout.jsx +36 -0
  28. package/src/layouts/admin/TabLayout.jsx +21 -1
  29. package/src/layouts/admin/index.jsx +45 -25
  30. package/src/layouts/admin/index.less +43 -23
  31. package/src/layouts/index.tsx +29 -4
  32. package/src/pages/article/article.less +13 -13
  33. package/src/pages/public/login.jsx +29 -15
  34. package/src/pages/public/login.less +243 -22
  35. package/src/pages/standalone/forceUpdatePwd.jsx +8 -7
  36. package/src/pages/system/codegen/index.jsx +56 -51
  37. package/src/pages/system/job/index.jsx +44 -7
  38. package/src/pages/system/role/index.jsx +17 -43
  39. package/src/pages/system/user/index.jsx +0 -4
  40. package/src/pages/system/user/userPerm.jsx +3 -3
  41. package/src/style/global.less +6 -6
@@ -1,6 +1,6 @@
1
- import {Button, Collapse, Drawer, Form, Input, message, Modal, Select, Space, Switch, Tag, Typography} from 'antd'
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
- message.warning('请先选择实体')
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
- message.error(e?.message || '预览失败')
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
- message.warning('请先选择实体')
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
- message.error(e?.message || '生成失败')
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(() => message.success('已复制'))
89
- .catch(() => message.error('复制失败'))
88
+ .then(() => getMessageApi().success('已复制'))
89
+ .catch(() => getMessageApi().error('复制失败'))
90
90
  } else {
91
- message.warning('当前浏览器不支持剪贴板')
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='代码生成' description='扫描实体,生成 Repository / Service / Controller / 前端页面 / 菜单 代码并写入项目源码目录'>
100
- <Form layout='vertical' style={{maxWidth: 720}}>
101
- <Form.Item label='实体' required>
102
- <Select
103
- showSearch
104
- optionFilterProp='label'
105
- placeholder='请选择实体'
106
- options={this.state.entities}
107
- value={this.state.className}
108
- onChange={this.handleEntityChange}
109
- />
110
- </Form.Item>
111
- {selected?.data?.frameworkEntity &&
112
- <Typography.Text type='warning'>
113
- 该实体属于框架内置包({selected.data.packageName}),生成的文件会写入同一包路径,请确认输出位置后再生成。
114
- </Typography.Text>
115
- }
116
- <Form.Item label='模块名(请求前缀 / 前端目录)'>
117
- <Input placeholder='如 customer'
118
- value={this.state.module}
119
- onChange={e => this.setState({module: e.target.value})}/>
120
- </Form.Item>
121
- <Form.Item label='中文名'>
122
- <Input value={this.state.label}
123
- onChange={e => this.setState({label: e.target.value})}/>
124
- </Form.Item>
125
- <Form.Item label='父菜单 id' tooltip='默认挂在系统管理(sys)下'>
126
- <Input value={this.state.parentMenu}
127
- onChange={e => this.setState({parentMenu: e.target.value})}/>
128
- </Form.Item>
129
- <Form.Item label='覆盖已存在文件'>
130
- <Switch checked={this.state.overwrite}
131
- onChange={checked => this.setState({overwrite: checked})}/>
132
- </Form.Item>
133
- <Space>
134
- <Perm code='sys-codegen:generate'>
135
- <Button type='primary' loading={this.state.loading} onClick={this.handlePreview}>预览</Button>
136
- </Perm>
137
- <Button onClick={this.loadEntities}>刷新实体</Button>
138
- </Space>
139
- </Form>
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 = message.loading("删除任务中...")
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, Modal, Transfer} from 'antd'
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
- usersModalOpen: false,
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({userList: rs.data.list, targetKeys: rs.data.selectedKeys})
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: this.state.targetKeys
125
+ userIdList
129
126
  }
130
127
  HttpClient.post('admin/sysRole/grant-users', params, null, {toastError: false}).then(() => {
131
- this.setState({usersModalOpen: false, usersModalLoading: false})
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
- <Modal title={'角色用户' + "【" + this.state.selectedRecord?.name + '】'}
189
- open={this.state.usersModalOpen}
190
- confirmLoading={this.state.usersModalLoading}
191
- destroyOnHidden
192
- mask={{ closable: false }}
193
- width={800}
194
- onCancel={() => this.setState({usersModalOpen: false})}
195
- onOk={this.handleSaveUsers}
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
 
@@ -80,10 +80,6 @@ export default class UserPage extends React.Component {
80
80
  },
81
81
 
82
82
 
83
- {
84
- title: '手机',
85
- dataIndex: 'phone'
86
- },
87
83
  {
88
84
  title: '邮箱',
89
85
  dataIndex: 'email'
@@ -1,6 +1,6 @@
1
- import {Form, Modal, message} from 'antd';
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
- message.error(HttpClient.errToMsg(e))
44
+ getMessageApi().error(HttpClient.errToMsg(e))
45
45
  this.setState({
46
46
  confirmLoading: false
47
47
  })
@@ -6,29 +6,29 @@ table.oa-table {
6
6
  table-layout: fixed;
7
7
 
8
8
  th {
9
- background-color: #f2f2f2; /* 背景色 */
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
  /* 响应式表格 - 在小屏幕上添加水平滚动 */