@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.
Files changed (142) hide show
  1. package/package.json +20 -8
  2. package/src/framework/GlobalData.ts +85 -0
  3. package/src/framework/biz/OrgTree.tsx +2 -7
  4. package/src/framework/biz/Perm.tsx +11 -0
  5. package/src/framework/biz/PermActions.tsx +18 -15
  6. package/src/framework/biz/index.tsx +34 -19
  7. package/src/framework/components/ContextMenu/index.tsx +105 -78
  8. package/src/framework/components/DownloadModal/index.tsx +15 -17
  9. package/src/framework/components/ErrorBoundary/index.tsx +2 -2
  10. package/src/framework/components/FormModal/index.tsx +76 -0
  11. package/src/framework/components/Gap/index.tsx +1 -1
  12. package/src/framework/components/NamedIcon/index.tsx +55 -9
  13. package/src/framework/components/Page/index.less +0 -1
  14. package/src/framework/components/Page/index.tsx +34 -27
  15. package/src/framework/components/PageLoading/index.less +15 -0
  16. package/src/framework/components/PageLoading/index.tsx +32 -16
  17. package/src/framework/components/ProTable/index.tsx +86 -15
  18. package/src/framework/components/index.ts +1 -0
  19. package/src/framework/config/index.ts +1 -0
  20. package/src/framework/config/themeConfig.ts +39 -0
  21. package/src/framework/fields/FieldBoolean/index.tsx +11 -2
  22. package/src/framework/fields/FieldDate/index.tsx +22 -7
  23. package/src/framework/fields/FieldDateRange/index.tsx +24 -6
  24. package/src/framework/fields/FieldDictSelect/index.tsx +1 -0
  25. package/src/framework/fields/FieldEditor/index.tsx +7 -1
  26. package/src/framework/fields/FieldNumberRange/index.tsx +14 -5
  27. package/src/framework/fields/FieldPercent/index.tsx +16 -5
  28. package/src/framework/fields/FieldRemoteSelect/index.tsx +4 -2
  29. package/src/framework/fields/FieldRemoteTreeSelect/index.tsx +2 -1
  30. package/src/framework/fields/FieldSysOrgTree/index.tsx +6 -1
  31. package/src/framework/fields/FieldSysOrgTreeSelect/index.tsx +6 -1
  32. package/src/framework/fields/FieldTable/index.tsx +35 -6
  33. package/src/framework/fields/FieldTableSelect/index.tsx +18 -2
  34. package/src/framework/fields/FieldUploadFile/index.tsx +69 -28
  35. package/src/framework/fields/index.ts +0 -1
  36. package/src/framework/index.ts +2 -0
  37. package/src/framework/router/Link.tsx +26 -0
  38. package/src/framework/router/PageFrame.tsx +31 -0
  39. package/src/framework/router/history.ts +50 -0
  40. package/src/framework/router/index.ts +4 -0
  41. package/src/framework/router/matcher.ts +58 -0
  42. package/src/framework/utils/{ArrayUtils.js → ArrayUtils.ts} +5 -5
  43. package/src/framework/utils/{DateUtils.js → DateUtils.ts} +19 -19
  44. package/src/framework/utils/{DeviceUtils.js → DeviceUtils.ts} +8 -1
  45. package/src/framework/utils/{EventBus.js → EventBus.ts} +13 -6
  46. package/src/framework/utils/MessageApi.ts +5 -0
  47. package/src/framework/utils/ModalApi.ts +5 -0
  48. package/src/framework/utils/ObjectUtils.ts +15 -0
  49. package/src/framework/utils/{StorageUtils.js → StorageUtils.ts} +7 -7
  50. package/src/framework/utils/{StringUtils.js → StringUtils.ts} +15 -15
  51. package/src/framework/utils/{TreeUtils.js → TreeUtils.ts} +19 -12
  52. package/src/framework/utils/{UrlUtils.js → UrlUtils.ts} +10 -10
  53. package/src/framework/utils/index.ts +2 -1
  54. package/src/framework/utils/system/{DictUtils.js → DictUtils.ts} +16 -4
  55. package/src/framework/utils/system/{HttpUtils.js → HttpUtils.ts} +25 -21
  56. package/src/framework/utils/system/PageUtils.ts +49 -0
  57. package/src/framework/utils/system/{PermUtils.js → PermUtils.ts} +5 -6
  58. package/src/framework/utils/system/index.ts +0 -2
  59. package/src/framework/views/ViewBoolean/index.tsx +6 -3
  60. package/src/framework/views/ViewFileButton/index.tsx +13 -8
  61. package/src/framework/views/ViewImage/index.tsx +19 -14
  62. package/src/framework/views/ViewRange/index.tsx +10 -7
  63. package/src/framework/views/ViewText/index.tsx +9 -11
  64. package/src/layouts/admin/AdminTabs.jsx +79 -0
  65. package/src/layouts/admin/AdminTabs.less +63 -0
  66. package/src/layouts/admin/HeaderRight.jsx +27 -13
  67. package/src/layouts/admin/TabLayout.jsx +124 -0
  68. package/src/layouts/admin/index.jsx +5 -6
  69. package/src/layouts/admin/index.less +1 -82
  70. package/src/layouts/index.tsx +148 -0
  71. package/src/main.jsx +8 -0
  72. package/src/pages/account/changePassword.jsx +14 -10
  73. package/src/pages/article/$code.jsx +2 -2
  74. package/src/pages/index.jsx +6 -4
  75. package/src/pages/{login.jsx → public/login.jsx} +7 -10
  76. package/src/pages/public/test.jsx +30 -0
  77. package/src/pages/{forceUpdatePwd.jsx → standalone/forceUpdatePwd.jsx} +3 -24
  78. package/src/pages/system/article/index.jsx +51 -56
  79. package/src/pages/system/dict/index.jsx +60 -91
  80. package/src/pages/system/file/index.jsx +23 -17
  81. package/src/pages/system/job/index.jsx +63 -72
  82. package/src/pages/system/log/index.jsx +11 -8
  83. package/src/pages/system/org/index.jsx +60 -80
  84. package/src/pages/system/role/index.jsx +50 -86
  85. package/src/pages/system/role/rolePerm.jsx +2 -2
  86. package/src/pages/system/user/index.jsx +120 -73
  87. package/src/pages/system/user/userPerm.jsx +2 -2
  88. package/vite-plugin/index.js +107 -0
  89. package/config/common-plugin.js +0 -145
  90. package/config/config.js +0 -5
  91. package/config/utils.js +0 -73
  92. package/src/.umi-production/appData.json +0 -2257
  93. package/src/.umi-production/core/EmptyRoute.tsx +0 -9
  94. package/src/.umi-production/core/defineApp.ts +0 -16
  95. package/src/.umi-production/core/helmet.ts +0 -10
  96. package/src/.umi-production/core/helmetContext.ts +0 -4
  97. package/src/.umi-production/core/history.ts +0 -72
  98. package/src/.umi-production/core/historyIntelli.ts +0 -132
  99. package/src/.umi-production/core/plugin.ts +0 -40
  100. package/src/.umi-production/core/pluginConfig.ts +0 -330
  101. package/src/.umi-production/core/pluginConfigJoi.d.ts +0 -7
  102. package/src/.umi-production/core/polyfill.ts +0 -220
  103. package/src/.umi-production/core/route.tsx +0 -47
  104. package/src/.umi-production/core/routeProps.js +0 -5
  105. package/src/.umi-production/core/routeProps.ts +0 -6
  106. package/src/.umi-production/core/terminal.ts +0 -37
  107. package/src/.umi-production/exports.ts +0 -17
  108. package/src/.umi-production/testBrowser.tsx +0 -90
  109. package/src/.umi-production/tsconfig.json +0 -43
  110. package/src/.umi-production/typings.d.ts +0 -136
  111. package/src/.umi-production/umi.ts +0 -83
  112. package/src/framework/GlobalData.d.ts +0 -10
  113. package/src/framework/GlobalData.js +0 -42
  114. package/src/framework/biz/HasPerm.tsx +0 -14
  115. package/src/framework/fields/FieldRemoteSelectMultiple/index.tsx +0 -29
  116. package/src/framework/utils/ArrayUtils.d.ts +0 -6
  117. package/src/framework/utils/DateUtils.d.ts +0 -20
  118. package/src/framework/utils/DeviceUtils.d.ts +0 -3
  119. package/src/framework/utils/EventBus.d.ts +0 -6
  120. package/src/framework/utils/MessageUtils.d.ts +0 -10
  121. package/src/framework/utils/MessageUtils.js +0 -43
  122. package/src/framework/utils/ObjectUtils.d.ts +0 -14
  123. package/src/framework/utils/ObjectUtils.js +0 -12
  124. package/src/framework/utils/StorageUtils.d.ts +0 -4
  125. package/src/framework/utils/StringUtils.d.ts +0 -17
  126. package/src/framework/utils/TreeUtils.d.ts +0 -13
  127. package/src/framework/utils/UrlUtils.d.ts +0 -6
  128. package/src/framework/utils/system/DictUtils.d.ts +0 -20
  129. package/src/framework/utils/system/FormRegistry.d.ts +0 -8
  130. package/src/framework/utils/system/FormRegistry.js +0 -31
  131. package/src/framework/utils/system/HttpUtils.d.ts +0 -14
  132. package/src/framework/utils/system/PageUtils.d.ts +0 -14
  133. package/src/framework/utils/system/PageUtils.js +0 -59
  134. package/src/framework/utils/system/PermUtils.d.ts +0 -5
  135. package/src/framework/utils/system/ThemeUtils.d.ts +0 -6
  136. package/src/framework/utils/system/ThemeUtils.js +0 -52
  137. package/src/layouts/PageRender.tsx +0 -29
  138. package/src/layouts/admin/TabPageRender.jsx +0 -210
  139. package/src/layouts/index.jsx +0 -120
  140. package/src/loading.jsx +0 -19
  141. package/src/pages/about.jsx +0 -16
  142. /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
