@jiangood/open-admin 2.5.9 → 3.0.1
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 +20 -8
- package/src/framework/GlobalData.ts +85 -0
- package/src/framework/biz/OrgTree.tsx +2 -7
- package/src/framework/biz/Perm.tsx +11 -0
- package/src/framework/biz/PermActions.tsx +18 -15
- package/src/framework/biz/index.tsx +34 -19
- package/src/framework/components/ContextMenu/index.tsx +105 -78
- package/src/framework/components/DownloadModal/index.tsx +15 -17
- package/src/framework/components/ErrorBoundary/index.tsx +2 -2
- package/src/framework/components/FormModal/index.tsx +76 -0
- package/src/framework/components/Gap/index.tsx +1 -1
- package/src/framework/components/NamedIcon/index.tsx +55 -9
- package/src/framework/components/Page/index.less +0 -1
- package/src/framework/components/Page/index.tsx +34 -27
- package/src/framework/components/PageLoading/index.less +15 -0
- package/src/framework/components/PageLoading/index.tsx +32 -16
- package/src/framework/components/ProTable/index.tsx +86 -15
- package/src/framework/components/index.ts +1 -0
- package/src/framework/config/index.ts +1 -0
- package/src/framework/config/themeConfig.ts +39 -0
- package/src/framework/fields/FieldBoolean/index.tsx +11 -2
- package/src/framework/fields/FieldDate/index.tsx +22 -7
- package/src/framework/fields/FieldDateRange/index.tsx +24 -6
- package/src/framework/fields/FieldDictSelect/index.tsx +1 -0
- package/src/framework/fields/FieldEditor/index.tsx +7 -1
- package/src/framework/fields/FieldNumberRange/index.tsx +14 -5
- package/src/framework/fields/FieldPercent/index.tsx +16 -5
- package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -2
- package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +2 -1
- package/src/framework/fields/FieldSysOrgTree/index.tsx +6 -1
- package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +6 -1
- package/src/framework/fields/FieldTable/index.tsx +35 -6
- package/src/framework/fields/FieldTableSelect/index.tsx +18 -2
- package/src/framework/fields/FieldUploadFile/index.tsx +69 -28
- package/src/framework/fields/index.ts +0 -1
- package/src/framework/index.ts +2 -0
- package/src/framework/router/Link.tsx +26 -0
- package/src/framework/router/PageFrame.tsx +31 -0
- package/src/framework/router/history.ts +50 -0
- package/src/framework/router/index.ts +4 -0
- package/src/framework/router/matcher.ts +58 -0
- package/src/framework/utils/{ArrayUtils.js → ArrayUtils.ts} +5 -5
- package/src/framework/utils/{DateUtils.js → DateUtils.ts} +19 -19
- package/src/framework/utils/{DeviceUtils.js → DeviceUtils.ts} +8 -1
- package/src/framework/utils/{EventBus.js → EventBus.ts} +13 -6
- package/src/framework/utils/MessageApi.ts +5 -0
- package/src/framework/utils/ModalApi.ts +5 -0
- package/src/framework/utils/ObjectUtils.ts +15 -0
- package/src/framework/utils/{StorageUtils.js → StorageUtils.ts} +7 -7
- package/src/framework/utils/{StringUtils.js → StringUtils.ts} +15 -15
- package/src/framework/utils/{TreeUtils.js → TreeUtils.ts} +19 -12
- package/src/framework/utils/{UrlUtils.js → UrlUtils.ts} +10 -10
- package/src/framework/utils/index.ts +2 -1
- package/src/framework/utils/system/{DictUtils.js → DictUtils.ts} +16 -4
- package/src/framework/utils/system/{HttpUtils.js → HttpUtils.ts} +25 -21
- package/src/framework/utils/system/PageUtils.ts +49 -0
- package/src/framework/utils/system/{PermUtils.js → PermUtils.ts} +5 -6
- package/src/framework/utils/system/index.ts +0 -2
- package/src/framework/views/ViewBoolean/index.tsx +6 -3
- package/src/framework/views/ViewFileButton/index.tsx +13 -8
- package/src/framework/views/ViewImage/index.tsx +19 -14
- package/src/framework/views/ViewRange/index.tsx +10 -7
- package/src/framework/views/ViewText/index.tsx +9 -11
- package/src/layouts/admin/AdminTabs.jsx +79 -0
- package/src/layouts/admin/AdminTabs.less +63 -0
- package/src/layouts/admin/HeaderRight.jsx +27 -13
- package/src/layouts/admin/TabLayout.jsx +124 -0
- package/src/layouts/admin/index.jsx +5 -6
- package/src/layouts/admin/index.less +1 -82
- package/src/layouts/index.tsx +148 -0
- package/src/main.jsx +8 -0
- package/src/pages/account/changePassword.jsx +14 -10
- package/src/pages/article/$code.jsx +2 -2
- package/src/pages/index.jsx +6 -4
- package/src/pages/{login.jsx → public/login.jsx} +7 -10
- package/src/pages/public/test.jsx +30 -0
- package/src/pages/{forceUpdatePwd.jsx → standalone/forceUpdatePwd.jsx} +3 -24
- package/src/pages/system/article/index.jsx +51 -56
- package/src/pages/system/dict/index.jsx +60 -91
- package/src/pages/system/file/index.jsx +23 -17
- package/src/pages/system/job/index.jsx +63 -72
- package/src/pages/system/log/index.jsx +11 -8
- package/src/pages/system/org/index.jsx +60 -80
- package/src/pages/system/role/index.jsx +50 -86
- package/src/pages/system/role/rolePerm.jsx +2 -2
- package/src/pages/system/user/index.jsx +120 -73
- package/src/pages/system/user/userPerm.jsx +2 -2
- package/vite-plugin/index.js +107 -0
- package/config/common-plugin.js +0 -145
- package/config/config.js +0 -5
- package/config/utils.js +0 -73
- package/src/.umi-production/appData.json +0 -2257
- package/src/.umi-production/core/EmptyRoute.tsx +0 -9
- package/src/.umi-production/core/defineApp.ts +0 -16
- package/src/.umi-production/core/helmet.ts +0 -10
- package/src/.umi-production/core/helmetContext.ts +0 -4
- package/src/.umi-production/core/history.ts +0 -72
- package/src/.umi-production/core/historyIntelli.ts +0 -132
- package/src/.umi-production/core/plugin.ts +0 -40
- package/src/.umi-production/core/pluginConfig.ts +0 -330
- package/src/.umi-production/core/pluginConfigJoi.d.ts +0 -7
- package/src/.umi-production/core/polyfill.ts +0 -220
- package/src/.umi-production/core/route.tsx +0 -47
- package/src/.umi-production/core/routeProps.js +0 -5
- package/src/.umi-production/core/routeProps.ts +0 -6
- package/src/.umi-production/core/terminal.ts +0 -37
- package/src/.umi-production/exports.ts +0 -17
- package/src/.umi-production/testBrowser.tsx +0 -90
- package/src/.umi-production/tsconfig.json +0 -43
- package/src/.umi-production/typings.d.ts +0 -136
- package/src/.umi-production/umi.ts +0 -83
- package/src/framework/GlobalData.d.ts +0 -10
- package/src/framework/GlobalData.js +0 -42
- package/src/framework/biz/HasPerm.tsx +0 -14
- package/src/framework/fields/FieldRemoteSelectMultiple/index.tsx +0 -29
- package/src/framework/utils/ArrayUtils.d.ts +0 -6
- package/src/framework/utils/DateUtils.d.ts +0 -20
- package/src/framework/utils/DeviceUtils.d.ts +0 -3
- package/src/framework/utils/EventBus.d.ts +0 -6
- package/src/framework/utils/MessageUtils.d.ts +0 -10
- package/src/framework/utils/MessageUtils.js +0 -43
- package/src/framework/utils/ObjectUtils.d.ts +0 -14
- package/src/framework/utils/ObjectUtils.js +0 -12
- package/src/framework/utils/StorageUtils.d.ts +0 -4
- package/src/framework/utils/StringUtils.d.ts +0 -17
- package/src/framework/utils/TreeUtils.d.ts +0 -13
- package/src/framework/utils/UrlUtils.d.ts +0 -6
- package/src/framework/utils/system/DictUtils.d.ts +0 -20
- package/src/framework/utils/system/FormRegistry.d.ts +0 -8
- package/src/framework/utils/system/FormRegistry.js +0 -31
- package/src/framework/utils/system/HttpUtils.d.ts +0 -14
- package/src/framework/utils/system/PageUtils.d.ts +0 -14
- package/src/framework/utils/system/PageUtils.js +0 -59
- package/src/framework/utils/system/PermUtils.d.ts +0 -5
- package/src/framework/utils/system/ThemeUtils.d.ts +0 -6
- package/src/framework/utils/system/ThemeUtils.js +0 -52
- package/src/layouts/PageRender.tsx +0 -29
- package/src/layouts/admin/TabPageRender.jsx +0 -210
- package/src/layouts/index.jsx +0 -120
- package/src/loading.jsx +0 -19
- package/src/pages/about.jsx +0 -16
- /package/src/pages/{login.less → public/login.less} +0 -0
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {InputNumber} from "antd";
|
|
3
3
|
import {StringUtils} from "../../utils";
|
|
4
|
+
import type {FieldProps} from '../types';
|
|
4
5
|
|
|
5
6
|
const SP = StringUtils.ISO_SPLITTER;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/** 数字范围值,形如 "1/100"(起止以 / 分隔) */
|
|
9
|
+
export type FieldNumberRangeValue = string;
|
|
10
|
+
|
|
11
|
+
export interface FieldNumberRangeProps extends FieldProps<FieldNumberRangeValue> {
|
|
12
|
+
/** 默认值(形如 "1/100"),value 为空时挂载后回填 */
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class FieldNumberRange extends React.Component<FieldNumberRangeProps> {
|
|
17
|
+
onChangeA = (a: number | string | null) => {
|
|
9
18
|
const {b} = this.parse(this.props.value);
|
|
10
19
|
this.props.onChange && this.props.onChange(this.merge(a, b));
|
|
11
20
|
};
|
|
12
21
|
|
|
13
|
-
onChangeB = (b) => {
|
|
22
|
+
onChangeB = (b: number | string | null) => {
|
|
14
23
|
const {a} = this.parse(this.props.value);
|
|
15
24
|
this.props.onChange && this.props.onChange(this.merge(a, b));
|
|
16
25
|
};
|
|
17
26
|
|
|
18
|
-
merge(a, b) {
|
|
27
|
+
merge(a: number | string | null, b: number | string | null): string {
|
|
19
28
|
if (a == null) {
|
|
20
29
|
a = '';
|
|
21
30
|
}
|
|
@@ -25,7 +34,7 @@ export class FieldNumberRange extends React.Component {
|
|
|
25
34
|
return a + SP + b;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
parse(v) {
|
|
37
|
+
parse(v: string | null | undefined): { a: number | null; b: number | null } {
|
|
29
38
|
if (v == null) {
|
|
30
39
|
return {a: null, b: null};
|
|
31
40
|
}
|
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import {InputNumber, Space} from 'antd';
|
|
2
|
+
import type {InputNumberProps} from 'antd';
|
|
2
3
|
import React from 'react';
|
|
4
|
+
import type {FieldProps} from '../types';
|
|
5
|
+
|
|
6
|
+
export interface FieldPercentProps extends Omit<InputNumberProps, 'value' | 'onChange'>, FieldProps<number> {
|
|
7
|
+
/** 小数位数,默认 2 */
|
|
8
|
+
precision?: number;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* 数字的百分数输入框
|
|
6
13
|
*/
|
|
7
|
-
export class FieldPercent extends React.Component {
|
|
14
|
+
export class FieldPercent extends React.Component<FieldPercentProps> {
|
|
15
|
+
static defaultProps = {
|
|
16
|
+
precision: 2,
|
|
17
|
+
};
|
|
18
|
+
|
|
8
19
|
render() {
|
|
9
|
-
const {value, onChange, ...rest} = this.props;
|
|
20
|
+
const {value, onChange, precision, ...rest} = this.props;
|
|
10
21
|
|
|
11
22
|
return (
|
|
12
23
|
<Space.Compact>
|
|
13
24
|
<InputNumber
|
|
14
25
|
min={0}
|
|
15
26
|
max={100}
|
|
16
|
-
value={Number((value * 100).toFixed(
|
|
27
|
+
value={value != null ? Number((value * 100).toFixed(precision)) : null}
|
|
17
28
|
onChange={v => {
|
|
18
|
-
if (v
|
|
19
|
-
const percentValue = Number((v / 100).toFixed(
|
|
29
|
+
if (v != null) {
|
|
30
|
+
const percentValue = Number((v / 100).toFixed(precision));
|
|
20
31
|
onChange && onChange(percentValue);
|
|
21
32
|
}
|
|
22
33
|
}}
|
|
@@ -4,9 +4,10 @@ import {BaseRemoteSelect} from '../BaseRemoteSelect';
|
|
|
4
4
|
import type {SelectProps} from "antd/es/select";
|
|
5
5
|
import type {FieldProps} from '../types';
|
|
6
6
|
|
|
7
|
-
interface FieldRemoteSelectProps extends Omit<SelectProps, 'value' | 'onChange'>, FieldProps<any> {
|
|
7
|
+
interface FieldRemoteSelectProps extends Omit<SelectProps, 'value' | 'onChange' | 'mode'>, FieldProps<any> {
|
|
8
8
|
url: string;
|
|
9
9
|
debounceTime?: number;
|
|
10
|
+
multiple?: boolean;
|
|
10
11
|
paramsProcessor?: (params: any) => any;
|
|
11
12
|
responseProcessor?: (response: any) => any;
|
|
12
13
|
}
|
|
@@ -17,7 +18,7 @@ export class FieldRemoteSelect extends BaseRemoteSelect<FieldRemoteSelectProps>
|
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
render() {
|
|
20
|
-
const {value, placeholder, ...rest} = this.props;
|
|
21
|
+
const {value, placeholder, multiple, ...rest} = this.props;
|
|
21
22
|
const {data, loading} = this.state;
|
|
22
23
|
|
|
23
24
|
return (
|
|
@@ -30,6 +31,7 @@ export class FieldRemoteSelect extends BaseRemoteSelect<FieldRemoteSelectProps>
|
|
|
30
31
|
loading={loading}
|
|
31
32
|
value={value}
|
|
32
33
|
placeholder={placeholder}
|
|
34
|
+
mode={multiple ? 'multiple' : undefined}
|
|
33
35
|
{...rest}
|
|
34
36
|
/>
|
|
35
37
|
);
|
|
@@ -14,13 +14,14 @@ export class FieldRemoteTreeSelect extends BaseRemoteSelect {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
render() {
|
|
17
|
-
const { value, onChange, treeDefaultExpandAll } = this.props;
|
|
17
|
+
const { value, onChange, treeDefaultExpandAll, ...rest } = this.props;
|
|
18
18
|
const { data, loading } = this.state;
|
|
19
19
|
|
|
20
20
|
if (loading) return <Spin />;
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<TreeSelect
|
|
24
|
+
{...rest}
|
|
24
25
|
style={{ width: '100%', minWidth: 200 }}
|
|
25
26
|
allowClear
|
|
26
27
|
styles={{ popup: { root: { maxHeight: 400, overflow: 'auto' } } }}
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import React from "react";
|
|
5
5
|
import {FieldRemoteTree} from "../FieldRemoteTree";
|
|
6
|
+
import type {FieldProps} from '../types';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface FieldSysOrgTreeProps extends FieldProps<string[]> {
|
|
9
|
+
type?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class FieldSysOrgTree extends React.Component<FieldSysOrgTreeProps> {
|
|
8
13
|
static defaultProps = {
|
|
9
14
|
type: 'dept',
|
|
10
15
|
};
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import React from "react";
|
|
5
5
|
import {FieldRemoteTreeSelect} from "../FieldRemoteTreeSelect";
|
|
6
|
+
import type {FieldProps} from '../types';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface FieldSysOrgTreeSelectProps extends FieldProps<string | string[]> {
|
|
9
|
+
type?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class FieldSysOrgTreeSelect extends React.Component<FieldSysOrgTreeSelectProps> {
|
|
8
13
|
|
|
9
14
|
static defaultProps = {
|
|
10
15
|
type: 'dept',
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import {Button, Input, Table} from 'antd'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import {DeleteOutlined, PlusOutlined} from "@ant-design/icons";
|
|
4
|
+
import type {FieldProps} from '../types';
|
|
4
5
|
import './styles.less'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 可编辑表格的列配置。
|
|
9
|
+
* render 返回的组件元素会被注入 value / onChange,用于编辑单元格。
|
|
10
|
+
*/
|
|
11
|
+
export interface FieldTableColumn {
|
|
12
|
+
/** 列标题 */
|
|
13
|
+
title?: React.ReactNode;
|
|
14
|
+
/** 字段名 */
|
|
15
|
+
dataIndex?: string;
|
|
16
|
+
/** 列宽 */
|
|
17
|
+
width?: number | string;
|
|
18
|
+
/** 自定义渲染(返回的组件会被注入 value/onChange) */
|
|
19
|
+
render?: (value: any, record: Record<string, any>, index: number) => React.ReactElement;
|
|
20
|
+
/** 其余 antd Table 列属性 */
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FieldTableProps extends FieldProps<Record<string, any>[]> {
|
|
25
|
+
/** 列配置(操作列由组件自动追加) */
|
|
26
|
+
columns: FieldTableColumn[];
|
|
27
|
+
/** 容器样式 */
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface FieldTableState {
|
|
32
|
+
dataSource: Record<string, any>[];
|
|
33
|
+
}
|
|
34
|
+
|
|
6
35
|
/**
|
|
7
36
|
* 可编辑表格
|
|
8
37
|
*/
|
|
9
|
-
export class FieldTable extends React.Component {
|
|
38
|
+
export class FieldTable extends React.Component<FieldTableProps, FieldTableState> {
|
|
10
39
|
|
|
11
|
-
columns = [];
|
|
40
|
+
columns: FieldTableColumn[] = [];
|
|
12
41
|
|
|
13
|
-
constructor(props) {
|
|
42
|
+
constructor(props: FieldTableProps) {
|
|
14
43
|
super(props);
|
|
15
44
|
|
|
16
45
|
this.columns = this.props.columns.map(col => {
|
|
@@ -55,7 +84,7 @@ export class FieldTable extends React.Component {
|
|
|
55
84
|
dataSource: []
|
|
56
85
|
};
|
|
57
86
|
|
|
58
|
-
onCellChange = (index, dataIndex, e) => {
|
|
87
|
+
onCellChange = (index: number, dataIndex: string | undefined, e: any) => {
|
|
59
88
|
const {dataSource} = this.state;
|
|
60
89
|
const row = dataSource[index];
|
|
61
90
|
|
|
@@ -65,7 +94,7 @@ export class FieldTable extends React.Component {
|
|
|
65
94
|
}
|
|
66
95
|
|
|
67
96
|
const next = [...dataSource];
|
|
68
|
-
next[index] = {...row, [dataIndex]: v};
|
|
97
|
+
next[index] = {...row, [dataIndex as string]: v};
|
|
69
98
|
this.setState({dataSource: next}, this.notifyParent);
|
|
70
99
|
};
|
|
71
100
|
|
|
@@ -75,7 +104,7 @@ export class FieldTable extends React.Component {
|
|
|
75
104
|
this.setState({dataSource}, this.notifyParent);
|
|
76
105
|
};
|
|
77
106
|
|
|
78
|
-
remove = (record) => {
|
|
107
|
+
remove = (record: Record<string, any>) => {
|
|
79
108
|
const {dataSource} = this.state;
|
|
80
109
|
this.setState({dataSource: dataSource.filter(item => item !== record)}, this.notifyParent);
|
|
81
110
|
};
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {Button, Select} from "antd";
|
|
3
|
+
import type {TableColumnsType} from "antd";
|
|
3
4
|
import {ProTable} from "../../components";
|
|
4
5
|
import {HttpUtils} from "../../utils";
|
|
6
|
+
import type {FieldProps} from '../types';
|
|
7
|
+
|
|
8
|
+
export interface FieldTableSelectProps extends FieldProps<string> {
|
|
9
|
+
/** 数据加载地址 */
|
|
10
|
+
url: string;
|
|
11
|
+
/** 表格列配置(操作列由组件自动追加) */
|
|
12
|
+
columns: TableColumnsType<Record<string, any>>;
|
|
13
|
+
/** 占位文本,默认 请搜索选择 */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FieldTableSelectState {
|
|
18
|
+
open: boolean;
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
5
21
|
|
|
6
22
|
/**
|
|
7
23
|
* 下拉表格
|
|
8
24
|
*
|
|
9
25
|
* 后端参考接口:
|
|
10
26
|
*/
|
|
11
|
-
export class FieldTableSelect extends React.Component {
|
|
27
|
+
export class FieldTableSelect extends React.Component<FieldTableSelectProps, FieldTableSelectState> {
|
|
12
28
|
|
|
13
29
|
static defaultProps = {
|
|
14
30
|
placeholder: '请搜索选择',
|
|
15
31
|
};
|
|
16
32
|
|
|
17
|
-
state = {
|
|
33
|
+
state: FieldTableSelectState = {
|
|
18
34
|
open: false,
|
|
19
35
|
label: '',
|
|
20
36
|
};
|
|
@@ -1,18 +1,48 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import ImgCrop from "antd-img-crop";
|
|
3
|
-
import {
|
|
3
|
+
import type {ImgCropProps} from "antd-img-crop";
|
|
4
|
+
import {message, Modal, Upload} from "antd";
|
|
5
|
+
import type {UploadChangeParam, UploadFile, UploadProps} from "antd";
|
|
4
6
|
import { UploadOutlined } from "@ant-design/icons";
|
|
5
7
|
import {ViewFile} from "../../views";
|
|
6
8
|
import {ObjectUtils, UrlUtils} from "../../utils";
|
|
9
|
+
import type {FieldProps} from '../types';
|
|
10
|
+
|
|
11
|
+
/** 框架内使用的上传文件对象,额外携带 sysFile 的 id */
|
|
12
|
+
export type SysUploadFile = UploadFile & { id?: string };
|
|
13
|
+
|
|
14
|
+
export interface FieldUploadFileProps extends FieldProps<string> {
|
|
15
|
+
/** 最大上传数量,默认 1 */
|
|
16
|
+
maxCount?: number;
|
|
17
|
+
/** 是否启用上传前裁切图片 */
|
|
18
|
+
cropImage?: boolean;
|
|
19
|
+
/** 裁切配置(antd-img-crop 的 cropperProps) */
|
|
20
|
+
cropperProps?: ImgCropProps['cropperProps'];
|
|
21
|
+
/** 上传列表的内建样式,默认 picture-card */
|
|
22
|
+
listType?: UploadProps['listType'];
|
|
23
|
+
/** 接受的文件类型,如 image/* */
|
|
24
|
+
accept?: string;
|
|
25
|
+
/** 文件列表变化回调(新增文件上传成功后才触发) */
|
|
26
|
+
onFileChange?: (fileList: SysUploadFile[]) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface FieldUploadFileState {
|
|
30
|
+
maxCount: number;
|
|
31
|
+
cropImage: boolean;
|
|
32
|
+
fileList: SysUploadFile[];
|
|
33
|
+
/** 逗号分隔的文件 id */
|
|
34
|
+
value: string | null;
|
|
35
|
+
accept?: string;
|
|
36
|
+
}
|
|
7
37
|
|
|
8
38
|
/**
|
|
9
39
|
* 上传图片前裁切, 单张图片
|
|
10
40
|
*
|
|
11
41
|
* 可参考 react-easy-crop
|
|
12
42
|
*/
|
|
13
|
-
export class FieldUploadFile extends React.Component {
|
|
43
|
+
export class FieldUploadFile extends React.Component<FieldUploadFileProps, FieldUploadFileState & { errorTitle?: string; errorContent?: string; previewFileId?: string }> {
|
|
14
44
|
|
|
15
|
-
state = {
|
|
45
|
+
state: FieldUploadFileState & { errorTitle?: string; errorContent?: string; previewFileId?: string } = {
|
|
16
46
|
// 传入的参数
|
|
17
47
|
maxCount: 1,
|
|
18
48
|
cropImage: false,
|
|
@@ -22,26 +52,34 @@ export class FieldUploadFile extends React.Component {
|
|
|
22
52
|
value: null, // 都好分隔的文件id
|
|
23
53
|
};
|
|
24
54
|
|
|
25
|
-
constructor(props) {
|
|
55
|
+
constructor(props: FieldUploadFileProps) {
|
|
26
56
|
super(props);
|
|
27
57
|
ObjectUtils.copyPropertyIfPresent(props, this.state);
|
|
28
58
|
this.state.fileList = this.convertInputToComponentValue(this.state.value);
|
|
29
59
|
}
|
|
30
60
|
|
|
31
|
-
componentDidUpdate(prevProps) {
|
|
32
|
-
const next = {};
|
|
61
|
+
componentDidUpdate(prevProps: FieldUploadFileProps) {
|
|
62
|
+
const next: Partial<FieldUploadFileState> = {};
|
|
33
63
|
if (this.props.maxCount !== prevProps.maxCount) next.maxCount = this.props.maxCount;
|
|
34
64
|
if (this.props.cropImage !== prevProps.cropImage) next.cropImage = this.props.cropImage;
|
|
65
|
+
|
|
66
|
+
const prevValue = prevProps.value ?? null;
|
|
67
|
+
const curValue = this.props.value ?? null;
|
|
68
|
+
if (curValue !== prevValue && curValue !== this.state.value) {
|
|
69
|
+
next.fileList = this.convertInputToComponentValue(curValue);
|
|
70
|
+
next.value = curValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
35
73
|
if (Object.keys(next).length > 0) this.setState(next);
|
|
36
74
|
}
|
|
37
75
|
|
|
38
|
-
convertInputToComponentValue(value) {
|
|
39
|
-
const list = [];
|
|
76
|
+
convertInputToComponentValue(value: string | null | undefined): SysUploadFile[] {
|
|
77
|
+
const list: SysUploadFile[] = [];
|
|
40
78
|
if (value && value.length > 0) {
|
|
41
79
|
const arr = value.split(",");
|
|
42
80
|
for (const id of arr) {
|
|
43
81
|
const url = UrlUtils.contextPath('/admin/sysFile/preview/' + id);
|
|
44
|
-
const file = {id, url, uid: id, name: id, status: 'done', fileName: id};
|
|
82
|
+
const file = {id, url, uid: id, name: id, status: 'done', fileName: id} as SysUploadFile;
|
|
45
83
|
list.push(file);
|
|
46
84
|
}
|
|
47
85
|
}
|
|
@@ -49,8 +87,8 @@ export class FieldUploadFile extends React.Component {
|
|
|
49
87
|
return list;
|
|
50
88
|
}
|
|
51
89
|
|
|
52
|
-
convertComponentValueToOutput(fileList) {
|
|
53
|
-
const fileIds = [];
|
|
90
|
+
convertComponentValueToOutput(fileList: SysUploadFile[]): string[] {
|
|
91
|
+
const fileIds: string[] = [];
|
|
54
92
|
for (const f of fileList) {
|
|
55
93
|
if (f.status === 'done') {
|
|
56
94
|
if (f.response) { // 新上传的
|
|
@@ -60,23 +98,20 @@ export class FieldUploadFile extends React.Component {
|
|
|
60
98
|
f.id = id;
|
|
61
99
|
fileIds.push(id);
|
|
62
100
|
} else {
|
|
63
|
-
|
|
101
|
+
this.setState({errorTitle: '上传文件失败', errorContent: ajaxResult.message});
|
|
64
102
|
}
|
|
65
103
|
} else { // 老的
|
|
66
|
-
fileIds.push(f.id);
|
|
104
|
+
fileIds.push(f.id as string);
|
|
67
105
|
}
|
|
68
106
|
}
|
|
69
107
|
}
|
|
70
108
|
return fileIds;
|
|
71
109
|
}
|
|
72
110
|
|
|
73
|
-
handleChange = ({fileList, event, file}) => {
|
|
111
|
+
handleChange = ({fileList, event, file}: UploadChangeParam<SysUploadFile>) => {
|
|
74
112
|
const rs = file.response;
|
|
75
113
|
if (rs != null && rs.success === false) {
|
|
76
|
-
|
|
77
|
-
title: '上传失败',
|
|
78
|
-
content: rs.message,
|
|
79
|
-
});
|
|
114
|
+
this.setState({errorTitle: '上传失败', errorContent: rs.message});
|
|
80
115
|
return;
|
|
81
116
|
}
|
|
82
117
|
|
|
@@ -84,26 +119,21 @@ export class FieldUploadFile extends React.Component {
|
|
|
84
119
|
message.success(`文件「${rs.data?.name || ''}」上传成功`);
|
|
85
120
|
}
|
|
86
121
|
|
|
87
|
-
this.setState({fileList});
|
|
88
|
-
|
|
89
122
|
const newIds = this.convertComponentValueToOutput(fileList);
|
|
123
|
+
const value = newIds.join(',');
|
|
124
|
+
this.setState({fileList, value});
|
|
125
|
+
|
|
90
126
|
if (newIds.length > 0 && this.props.onFileChange) {
|
|
91
127
|
this.props.onFileChange(fileList);
|
|
92
128
|
}
|
|
93
129
|
if (this.props.onChange) {
|
|
94
|
-
const value = newIds.join(',');
|
|
95
130
|
this.props.onChange(value);
|
|
96
131
|
}
|
|
97
132
|
|
|
98
133
|
};
|
|
99
134
|
|
|
100
135
|
handlePreview = (file) => {
|
|
101
|
-
|
|
102
|
-
title: '文件预览',
|
|
103
|
-
width: '80vw',
|
|
104
|
-
content: <ViewFile value={file.id} height='70vh'/>
|
|
105
|
-
});
|
|
106
|
-
|
|
136
|
+
this.setState({previewFileId: file.id});
|
|
107
137
|
};
|
|
108
138
|
|
|
109
139
|
render() {
|
|
@@ -113,7 +143,18 @@ export class FieldUploadFile extends React.Component {
|
|
|
113
143
|
</ImgCrop>;
|
|
114
144
|
}
|
|
115
145
|
|
|
116
|
-
return
|
|
146
|
+
return <>
|
|
147
|
+
{this.getUpload()}
|
|
148
|
+
<Modal open={!!this.state.errorTitle} title={this.state.errorTitle} okText="确定"
|
|
149
|
+
onCancel={() => this.setState({errorTitle: undefined})}
|
|
150
|
+
onOk={() => this.setState({errorTitle: undefined})}>
|
|
151
|
+
{this.state.errorContent}
|
|
152
|
+
</Modal>
|
|
153
|
+
<Modal open={!!this.state.previewFileId} title="文件预览" width="80vw" footer={null}
|
|
154
|
+
onCancel={() => this.setState({previewFileId: undefined})}>
|
|
155
|
+
{this.state.previewFileId && <ViewFile value={this.state.previewFileId} height='70vh'/>}
|
|
156
|
+
</Modal>
|
|
157
|
+
</>
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
getUpload = () => {
|
package/src/framework/index.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, {type AnchorHTMLAttributes} from 'react';
|
|
2
|
+
import {history} from './history';
|
|
3
|
+
|
|
4
|
+
interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'onClick'> {
|
|
5
|
+
to: string;
|
|
6
|
+
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Link extends React.Component<LinkProps> {
|
|
10
|
+
render() {
|
|
11
|
+
const {to, children, onClick: userOnClick, ...domProps} = this.props;
|
|
12
|
+
return (
|
|
13
|
+
<a href={'#' + to}
|
|
14
|
+
{...domProps}
|
|
15
|
+
onClick={e => {
|
|
16
|
+
userOnClick?.(e);
|
|
17
|
+
if (!e.defaultPrevented) {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
history.push(to);
|
|
20
|
+
}
|
|
21
|
+
}}>
|
|
22
|
+
{children}
|
|
23
|
+
</a>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Result} from 'antd';
|
|
3
|
+
import {matchRoute} from './matcher';
|
|
4
|
+
import {UrlUtils} from '../utils/UrlUtils';
|
|
5
|
+
import ErrorBoundary from '../components/ErrorBoundary';
|
|
6
|
+
|
|
7
|
+
interface PageFrameProps {
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PageFrame extends React.Component<PageFrameProps> {
|
|
12
|
+
render() {
|
|
13
|
+
const {url} = this.props;
|
|
14
|
+
const qIndex = url.indexOf('?');
|
|
15
|
+
const pathname = qIndex === -1 ? url : url.substring(0, qIndex);
|
|
16
|
+
const search = qIndex === -1 ? '' : url.substring(qIndex);
|
|
17
|
+
|
|
18
|
+
const matched = matchRoute(pathname);
|
|
19
|
+
|
|
20
|
+
let content;
|
|
21
|
+
if (matched) {
|
|
22
|
+
const {component: Comp, params} = matched;
|
|
23
|
+
const location = {pathname, search, query: UrlUtils.getParams(url)};
|
|
24
|
+
content = <Comp params={params} location={location}/>;
|
|
25
|
+
} else {
|
|
26
|
+
content = <Result status={404} title='页面不存在!' subTitle={<div>路由地址:{pathname}</div>}/>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <ErrorBoundary>{content}</ErrorBoundary>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function currentUrl() {
|
|
2
|
+
return window.location.hash.substring(1) || '/';
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function parseLocation() {
|
|
6
|
+
const url = currentUrl();
|
|
7
|
+
const qIndex = url.indexOf('?');
|
|
8
|
+
if (qIndex === -1) {
|
|
9
|
+
return {pathname: url, search: ''};
|
|
10
|
+
}
|
|
11
|
+
return {pathname: url.substring(0, qIndex), search: url.substring(qIndex)};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const listeners = new Set();
|
|
15
|
+
let lastNotifiedUrl = currentUrl();
|
|
16
|
+
|
|
17
|
+
function notify() {
|
|
18
|
+
lastNotifiedUrl = currentUrl();
|
|
19
|
+
const location = parseLocation();
|
|
20
|
+
history.location = location;
|
|
21
|
+
listeners.forEach(fn => fn({location}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
window.addEventListener('hashchange', () => {
|
|
25
|
+
// push/replace 已同步通知;此处仅处理浏览器前进/后退或手动修改 hash
|
|
26
|
+
if (currentUrl() === lastNotifiedUrl) return;
|
|
27
|
+
notify();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const history = {
|
|
31
|
+
location: parseLocation(),
|
|
32
|
+
|
|
33
|
+
push(url) {
|
|
34
|
+
if (url === currentUrl()) return;
|
|
35
|
+
window.location.hash = url;
|
|
36
|
+
notify();
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
replace(url) {
|
|
40
|
+
if (url === currentUrl()) return;
|
|
41
|
+
const base = window.location.href.split('#')[0];
|
|
42
|
+
window.location.replace(base + '#' + url);
|
|
43
|
+
notify();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
listen(fn) {
|
|
47
|
+
listeners.add(fn);
|
|
48
|
+
return () => listeners.delete(fn);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {type ComponentType} from 'react';
|
|
2
|
+
|
|
3
|
+
function safeDecodeURIComponent(s: string): string {
|
|
4
|
+
try { return decodeURIComponent(s); } catch { return s; }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RouteDef {
|
|
8
|
+
path: string;
|
|
9
|
+
component: ComponentType<any>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface InternalRoute extends RouteDef {
|
|
13
|
+
segments: ({ param: string } | { name: string })[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface MatchResult {
|
|
17
|
+
component: ComponentType<any>;
|
|
18
|
+
params: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const routes: InternalRoute[] = [];
|
|
22
|
+
|
|
23
|
+
function compile(path: string): InternalRoute['segments'] {
|
|
24
|
+
return path.split('/').filter(Boolean).map(seg =>
|
|
25
|
+
seg.startsWith(':') ? {param: seg.slice(1)} : {name: seg}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerRoutes(defs: RouteDef[]): void {
|
|
30
|
+
for (const {path, component} of defs) {
|
|
31
|
+
routes.push({path, component, segments: compile(path)});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function matchRoute(pathname: string): MatchResult | null {
|
|
36
|
+
const segs = pathname.split('/').filter(Boolean);
|
|
37
|
+
let best = null;
|
|
38
|
+
for (const route of routes) {
|
|
39
|
+
if (route.segments.length !== segs.length) continue;
|
|
40
|
+
const params = {};
|
|
41
|
+
let ok = true;
|
|
42
|
+
let dynamic = false;
|
|
43
|
+
for (let i = 0; i < segs.length; i++) {
|
|
44
|
+
const seg = route.segments[i];
|
|
45
|
+
if (seg.param) {
|
|
46
|
+
params[seg.param] = safeDecodeURIComponent(segs[i]);
|
|
47
|
+
dynamic = true;
|
|
48
|
+
} else if (seg.name !== segs[i]) {
|
|
49
|
+
ok = false;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!ok) continue;
|
|
54
|
+
if (!dynamic) return {component: route.component, params};
|
|
55
|
+
if (!best) best = {component: route.component, params};
|
|
56
|
+
}
|
|
57
|
+
return best;
|
|
58
|
+
}
|