@jiangood/open-admin 3.0.14 → 3.1.0
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 +15 -2
- package/src/framework/biz/OrgTree.tsx +10 -6
- package/src/framework/biz/PermActions.tsx +5 -3
- package/src/framework/biz/RoleTree.tsx +10 -6
- package/src/framework/biz/index.tsx +1 -0
- package/src/framework/components/ContextMenu/index.tsx +17 -1
- package/src/framework/components/DownloadModal/index.tsx +40 -33
- package/src/framework/components/FormModal/index.tsx +10 -8
- package/src/framework/components/NamedIcon/index.tsx +2 -2
- package/src/framework/components/OrgSwitcher/index.tsx +6 -7
- package/src/framework/components/PageLoading/index.tsx +3 -3
- package/src/framework/components/ProTable/index.tsx +28 -25
- package/src/framework/components/ValueType/index.tsx +1 -1
- package/src/framework/config/themeConfig.ts +5 -5
- package/src/framework/fields/BaseRemoteSelect/index.tsx +23 -21
- package/src/framework/fields/FieldBoolean/index.tsx +6 -6
- package/src/framework/fields/FieldDate/index.tsx +10 -10
- package/src/framework/fields/FieldDateRange/index.tsx +20 -20
- package/src/framework/fields/FieldDictSelect/index.tsx +2 -2
- package/src/framework/fields/FieldEditor/index.tsx +1 -1
- package/src/framework/fields/FieldNumberRange/index.tsx +16 -17
- package/src/framework/fields/FieldPercent/index.tsx +2 -2
- package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -4
- package/src/framework/fields/FieldRemoteTree/index.tsx +1 -1
- package/src/framework/fields/FieldRemoteTreeCascader/index.tsx +1 -1
- package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +1 -1
- package/src/framework/fields/FieldSysOrgTree/index.tsx +1 -1
- package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +1 -1
- package/src/framework/fields/FieldTable/index.tsx +15 -13
- package/src/framework/fields/FieldTableSelect/index.tsx +5 -5
- package/src/framework/fields/FieldUploadFile/index.tsx +3 -3
- package/src/framework/fields/FieldUploadImage/index.tsx +31 -29
- package/src/framework/fields/types.ts +0 -2
- package/src/framework/router/PageFrame.tsx +1 -1
- package/src/framework/router/matcher.ts +2 -2
- package/src/framework/utils/ArrayUtils.ts +2 -2
- package/src/framework/utils/DateUtils.ts +1 -1
- package/src/framework/utils/{system/DictUtils.ts → DictUtils.ts} +1 -1
- package/src/framework/utils/EventBus.ts +7 -7
- package/src/framework/utils/HttpClient.ts +283 -0
- package/src/framework/utils/{system/PageUtils.ts → PageUtils.ts} +4 -4
- package/src/framework/utils/{system/PermUtils.ts → PermUtils.ts} +3 -3
- package/src/framework/utils/StringUtils.ts +10 -8
- package/src/framework/utils/TreeUtils.ts +3 -3
- package/src/framework/utils/UrlUtils.ts +3 -2
- package/src/framework/utils/index.ts +4 -1
- package/src/framework/views/ViewApproveStatus/index.tsx +1 -2
- package/src/framework/views/ViewBoolean/index.tsx +4 -1
- package/src/framework/views/ViewFile/index.tsx +4 -4
- package/src/framework/views/ViewImage/index.tsx +2 -2
- package/src/framework/views/ViewPassword/index.tsx +3 -2
- package/src/framework/views/ViewText/index.tsx +12 -3
- package/src/framework/views/types.ts +2 -4
- package/src/layouts/admin/AdminTabs.jsx +9 -0
- package/src/layouts/admin/AdminTabs.less +2 -2
- package/src/layouts/admin/HeaderRight.jsx +8 -7
- package/src/layouts/admin/TabLayout.jsx +2 -2
- package/src/layouts/admin/index.jsx +15 -11
- package/src/layouts/admin/index.less +16 -1
- package/src/layouts/index.tsx +24 -18
- package/src/pages/404.jsx +1 -1
- package/src/pages/article/$code.jsx +4 -4
- package/src/pages/index.jsx +1 -1
- package/src/pages/public/login.jsx +34 -27
- package/src/pages/public/test.jsx +1 -1
- package/src/pages/standalone/forceUpdatePwd.jsx +9 -7
- package/src/pages/system/article/index.jsx +9 -8
- package/src/pages/system/dict/index.jsx +19 -16
- package/src/pages/system/file/index.jsx +9 -9
- package/src/pages/system/job/index.jsx +17 -17
- package/src/pages/system/log/index.jsx +5 -7
- package/src/pages/system/org/index.jsx +31 -18
- package/src/pages/system/role/index.jsx +13 -12
- package/src/pages/system/role/rolePerm.jsx +25 -21
- package/src/pages/system/user/index.jsx +35 -33
- package/src/pages/system/user/userPerm.jsx +12 -12
- package/src/pages/userCenter/index.jsx +11 -9
- package/src/pages/userCenter/permView.jsx +7 -7
- package/src/framework/utils/system/HttpUtils.ts +0 -97
- package/src/framework/utils/system/index.ts +0 -4
|
@@ -3,14 +3,14 @@ export class StringUtils {
|
|
|
3
3
|
static readonly ISO_SPLITTER: string = "/";
|
|
4
4
|
|
|
5
5
|
static removePrefix(str: string | null | undefined, ch: string): string {
|
|
6
|
-
if (str
|
|
6
|
+
if (str?.startsWith(ch)) {
|
|
7
7
|
return str.substring(ch.length);
|
|
8
8
|
}
|
|
9
9
|
return str ?? '';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
static removeSuffix(str: string | null | undefined, ch: string): string {
|
|
13
|
-
if (str
|
|
13
|
+
if (str?.endsWith(ch)) {
|
|
14
14
|
return str.substring(0, str.length - ch.length);
|
|
15
15
|
}
|
|
16
16
|
return str ?? '';
|
|
@@ -18,9 +18,11 @@ export class StringUtils {
|
|
|
18
18
|
|
|
19
19
|
static random(length: number): string {
|
|
20
20
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
21
|
+
const values = new Uint32Array(length);
|
|
22
|
+
crypto.getRandomValues(values);
|
|
21
23
|
let result = '';
|
|
22
24
|
for (let i = 0; i < length; i++) {
|
|
23
|
-
result += characters.charAt(
|
|
25
|
+
result += characters.charAt(values[i] % characters.length);
|
|
24
26
|
}
|
|
25
27
|
return result;
|
|
26
28
|
}
|
|
@@ -61,7 +63,7 @@ export class StringUtils {
|
|
|
61
63
|
static getWidth(str: string | null | undefined): number {
|
|
62
64
|
if (str == null || str.length === 0) return 0;
|
|
63
65
|
return str.split('').reduce((pre, cur) => {
|
|
64
|
-
const charCode = cur.
|
|
66
|
+
const charCode = cur.codePointAt(0);
|
|
65
67
|
return pre + (charCode >= 0 && charCode <= 128 ? 1 : 2);
|
|
66
68
|
}, 0);
|
|
67
69
|
}
|
|
@@ -69,7 +71,7 @@ export class StringUtils {
|
|
|
69
71
|
static cutByWidth(str: string, maxWidth: number): string {
|
|
70
72
|
let showLength = 0;
|
|
71
73
|
return str.split('').reduce((pre, cur) => {
|
|
72
|
-
const charCode = cur.
|
|
74
|
+
const charCode = cur.codePointAt(0);
|
|
73
75
|
const charWidth = (charCode >= 0 && charCode <= 128) ? 1 : 2;
|
|
74
76
|
if (showLength + charWidth <= maxWidth) {
|
|
75
77
|
showLength += charWidth;
|
|
@@ -88,17 +90,17 @@ export class StringUtils {
|
|
|
88
90
|
return StringUtils.cutByWidth(str, len) + suffix;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
static isString(value:
|
|
93
|
+
static isString(value: unknown): value is string {
|
|
92
94
|
return typeof value === 'string';
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
static split(str:
|
|
97
|
+
static split(str: string | string[] | null | undefined, sp: string): string[] {
|
|
96
98
|
if (str == null || str.length === 0) return [];
|
|
97
99
|
if (Array.isArray(str)) return str;
|
|
98
100
|
return str.split(sp);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
static join(arr:
|
|
103
|
+
static join(arr: unknown, sp: string): string {
|
|
102
104
|
if (arr == null || !Array.isArray(arr)) {
|
|
103
105
|
return '';
|
|
104
106
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export interface TreeNode<T =
|
|
1
|
+
export interface TreeNode<T = unknown> {
|
|
2
2
|
id: string | number;
|
|
3
3
|
pid: string | number | null;
|
|
4
4
|
children?: TreeNode<T>[];
|
|
5
|
-
[key: string]:
|
|
5
|
+
[key: string]: unknown;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export class TreeUtils {
|
|
@@ -19,7 +19,7 @@ export class TreeUtils {
|
|
|
19
19
|
static findByKey<T extends TreeNode>(key: string | number, list: T[], keyName: keyof T | 'id' = 'id'): T | undefined {
|
|
20
20
|
for (const item of list) {
|
|
21
21
|
if (item[keyName as keyof T] === key) return item;
|
|
22
|
-
if (item.children
|
|
22
|
+
if (item.children?.length) {
|
|
23
23
|
const rs = TreeUtils.findByKey(key, item.children as T[], keyName);
|
|
24
24
|
if (rs) return rs;
|
|
25
25
|
}
|
|
@@ -28,7 +28,7 @@ export class UrlUtils {
|
|
|
28
28
|
if (!params) return "";
|
|
29
29
|
const buffer: string[] = [];
|
|
30
30
|
for (const k in params) {
|
|
31
|
-
if (Object.
|
|
31
|
+
if (Object.hasOwn(params, k)) {
|
|
32
32
|
const v = params[k];
|
|
33
33
|
if (v !== null && v !== undefined) {
|
|
34
34
|
buffer.push(`${k}=${v}`);
|
|
@@ -66,6 +66,7 @@ export class UrlUtils {
|
|
|
66
66
|
*/
|
|
67
67
|
static contextPath(path: string): string {
|
|
68
68
|
const base = import.meta.env.VITE_SERVER_SERVLET_CONTEXT_PATH;
|
|
69
|
-
|
|
69
|
+
if (base === '/' || base === '') return path;
|
|
70
|
+
return base.replace(/\/+$/, '') + path; // NOSONAR: 需去除尾部斜杠,正则已最简
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -7,6 +7,9 @@ export * from "./TreeUtils";
|
|
|
7
7
|
export * from './StorageUtils'
|
|
8
8
|
export * from './DeviceUtils'
|
|
9
9
|
export * from './ObjectUtils'
|
|
10
|
-
export * from './
|
|
10
|
+
export * from './DictUtils'
|
|
11
|
+
export * from './PermUtils'
|
|
12
|
+
export * from './PageUtils'
|
|
13
|
+
export * from './HttpClient'
|
|
11
14
|
export * from './MessageApi'
|
|
12
15
|
export * from './ModalApi'
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {DictUtils} from "../../utils";
|
|
2
|
-
import {Tag} from "antd";
|
|
3
2
|
import {Component} from "react";
|
|
4
3
|
import type {ViewProps} from "../types";
|
|
5
4
|
|
|
6
|
-
export class ViewApproveStatus extends Component<ViewProps<
|
|
5
|
+
export class ViewApproveStatus extends Component<ViewProps<string>> {
|
|
7
6
|
render() {
|
|
8
7
|
const {value} = this.props;
|
|
9
8
|
return DictUtils.dictTag('approveStatus', value);
|
|
@@ -4,6 +4,9 @@ import type {ViewProps} from "../types";
|
|
|
4
4
|
export class ViewBoolean extends React.Component<ViewProps<boolean>> {
|
|
5
5
|
render() {
|
|
6
6
|
const {value} = this.props;
|
|
7
|
-
|
|
7
|
+
if (value == null) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return value ? '是' : '否';
|
|
8
11
|
}
|
|
9
12
|
}
|
|
@@ -23,8 +23,8 @@ export class ViewFile extends React.Component {
|
|
|
23
23
|
return <iframe
|
|
24
24
|
src={url}
|
|
25
25
|
width='99%'
|
|
26
|
-
|
|
27
|
-
style={{height}}
|
|
26
|
+
title="文件预览"
|
|
27
|
+
style={{height, border: 0}}
|
|
28
28
|
/>
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -34,8 +34,8 @@ export class ViewFile extends React.Component {
|
|
|
34
34
|
<iframe
|
|
35
35
|
src={url}
|
|
36
36
|
width='99%'
|
|
37
|
-
|
|
38
|
-
style={{height}}
|
|
37
|
+
title="文件预览"
|
|
38
|
+
style={{height, border: 0}}
|
|
39
39
|
/>
|
|
40
40
|
</div>
|
|
41
41
|
});
|
|
@@ -16,7 +16,7 @@ import {UrlUtils} from '../../utils';
|
|
|
16
16
|
*/
|
|
17
17
|
export class ViewImage extends React.Component {
|
|
18
18
|
|
|
19
|
-
static previewGroupProps = {
|
|
19
|
+
static readonly previewGroupProps = {
|
|
20
20
|
preview: {
|
|
21
21
|
scaleStep: 0.3,
|
|
22
22
|
},
|
|
@@ -61,7 +61,7 @@ export class ViewImage extends React.Component {
|
|
|
61
61
|
|
|
62
62
|
return (
|
|
63
63
|
<Image.PreviewGroup preview={previewConfig}>
|
|
64
|
-
{items.map((item
|
|
64
|
+
{items.map((item) => (
|
|
65
65
|
<Image
|
|
66
66
|
key={item.url}
|
|
67
67
|
src={item.thumb}
|
|
@@ -16,9 +16,10 @@ export class ViewPassword extends React.Component {
|
|
|
16
16
|
const visible = this.state.visible;
|
|
17
17
|
return <Space>
|
|
18
18
|
<span>{this.state.visible ? v : '******'}</span>
|
|
19
|
-
<
|
|
19
|
+
<button type="button" style={{background: 'none', border: 'none', padding: 0, font: 'inherit', cursor: 'pointer'}}
|
|
20
|
+
onClick={() => this.setState({visible: !visible})}>
|
|
20
21
|
{visible ? <EyeOutlined/> : <EyeInvisibleOutlined/>}
|
|
21
|
-
</
|
|
22
|
+
</button>
|
|
22
23
|
</Space>
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -8,7 +8,7 @@ export class ViewText extends React.Component {
|
|
|
8
8
|
modalOpen: false,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
static defaultProps = {
|
|
11
|
+
static readonly defaultProps = {
|
|
12
12
|
ellipsis: false,
|
|
13
13
|
maxLength: 15
|
|
14
14
|
}
|
|
@@ -27,9 +27,18 @@ export class ViewText extends React.Component {
|
|
|
27
27
|
const short = StringUtils.ellipsis(value, maxLength);
|
|
28
28
|
return (
|
|
29
29
|
<Tooltip placement="topLeft" title={value}>
|
|
30
|
-
<
|
|
30
|
+
<button type="button"
|
|
31
|
+
style={{
|
|
32
|
+
background: 'none',
|
|
33
|
+
border: 'none',
|
|
34
|
+
borderBottom: '1px dashed #d9d9d9',
|
|
35
|
+
padding: 0,
|
|
36
|
+
font: 'inherit',
|
|
37
|
+
cursor: 'pointer',
|
|
38
|
+
}}
|
|
39
|
+
onClick={() => this.setState({modalOpen: true})}>
|
|
31
40
|
{short}
|
|
32
|
-
</
|
|
41
|
+
</button>
|
|
33
42
|
<Modal open={this.state.modalOpen} title="长文本内容" width={700} footer={null}
|
|
34
43
|
onCancel={() => this.setState({modalOpen: false})}>
|
|
35
44
|
<div style={{height: 500, overflowY: 'auto'}}>{value}</div>
|
|
@@ -15,12 +15,10 @@ export interface ViewProps<T> {
|
|
|
15
15
|
* 通用视图组件类类型
|
|
16
16
|
* @template T - value 的类型
|
|
17
17
|
*/
|
|
18
|
-
export
|
|
19
|
-
}
|
|
18
|
+
export type ViewComponent<T> = React.ComponentClass<ViewProps<T>>;
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* 通用视图组件函数类型
|
|
23
22
|
* @template T - value 的类型
|
|
24
23
|
*/
|
|
25
|
-
export
|
|
26
|
-
}
|
|
24
|
+
export type ViewFunctionComponent<T> = React.FC<ViewProps<T>>;
|
|
@@ -43,7 +43,16 @@ export class AdminTabs extends React.Component {
|
|
|
43
43
|
<div
|
|
44
44
|
key={tab.key}
|
|
45
45
|
className={`admin-tab${tab.key === activeKey ? ' active' : ''}`}
|
|
46
|
+
role="tab"
|
|
47
|
+
tabIndex={tab.key === activeKey ? 0 : -1}
|
|
48
|
+
aria-selected={tab.key === activeKey}
|
|
46
49
|
onClick={() => onChange(tab.key)}
|
|
50
|
+
onKeyDown={e => {
|
|
51
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
onChange(tab.key);
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
47
56
|
onContextMenu={e => this.handleContextMenu(e, tab.key)}
|
|
48
57
|
>
|
|
49
58
|
<span className="admin-tab-label">{tab.label}</span>
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
height: 16px;
|
|
53
53
|
font-size: 12px;
|
|
54
54
|
border-radius: 50%;
|
|
55
|
-
color: rgba(0, 0, 0, 0.45);
|
|
55
|
+
color: rgba(0, 0, 0, 0.45); /* NOSONAR: 次要文本颜色,设计如此 */
|
|
56
56
|
|
|
57
57
|
&:hover {
|
|
58
58
|
background: rgba(0, 0, 0, 0.08);
|
|
59
|
-
color: rgba(0, 0, 0, 0.88);
|
|
59
|
+
color: rgba(0, 0, 0, 0.88); /* NOSONAR: 悬停态主文本色,对比度设计如此 */
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Avatar, Dropdown, Modal} from "antd";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import {DeviceUtils,
|
|
3
|
+
import {DeviceUtils, HttpClient, PageUtils, GlobalData, getToken, history, EventBus, ARTICLE_HEADER_AVATAR_DROPDOWN, ARTICLE_HEADER_RIGHT} from "../../framework";
|
|
4
4
|
|
|
5
5
|
export class HeaderRight extends React.Component {
|
|
6
6
|
|
|
@@ -28,10 +28,10 @@ export class HeaderRight extends React.Component {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
logout = () => {
|
|
31
|
-
|
|
31
|
+
HttpClient.post('admin/auth/logout', null, null, {toastError: false}).then(() => {
|
|
32
32
|
localStorage.clear()
|
|
33
33
|
this.setState({alertVisible: true})
|
|
34
|
-
}).catch(
|
|
34
|
+
}).catch(e => {
|
|
35
35
|
console.error('[HeaderRight] 退出登录失败:', e);
|
|
36
36
|
this.setState({confirmVisible: true})
|
|
37
37
|
})
|
|
@@ -51,7 +51,8 @@ export class HeaderRight extends React.Component {
|
|
|
51
51
|
|
|
52
52
|
if (this.state.isMobileDevice) {
|
|
53
53
|
return <div className='header-right'>
|
|
54
|
-
<
|
|
54
|
+
<button type="button" style={{background: 'none', border: 'none', padding: 0, font: 'inherit', cursor: 'pointer'}}
|
|
55
|
+
onClick={this.logout}>退出</button>
|
|
55
56
|
</div>
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -68,10 +69,10 @@ export class HeaderRight extends React.Component {
|
|
|
68
69
|
|
|
69
70
|
return <div className='header-right'>
|
|
70
71
|
{headerArticles.map(a => (
|
|
71
|
-
<
|
|
72
|
-
|
|
72
|
+
<button key={a.code} type="button" className='item'
|
|
73
|
+
onClick={() => this.openArticle(a.code, a.title)}>
|
|
73
74
|
{a.title}
|
|
74
|
-
</
|
|
75
|
+
</button>
|
|
75
76
|
))}
|
|
76
77
|
|
|
77
78
|
<Dropdown menu={{
|
|
@@ -34,7 +34,7 @@ export class TabLayout extends React.Component {
|
|
|
34
34
|
const pathname = qIndex === -1 ? url : url.substring(0, qIndex);
|
|
35
35
|
const search = qIndex === -1 ? '' : url.substring(qIndex);
|
|
36
36
|
this.setState(prev => ({
|
|
37
|
-
tabs: prev.tabs.
|
|
37
|
+
tabs: prev.tabs.some(t => t.key === url) ? prev.tabs
|
|
38
38
|
: [...prev.tabs, {key: url, label: getLabel(pathname, search, this.props.pathMenuMap), refreshKey: 0}],
|
|
39
39
|
activeKey: url,
|
|
40
40
|
}));
|
|
@@ -42,7 +42,7 @@ export class TabLayout extends React.Component {
|
|
|
42
42
|
|
|
43
43
|
closeTab = (key) => {
|
|
44
44
|
this.setState(prev => {
|
|
45
|
-
if (!prev.tabs.
|
|
45
|
+
if (!prev.tabs.some(t => t.key === key)) return null;
|
|
46
46
|
const next = prev.tabs.filter(t => t.key !== key);
|
|
47
47
|
if (next.length === 0) {
|
|
48
48
|
setTimeout(() => history.push('/'), 0);
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import {Layout, Menu, Skeleton, Watermark} from 'antd';
|
|
3
3
|
|
|
4
4
|
import "./index.less"
|
|
5
|
-
import {
|
|
5
|
+
import {HttpClient, NamedIcon, PageUtils, GlobalData, TreeUtils, history, Link, ARTICLE_HEADER_LEFT, OrgSwitcher} from "../../framework";
|
|
6
6
|
|
|
7
7
|
import { HeaderRight } from "./HeaderRight";
|
|
8
8
|
import { TabLayout } from "./TabLayout";
|
|
@@ -10,7 +10,7 @@ const {Header, Sider, Content} = Layout;
|
|
|
10
10
|
/**
|
|
11
11
|
* 带菜单的布局,主要处理布局宇框架结构
|
|
12
12
|
*/
|
|
13
|
-
export default class extends React.Component {
|
|
13
|
+
export default class AdminLayout extends React.Component {
|
|
14
14
|
|
|
15
15
|
state = {
|
|
16
16
|
loginInfo: {},
|
|
@@ -50,8 +50,8 @@ export default class extends React.Component {
|
|
|
50
50
|
|
|
51
51
|
initMenu = () => {
|
|
52
52
|
this.setState({menuLoading: true})
|
|
53
|
-
|
|
54
|
-
const {menuTree, pathMenuMap, menuMap} = info
|
|
53
|
+
HttpClient.get('/admin/menu-info', null, {toastError: false}).then(info => {
|
|
54
|
+
const {menuTree, pathMenuMap, menuMap} = info.data
|
|
55
55
|
this.setState({menuMap})
|
|
56
56
|
|
|
57
57
|
const pathname = PageUtils.currentPathname();
|
|
@@ -86,15 +86,15 @@ export default class extends React.Component {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
this.setState({menuTree, pathMenuMap, topMenus, sideMenus, activeTopMenuKey, currentMenuKey})
|
|
89
|
+
this.setState({menuLoading: false})
|
|
89
90
|
|
|
90
91
|
}).catch(err => {
|
|
91
92
|
console.error('加载菜单失败:', err)
|
|
92
93
|
this.setState({menuTree: [], topMenus: [], sideMenus: []})
|
|
93
|
-
}).finally(()=>{
|
|
94
94
|
this.setState({menuLoading: false})
|
|
95
95
|
})
|
|
96
96
|
}
|
|
97
|
-
actionRef = React.createRef()
|
|
97
|
+
actionRef = React.createRef() // NOSONAR: React ref 由父组件通过 ref 访问
|
|
98
98
|
|
|
99
99
|
classifyMenus = (menuTree) => {
|
|
100
100
|
const topMenus = [];
|
|
@@ -166,13 +166,14 @@ export default class extends React.Component {
|
|
|
166
166
|
return (
|
|
167
167
|
<nav className="top-nav">
|
|
168
168
|
{topMenus.map(item => (
|
|
169
|
-
<
|
|
169
|
+
<button
|
|
170
170
|
key={item.key}
|
|
171
|
+
type="button"
|
|
171
172
|
className={'top-nav-item' + (item.key === activeTopMenuKey ? ' active' : '')}
|
|
172
173
|
onClick={() => this.onTopMenuClick({key: item.key})}
|
|
173
174
|
>
|
|
174
175
|
{item.label}
|
|
175
|
-
</
|
|
176
|
+
</button>
|
|
176
177
|
))}
|
|
177
178
|
</nav>
|
|
178
179
|
);
|
|
@@ -188,7 +189,10 @@ return <Layout className='main-layout'>
|
|
|
188
189
|
breakpoint={'md'}
|
|
189
190
|
>
|
|
190
191
|
<div className='sider-header'>
|
|
191
|
-
<
|
|
192
|
+
<button type="button" className='logo-img'
|
|
193
|
+
onClick={() => history.push('/')}>
|
|
194
|
+
<img src="./logo.png" alt='logo'/>
|
|
195
|
+
</button>
|
|
192
196
|
<h3 className='hide-on-mobile'>
|
|
193
197
|
<Link to="/" style={{color: 'rgba(255,255,255,0.85)'}}>{siteInfo.title}</Link>
|
|
194
198
|
</h3>
|
|
@@ -201,8 +205,8 @@ return <Layout className='main-layout'>
|
|
|
201
205
|
<Header className='header'>
|
|
202
206
|
{this.renderTopMenu()}
|
|
203
207
|
{this.state.headerLeftArticles.map(a => (
|
|
204
|
-
<
|
|
205
|
-
|
|
208
|
+
<button key={a.code} type="button" className='item'
|
|
209
|
+
onClick={() => PageUtils.open('/article/' + a.code, a.title)}>{a.title}</button>
|
|
206
210
|
))}
|
|
207
211
|
{this.props.showOrgSwitcher?.({
|
|
208
212
|
activeTopMenu: this.getActiveTopMenu(),
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
color: rgba(0, 0, 0, 0.65);
|
|
28
28
|
transition: color 0.2s;
|
|
29
29
|
white-space: nowrap;
|
|
30
|
+
background: none;
|
|
31
|
+
border: none;
|
|
32
|
+
font: inherit;
|
|
30
33
|
|
|
31
34
|
&:hover {
|
|
32
35
|
color: var(--primary-color, var(--primary-color, #1961AC));
|
|
@@ -50,10 +53,13 @@
|
|
|
50
53
|
align-items: center;
|
|
51
54
|
padding: 0 0.5rem;
|
|
52
55
|
cursor: pointer;
|
|
56
|
+
background: none;
|
|
57
|
+
border: none;
|
|
58
|
+
font: inherit;
|
|
53
59
|
|
|
54
60
|
&:hover {
|
|
55
61
|
background: var(--primary-color-hover, #4990CD);
|
|
56
|
-
color: white;
|
|
62
|
+
color: white; /* NOSONAR: 主题色悬停态,对比度由主题色决定 */
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -74,6 +80,15 @@
|
|
|
74
80
|
.logo-img {
|
|
75
81
|
height: 36px;
|
|
76
82
|
cursor: pointer;
|
|
83
|
+
background: none;
|
|
84
|
+
border: none;
|
|
85
|
+
padding: 0;
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
|
|
89
|
+
img {
|
|
90
|
+
height: 36px;
|
|
91
|
+
}
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
h3 {
|
package/src/layouts/index.tsx
CHANGED
|
@@ -3,12 +3,10 @@ import {ConfigProvider, Modal} from "antd";
|
|
|
3
3
|
import zhCN from 'antd/locale/zh_CN';
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
import 'dayjs/locale/zh-cn';
|
|
6
|
-
import {history,
|
|
6
|
+
import {ErrorBoundary, GlobalData, HttpClient, PageFrame, PageLoading, PageUtils, history, getThemeConfig, setThemeColors, EventBus} from "../framework";
|
|
7
|
+
import type {ThemeColors} from "../framework";
|
|
7
8
|
|
|
8
9
|
import AdminLayout from "./admin"
|
|
9
|
-
import {HttpUtils, PageLoading, PageUtils, GlobalData, getThemeConfig, setThemeColors, EventBus} from "../framework";
|
|
10
|
-
import {ErrorBoundary} from "../framework";
|
|
11
|
-
import type {ThemeColors} from "../framework";
|
|
12
10
|
|
|
13
11
|
import '../style/global.less'
|
|
14
12
|
import './index.less'
|
|
@@ -76,8 +74,8 @@ export class Layouts extends React.Component<LayoutsProps> {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
loadSiteInfo() {
|
|
79
|
-
|
|
80
|
-
GlobalData.setSiteInfo(data);
|
|
77
|
+
HttpClient.get("/admin/public/site-info", null, {toastError: false}).then(data => {
|
|
78
|
+
GlobalData.setSiteInfo(data.data);
|
|
81
79
|
this.setState({siteInfoLoaded: true});
|
|
82
80
|
}).catch(() => {
|
|
83
81
|
console.error('[Layout] 加载站点信息失败');
|
|
@@ -85,12 +83,12 @@ export class Layouts extends React.Component<LayoutsProps> {
|
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
loadLoginInfo() {
|
|
88
|
-
|
|
89
|
-
GlobalData.setDictInfo(data.dictInfo);
|
|
90
|
-
GlobalData.setLoginInfo(data.loginInfo);
|
|
91
|
-
GlobalData.setSiteArticles(data.siteArticles);
|
|
86
|
+
HttpClient.get('/admin/public/login-info', null, {toastError: false}).then(data => {
|
|
87
|
+
GlobalData.setDictInfo(data.data.dictInfo);
|
|
88
|
+
GlobalData.setLoginInfo(data.data.loginInfo);
|
|
89
|
+
GlobalData.setSiteArticles(data.data.siteArticles);
|
|
92
90
|
|
|
93
|
-
if (data.needUpdatePwd) {
|
|
91
|
+
if (data.data.needUpdatePwd) {
|
|
94
92
|
this.setState({loginChecked: true});
|
|
95
93
|
history.push('/standalone/forceUpdatePwd');
|
|
96
94
|
return;
|
|
@@ -116,7 +114,7 @@ export class Layouts extends React.Component<LayoutsProps> {
|
|
|
116
114
|
this.loadData();
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
componentDidUpdate(prevProps:
|
|
117
|
+
componentDidUpdate(prevProps: LayoutsProps, prevState: typeof this.state) {
|
|
120
118
|
if (this.state.location !== prevState.location) {
|
|
121
119
|
this.loadData();
|
|
122
120
|
}
|
|
@@ -144,18 +142,26 @@ export class Layouts extends React.Component<LayoutsProps> {
|
|
|
144
142
|
return (
|
|
145
143
|
<ErrorBoundary minimal>
|
|
146
144
|
<ConfigProvider {...baseConfigProps} theme={getThemeConfig()}>
|
|
147
|
-
{showPageFrame
|
|
148
|
-
<PageLoading messages={[
|
|
149
|
-
!siteInfoLoaded && '加载站点信息...',
|
|
150
|
-
!loginChecked && '检查登录中...',
|
|
151
|
-
].filter(Boolean)}/>
|
|
152
|
-
)}
|
|
145
|
+
{this.renderContent(showPageFrame, ready, pathname, search)}
|
|
153
146
|
{this.renderLoginExpiredModal()}
|
|
154
147
|
</ConfigProvider>
|
|
155
148
|
</ErrorBoundary>
|
|
156
149
|
);
|
|
157
150
|
}
|
|
158
151
|
|
|
152
|
+
renderContent(showPageFrame: boolean, ready: boolean, pathname: string, search: string) {
|
|
153
|
+
if (showPageFrame) {
|
|
154
|
+
return <PageFrame url={pathname + search}/>;
|
|
155
|
+
}
|
|
156
|
+
if (ready) {
|
|
157
|
+
return <AdminLayout headerExtra={this.props.headerExtra} showOrgSwitcher={this.props.showOrgSwitcher} loginInfo={GlobalData.getLoginInfo()}/>;
|
|
158
|
+
}
|
|
159
|
+
return <PageLoading messages={[
|
|
160
|
+
!this.state.siteInfoLoaded && '加载站点信息...',
|
|
161
|
+
!this.state.loginChecked && '检查登录中...',
|
|
162
|
+
].filter(Boolean)}/>;
|
|
163
|
+
}
|
|
164
|
+
|
|
159
165
|
renderLoginExpiredModal() {
|
|
160
166
|
return (
|
|
161
167
|
<Modal open={this.state.loginExpiredVisible} title="确认操作" okText="确定"
|
package/src/pages/404.jsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {HttpClient, Page, UrlUtils} from "../../framework";
|
|
3
3
|
import "./article.less";
|
|
4
4
|
|
|
5
|
-
export default class extends React.Component {
|
|
5
|
+
export default class ArticlePage extends React.Component {
|
|
6
6
|
|
|
7
7
|
state = {
|
|
8
8
|
article: null,
|
|
@@ -12,8 +12,8 @@ export default class extends React.Component {
|
|
|
12
12
|
componentDidMount() {
|
|
13
13
|
const code = this.props.params?.code
|
|
14
14
|
if (code) {
|
|
15
|
-
|
|
16
|
-
this.setState({article: rs, loading: false})
|
|
15
|
+
HttpClient.get('admin/article/getByCode', {code}, {toastError: false}).then(rs => {
|
|
16
|
+
this.setState({article: rs.data, loading: false})
|
|
17
17
|
}).catch(() => {
|
|
18
18
|
this.setState({loading: false})
|
|
19
19
|
})
|
package/src/pages/index.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import {Card} from "antd";
|
|
3
3
|
import {Page} from "../framework";
|
|
4
4
|
|
|
5
|
-
export default class extends React.Component {
|
|
5
|
+
export default class HomePage extends React.Component {
|
|
6
6
|
render() {
|
|
7
7
|
return <Page title="首页" description="欢迎使用本系统">
|
|
8
8
|
<Card>欢迎使用本系统</Card>
|