- export class FieldNumberRange extends React.Component {
8
- onChangeA = (a) => {
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(0))}
27
+ value={value != null ? Number((value * 100).toFixed(precision)) : null}
17
28
  onChange={v => {
18
- if (v !== null) {
19
- const percentValue = Number((v / 100).toFixed(2));
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
- export class FieldSysOrgTree extends React.Component {
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
- export class FieldSysOrgTreeSelect extends React.Component {
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 {Modal, Upload, message} from "antd";
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
- Modal.error({title: '上传文件失败', content: ajaxResult.message});
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
- Modal.error({
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
- Modal.info({
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 this.getUpload();
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 = () => {
@@ -1,7 +1,6 @@
1
1
  // 表单组件,主要属性: value onChange
2
2
  export * from './types';
3
3
  export * from './FieldRemoteSelect';
4
- export * from './FieldRemoteSelectMultiple';
5
4
  export * from './FieldRemoteTree';
6
5
  export * from './FieldDictSelect';
7
6
  export * from './FieldEditor'
@@ -3,4 +3,6 @@ export * from './components'
3
3
  export * from './utils'
4
4
  export * from './fields'
5
5
  export * from './views'
6
+ export * from './config'
6
7
  export * from './GlobalData'
8
+ export * from './router'
@@ -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,4 @@
1
+ export {history} from './history';
2
+ export {registerRoutes, matchRoute} from './matcher';
3
+ export {PageFrame} from './PageFrame';
4
+ export {Link} from './Link';
@@ -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
+ }