@jiangood/open-admin 3.1.10 → 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 (59) hide show
  1. package/package.json +3 -3
  2. package/src/framework/GlobalData.ts +4 -0
  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/CronEditor/index.tsx +318 -0
  8. package/src/framework/components/LinkButton/index.tsx +4 -0
  9. package/src/framework/components/OrgSwitcher/index.tsx +2 -2
  10. package/src/framework/components/Page/index.tsx +6 -14
  11. package/src/framework/components/ProTable/index.tsx +3 -1
  12. package/src/framework/components/ThemeSettings/index.tsx +5 -38
  13. package/src/framework/components/index.ts +1 -0
  14. package/src/framework/config/index.ts +12 -0
  15. package/src/framework/config/layoutConfig.ts +47 -8
  16. package/src/framework/config/themeConfig.ts +93 -25
  17. package/src/framework/config/uiDefaults.ts +26 -0
  18. package/src/framework/fields/FieldEditor/index.tsx +1 -1
  19. package/src/framework/fields/FieldUploadFile/index.tsx +1 -1
  20. package/src/framework/fields/FieldUploadImage/index.tsx +42 -10
  21. package/src/framework/fields/FieldUserPicker/index.tsx +3 -3
  22. package/src/framework/fields/orgTree.ts +1 -1
  23. package/src/framework/router/index.ts +1 -0
  24. package/src/framework/router/matcher.ts +19 -4
  25. package/src/framework/utils/AvatarUtils.ts +64 -0
  26. package/src/framework/utils/CronUtils.ts +562 -0
  27. package/src/framework/utils/HttpClient.ts +5 -5
  28. package/src/framework/utils/PageUtils.ts +38 -7
  29. package/src/framework/utils/TitleUtils.ts +38 -0
  30. package/src/framework/utils/UrlUtils.ts +3 -3
  31. package/src/framework/utils/index.ts +3 -0
  32. package/src/layouts/admin/HeaderRight.jsx +62 -16
  33. package/src/layouts/admin/SinglePageLayout.jsx +13 -2
  34. package/src/layouts/admin/TabLayout.jsx +38 -19
  35. package/src/layouts/admin/index.jsx +12 -3
  36. package/src/layouts/index.tsx +52 -15
  37. package/src/pages/404.jsx +4 -0
  38. package/src/pages/article/$code.jsx +5 -1
  39. package/src/pages/login.jsx +157 -0
  40. package/src/pages/{public/login.less → login.less} +16 -1
  41. package/src/pages/public/login.jsx +11 -145
  42. package/src/pages/standalone/{forceUpdatePwd.jsx → force-update-pwd.jsx} +10 -4
  43. package/src/pages/system/article/index.jsx +1 -1
  44. package/src/pages/system/config/index.jsx +335 -0
  45. package/src/pages/system/file/index.jsx +3 -3
  46. package/src/pages/system/job/index.jsx +62 -19
  47. package/src/pages/system/log/index.jsx +1 -1
  48. package/src/pages/system/online/index.jsx +95 -0
  49. package/src/pages/system/org/index.jsx +7 -7
  50. package/src/pages/system/role/index.jsx +8 -8
  51. package/src/pages/system/role/{rolePerm.jsx → role-perm.jsx} +9 -4
  52. package/src/pages/system/user/{userPerm.jsx → UserPermModal.jsx} +3 -3
  53. package/src/pages/system/user/index.jsx +6 -6
  54. package/src/pages/user-center/AvatarPicker.jsx +76 -0
  55. package/src/pages/{userCenter/permView.jsx → user-center/PermView.jsx} +48 -25
  56. package/src/pages/user-center/RecentOps.jsx +36 -0
  57. package/src/pages/user-center/index.jsx +393 -0
  58. package/vite-plugin/index.js +64 -6
  59. package/src/pages/userCenter/index.jsx +0 -93
