@jiangood/open-admin 3.1.11 → 3.1.12

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 (56) hide show
  1. package/package.json +3 -3
  2. package/src/framework/GlobalData.ts +3 -1
  3. package/src/framework/biz/OrgTree.tsx +1 -1
  4. package/src/framework/biz/RoleTree.tsx +1 -1
  5. package/src/framework/biz/UserPicker.tsx +4 -4
  6. package/src/framework/biz/index.tsx +4 -4
  7. package/src/framework/components/LinkButton/index.tsx +4 -0
  8. package/src/framework/components/OrgSwitcher/index.tsx +2 -2
  9. package/src/framework/components/Page/index.tsx +6 -14
  10. package/src/framework/components/ProTable/index.tsx +3 -1
  11. package/src/framework/components/ThemeSettings/index.tsx +5 -38
  12. package/src/framework/config/index.ts +12 -0
  13. package/src/framework/config/layoutConfig.ts +47 -8
  14. package/src/framework/config/themeConfig.ts +93 -25
  15. package/src/framework/config/uiDefaults.ts +26 -0
  16. package/src/framework/fields/FieldEditor/index.tsx +1 -1
  17. package/src/framework/fields/FieldUploadFile/index.tsx +1 -1
  18. package/src/framework/fields/FieldUploadImage/index.tsx +42 -10
  19. package/src/framework/fields/FieldUserPicker/index.tsx +3 -3
  20. package/src/framework/fields/orgTree.ts +1 -1
  21. package/src/framework/router/index.ts +1 -0
  22. package/src/framework/router/matcher.ts +19 -4
  23. package/src/framework/utils/AvatarUtils.ts +64 -0
  24. package/src/framework/utils/HttpClient.ts +5 -5
  25. package/src/framework/utils/PageUtils.ts +38 -7
  26. package/src/framework/utils/TitleUtils.ts +38 -0
  27. package/src/framework/utils/UrlUtils.ts +3 -3
  28. package/src/framework/utils/index.ts +2 -0
  29. package/src/layouts/admin/HeaderRight.jsx +62 -16
  30. package/src/layouts/admin/SinglePageLayout.jsx +13 -2
  31. package/src/layouts/admin/TabLayout.jsx +38 -19
  32. package/src/layouts/admin/index.jsx +4 -4
  33. package/src/layouts/index.tsx +52 -15
  34. package/src/pages/404.jsx +4 -0
  35. package/src/pages/article/$code.jsx +5 -1
  36. package/src/pages/login.jsx +157 -0
  37. package/src/pages/{public/login.less → login.less} +16 -1
  38. package/src/pages/public/login.jsx +11 -145
  39. package/src/pages/standalone/{forceUpdatePwd.jsx → force-update-pwd.jsx} +10 -4
  40. package/src/pages/system/article/index.jsx +1 -1
  41. package/src/pages/system/config/index.jsx +46 -20
  42. package/src/pages/system/file/index.jsx +3 -3
  43. package/src/pages/system/job/index.jsx +1 -1
  44. package/src/pages/system/log/index.jsx +1 -1
  45. package/src/pages/system/online/index.jsx +95 -0
  46. package/src/pages/system/org/index.jsx +7 -7
  47. package/src/pages/system/role/index.jsx +8 -8
  48. package/src/pages/system/role/{rolePerm.jsx → role-perm.jsx} +9 -4
  49. package/src/pages/system/user/{userPerm.jsx → UserPermModal.jsx} +3 -3
  50. package/src/pages/system/user/index.jsx +6 -6
  51. package/src/pages/user-center/AvatarPicker.jsx +76 -0
  52. package/src/pages/{userCenter/permView.jsx → user-center/PermView.jsx} +48 -25
  53. package/src/pages/user-center/RecentOps.jsx +36 -0
  54. package/src/pages/user-center/index.jsx +393 -0
  55. package/vite-plugin/index.js +64 -6
  56. package/src/pages/userCenter/index.jsx +0 -93
@@ -1,17 +1,23 @@
1
1
  import React from "react";
2
2
  import {Button, Form, Input} from "antd";
