@hsu-react/ui 0.0.18 → 0.0.19
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/es/components/Chart/Bar/index.js +21 -6
- package/es/components/Chart/Line/index.js +21 -6
- package/es/components/Chart/_utils/autoScrollLegend.d.ts +5 -0
- package/es/components/Chart/_utils/autoScrollLegend.js +16 -6
- package/es/components/FilePreview/_components/XlsxPreviewPane/index.d.ts +16 -0
- package/es/components/FilePreview/_components/XlsxPreviewPane/index.js +27 -0
- package/es/components/FilePreview/index.js +52 -23
- package/es/components/FormItem/FormInput/FormPasswordStrength/index.js +43 -4
- package/es/components/FormItem/index.d.ts +2 -2
- package/es/components/FormItem/index.js +46 -5
- package/lib/components/Chart/Bar/index.js +18 -3
- package/lib/components/Chart/Line/index.js +18 -3
- package/lib/components/Chart/_utils/autoScrollLegend.d.ts +5 -0
- package/lib/components/Chart/_utils/autoScrollLegend.js +14 -6
- package/lib/components/FilePreview/_components/XlsxPreviewPane/index.d.ts +16 -0
- package/lib/components/FilePreview/_components/XlsxPreviewPane/index.js +35 -0
- package/lib/components/FilePreview/index.js +47 -23
- package/lib/components/FormItem/FormInput/FormPasswordStrength/index.js +37 -5
- package/lib/components/FormItem/index.d.ts +2 -2
- package/lib/components/FormItem/index.js +43 -7
- package/package.json +4 -3
- package/src/components/Chart/Bar/index.tsx +26 -6
- package/src/components/Chart/Line/index.tsx +26 -6
- package/src/components/Chart/_utils/autoScrollLegend.ts +17 -3
- package/src/components/FilePreview/_components/XlsxPreviewPane/index.tsx +33 -0
- package/src/components/FilePreview/index.md +11 -0
- package/src/components/FilePreview/index.tsx +46 -19
- package/src/components/FormItem/FormInput/FormPasswordStrength/index.tsx +43 -4
- package/src/components/FormItem/index.md +19 -0
- package/src/components/FormItem/index.tsx +44 -5
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface XlsxPreviewPaneProps {
|
|
3
|
+
fileUrl?: string;
|
|
4
|
+
fileType?: string;
|
|
5
|
+
onClose?: () => void;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* xlsx 预览的完整单元:取数(useXlsxData → xlsx)+ 渲染(XlsxPreview → x-data-spreadsheet)。
|
|
10
|
+
*
|
|
11
|
+
* 单独成文件是为了让 FilePreview 能整体 lazy 掉它。useXlsxData 是 hook,不能写在
|
|
12
|
+
* switch 的某个分支里,若留在 FilePreview 顶层就会把 xlsx 静态拖进依赖图——那正是
|
|
13
|
+
* 要避免的。把「取数 + 渲染」收进同一个组件,整块按需加载。
|
|
14
|
+
*/
|
|
15
|
+
declare const XlsxPreviewPane: React.FC<XlsxPreviewPaneProps>;
|
|
16
|
+
export default XlsxPreviewPane;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _XlsxPreview = _interopRequireDefault(require("../XlsxPreview"));
|
|
9
|
+
var _hooks = require("../../_hooks");
|
|
10
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
/**
|
|
13
|
+
* xlsx 预览的完整单元:取数(useXlsxData → xlsx)+ 渲染(XlsxPreview → x-data-spreadsheet)。
|
|
14
|
+
*
|
|
15
|
+
* 单独成文件是为了让 FilePreview 能整体 lazy 掉它。useXlsxData 是 hook,不能写在
|
|
16
|
+
* switch 的某个分支里,若留在 FilePreview 顶层就会把 xlsx 静态拖进依赖图——那正是
|
|
17
|
+
* 要避免的。把「取数 + 渲染」收进同一个组件,整块按需加载。
|
|
18
|
+
*/
|
|
19
|
+
const XlsxPreviewPane = ({
|
|
20
|
+
fileUrl,
|
|
21
|
+
fileType,
|
|
22
|
+
onClose,
|
|
23
|
+
className
|
|
24
|
+
}) => {
|
|
25
|
+
const xlsxData = (0, _hooks.useXlsxData)({
|
|
26
|
+
fileType,
|
|
27
|
+
fileUrl
|
|
28
|
+
});
|
|
29
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_XlsxPreview.default, {
|
|
30
|
+
xlsxData: xlsxData,
|
|
31
|
+
onClose: onClose,
|
|
32
|
+
className: className
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
var _default = exports.default = XlsxPreviewPane;
|
|
@@ -10,17 +10,35 @@ Object.defineProperty(exports, "FilePreviewTypeArr", {
|
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
12
|
exports.default = void 0;
|
|
13
|
-
var _react =
|
|
14
|
-
var _PdfPreview = _interopRequireDefault(require("./_components/PdfPreview"));
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
15
14
|
var _VideoPreview = _interopRequireDefault(require("./_components/VideoPreview"));
|
|
16
15
|
var _TextPreview = _interopRequireDefault(require("./_components/TextPreview"));
|
|
17
|
-
var _MarkdownPreview = _interopRequireDefault(require("./_components/MarkdownPreview"));
|
|
18
16
|
var _ImagePreview = _interopRequireDefault(require("./_components/ImagePreview"));
|
|
19
|
-
var _XlsxPreview = _interopRequireDefault(require("./_components/XlsxPreview"));
|
|
20
|
-
var _hooks = require("./_hooks");
|
|
21
17
|
var _utils = require("./_utils");
|
|
22
18
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
23
19
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
21
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
22
|
+
/**
|
|
23
|
+
* 重型格式按需加载。
|
|
24
|
+
*
|
|
25
|
+
* FilePreview 是个按 fileType 分发的 dispatcher,而它处在 Upload / FormItem 的静态
|
|
26
|
+
* 依赖图上(FormItem → FormUpload → Upload → FilePreview),业务侧只要用到任何一种
|
|
27
|
+
* 表单项,下面这些只服务于特定格式的库就会被打进首屏:
|
|
28
|
+
*
|
|
29
|
+
* pdf → PdfPreview → hsu-utils 的 RenderPDF → pdfjs-dist 约 374 KB
|
|
30
|
+
* xlsx → XlsxPreviewPane → xlsx + x-data-spreadsheet
|
|
31
|
+
* md → MarkdownPreview → Markdown → katex 等
|
|
32
|
+
*
|
|
33
|
+
* 修 dispatcher 而不是逐个改它的引用方:这里改一次,所有静态 import FilePreview 的
|
|
34
|
+
* 地方(Upload、UploadedItem、FormImage…)都受益,也不会因为将来多一个引用方而回归。
|
|
35
|
+
*
|
|
36
|
+
* 图片/文本/视频三种是轻量实现,保持静态——它们也是最常命中的格式,不值得为其
|
|
37
|
+
* 付一次异步往返。
|
|
38
|
+
*/
|
|
39
|
+
const PdfPreview = /*#__PURE__*/(0, _react.lazy)(() => Promise.resolve().then(() => _interopRequireWildcard(require("./_components/PdfPreview"))));
|
|
40
|
+
const MarkdownPreview = /*#__PURE__*/(0, _react.lazy)(() => Promise.resolve().then(() => _interopRequireWildcard(require("./_components/MarkdownPreview"))));
|
|
41
|
+
const XlsxPreviewPane = /*#__PURE__*/(0, _react.lazy)(() => Promise.resolve().then(() => _interopRequireWildcard(require("./_components/XlsxPreviewPane"))));
|
|
24
42
|
const FilePreview = props => {
|
|
25
43
|
const {
|
|
26
44
|
fileUrl,
|
|
@@ -31,10 +49,6 @@ const FilePreview = props => {
|
|
|
31
49
|
className,
|
|
32
50
|
pagination
|
|
33
51
|
} = props;
|
|
34
|
-
const xlsxData = (0, _hooks.useXlsxData)({
|
|
35
|
-
fileType,
|
|
36
|
-
fileUrl
|
|
37
|
-
});
|
|
38
52
|
if (!open) {
|
|
39
53
|
return null;
|
|
40
54
|
}
|
|
@@ -49,12 +63,15 @@ const FilePreview = props => {
|
|
|
49
63
|
}
|
|
50
64
|
case "pdf":
|
|
51
65
|
{
|
|
52
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
|
|
67
|
+
fallback: null,
|
|
68
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(PdfPreview, {
|
|
69
|
+
fileUrl: fileUrl,
|
|
70
|
+
open: open,
|
|
71
|
+
onClose: onClose,
|
|
72
|
+
className: className,
|
|
73
|
+
pagination: pagination
|
|
74
|
+
})
|
|
58
75
|
});
|
|
59
76
|
}
|
|
60
77
|
case "jpg":
|
|
@@ -78,18 +95,25 @@ const FilePreview = props => {
|
|
|
78
95
|
}
|
|
79
96
|
case "md":
|
|
80
97
|
{
|
|
81
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
98
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
|
|
99
|
+
fallback: null,
|
|
100
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(MarkdownPreview, {
|
|
101
|
+
text: text,
|
|
102
|
+
onClose: onClose,
|
|
103
|
+
className: className
|
|
104
|
+
})
|
|
85
105
|
});
|
|
86
106
|
}
|
|
87
107
|
case "xlsx":
|
|
88
108
|
{
|
|
89
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
|
|
110
|
+
fallback: null,
|
|
111
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(XlsxPreviewPane, {
|
|
112
|
+
fileUrl: fileUrl,
|
|
113
|
+
fileType: fileType,
|
|
114
|
+
onClose: onClose,
|
|
115
|
+
className: className
|
|
116
|
+
})
|
|
93
117
|
});
|
|
94
118
|
}
|
|
95
119
|
default:
|
|
@@ -8,11 +8,31 @@ var _Input = _interopRequireDefault(require("../../../Input"));
|
|
|
8
8
|
var _ItemContainer = _interopRequireDefault(require("../../ItemContainer"));
|
|
9
9
|
var _react = _interopRequireWildcard(require("react"));
|
|
10
10
|
var _indexModule = _interopRequireDefault(require("./index.module.scss"));
|
|
11
|
-
var _zxcvbn = _interopRequireDefault(require("zxcvbn"));
|
|
12
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
-
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
|
-
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
14
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /**
|
|
15
|
+
* zxcvbn 约 793 KB(未压缩),却只在这一个字段的 onChange 里用来算强度分。
|
|
16
|
+
* 静态 import 会让它跟着 FormInput → FormItem 进入每个引入过表单项的页面的
|
|
17
|
+
* 首屏 chunk。改成动态 import:只有真的渲染了 PASSWORDSTRENGTH 字段才去拉。
|
|
18
|
+
*
|
|
19
|
+
* 模块级缓存 Promise,多个密码框共用一次加载;失败时清掉缓存,下次重试。
|
|
20
|
+
*/
|
|
21
|
+
let zxcvbnPromise;
|
|
22
|
+
const loadZxcvbn = () => {
|
|
23
|
+
if (!zxcvbnPromise) {
|
|
24
|
+
zxcvbnPromise = Promise.resolve().then(() => _interopRequireWildcard(require("zxcvbn"))).then(mod => {
|
|
25
|
+
// zxcvbn 是 `export =` 的 CJS 模块:不同打包器/interop 下动态 import 拿到的
|
|
26
|
+
// 可能是函数本身,也可能是 { default: 函数 },两种都要兼容
|
|
27
|
+
const m = mod;
|
|
28
|
+
return m.default ?? m;
|
|
29
|
+
}).catch(err => {
|
|
30
|
+
zxcvbnPromise = undefined;
|
|
31
|
+
throw err;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return zxcvbnPromise;
|
|
35
|
+
};
|
|
16
36
|
const PasswordStrength = props => {
|
|
17
37
|
const {
|
|
18
38
|
placeholder,
|
|
@@ -20,6 +40,13 @@ const PasswordStrength = props => {
|
|
|
20
40
|
...inputConfig
|
|
21
41
|
} = props;
|
|
22
42
|
const [fraction, setFraction] = (0, _react.useState)(0);
|
|
43
|
+
// 挂载即预取:chunk 仍是独立的,但用户敲第一个字符时通常已经就绪
|
|
44
|
+
(0, _react.useEffect)(() => {
|
|
45
|
+
loadZxcvbn().catch(() => undefined);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// 打字比加载快时会有多次并发计算,迟到的结果不能盖掉最新一次
|
|
49
|
+
const seqRef = (0, _react.useRef)(0);
|
|
23
50
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
24
51
|
className: _indexModule.default.passwordStrength,
|
|
25
52
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Input.default.Password, {
|
|
@@ -27,8 +54,13 @@ const PasswordStrength = props => {
|
|
|
27
54
|
placeholder: placeholder ?? "请输入",
|
|
28
55
|
onChange: value => {
|
|
29
56
|
onChange && onChange(value);
|
|
30
|
-
const
|
|
31
|
-
|
|
57
|
+
const seq = ++seqRef.current;
|
|
58
|
+
loadZxcvbn().then(zxcvbn => {
|
|
59
|
+
if (seq !== seqRef.current) return;
|
|
60
|
+
setFraction(zxcvbn(value).guesses_log10);
|
|
61
|
+
})
|
|
62
|
+
// 强度条只是辅助提示,拉不到就不显示,不打断用户输入
|
|
63
|
+
.catch(() => undefined);
|
|
32
64
|
},
|
|
33
65
|
allowClear: true,
|
|
34
66
|
autoComplete: "new-password"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FormAutoProps } from "./FormAuto";
|
|
2
2
|
import { FormCheckboxGroupProps, FormCheckboxProps } from "./FormCheckbox";
|
|
3
3
|
import { FormDatePickerProps, FormRangePickerProps, FormStepPickerProps } from "./FormDatePicker";
|
|
4
|
-
import { FormEditorProps } from "./FormEditor";
|
|
4
|
+
import type { FormEditorProps } from "./FormEditor";
|
|
5
5
|
import { FormInputNumberProps, FormInputProps, FormPasswordProps, FormPasswordStrengthProps, FormRangeInputProps, FormTextAreaInputProps } from "./FormInput";
|
|
6
6
|
import { FormRadioProps } from "./FormRadio";
|
|
7
7
|
import { FormSegmentedProps } from "./FormSegmented";
|
|
@@ -12,7 +12,7 @@ import { FormTreeProps } from "./FormTree";
|
|
|
12
12
|
import { FormImageProps, FormUploadProps } from "./FormUpload";
|
|
13
13
|
import ItemContainer, { ItemContainerProps } from "./ItemContainer";
|
|
14
14
|
import { FormTextProps } from "./FormText";
|
|
15
|
-
import { FormCodeMirrorProps } from "./FormCodeMirror";
|
|
15
|
+
import type { FormCodeMirrorProps } from "./FormCodeMirror";
|
|
16
16
|
import React from "react";
|
|
17
17
|
export { ItemContainer as FormItemContainer };
|
|
18
18
|
export type { ItemContainerProps as FormItemContainerProps };
|
|
@@ -13,7 +13,6 @@ exports.default = exports.PlaceholderDictEn = exports.PlaceholderDict = void 0;
|
|
|
13
13
|
var _FormAuto = _interopRequireDefault(require("./FormAuto"));
|
|
14
14
|
var _FormCheckbox = _interopRequireDefault(require("./FormCheckbox"));
|
|
15
15
|
var _FormDatePicker = _interopRequireDefault(require("./FormDatePicker"));
|
|
16
|
-
var _FormEditor = _interopRequireDefault(require("./FormEditor"));
|
|
17
16
|
var _FormInput = _interopRequireDefault(require("./FormInput"));
|
|
18
17
|
var _FormRadio = _interopRequireDefault(require("./FormRadio"));
|
|
19
18
|
var _FormSegmented = _interopRequireDefault(require("./FormSegmented"));
|
|
@@ -24,10 +23,43 @@ var _FormTree = _interopRequireDefault(require("./FormTree"));
|
|
|
24
23
|
var _FormUpload = _interopRequireDefault(require("./FormUpload"));
|
|
25
24
|
var _ItemContainer = _interopRequireDefault(require("./ItemContainer"));
|
|
26
25
|
var _FormText = _interopRequireDefault(require("./FormText"));
|
|
27
|
-
var
|
|
28
|
-
var _react = _interopRequireDefault(require("react"));
|
|
26
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
29
27
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
30
28
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
30
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /**
|
|
31
|
+
* 重型字段按需加载。
|
|
32
|
+
*
|
|
33
|
+
* FormItem 是个静态分发器,几乎每个业务页面都会引入它。若在这里静态 import
|
|
34
|
+
* 全部渲染器,那么**只要用到任何一种表单项**,富文本、代码编辑器这些巨型依赖
|
|
35
|
+
* 就会被一并拉进消费方的首屏 chunk:
|
|
36
|
+
*
|
|
37
|
+
* FormItem → FormEditor → Editor → @wangeditor/editor (约 814 KB)
|
|
38
|
+
* FormItem → FormCodeMirror → CodeMirror → @codemirror/* (约 380 KB)
|
|
39
|
+
*
|
|
40
|
+
* 消费方实测:某后台项目仅因入口图里存在 FormItem,首屏就多背了约 2.5 MB
|
|
41
|
+
* 未压缩的三方库;改为按需加载后首屏从 3.72 MB 降到 965 KB(gzip)。
|
|
42
|
+
*
|
|
43
|
+
* 只有 type 真的命中 EDITOR / CODEMIRROR 时才会去拉对应 chunk。类型仍走静态
|
|
44
|
+
* `import type`(编译期擦除,不产生运行时依赖),所以 FormItemMap 不受影响。
|
|
45
|
+
* 同一目录下的 CodeMirror 早就用 `import()` 按需加载语言包/校验器,这里是同一思路。
|
|
46
|
+
*/
|
|
47
|
+
const FormEditor = /*#__PURE__*/(0, _react.lazy)(() => Promise.resolve().then(() => _interopRequireWildcard(require("./FormEditor"))));
|
|
48
|
+
const FormCodeMirror = /*#__PURE__*/(0, _react.lazy)(() => Promise.resolve().then(() => _interopRequireWildcard(require("./FormCodeMirror"))));
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 重型字段的 Suspense 包装。
|
|
52
|
+
*
|
|
53
|
+
* fallback 用 null 而不是骨架:这些字段都由 ItemContainer 渲染 antd 的
|
|
54
|
+
* Form.Item,加载期间不占位反而不会让表单布局跳动;antd Form 的值存在 store 里,
|
|
55
|
+
* 字段挂载晚于 setFieldsValue 也能正确回填,不影响编辑态回显。
|
|
56
|
+
*/
|
|
57
|
+
const LazyField = ({
|
|
58
|
+
children
|
|
59
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.Suspense, {
|
|
60
|
+
fallback: null,
|
|
61
|
+
children: children
|
|
62
|
+
});
|
|
31
63
|
// Common base type
|
|
32
64
|
|
|
33
65
|
// Core mapping (sorted by group)
|
|
@@ -252,12 +284,16 @@ const FormItem = props => {
|
|
|
252
284
|
...props
|
|
253
285
|
});
|
|
254
286
|
case "EDITOR":
|
|
255
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
256
|
-
|
|
287
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(LazyField, {
|
|
288
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FormEditor, {
|
|
289
|
+
...props
|
|
290
|
+
})
|
|
257
291
|
});
|
|
258
292
|
case "CODEMIRROR":
|
|
259
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
260
|
-
|
|
293
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(LazyField, {
|
|
294
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FormCodeMirror, {
|
|
295
|
+
...props
|
|
296
|
+
})
|
|
261
297
|
});
|
|
262
298
|
case "TEXT":
|
|
263
299
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_FormText.default, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hsu-react/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "一套基于 React + Ant Design 的中后台业务组件库",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
"dev": "dumi dev",
|
|
42
42
|
"docs:build": "dumi build",
|
|
43
43
|
"docs:preview": "dumi preview",
|
|
44
|
-
"build": "father build",
|
|
45
|
-
"prepublishOnly": "father build"
|
|
44
|
+
"build": "father build && node scripts/check-heavy-deps.cjs",
|
|
45
|
+
"prepublishOnly": "father build && node scripts/check-heavy-deps.cjs",
|
|
46
|
+
"check:deps": "node scripts/check-heavy-deps.cjs"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@ant-design/x": "^2.0.1",
|
|
@@ -280,14 +280,28 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
280
280
|
// Set the option: notMerge resets the dataZoom window to its initial position,
|
|
281
281
|
// so when x-axis data is unchanged (e.g. re-render triggered by axis recalculation / legend selection), record the current window first and restore it afterwards
|
|
282
282
|
const xAxisKey = xAxisData?.join("\u0001") ?? "";
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
283
|
+
const sameXAxis = prevXAxisKeyRef.current === xAxisKey;
|
|
284
|
+
const prevZoomRanges = sameXAxis
|
|
285
|
+
? ((chart.getOption() as ChartsOption | undefined)?.dataZoom as
|
|
286
|
+
| Array<{ start?: number; end?: number }>
|
|
287
|
+
| undefined)
|
|
288
|
+
: undefined;
|
|
289
|
+
// 滚动图例的翻页位置同样会被 notMerge 重置,x 轴未变时恢复,
|
|
290
|
+
// 否则点一次图例、或坐标轴重算触发一次重渲染,图例就跳回第一页
|
|
291
|
+
const prevLegendScrollIndex = sameXAxis
|
|
292
|
+
? (
|
|
293
|
+
(chart.getOption() as ChartsOption | undefined)?.legend as
|
|
294
|
+
| Array<{ scrollDataIndex?: number }>
|
|
295
|
+
| undefined
|
|
296
|
+
)?.[0]?.scrollDataIndex
|
|
297
|
+
: undefined;
|
|
289
298
|
prevXAxisKeyRef.current = xAxisKey;
|
|
290
299
|
chart.setOption(chartOption as ChartOptionType, true);
|
|
300
|
+
if (prevLegendScrollIndex) {
|
|
301
|
+
chart.setOption({
|
|
302
|
+
legend: { scrollDataIndex: prevLegendScrollIndex },
|
|
303
|
+
} as unknown as ChartOptionType);
|
|
304
|
+
}
|
|
291
305
|
const nextZooms = (chartOption as ChartsOption).dataZoom;
|
|
292
306
|
if (
|
|
293
307
|
prevZoomRanges?.length &&
|
|
@@ -406,6 +420,10 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
406
420
|
visibleCount: legendVisibleCount,
|
|
407
421
|
interval: legendScrollInterval,
|
|
408
422
|
autoStart: true,
|
|
423
|
+
// 滚轮已被 x 轴平移占用时不再让图例抢,否则一次滚动两处都动
|
|
424
|
+
enableWheel:
|
|
425
|
+
!normalizedScroll.zoomEnabled ||
|
|
426
|
+
normalizedScroll.wheelModeWhenSliderHidden !== "scroll",
|
|
409
427
|
});
|
|
410
428
|
}
|
|
411
429
|
|
|
@@ -443,6 +461,8 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
443
461
|
enableLegendAutoScroll,
|
|
444
462
|
legendVisibleCount,
|
|
445
463
|
legendScrollInterval,
|
|
464
|
+
// 图例是否接管滚轮由它推导,漏了会拿到过期的滚动配置
|
|
465
|
+
normalizedScroll,
|
|
446
466
|
]);
|
|
447
467
|
|
|
448
468
|
// Handle auto play
|
|
@@ -277,14 +277,28 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
277
277
|
// Set the option: notMerge resets the dataZoom window to its initial position,
|
|
278
278
|
// so when x-axis data is unchanged (e.g. re-render triggered by axis recalculation / legend selection), record the current window first and restore it afterwards
|
|
279
279
|
const xAxisKey = xAxisData?.join("\u0001") ?? "";
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
280
|
+
const sameXAxis = prevXAxisKeyRef.current === xAxisKey;
|
|
281
|
+
const prevZoomRanges = sameXAxis
|
|
282
|
+
? ((chart.getOption() as ChartsOption | undefined)?.dataZoom as
|
|
283
|
+
| Array<{ start?: number; end?: number }>
|
|
284
|
+
| undefined)
|
|
285
|
+
: undefined;
|
|
286
|
+
// 滚动图例的翻页位置同样会被 notMerge 重置,x 轴未变时恢复,
|
|
287
|
+
// 否则点一次图例、或坐标轴重算触发一次重渲染,图例就跳回第一页
|
|
288
|
+
const prevLegendScrollIndex = sameXAxis
|
|
289
|
+
? (
|
|
290
|
+
(chart.getOption() as ChartsOption | undefined)?.legend as
|
|
291
|
+
| Array<{ scrollDataIndex?: number }>
|
|
292
|
+
| undefined
|
|
293
|
+
)?.[0]?.scrollDataIndex
|
|
294
|
+
: undefined;
|
|
286
295
|
prevXAxisKeyRef.current = xAxisKey;
|
|
287
296
|
chart.setOption(chartOption as ChartOptionType, true);
|
|
297
|
+
if (prevLegendScrollIndex) {
|
|
298
|
+
chart.setOption({
|
|
299
|
+
legend: { scrollDataIndex: prevLegendScrollIndex },
|
|
300
|
+
} as unknown as ChartOptionType);
|
|
301
|
+
}
|
|
288
302
|
const nextZooms = (chartOption as ChartsOption).dataZoom;
|
|
289
303
|
if (
|
|
290
304
|
prevZoomRanges?.length &&
|
|
@@ -403,6 +417,10 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
403
417
|
visibleCount: legendVisibleCount,
|
|
404
418
|
interval: legendScrollInterval,
|
|
405
419
|
autoStart: true,
|
|
420
|
+
// 滚轮已被 x 轴平移占用时不再让图例抢,否则一次滚动两处都动
|
|
421
|
+
enableWheel:
|
|
422
|
+
!normalizedScroll.zoomEnabled ||
|
|
423
|
+
normalizedScroll.wheelModeWhenSliderHidden !== "scroll",
|
|
406
424
|
});
|
|
407
425
|
}
|
|
408
426
|
|
|
@@ -440,6 +458,8 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
440
458
|
enableLegendAutoScroll,
|
|
441
459
|
legendVisibleCount,
|
|
442
460
|
legendScrollInterval,
|
|
461
|
+
// 图例是否接管滚轮由它推导,漏了会拿到过期的滚动配置
|
|
462
|
+
normalizedScroll,
|
|
443
463
|
]);
|
|
444
464
|
|
|
445
465
|
// Handle auto play
|
|
@@ -7,6 +7,11 @@ export interface AutoScrollLegendProps {
|
|
|
7
7
|
interval?: number;
|
|
8
8
|
autoStart?: boolean;
|
|
9
9
|
stopOnWheel?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 是否接管滚轮翻图例,默认 true。
|
|
12
|
+
* 图表自身已用滚轮平移 x 轴时传 false,避免一次滚动既翻图例又移坐标轴。
|
|
13
|
+
*/
|
|
14
|
+
enableWheel?: boolean;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export function autoScrollLegend(props: AutoScrollLegendProps) {
|
|
@@ -17,9 +22,14 @@ export function autoScrollLegend(props: AutoScrollLegendProps) {
|
|
|
17
22
|
interval = 1500,
|
|
18
23
|
autoStart = true,
|
|
19
24
|
stopOnWheel = true,
|
|
25
|
+
enableWheel = true,
|
|
20
26
|
} = props;
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
// 组件重渲染会重建滚动器,从图例当前位置接着轮播,避免每次都跳回第一项
|
|
29
|
+
const legendOption = (
|
|
30
|
+
chart.getOption() as { legend?: Array<{ scrollDataIndex?: number }> }
|
|
31
|
+
)?.legend?.[0];
|
|
32
|
+
let currentIndex = legendOption?.scrollDataIndex ?? 0;
|
|
23
33
|
let scrollInterval: NodeJS.Timeout | null = null;
|
|
24
34
|
let isManualScrolling = false;
|
|
25
35
|
|
|
@@ -90,13 +100,17 @@ export function autoScrollLegend(props: AutoScrollLegendProps) {
|
|
|
90
100
|
const chartDom = chart.getDom();
|
|
91
101
|
chartDom.addEventListener("mouseover", handleMouseOver);
|
|
92
102
|
chartDom.addEventListener("mouseout", handleMouseOut);
|
|
93
|
-
|
|
103
|
+
if (enableWheel) {
|
|
104
|
+
chartDom.addEventListener("wheel", handleWheel, { passive: false });
|
|
105
|
+
}
|
|
94
106
|
|
|
95
107
|
function dispose() {
|
|
96
108
|
stop();
|
|
97
109
|
chartDom.removeEventListener("mouseover", handleMouseOver);
|
|
98
110
|
chartDom.removeEventListener("mouseout", handleMouseOut);
|
|
99
|
-
|
|
111
|
+
if (enableWheel) {
|
|
112
|
+
chartDom.removeEventListener("wheel", handleWheel);
|
|
113
|
+
}
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
if (autoStart) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import XlsxPreview from "../XlsxPreview";
|
|
4
|
+
import { useXlsxData } from "../../_hooks";
|
|
5
|
+
|
|
6
|
+
export interface XlsxPreviewPaneProps {
|
|
7
|
+
fileUrl?: string;
|
|
8
|
+
fileType?: string;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* xlsx 预览的完整单元:取数(useXlsxData → xlsx)+ 渲染(XlsxPreview → x-data-spreadsheet)。
|
|
15
|
+
*
|
|
16
|
+
* 单独成文件是为了让 FilePreview 能整体 lazy 掉它。useXlsxData 是 hook,不能写在
|
|
17
|
+
* switch 的某个分支里,若留在 FilePreview 顶层就会把 xlsx 静态拖进依赖图——那正是
|
|
18
|
+
* 要避免的。把「取数 + 渲染」收进同一个组件,整块按需加载。
|
|
19
|
+
*/
|
|
20
|
+
const XlsxPreviewPane: React.FC<XlsxPreviewPaneProps> = ({
|
|
21
|
+
fileUrl,
|
|
22
|
+
fileType,
|
|
23
|
+
onClose,
|
|
24
|
+
className,
|
|
25
|
+
}) => {
|
|
26
|
+
const xlsxData = useXlsxData({ fileType, fileUrl });
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<XlsxPreview xlsxData={xlsxData} onClose={onClose} className={className} />
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default XlsxPreviewPane;
|
|
@@ -59,3 +59,14 @@ export default () => {
|
|
|
59
59
|
| onClose | 关闭回调 | `() => void` | - |
|
|
60
60
|
|
|
61
61
|
> 另导出类型 `FilePreviewType` 与常量 `FilePreviewTypeArr`(支持的文件类型数组)。
|
|
62
|
+
|
|
63
|
+
### 重型格式按需加载
|
|
64
|
+
|
|
65
|
+
`pdf`、`xlsx`、`md` 三种预览器依赖体积很大的三方库(pdfjs、xlsx + x-data-spreadsheet、
|
|
66
|
+
katex 等),已改为按需加载:只有 `fileType` 真的命中时才去拉对应 chunk。
|
|
67
|
+
`mp4` / 图片 / `txt` 是轻量实现,保持同步渲染。
|
|
68
|
+
|
|
69
|
+
这一点对使用方很重要:`FilePreview` 在 `Upload`、`FormItem`(`FILE` / `IMAGEFILE`)的
|
|
70
|
+
静态依赖图上,若不这么做,**业务侧只要用到任何一种表单项,上述库就会被打进首屏**。
|
|
71
|
+
|
|
72
|
+
用法不变,组件内部已包 `Suspense`。首次预览某种重型格式时弹窗内容会晚一拍出现。
|
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import PdfPreview from "./_components/PdfPreview";
|
|
1
|
+
import React, { Suspense, lazy } from "react";
|
|
3
2
|
import VideoPreview from "./_components/VideoPreview";
|
|
4
3
|
import TextPreview from "./_components/TextPreview";
|
|
5
|
-
import MarkdownPreview from "./_components/MarkdownPreview";
|
|
6
4
|
import ImagePreview from "./_components/ImagePreview";
|
|
7
|
-
import XlsxPreview from "./_components/XlsxPreview";
|
|
8
|
-
import { useXlsxData } from "./_hooks";
|
|
9
5
|
import { FilePreviewType, FilePreviewTypeArr } from "./_utils";
|
|
10
6
|
|
|
11
7
|
export type { FilePreviewType };
|
|
12
8
|
export { FilePreviewTypeArr };
|
|
13
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 重型格式按需加载。
|
|
12
|
+
*
|
|
13
|
+
* FilePreview 是个按 fileType 分发的 dispatcher,而它处在 Upload / FormItem 的静态
|
|
14
|
+
* 依赖图上(FormItem → FormUpload → Upload → FilePreview),业务侧只要用到任何一种
|
|
15
|
+
* 表单项,下面这些只服务于特定格式的库就会被打进首屏:
|
|
16
|
+
*
|
|
17
|
+
* pdf → PdfPreview → hsu-utils 的 RenderPDF → pdfjs-dist 约 374 KB
|
|
18
|
+
* xlsx → XlsxPreviewPane → xlsx + x-data-spreadsheet
|
|
19
|
+
* md → MarkdownPreview → Markdown → katex 等
|
|
20
|
+
*
|
|
21
|
+
* 修 dispatcher 而不是逐个改它的引用方:这里改一次,所有静态 import FilePreview 的
|
|
22
|
+
* 地方(Upload、UploadedItem、FormImage…)都受益,也不会因为将来多一个引用方而回归。
|
|
23
|
+
*
|
|
24
|
+
* 图片/文本/视频三种是轻量实现,保持静态——它们也是最常命中的格式,不值得为其
|
|
25
|
+
* 付一次异步往返。
|
|
26
|
+
*/
|
|
27
|
+
const PdfPreview = lazy(() => import("./_components/PdfPreview"));
|
|
28
|
+
const MarkdownPreview = lazy(() => import("./_components/MarkdownPreview"));
|
|
29
|
+
const XlsxPreviewPane = lazy(() => import("./_components/XlsxPreviewPane"));
|
|
30
|
+
|
|
14
31
|
interface FilePreviewProps {
|
|
15
32
|
fileUrl?: string;
|
|
16
33
|
fileType?: FilePreviewType;
|
|
@@ -25,7 +42,6 @@ interface FilePreviewProps {
|
|
|
25
42
|
const FilePreview: React.FC<FilePreviewProps> = (props) => {
|
|
26
43
|
const { fileUrl, fileType, open, onClose, text, className, pagination } =
|
|
27
44
|
props;
|
|
28
|
-
const xlsxData = useXlsxData({ fileType, fileUrl });
|
|
29
45
|
|
|
30
46
|
if (!open) {
|
|
31
47
|
return null;
|
|
@@ -43,13 +59,15 @@ const FilePreview: React.FC<FilePreviewProps> = (props) => {
|
|
|
43
59
|
}
|
|
44
60
|
case "pdf": {
|
|
45
61
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
<Suspense fallback={null}>
|
|
63
|
+
<PdfPreview
|
|
64
|
+
fileUrl={fileUrl}
|
|
65
|
+
open={open}
|
|
66
|
+
onClose={onClose}
|
|
67
|
+
className={className}
|
|
68
|
+
pagination={pagination}
|
|
69
|
+
/>
|
|
70
|
+
</Suspense>
|
|
53
71
|
);
|
|
54
72
|
}
|
|
55
73
|
case "jpg":
|
|
@@ -71,16 +89,25 @@ const FilePreview: React.FC<FilePreviewProps> = (props) => {
|
|
|
71
89
|
}
|
|
72
90
|
case "md": {
|
|
73
91
|
return (
|
|
74
|
-
<
|
|
92
|
+
<Suspense fallback={null}>
|
|
93
|
+
<MarkdownPreview
|
|
94
|
+
text={text}
|
|
95
|
+
onClose={onClose}
|
|
96
|
+
className={className}
|
|
97
|
+
/>
|
|
98
|
+
</Suspense>
|
|
75
99
|
);
|
|
76
100
|
}
|
|
77
101
|
case "xlsx": {
|
|
78
102
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
<Suspense fallback={null}>
|
|
104
|
+
<XlsxPreviewPane
|
|
105
|
+
fileUrl={fileUrl}
|
|
106
|
+
fileType={fileType}
|
|
107
|
+
onClose={onClose}
|
|
108
|
+
className={className}
|
|
109
|
+
/>
|
|
110
|
+
</Suspense>
|
|
84
111
|
);
|
|
85
112
|
}
|
|
86
113
|
default: {
|