@@ -0,0 +1,157 @@
1
+ import React from 'react';
2
+ import {Button, Form, Input} from 'antd';
3
+ import {LockOutlined, SafetyCertificateOutlined, UserOutlined, WarningOutlined} from '@ant-design/icons';
4
+ import {EventBus, HttpClient, GlobalData, getMessageApi, history} from "../framework";
5
+
6
+ import "./login.less"
7
+
8
+ export const pageMeta = {
9
+ requiresAuth: false,
10
+ layout: 'blank',
11
+ title: '登录',
12
+ };
13
+
14
+ function getRedirect(query) {
15
+ let redirect = query?.redirect;
16
+ if (!redirect) {
17
+ redirect = '/'
18
+ }
19
+ return redirect;
20
+ }
21
+
22
+ function postLogin(values, query, success, error) {
23
+ HttpClient.post('/admin/auth/login', values, null, {toastError: false}).then(rs => {
24
+ EventBus.emit('loginSuccess')
25
+ history.push(getRedirect(query))
26
+ success?.(rs)
27
+ }).catch(e => {
28
+ console.error('[Login] 登录失败:', e);
29
+ getMessageApi().error(HttpClient.errToMsg(e))
30
+ error?.(e)
31
+ })
32
+ }
33
+
34
+ function encodePassword(pwd) {
35
+ const chars = [];
36
+ for (let i = 0; i < pwd.length; i++)
37
+ chars.push(pwd.codePointAt(i) + i + 2);
38
+ return btoa(String.fromCodePoint(...chars));
39
+ }
40
+
41
+ export default class LoginPage extends React.Component {
42
+
43
+ state = {
44
+ logging: false,
45
+ siteInfo: {},
46
+ /* logo.png 缺失或加载失败时,回退到矢量标记,避免首屏出现破损图 */
47
+ logoError: false
48
+ }
49
+
50
+ componentDidMount() {
51
+ const siteInfo = GlobalData.getSiteInfo()
52
+ if (siteInfo?.title) {
53
+ this.setState({siteInfo})
54
+ return
55
+ }
56
+ // localStorage 中无站点信息,从服务端重新加载
57
+ HttpClient.get('/admin/public/site-info', null, {toastError: false}).then(rs => {
58
+ GlobalData.setSiteInfo(rs.data)
59
+ this.setState({siteInfo: rs.data})
60
+ }).catch(() => {
61
+ console.error('[Login] 加载站点信息失败');
62
+ getMessageApi().error('加载站点信息失败,请刷新页面重试')
63
+ })
64
+ }
65
+
66
+ submit = values => {
67
+ this.setState({logging: true})
68
+ try {
69
+ values.password = encodePassword(values.password)
70
+ } catch (e) {
71
+ console.error('[Login] 密码编码失败:', e);
72
+ this.setState({logging: false})
73
+ getMessageApi().error('密码含不支持字符,请联系管理员重置')
74
+ return
75
+ }
76
+ postLogin(values, this.props.location?.query, () => {
77
+ this.setState({logging: false})
78
+ }, () => {
79
+ this.setState({logging: false})
80
+ })
81
+ }
82
+
83
+ render() {
84
+ const pageStyle = {
85
+ backgroundImage: `url("./login_bg.jpg")`
86
+ }
87
+
88
+ return (
89
+ <section className='login-page' style={pageStyle}>
90
+ <div className="login-content">
91
+ {this.renderBrand()}
92
+ {this.getForm()}
93
+ {this.renderFormBottom()}
94
+ </div>
95
+ </section>
96
+ );
97
+ }
98
+
99
+ renderBrand() {
100
+ const {siteInfo = {}, logoError} = this.state
101
+ return (
102
+ <div className="login-brand">
103
+ <span className="login-brand-logo">
104
+ {logoError
105
+ ? <SafetyCertificateOutlined/>
106
+ : <img src="./logo.png" alt="" onError={() => this.setState({logoError: true})}/>}
107
+ </span>
108
+ {siteInfo.title && <h1>{siteInfo.title}</h1>}
109
+ </div>
110
+ )
111
+ }
112
+
113
+ getForm = () => {
114
+ const form = (
115
+ <Form
116
+ name="normal_login"
117
+ className="login-form"
118
+ initialValues={{remember: true}}
119
+ onFinish={this.submit}
120
+ requiredMark={false}
121
+ colon={false}
122
+ >
123
+ <Form.Item name="username" rules={[{required: true, message: '请输入用户名!'}]}>
124
+ <Input size='large' prefix={<UserOutlined/>} placeholder="用户名" autoComplete="username"/>
125
+ </Form.Item>
126
+ <Form.Item name="password" rules={[{required: true, message: '请输入密码!'}]}>
127
+ <Input.Password autoComplete="current-password" prefix={<LockOutlined/>} placeholder="密码"
128
+ size='large'
129
+ />
130
+ </Form.Item>
131
+
132
+ <Form.Item className="login-submit">
133
+ <Button loading={this.state.logging} type="primary" htmlType="submit"
134
+ block size='large'>
135
+ 登录
136
+ </Button>
137
+ </Form.Item>
138
+ </Form>
139
+ );
140
+
141
+ if (this.props.formRender) {
142
+ return this.props.formRender(form);
143
+ }
144
+ return form;
145
+ };
146
+
147
+ renderFormBottom() {
148
+ const siteInfo = this.state.siteInfo || {};
149
+ if (siteInfo.loginBoxBottomTip) {
150
+ return (
151
+ <div className="login-bottom-tip">
152
+ <WarningOutlined/> {siteInfo.loginBoxBottomTip}
153
+ </div>
154
+ )
155
+ }
156
+ }
157
+ }
@@ -128,6 +128,9 @@
128
128
  height: 46px;