3
- import {getMessageApi, getToken, HttpClient, history} from "../../framework";
3
+ import {getMessageApi, getToken, HttpClient, history, LOGIN_PATH} from "../../framework";
4
+
5
+ export const pageMeta = {
6
+ requiresAuth: true,
7
+ layout: 'blank',
8
+ title: '修改密码',
9
+ };
4
10
 
5
11
  export default class ForceUpdatePwdPage extends React.Component {
6
12
 
7
13
  onFinish = (values) => {
8
- HttpClient.post('admin/userCenter/update-pwd', values, null).then(() => {
14
+ HttpClient.post('admin/user-center/update-pwd', values, null).then(() => {
9
15
  getMessageApi().success('修改密码成功,请重新登录');
10
- history.push('/public/login');
16
+ history.push(LOGIN_PATH);
11
17
  })
12
18
  }
13
19
 
14
- validator = (rule, value) => HttpClient.get("admin/sysUser/pwd-strength", {password: value}, {toastError: false})
20
+ validator = (rule, value) => HttpClient.get("admin/sys-user/pwd-strength", {password: value}, {toastError: false})
15
21
  .catch(e => {
16
22
  throw new Error(e.message)
17
23
  })
@@ -36,7 +36,7 @@ export default class ArticleListPage extends React.Component {
36
36
  }
37
37
 
38
38
  handlePreview = record => {
39
- PageUtils.open('/article/' + record.code, record.title)
39
+ PageUtils.open('/article/' + record.code)
40
40
  }
41
41
 
42
42
  onFinish = async values => {
@@ -3,7 +3,6 @@ import {
3
3
  Button,
4
4
  Card,
5
5
  Col,
6
- Divider,
7
6
  Form,
8
7
  Input,
9
8
  InputNumber,
@@ -21,7 +20,7 @@ import {QuestionCircleOutlined, UndoOutlined} from '@ant-design/icons'
21
20
  import {EventBus, GlobalData, HttpClient, Page, Perm} from '../../../framework'
22
21
 
23
22
  /**
24
- * 系统配置页。
23
+ * 参数设置页。
25
24
  *
26
25
  * 配置项由后端 ConfigProvider 声明(分组 / 类型 / 默认值 / 范围 / 说明),
27
26
  * 本页面只负责按元数据渲染、收集修改项并提交差异。
@@ -32,6 +31,7 @@ export default class SysConfigPage extends React.Component {
32
31
  groups: [],
33
32
  loading: true,
34
33
  saving: false,
34
+ testingMail: false,
35
35
  dirty: false,
36
36
  }
37
37
 
@@ -141,6 +141,19 @@ export default class SysConfigPage extends React.Component {
141
141
  }
142
142
  }
143
143
 
144
+ /** 用已保存的配置发一封测试邮件到发件邮箱自身 */
145
+ sendTestMail = async () => {
146
+ this.setState({testingMail: true})
147
+ try {
148
+ // 成功提示由后端 message 自动弹出
149
+ await HttpClient.post('admin/mail/test')
150
+ } catch {
151
+ // 错误提示由 HttpClient 统一处理
152
+ } finally {
153
+ this.setState({testingMail: false})
154
+ }
155
+ }
156
+
144
157
  resetKeys = async (keys) => {
145
158
  try {
146
159
  const rs = await HttpClient.post('admin/sys-config/reset', {keys})
@@ -189,6 +202,8 @@ export default class SysConfigPage extends React.Component {
189
202
  }))}/>
190
203
  case 'text':
191
204
  return <Input.TextArea rows={3}/>
205
+ case 'proxy':
206
+ return <Input placeholder="http://user:pass@192.168.1.10:8080" autoComplete="off"/>
192
207
  case 'string':
193
208
  return item.secret
194
209
  ? <Input.Password autoComplete="new-password"
@@ -257,32 +272,43 @@ export default class SysConfigPage extends React.Component {
257
272
  }
258
273
 
259
274
  render() {
260
- const {groups, loading, saving, dirty} = this.state
275
+ const {groups, loading, saving, testingMail, dirty} = this.state
261
276
  const hasOverridden = groups.some(group => group.items.some(item => item.overridden))
277
+ // 首次加载显示骨架屏;切回 Tab 等刷新时保留现有内容,避免闪白
278
+ const initialLoading = loading && groups.length === 0
262
279
 
263
280
  return <Page>
264
281
  <Form ref={this.formRef} layout="vertical" onValuesChange={this.onValuesChange}>
265
- <Card loading={loading}>
266
- {groups.map((group, index) => (
267
- <div key={group.code}>
268
- <div style={{marginBottom: 12}}>
269
- <Typography.Title level={5} style={{margin: 0}}>{group.label}</Typography.Title>
270
- {group.description && (
271
- <Typography.Text type="secondary" style={{fontSize: 12}}>
272
- {group.description}
273
- </Typography.Text>
274
- )}
275
- </div>
282
+ {initialLoading && <Card loading style={{marginBottom: 16}}/>}
283
+ <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
284
+ {groups.map(group => (
285
+ <Card key={group.code}
286
+ title={group.label}
287
+ extra={group.code === 'mail' && (
288
+ <Perm code='sys-config:update'>
289
+ <Button loading={testingMail}
290
+ disabled={dirty}
291
+ title={dirty ? '请先保存配置修改' : '发送一封测试邮件到发件邮箱'}
292
+ onClick={this.sendTestMail}>
293
+ 发送测试邮件
294
+ </Button>
295
+ </Perm>
296
+ )}>
297
+ {group.description && (
298
+ <Typography.Text type="secondary"
299
+ style={{display: 'block', fontSize: 12, marginBottom: 16}}>
300
+ {group.description}
301
+ </Typography.Text>
302
+ )}
276
303
  <Row gutter={16}>
277
304
  {group.items.map(this.renderItem)}
278
305
  </Row>
279
- {index < groups.length - 1 && <Divider style={{margin: '8px 0 20px'}}/>}
280
- </div>
306
+ </Card>
281
307
  ))}
308
+ </div>
282
309
 
283
- {groups.length > 0 && <Divider style={{margin: '4px 0 16px'}}/>}
284
-
285
- <Space wrap>
310
+ {groups.length > 0 && (
311
+ <Space wrap style={{marginTop: 16}}>
286
312
  <Perm code='sys-config:update'>
287
313
  <Button type="primary" loading={saving} disabled={!dirty} onClick={this.save}>
288
314
  保存修改
@@ -302,7 +328,7 @@ export default class SysConfigPage extends React.Component {
302
328
  配置默认值来自 application.yml 或代码声明
303
329
  </Typography.Text>
304
330
  </Space>
305
- </Card>
331
+ )}
306
332
  </Form>
307
333
  </Page>
308
334
  }
@@ -15,13 +15,13 @@ export default class FilePage extends React.Component {
15
15
  formRef = React.createRef()
16
16
 
17
17
  handleDelete = row => {
18
- HttpClient.post('admin/sysFile/delete', {value: row.objectName}, null).then(() => {
18
+ HttpClient.post('admin/sys-file/delete', {value: row.objectName}, null).then(() => {
19
19
  this.tableRef.current.reload()
20
20
  })
21
21
  }
22
22
 
23
23
  handleBatchDelete = ids => {
24
- HttpClient.post('admin/sysFile/deleteBatch', ids, null).then(() => {
24
+ HttpClient.post('admin/sys-file/delete-batch', ids, null).then(() => {
25
25
  this.tableRef.current.reload()
26
26
  this.tableRef.current.clearSelection()
27
27
  })
@@ -167,7 +167,7 @@ export default class FilePage extends React.Component {
167
167
  </Space>
168
168
  )}
169
169
  request={(params) => {
170
- return HttpClient.get('admin/sysFile/page', params);
170
+ return HttpClient.get('admin/sys-file/page', params);
171
171
  }}
172
172
 
173
173
  columns={this.columns}
@@ -362,7 +362,7 @@ export default class JobPage extends React.Component {
362
362
  onFinish={this.onFinish}
363
363
  onValuesChange={this.onValuesChange}>
364
364
  <Form.Item label='执行类' name='jobClass' rules={[{required: true}]}
365
- tooltip='继承 BaseJob,参考 HelloWorldJob'>
365
+ tooltip='继承 BaseJob'>
366
366
  <Select options={this.state.jobClassOptions}/>
367
367
  </Form.Item>
368
368
  <Form.Item label='名称' name='name' rules={[{required: true}]}>
@@ -55,7 +55,7 @@ export default class LogPage extends React.Component {
55
55
  render() {
56
56
  return <Page>
57
57
  <ProTable
58
- request={(params) => HttpClient.get('admin/sysLog/page', params)}
58
+ request={(params) => HttpClient.get('admin/sys-log/page', params)}
59
59
  columns={this.columns}
60
60
  searchFormRender={() => (
61
61
  <>
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import {Form, Input, Tag} from 'antd';
3
+ import {HttpClient, Page, PermActions, ProTable} from "../../../framework";
4
+
5
+ /**
6
+ * 在线用户:一个登录会话一行,支持强制下线。
7
+ * 数据来自内存中的 HttpSession 元数据,仅单实例部署准确。
8
+ */
9
+ export default class OnlineUserPage extends React.Component {
10
+
11
+ tableRef = React.createRef();
12
+
13
+ handleKick = row => {
14
+ HttpClient.post('admin/sys-online-user/kick', {id: row.id}).then(() => {
15
+ this.tableRef.current.reload();
16
+ });
17
+ };
18
+
19
+ columns = [
20
+ {
21
+ title: '登录账号',
22
+ dataIndex: 'username',
23
+ width: 140,
24
+ },
25
+ {
26
+ title: '姓名',
27
+ dataIndex: 'name',
28
+ width: 120,
29
+ },
30
+ {
31
+ title: '所属机构',
32
+ dataIndex: 'unitName',
33
+ width: 180,
34
+ render: (_, record) => {
35
+ // 单位与机构可能指向同一节点(如 admin 挂在根单位),去重后避免「默认单位 / 默认单位」
36
+ const names = [...new Set([record.unitName, record.orgName].filter(Boolean))];
37
+ return names.length ? names.join(' / ') : '-';
38
+ },
39
+ },
40
+ {
41
+ title: 'IP',
42
+ dataIndex: 'ip',
43
+ width: 140,
44
+ },
45
+ {
46
+ title: '登录时间',
47
+ dataIndex: 'loginTime',
48
+ width: 170,
49
+ render: v => v || '-',
50
+ },
51
+ {
52
+ title: '最后活跃',
53
+ dataIndex: 'lastActiveTime',
54
+ width: 170,
55
+ render: v => v || '-',
56
+ },
57
+ {
58
+ title: '操作',
59
+ dataIndex: 'option',
60
+ width: 130,
61
+ fixed: 'right',
62
+ render: (_, record) => {
63
+ if (record.current) {
64
+ return <Tag color="green">当前会话</Tag>;
65
+ }
66
+ return <PermActions
67
+ size="small"
68
+ actions={[{
69
+ label: '强制下线',
70
+ perm: 'sys-online:kick',
71
+ danger: true,
72
+ confirm: `确定强制下线「${record.name || record.username}」吗?`,
73
+ onClick: () => this.handleKick(record),
74
+ }]}
75
+ />;
76
+ },
77
+ },
78
+ ];
79
+
80
+ render() {
81
+ return <Page>
82
+ <ProTable
83
+ actionRef={this.tableRef}
84
+ request={params => HttpClient.get('admin/sys-online-user/page', params)}
85
+ columns={this.columns}
86
+ searchFormRender={() => (
87
+ <Form.Item label='账号/姓名/IP' name='keyword'>
88
+ <Input placeholder='支持账号、姓名、IP 模糊搜索' allowClear/>
89
+ </Form.Item>
90
+ )}
91
+ />
92
+ </Page>;
93
+ }
94
+
95
+ }
@@ -38,7 +38,7 @@ export default class OrgPage extends React.Component {
38
38
 
39
39
  loadTree = () => {
40
40
  this.setState({treeLoading: true})
41
- HttpClient.get('admin/sysOrg/tree', this.state.params, {toastError: false}).then(rs => {
41
+ HttpClient.get('admin/sys-org/tree', this.state.params, {toastError: false}).then(rs => {
42
42
  this.setState({treeData: rs.data})
43
43
  this.setState({treeLoading: false});
44
44
  }).catch(() => {
@@ -53,7 +53,7 @@ export default class OrgPage extends React.Component {
53
53
  handleDelete = () => {
54
54
  const {selectedOrg} = this.state
55
55
  if (!selectedOrg) return
56
- HttpClient.post('admin/sysOrg/delete', {id: selectedOrg.id}, null).then(() => {
56
+ HttpClient.post('admin/sys-org/delete', {id: selectedOrg.id}, null).then(() => {
57
57
  this.setState({selectedOrg: null, deleteModalOpen: false})
58
58
  this.loadTree()
59
59
  })
@@ -64,7 +64,7 @@ export default class OrgPage extends React.Component {
64
64
  this.setState({selectedOrg: null})
65
65
  return
66
66
  }
67
- HttpClient.get("admin/sysOrg/detail", {id: selectedKeys[0]}).then(rs => {
67
+ HttpClient.get("admin/sys-org/detail", {id: selectedKeys[0]}).then(rs => {
68
68
  this.setState({selectedOrg: rs.data})
69
69
  })
70
70
  }
@@ -80,7 +80,7 @@ export default class OrgPage extends React.Component {
80
80
 
81
81
  handleModalFinish = async values => {
82
82
  const isNew = !values.id
83
- const url = isNew ? 'admin/sysOrg/create' : 'admin/sysOrg/update'
83
+ const url = isNew ? 'admin/sys-org/create' : 'admin/sys-org/update'
84
84
  await HttpClient.post(url, values)
85
85
  this.loadTree()
86
86
  }
@@ -189,13 +189,13 @@ export default class OrgPage extends React.Component {
189
189
  onFinish={this.handleModalFinish}
190
190
  labelCol={{flex: '120px'}}>
191
191
  <Form.Item label='父节点' name='pid'>
192
- <FieldRemoteTreeSelect url='admin/sysOrg/tree'/>
192
+ <FieldRemoteTreeSelect url='admin/sys-org/tree'/>
193
193
  </Form.Item>
194
194
  <Form.Item label='名称' name='name' rules={[{required: true}]}>
195
195
  <Input/>
196
196
  </Form.Item>
197
197
  <Form.Item label='类型' name='type' rules={[{required: true}]}>
198
- <FieldRemoteSelect url='admin/sysOrg/type-options' placeholder='请选择类型'/>
198
+ <FieldRemoteSelect url='admin/sys-org/type-options' placeholder='请选择类型'/>
199
199
  </Form.Item>
200
200
  <Form.Item label='部门领导' name={['leader', 'id']}>
201
201
  <FieldUserSelect/>
@@ -233,7 +233,7 @@ export default class OrgPage extends React.Component {
233
233
  const dragKey = dragNode.key;
234
234
  const dropPos = e.node.pos.split('-');
235
235
  const dropPosition = e.dropPosition - Number(dropPos[dropPos.length - 1]);
236
- HttpClient.post('admin/sysOrg/sort', {dropPosition, dropToGap, dropKey, dragKey}, null).then(() => this.loadTree())
236
+ HttpClient.post('admin/sys-org/sort', {dropPosition, dropToGap, dropKey, dragKey}, null).then(() => this.loadTree())
237
237
  };
238
238
  }
239
239
 
@@ -26,20 +26,20 @@ export default class RolePage extends React.Component {
26
26
  }
27
27
 
28
28
  handleEditUser = record => {
29
- HttpClient.get('admin/sysRole/user-list', {id: record.id}).then(rs => {
29
+ HttpClient.get('admin/sys-role/user-list', {id: record.id}).then(rs => {
30
30
  this.setState({pickerOpen: true, selectedRecord: record, targetKeys: rs.data?.selectedKeys || []})
31
31
  })
32
32
  }
33
33
 
34
34
  onFinish = async values => {
35
35
  const isNew = !values.id;
36
- const url = isNew ? 'admin/sysRole/create' : 'admin/sysRole/update';
36
+ const url = isNew ? 'admin/sys-role/create' : 'admin/sys-role/update';
37
37
  await HttpClient.post(url, values)
38
38
  this.tableRef.current.reload()
39
39
  }
40
40
 
41
41
  handleDelete = record => {
42
- HttpClient.post('admin/sysRole/delete', {id: record.id}, null).then(() => {
42
+ HttpClient.post('admin/sys-role/delete', {id: record.id}, null).then(() => {
43
43
  this.tableRef.current.reload()
44
44
  })
45
45
  }
@@ -109,7 +109,7 @@ export default class RolePage extends React.Component {
109
109
  size="small"
110
110
  actions={[
111
111
  {label: '用户设置', perm: 'sys-role:grant-permission', onClick: () => this.handleEditUser(record)},
112
- {label: '权限设置', perm: 'sys-role:grant-permission', onClick: () => PageUtils.open('/system/role/rolePerm?id=' + record.id, '角色权限设置')},
112
+ {label: '权限设置', perm: 'sys-role:grant-permission', onClick: () => PageUtils.open('/system/role/role-perm?id=' + record.id)},
113
113
  {label: '编辑', perm: 'sys-role:update', onClick: () => this.handleEdit(record)},
114
114
  {label: '删除', perm: 'sys-role:delete', confirm: '是否确定删除系统角色', onClick: () => this.handleDelete(record)},
115
115
  ]}
@@ -124,7 +124,7 @@ export default class RolePage extends React.Component {
124
124
  id: this.state.selectedRecord.id,
125
125
  userIdList
126
126
  }
127
- HttpClient.post('admin/sysRole/grant-users', params, null, {toastError: false}).then(() => {
127
+ HttpClient.post('admin/sys-role/grant-users', params, null, {toastError: false}).then(() => {
128
128
  this.setState({pickerOpen: false, usersModalLoading: false})
129
129
  }).catch(() => {
130
130
  this.setState({usersModalLoading: false})
@@ -141,7 +141,7 @@ export default class RolePage extends React.Component {
141
141
  {label: '新增', type: 'primary', icon: <PlusOutlined/>, perm: 'sys-role:create', onClick: this.handleAdd},
142
142
  ]}/>
143
143
  )}
144
- request={(params) => HttpClient.get('admin/sysRole/page', params)}
144
+ request={(params) => HttpClient.get('admin/sys-role/page', params)}
145
145
  columns={this.columns}
146
146
  searchInline
147
147
  searchFormRender={() => (
@@ -186,8 +186,8 @@ export default class RolePage extends React.Component {
186
186
  title={'为「' + (this.state.selectedRecord?.name || '') + '」角色分配用户'}
187
187
  value={this.state.targetKeys}
188
188
  confirmLoading={this.state.usersModalLoading}
189
- requestUrl='admin/sysRole/user-page'
190
- usersUrl='admin/sysRole/user-by-ids'
189
+ requestUrl='admin/sys-role/user-page'
190
+ usersUrl='admin/sys-role/user-by-ids'
191
191
  onOk={this.handleSaveUsers}
192
192
  onCancel={() => this.setState({pickerOpen: false})}
193
193
  />
@@ -2,6 +2,11 @@ import React from "react";
2
2
  import {Button, Card, Checkbox, Table, Typography} from "antd";
3
3
  import {SaveOutlined} from "@ant-design/icons";
4
4
  import {HttpClient, Page} from "../../../framework";
5
+
6
+ export const pageMeta = {
7
+ title: '角色权限设置',
8
+ };
9
+
5
10
  export default class RolePermPage extends React.Component {
6
11
 
7
12
 
@@ -62,15 +67,15 @@ export default class RolePermPage extends React.Component {
62
67
  this.setState({loading: false})
63
68
  }
64
69
  };
65
- HttpClient.get('admin/sysRole/get', {id: this.roleId}).then(rs => {
70
+ HttpClient.get('admin/sys-role/get', {id: this.roleId}).then(rs => {
66
71
  this.setState({roleInfo: rs.data})
67
72
  onFinish()
68
73
  });
69
- HttpClient.get('admin/sysRole/perm-tree-table', {id: this.roleId}).then(rs => {
74
+ HttpClient.get('admin/sys-role/perm-tree-table', {id: this.roleId}).then(rs => {
70
75
  this.setState({dataSource: rs.data})
71
76
  onFinish()
72
77
  });
73
- HttpClient.get('admin/sysRole/own-perms', {id: this.roleId}).then(rs => {
78
+ HttpClient.get('admin/sys-role/own-perms', {id: this.roleId}).then(rs => {
74
79
  this.setState({rowSelectedKeys: rs.data})
75
80
  onFinish()
76
81
  });
@@ -88,7 +93,7 @@ export default class RolePermPage extends React.Component {
88
93
  menus.push(menuId)
89
94
  perms.push(...ks)
90
95
  }
91
- HttpClient.post('admin/sysRole/save-perms', {id: this.roleId, perms, menus})
96
+ HttpClient.post('admin/sys-role/save-perms', {id: this.roleId, perms, menus})
92
97
  };
93
98
 
94
99
 
@@ -20,7 +20,7 @@ export default class UserPerm extends React.Component {
20
20
  show(item) {
21
21
  this.setState({visible: true})
22
22
 
23
- HttpClient.get('admin/sysUser/get-perm-info', {id: item.id}).then(rs => {
23
+ HttpClient.get('admin/sys-user/get-perm-info', {id: item.id}).then(rs => {
24
24
  this.setState({formValues: rs.data})
25
25
  this.formRef.current.setFieldsValue(rs.data)
26
26
  })
@@ -34,7 +34,7 @@ export default class UserPerm extends React.Component {
34
34
  })
35
35
 
36
36
 
37
- HttpClient.post('admin/sysUser/grant-perm', values, null, {toastError: false}).then(() => {
37
+ HttpClient.post('admin/sys-user/grant-perm', values, null, {toastError: false}).then(() => {
38
38
  this.setState({
39
39
  visible: false,
40
40
  confirmLoading: false
@@ -70,7 +70,7 @@ export default class UserPerm extends React.Component {
70
70
  >
71
71
  <Form.Item name='id' noStyle></Form.Item>
72
72
  <Form.Item label='角色' name='roleIds' rules={[{required: true}]}>
73
- <FieldRemoteSelect url='admin/sysRole/options' multiple />
73
+ <FieldRemoteSelect url='admin/sys-role/options' multiple />
74
74
  </Form.Item>
75
75
  <Form.Item label='数据权限' name='dataPermType' rules={[{required: true}]}>
76
76
  <FieldDictSelect typeCode='dataPermType'/>
@@ -14,7 +14,7 @@ import {
14
14
  ProTable,
15
15
  ViewText,
16
16
  } from "../../../framework";
17
- import UserPerm from "./userPerm";
17
+ import UserPerm from "./UserPermModal";
18
18
 
19
19
  export default class UserPage extends React.Component {
20
20
 
@@ -36,13 +36,13 @@ export default class UserPage extends React.Component {
36
36
  }
37
37
 
38
38
  onFinishResetPwd = async values => {
39
- await HttpClient.post('admin/sysUser/reset-pwd', {id: this.state.resetPwdUser.id, password: values.password})
39
+ await HttpClient.post('admin/sys-user/reset-pwd', {id: this.state.resetPwdUser.id, password: values.password})
40
40
  this.setState({resetPwdUser: null})
41
41
  this.tableRef.current?.reload()
42
42
  }
43
43
 
44
44
  handleDelete = r => {
45
- HttpClient.post('admin/sysUser/delete', {id: r.id}, null).then(() => {
45
+ HttpClient.post('admin/sys-user/delete', {id: r.id}, null).then(() => {
46
46
  this.tableRef.current.reload();
47
47
  })
48
48
  }
@@ -135,7 +135,7 @@ export default class UserPage extends React.Component {
135
135
 
136
136
  onFinish = async values => {
137
137
  const isNew = !values.id;
138
- const url = isNew ? 'admin/sysUser/create' : 'admin/sysUser/update';
138
+ const url = isNew ? 'admin/sys-user/create' : 'admin/sys-user/update';
139
139
  const result = await HttpClient.post(url, values);
140
140
  if (result?.data?.password) {
141
141
  this.setState({
@@ -170,7 +170,7 @@ export default class UserPage extends React.Component {
170
170
  )}
171
171
  request={(params) => {
172
172
  params.orgId = this.state.currentOrgId
173
- return HttpClient.get('admin/sysUser/page', params)
173
+ return HttpClient.get('admin/sys-user/page', params)
174
174
  }}
175
175
  columns={this.columns}
176
176
  searchFormRender={() => (
@@ -185,7 +185,7 @@ export default class UserPage extends React.Component {
185
185
  <Input/>
186
186
  </Form.Item>
187
187
  <Form.Item label='角色' name='roleId'>
188
- <FieldRemoteSelect url='admin/sysRole/options' placeholder='请选择角色'/>
188
+ <FieldRemoteSelect url='admin/sys-role/options' placeholder='请选择角色'/>
189
189
  </Form.Item>
190
190
  <Form.Item label='状态' name='enabled'>
191
191
  <Select allowClear placeholder='全部' options={[{value: true, label: '启用'}, {value: false, label: '停用'}]}/>
@@ -0,0 +1,76 @@
1
+ import React from "react";
2
+ import {Avatar, Tooltip} from "antd";
3
+ import {
4
+ AvatarUtils,
5
+ FALLBACK_AVATAR_PRESETS,
6
+ FieldUploadImage,
7
+ getToken,
8
+ HttpClient,
9
+ } from "../../framework";
10
+
11
+ const ITEM_SIZE = 44;
12
+
13
+ /**
14
+ * 头像选择:内置头像 + 自定义上传
15
+ * <p>
16
+ * 内置头像从后端 admin/public/avatar-presets 获取,移动端可复用同一接口;
17
+ * 上传入口以虚线圆形按钮排在列表末尾,选中/上传后直接在同一排展示结果。
18
+ */
19
+ export default class AvatarPicker extends React.Component {
20
+
21
+ state = {
22
+ presets: FALLBACK_AVATAR_PRESETS,
23
+ }
24
+
25
+ componentDidMount() {
26
+ HttpClient.get('admin/public/avatar-presets', null, {toastError: false}).then(rs => {
27
+ const presets = rs.data?.presets;
28
+ if (Array.isArray(presets) && presets.length > 0) {
29
+ this.setState({presets});
30
+ }
31
+ }).catch(() => {
32
+ // 接口不可用时保留前端兜底列表
33
+ })
34
+ }
35
+
36
+ render() {
37
+ const {value, onChange} = this.props;
38
+ const {presets} = this.state;
39
+ const uploaded = value && !AvatarUtils.isPreset(value) ? value : '';
40
+ const primary = getToken().colorPrimary;
41
+
42
+ return <div style={{display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10}}>
43
+ {presets.map(preset => {
44
+ const selected = (value || '') === preset.key;
45
+ return <Tooltip key={preset.key} title={preset.label}>
46
+ <button type="button"
47
+ aria-label={preset.label}
48
+ aria-pressed={selected}
49
+ onClick={() => onChange?.(preset.key)}
50
+ style={{
51
+ width: ITEM_SIZE,
52
+ height: ITEM_SIZE,
53
+ padding: 0,
54
+ border: 'none',
55
+ borderRadius: '50%',
56
+ background: 'transparent',
57
+ cursor: 'pointer',
58
+ lineHeight: 0,
59
+ boxShadow: selected ? `0 0 0 2px ${primary}` : 'none',
60
+ transition: 'box-shadow .2s',
61
+ }}>
62
+ <Avatar size={ITEM_SIZE} src={preset.url}/>
63
+ </button>
64
+ </Tooltip>;
65
+ })}
66
+
67
+ <FieldUploadImage value={uploaded}
68
+ onChange={(v) => onChange?.(v || '')}
69
+ maxCount={1}
70
+ shape="circle"
71
+ size={ITEM_SIZE}
72
+ triggerTitle="上传自定义头像"
73
+ defaultCropRatio={{width: 1, height: 1}}/>
74
+ </div>;
75
+ }
76
+ }