@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.
- package/package.json +3 -3
- package/src/framework/GlobalData.ts +3 -1
- package/src/framework/biz/OrgTree.tsx +1 -1
- package/src/framework/biz/RoleTree.tsx +1 -1
- package/src/framework/biz/UserPicker.tsx +4 -4
- package/src/framework/biz/index.tsx +4 -4
- package/src/framework/components/LinkButton/index.tsx +4 -0
- package/src/framework/components/OrgSwitcher/index.tsx +2 -2
- package/src/framework/components/Page/index.tsx +6 -14
- package/src/framework/components/ProTable/index.tsx +3 -1
- package/src/framework/components/ThemeSettings/index.tsx +5 -38
- package/src/framework/config/index.ts +12 -0
- package/src/framework/config/layoutConfig.ts +47 -8
- package/src/framework/config/themeConfig.ts +93 -25
- package/src/framework/config/uiDefaults.ts +26 -0
- package/src/framework/fields/FieldEditor/index.tsx +1 -1
- package/src/framework/fields/FieldUploadFile/index.tsx +1 -1
- package/src/framework/fields/FieldUploadImage/index.tsx +42 -10
- package/src/framework/fields/FieldUserPicker/index.tsx +3 -3
- package/src/framework/fields/orgTree.ts +1 -1
- package/src/framework/router/index.ts +1 -0
- package/src/framework/router/matcher.ts +19 -4
- package/src/framework/utils/AvatarUtils.ts +64 -0
- package/src/framework/utils/HttpClient.ts +5 -5
- package/src/framework/utils/PageUtils.ts +38 -7
- package/src/framework/utils/TitleUtils.ts +38 -0
- package/src/framework/utils/UrlUtils.ts +3 -3
- package/src/framework/utils/index.ts +2 -0
- package/src/layouts/admin/HeaderRight.jsx +62 -16
- package/src/layouts/admin/SinglePageLayout.jsx +13 -2
- package/src/layouts/admin/TabLayout.jsx +38 -19
- package/src/layouts/admin/index.jsx +4 -4
- package/src/layouts/index.tsx +52 -15
- package/src/pages/404.jsx +4 -0
- package/src/pages/article/$code.jsx +5 -1
- package/src/pages/login.jsx +157 -0
- package/src/pages/{public/login.less → login.less} +16 -1
- package/src/pages/public/login.jsx +11 -145
- package/src/pages/standalone/{forceUpdatePwd.jsx → force-update-pwd.jsx} +10 -4
- package/src/pages/system/article/index.jsx +1 -1
- package/src/pages/system/config/index.jsx +46 -20
- package/src/pages/system/file/index.jsx +3 -3
- package/src/pages/system/job/index.jsx +1 -1
- package/src/pages/system/log/index.jsx +1 -1
- package/src/pages/system/online/index.jsx +95 -0
- package/src/pages/system/org/index.jsx +7 -7
- package/src/pages/system/role/index.jsx +8 -8
- package/src/pages/system/role/{rolePerm.jsx → role-perm.jsx} +9 -4
- package/src/pages/system/user/{userPerm.jsx → UserPermModal.jsx} +3 -3
- package/src/pages/system/user/index.jsx +6 -6
- package/src/pages/user-center/AvatarPicker.jsx +76 -0
- package/src/pages/{userCenter/permView.jsx → user-center/PermView.jsx} +48 -25
- package/src/pages/user-center/RecentOps.jsx +36 -0
- package/src/pages/user-center/index.jsx +393 -0
- package/vite-plugin/index.js +64 -6
- 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/
|
|
14
|
+
HttpClient.post('admin/user-center/update-pwd', values, null).then(() => {
|
|
9
15
|
getMessageApi().success('修改密码成功,请重新登录');
|
|
10
|
-
history.push(
|
|
16
|
+
history.push(LOGIN_PATH);
|
|
11
17
|
})
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
validator = (rule, value) => HttpClient.get("admin/
|
|
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
|
})
|
|
@@ -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={
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
280
|
-
</div>
|
|
306
|
+
</Card>
|
|
281
307
|
))}
|
|
308
|
+
</div>
|
|
282
309
|
|
|
283
|
-
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
190
|
-
usersUrl='admin/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 "./
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
+
}
|