129
129
  padding: 0 14px;
130
130
  border-radius: var(--login-field-radius);
131
+ /* antd v6 给内部 input 声明了 `color: inherit`,文字色由容器决定。
132
+ 容器必须显式给浅色,否则会继承 antd 浅色主题的 colorText(黑字压在深色玻璃上不可读) */
133
+ color: var(--login-text);
131
134
  background-color: var(--login-field-bg);
132
135
  border: 1px solid var(--login-hairline);
133
136
  box-shadow: none;
@@ -154,10 +157,22 @@
154
157
  /* 密码可见性切换按钮 */
155
158
  .ant-input-suffix {
156
159
  color: rgba(255, 255, 255, 0.55);
160
+
161
+ /* antd 给密码图标单独着色(colorIcon),不跟随 .ant-input-suffix,需显式覆盖 */
162
+ .ant-input-password-icon {
163
+ color: inherit;
164
+
165
+ &:hover {
166
+ color: var(--login-text);
167
+ }
168
+ }
157
169
  }
158
170
  }
159
171
 
160
- .ant-input {
172
+ /* antd v6 的 `> input.ant-input { color: inherit }` 特异性为 0,2,1,
173
+ 会压过 .login-form .ant-input(0,2,0);这里提升特异性后显式上色,
174
+ 避免 antd 调整内部选择器时文字色再次失效 */
175
+ .login-page & .ant-input {
161
176
  color: var(--login-text);
162
177
  font-size: 15px;
163
178
  background: transparent;
@@ -1,151 +1,17 @@
1
- import React from 'react';
2
- import {Button, Form, Input} from 'antd';
3
- import {LockOutlined, SafetyCertificateOutlined, UserOutlined, WarningOutlined} from '@ant-design/icons';
4
- import {EventBus, HttpClient, GlobalData, getMessageApi, history} from "../../framework";
5
-
6
- import "./login.less"
7
-
8
- function getRedirect(query) {
9
- let redirect = query?.redirect;
10
- if (!redirect) {
11
- redirect = '/'
12
- }
13
- return redirect;
14
- }
15
-
16
- function postLogin(values, query, success, error) {
17
- HttpClient.post('/admin/auth/login', values, null, {toastError: false}).then(rs => {
18
- EventBus.emit('loginSuccess')
19
- history.push(getRedirect(query))
20
- success?.(rs)
21
- }).catch(e => {
22
- console.error('[Login] 登录失败:', e);
23
- getMessageApi().error(HttpClient.errToMsg(e))
24
- error?.(e)
25
- })
26
- }
27
-
28
- function encodePassword(pwd) {
29
- const chars = [];
30
- for (let i = 0; i < pwd.length; i++)
31
- chars.push(pwd.codePointAt(i) + i + 2);
32
- return btoa(String.fromCodePoint(...chars));
33
- }
34
-
35
- export default class LoginPage extends React.Component {
36
-
37
- state = {
38
- logging: false,
39
- siteInfo: {},
40
- /* logo.png 缺失或加载失败时,回退到矢量标记,避免首屏出现破损图 */
41
- logoError: false
42
- }
43
-
1
+ import React from "react";
2
+ import {history, LOGIN_PATH} from "../../framework";
3
+
4
+ /**
5
+ * 旧版登录地址兼容:/public/login?redirect=... → /login?redirect=...
6
+ * 保留一个版本后删除
7
+ */
8
+ export default class LegacyLoginRedirect extends React.Component {
44
9
  componentDidMount() {
45
- const siteInfo = GlobalData.getSiteInfo()
46
- if (siteInfo?.title) {
47
- this.setState({siteInfo})
48
- return
49
- }
50
- // localStorage 中无站点信息,从服务端重新加载
51
- HttpClient.get('/admin/public/site-info', null, {toastError: false}).then(rs => {
52
- GlobalData.setSiteInfo(rs.data)
53
- this.setState({siteInfo: rs.data})
54
- }).catch(() => {
55
- console.error('[Login] 加载站点信息失败');
56
- getMessageApi().error('加载站点信息失败,请刷新页面重试')
57
- })
58
- }
59
-
60
- submit = values => {
61
- this.setState({logging: true})
62
- try {
63
- values.password = encodePassword(values.password)
64
- } catch (e) {
65
- console.error('[Login] 密码编码失败:', e);
66
- this.setState({logging: false})
67
- getMessageApi().error('密码含不支持字符,请联系管理员重置')
68
- return
69
- }
70
- postLogin(values, this.props.location?.query, () => {
71
- this.setState({logging: false})
72
- }, () => {
73
- this.setState({logging: false})
74
- })
10
+ const search = this.props.location?.search || '';
11
+ history.replace(LOGIN_PATH + search);
75
12
  }
76
13
 
77
14
  render() {
78
- const pageStyle = {
79
- backgroundImage: `url("./login_bg.jpg")`
80
- }
81
-
82
- return (
83
- <section className='login-page' style={pageStyle}>
84
- <div className="login-content">
85
- {this.renderBrand()}
86
- {this.getForm()}
87
- {this.renderFormBottom()}
88
- </div>
89
- </section>
90
- );
91
- }
92
-
93
- renderBrand() {
94
- const {siteInfo = {}, logoError} = this.state
95
- return (
96
- <div className="login-brand">
97
- <span className="login-brand-logo">
98
- {logoError
99
- ? <SafetyCertificateOutlined/>
100
- : <img src="./logo.png" alt="" onError={() => this.setState({logoError: true})}/>}
101
- </span>
102
- {siteInfo.title && <h1>{siteInfo.title}</h1>}
103
- </div>
104
- )
105
- }
106
-
107
- getForm = () => {
108
- const form = (
109
- <Form
110
- name="normal_login"
111
- className="login-form"
112
- initialValues={{remember: true}}
113
- onFinish={this.submit}
114
- requiredMark={false}
115
- colon={false}
116
- >
117
- <Form.Item name="username" rules={[{required: true, message: '请输入用户名!'}]}>
118
- <Input size='large' prefix={<UserOutlined/>} placeholder="用户名" autoComplete="username"/>
119
- </Form.Item>
120
- <Form.Item name="password" rules={[{required: true, message: '请输入密码!'}]}>
121
- <Input.Password autoComplete="current-password" prefix={<LockOutlined/>} placeholder="密码"
122
- size='large'
123
- />
124
- </Form.Item>
125
-
126
- <Form.Item className="login-submit">
127
- <Button loading={this.state.logging} type="primary" htmlType="submit"
128
- block size='large'>
129
- 登录
130
- </Button>
131
- </Form.Item>
132
- </Form>
133
- );
134
-
135
- if (this.props.formRender) {
136
- return this.props.formRender(form);
137
- }
138
- return form;
139
- };
140
-
141
- renderFormBottom() {
142
- const siteInfo = this.state.siteInfo || {};
143
- if (siteInfo.loginBoxBottomTip) {
144
- return (
145
- <div className="login-bottom-tip">
146
- <WarningOutlined/> {siteInfo.loginBoxBottomTip}
147
- </div>
148
- )
149
- }
15
+ return null;
150
16
  }
151
17
  }
@@ -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 => {