@qy_better_lib/hooks 0.1.10 → 0.2.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/DOCUMENTATION.md +704 -0
- package/__tests__/use-chart/index.test.ts +287 -0
- package/__tests__/use-emit/index.test.ts +248 -0
- package/__tests__/use-fullscreen/index.test.ts +162 -0
- package/__tests__/use-image/index.test.ts +230 -0
- package/__tests__/use-layout-flow/index.test.ts +382 -0
- package/__tests__/use-mqtt/index.test.ts +392 -0
- package/__tests__/use-print/index.test.ts +378 -0
- package/__tests__/use-watermark/index.test.ts +277 -0
- package/__tests__/use-websocket/index.test.ts +402 -0
- package/dist/hooks.min.js +76 -0
- package/lib/index.d.ts +5 -2
- package/lib/index.js +20 -31
- package/lib/use-chart/config.d.ts +2 -3
- package/lib/use-chart/config.js +78 -0
- package/lib/use-chart/index.d.ts +5 -13
- package/lib/use-chart/index.js +196 -0
- package/lib/use-chart/type.d.ts +92 -4
- package/lib/use-emit/extend.d.ts +2 -1
- package/lib/use-emit/extend.js +34 -15
- package/lib/use-emit/index.d.ts +2 -13
- package/lib/use-emit/index.js +22 -17
- package/lib/use-emit/type.d.ts +16 -0
- package/lib/use-fullscreen/index.d.ts +23 -0
- package/lib/use-fullscreen/index.js +51 -0
- package/lib/use-image/index.d.ts +18 -52
- package/lib/use-image/index.js +187 -67
- package/lib/use-image/type.d.ts +8 -10
- package/lib/use-image/type.js +7 -6
- package/lib/use-layout-flow/index.d.ts +14 -40
- package/lib/use-layout-flow/index.js +284 -0
- package/lib/use-layout-flow/type.d.ts +46 -0
- package/lib/use-mqtt/index.d.ts +9 -18
- package/lib/use-mqtt/index.js +179 -0
- package/lib/use-mqtt/type.d.ts +78 -0
- package/lib/use-print/index.d.ts +5 -9
- package/lib/use-print/index.js +274 -40
- package/lib/use-print/type.d.ts +58 -0
- package/lib/use-watermark/index.d.ts +7 -0
- package/lib/use-watermark/index.js +131 -0
- package/lib/use-watermark/type.d.ts +55 -0
- package/lib/use-websocket/index.d.ts +6 -13
- package/lib/use-websocket/index.js +190 -39
- package/lib/use-websocket/type.d.ts +54 -0
- package/package.json +6 -3
- package/dist/@qy_better_lib/hooks.min.js +0 -15
- package/lib/use-chart/utils.d.ts +0 -0
- package/lib/use-file/index.d.ts +0 -14
- package/lib/use-file/index.js +0 -26
- package/lib/use-image/canvastoDataURL.d.ts +0 -11
- package/lib/use-image/canvastoDataURL.js +0 -7
- package/lib/use-image/canvastoFile.d.ts +0 -11
- package/lib/use-image/canvastoFile.js +0 -9
- package/lib/use-image/dataURLtoFile.d.ts +0 -10
- package/lib/use-image/dataURLtoFile.js +0 -16
- package/lib/use-image/dataURLtoImage.d.ts +0 -7
- package/lib/use-image/dataURLtoImage.js +0 -9
- package/lib/use-image/downloadFile.d.ts +0 -7
- package/lib/use-image/downloadFile.js +0 -9
- package/lib/use-image/filetoDataURL.d.ts +0 -7
- package/lib/use-image/filetoDataURL.js +0 -9
- package/lib/use-image/imagetoCanvas.d.ts +0 -26
- package/lib/use-image/imagetoCanvas.js +0 -41
- package/lib/use-image/urltoBlob.d.ts +0 -8
- package/lib/use-image/urltoBlob.js +0 -6
- package/lib/use-image/urltoImage.d.ts +0 -7
- package/lib/use-image/urltoImage.js +0 -13
- package/lib/use-utils/index.d.ts +0 -1
- package/lib/use-utils/use-fullscreen.d.ts +0 -9
- package/lib/use-waterMark/index.d.ts +0 -17
- package/lib/use-waterMark/index.js +0 -29
package/lib/use-image/index.js
CHANGED
|
@@ -1,72 +1,192 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { EImageType
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
async function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { download_file, to_base64, compress_image } from "@qy_better_lib/core";
|
|
2
|
+
import { EImageType } from "./type.js";
|
|
3
|
+
function use_image() {
|
|
4
|
+
function check_image_type(type) {
|
|
5
|
+
return Object.values(EImageType).includes(type);
|
|
6
|
+
}
|
|
7
|
+
async function canvas_to_data_url(canvas, quality = 0.92, type = EImageType.JPEG) {
|
|
8
|
+
if (!check_image_type(type)) {
|
|
9
|
+
type = EImageType.JPEG;
|
|
10
|
+
}
|
|
11
|
+
return canvas.toDataURL(type, quality);
|
|
12
|
+
}
|
|
13
|
+
async function canvas_to_file(canvas, file_name = "image", quality = 0.92, type = EImageType.JPEG) {
|
|
14
|
+
const data_url = await canvas_to_data_url(canvas, quality, type);
|
|
15
|
+
return data_url_to_file(data_url, type, file_name);
|
|
16
|
+
}
|
|
17
|
+
async function data_url_to_file(data_url, type = EImageType.JPEG, file_name = "image") {
|
|
18
|
+
if (!check_image_type(type)) {
|
|
19
|
+
type = EImageType.JPEG;
|
|
20
|
+
}
|
|
21
|
+
const arr = data_url.split(",");
|
|
22
|
+
const mime = arr[0].match(/:(.*?);/)?.[1] || type;
|
|
23
|
+
const bstr = atob(arr[1]);
|
|
24
|
+
let n = bstr.length;
|
|
25
|
+
const u8arr = new Uint8Array(n);
|
|
26
|
+
while (n--) {
|
|
27
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
28
|
+
}
|
|
29
|
+
const extension = mime.split("/")[1];
|
|
30
|
+
const full_name = `${file_name}.${extension}`;
|
|
31
|
+
return new File([u8arr], full_name, { type: mime });
|
|
32
|
+
}
|
|
33
|
+
async function data_url_to_image(data_url) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const image = new Image();
|
|
36
|
+
image.onload = () => resolve(image);
|
|
37
|
+
image.onerror = reject;
|
|
38
|
+
image.src = data_url;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function download_file$1(blob, file_name = "download") {
|
|
42
|
+
const url = URL.createObjectURL(blob);
|
|
43
|
+
download_file({ path: url, name: file_name });
|
|
44
|
+
URL.revokeObjectURL(url);
|
|
45
|
+
}
|
|
46
|
+
async function file_to_data_url(file) {
|
|
47
|
+
const result = await to_base64(file);
|
|
48
|
+
if (!result) {
|
|
49
|
+
throw new Error("转换文件为DataURL失败");
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async function image_to_canvas(image, config = {}) {
|
|
54
|
+
const canvas = document.createElement("canvas");
|
|
55
|
+
const context = canvas.getContext("2d");
|
|
56
|
+
if (!context) {
|
|
57
|
+
throw new Error("创建Canvas上下文失败");
|
|
58
|
+
}
|
|
59
|
+
const width = config.width || image.width;
|
|
60
|
+
const height = config.height || image.height;
|
|
61
|
+
const scale = config.scale || 1;
|
|
62
|
+
canvas.width = width * scale;
|
|
63
|
+
canvas.height = height * scale;
|
|
64
|
+
context.scale(scale, scale);
|
|
65
|
+
context.drawImage(image, 0, 0, width, height);
|
|
66
|
+
return canvas;
|
|
67
|
+
}
|
|
68
|
+
async function url_to_blob(url) {
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`获取图片失败: ${response.statusText}`);
|
|
72
|
+
}
|
|
73
|
+
return response.blob();
|
|
74
|
+
}
|
|
75
|
+
async function url_to_image(url) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const image = new Image();
|
|
78
|
+
image.onload = () => resolve(image);
|
|
79
|
+
image.onerror = reject;
|
|
80
|
+
image.src = url;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function compress(file, config = {}) {
|
|
84
|
+
if (!(file instanceof Blob)) {
|
|
85
|
+
throw new Error("compress(): 第一个参数必须是Blob对象或File对象");
|
|
86
|
+
}
|
|
87
|
+
const quality = Math.max(0, Math.min(1, isNaN(Number(config.quality)) ? 0.92 : Number(config.quality)));
|
|
88
|
+
config.quality = quality;
|
|
89
|
+
const data_url = await file_to_data_url(file);
|
|
90
|
+
const original_mime = data_url.split(",")[0].match(/:(.*?);/)?.[1];
|
|
91
|
+
let mime = EImageType.JPEG;
|
|
92
|
+
if (check_image_type(config.type)) {
|
|
93
|
+
mime = config.type;
|
|
94
|
+
}
|
|
95
|
+
const image = await data_url_to_image(data_url);
|
|
96
|
+
const canvas = await image_to_canvas(image, config);
|
|
97
|
+
const compress_data_url = await canvas_to_data_url(canvas, config.quality, mime);
|
|
98
|
+
const compress_file = await data_url_to_file(compress_data_url, original_mime || mime);
|
|
99
|
+
if (compress_file.size > file.size) {
|
|
100
|
+
return file;
|
|
101
|
+
}
|
|
102
|
+
return compress_file;
|
|
103
|
+
}
|
|
104
|
+
async function compress_accurately(file, config = {}) {
|
|
105
|
+
if (!(file instanceof Blob)) {
|
|
106
|
+
throw new Error("compressAccurately(): 第一个参数必须是Blob对象或File对象");
|
|
107
|
+
}
|
|
108
|
+
const size = Math.max(0, isNaN(Number(config.size)) ? 0 : Number(config.size));
|
|
109
|
+
config.size = size;
|
|
110
|
+
const accuracy = Math.max(0.8, Math.min(0.99, isNaN(Number(config.accuracy)) ? 0.95 : Number(config.accuracy)));
|
|
111
|
+
config.accuracy = accuracy;
|
|
112
|
+
if (isNaN(config.size)) {
|
|
113
|
+
return file;
|
|
114
|
+
}
|
|
115
|
+
if (config.size * 1024 > file.size) {
|
|
116
|
+
return file;
|
|
117
|
+
}
|
|
118
|
+
if (!config.accuracy || config.accuracy < 0.8 || config.accuracy > 0.99) {
|
|
119
|
+
config.accuracy = 0.95;
|
|
120
|
+
}
|
|
121
|
+
const result_size = {
|
|
122
|
+
max: config.size * (2 - config.accuracy) * 1024,
|
|
123
|
+
accurate: config.size * 1024,
|
|
124
|
+
min: config.size * config.accuracy * 1024
|
|
125
|
+
};
|
|
126
|
+
const data_url = await file_to_data_url(file);
|
|
127
|
+
const original_mime = data_url.split(",")[0].match(/:(.*?);/)?.[1];
|
|
128
|
+
let mime = EImageType.JPEG;
|
|
129
|
+
if (check_image_type(config.type)) {
|
|
130
|
+
mime = config.type;
|
|
131
|
+
}
|
|
132
|
+
const image = await data_url_to_image(data_url);
|
|
133
|
+
const canvas = await image_to_canvas(image, config);
|
|
134
|
+
const proportion = 0.75;
|
|
135
|
+
let image_quality = 1;
|
|
136
|
+
let compress_data_url = "";
|
|
137
|
+
const temp_data_urls = [];
|
|
138
|
+
for (let x = 1; x <= 7; x++) {
|
|
139
|
+
compress_data_url = await canvas_to_data_url(canvas, image_quality, mime);
|
|
140
|
+
const calculation_size = compress_data_url.length * proportion;
|
|
141
|
+
if (x === 7) {
|
|
142
|
+
if (result_size.max < calculation_size || result_size.min > calculation_size) {
|
|
143
|
+
compress_data_url = [compress_data_url, ...temp_data_urls].filter(Boolean).sort(
|
|
144
|
+
(a, b) => Math.abs(a.length * proportion - result_size.accurate) - Math.abs(b.length * proportion - result_size.accurate)
|
|
145
|
+
)[0];
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
if (result_size.max < calculation_size) {
|
|
150
|
+
temp_data_urls[1] = compress_data_url;
|
|
151
|
+
image_quality -= 0.5 ** (x + 1);
|
|
152
|
+
} else if (result_size.min > calculation_size) {
|
|
153
|
+
temp_data_urls[0] = compress_data_url;
|
|
154
|
+
image_quality += 0.5 ** (x + 1);
|
|
155
|
+
} else {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const compress_file = await data_url_to_file(
|
|
160
|
+
compress_data_url,
|
|
161
|
+
original_mime || mime
|
|
26
162
|
);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
accurate: a.size * 1024,
|
|
35
|
-
min: a.size * a.accuracy * 1024
|
|
36
|
-
}, i = await R(t);
|
|
37
|
-
let m = i.split(",")[0].match(/:(.*?);/)?.[1], o = h.JPEG;
|
|
38
|
-
j(a.type) && (o = a.type, m = a.type);
|
|
39
|
-
const p = await N(i), y = await U(p, Object.assign({}, a)), s = 0.75;
|
|
40
|
-
let l = 1, r;
|
|
41
|
-
const b = new Array(2);
|
|
42
|
-
for (let c = 1; c <= 7; c++) {
|
|
43
|
-
r = await z(y, l, o);
|
|
44
|
-
const u = r.length * s;
|
|
45
|
-
if (c === 7) {
|
|
46
|
-
(e.max < u || e.min > u) && (r = [r, ...b].filter((n) => n).sort(
|
|
47
|
-
(n, F) => Math.abs(n.length * s - e.accurate) - Math.abs(F.length * s - e.accurate)
|
|
48
|
-
)[0]);
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
if (e.max < u)
|
|
52
|
-
b[1] = r, l -= 0.5 ** (c + 1);
|
|
53
|
-
else if (e.min > u)
|
|
54
|
-
b[0] = r, l += 0.5 ** (c + 1);
|
|
55
|
-
else
|
|
56
|
-
break;
|
|
163
|
+
if (compress_file.size > file.size) {
|
|
164
|
+
return file;
|
|
165
|
+
}
|
|
166
|
+
return compress_file;
|
|
167
|
+
}
|
|
168
|
+
async function compress_image$1(file, max_width = 800, max_height = 800, quality = 0.8) {
|
|
169
|
+
return compress_image(file, max_width, max_height, quality);
|
|
57
170
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
171
|
+
return {
|
|
172
|
+
// 压缩功能
|
|
173
|
+
compress,
|
|
174
|
+
compress_accurately,
|
|
175
|
+
compress_image: compress_image$1,
|
|
176
|
+
// 转换功能
|
|
177
|
+
canvas_to_data_url,
|
|
178
|
+
canvas_to_file,
|
|
179
|
+
data_url_to_file,
|
|
180
|
+
data_url_to_image,
|
|
181
|
+
file_to_data_url,
|
|
182
|
+
image_to_canvas,
|
|
183
|
+
url_to_blob,
|
|
184
|
+
url_to_image,
|
|
185
|
+
// 工具功能
|
|
186
|
+
download_file: download_file$1
|
|
187
|
+
};
|
|
63
188
|
}
|
|
64
189
|
export {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
M as compressAccurately,
|
|
68
|
-
L as dataURLtoFile,
|
|
69
|
-
N as dataURLtoImage,
|
|
70
|
-
R as filetoDataURL,
|
|
71
|
-
U as imagetoCanvas
|
|
190
|
+
EImageType,
|
|
191
|
+
use_image
|
|
72
192
|
};
|
package/lib/use-image/type.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare enum EImageType {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
PNG = "image/png",
|
|
3
|
+
JPEG = "image/jpeg",
|
|
4
|
+
GIF = "image/gif"
|
|
5
5
|
}
|
|
6
|
-
interface IBaseConfig {
|
|
6
|
+
export interface IBaseConfig {
|
|
7
7
|
[key: string]: any;
|
|
8
8
|
}
|
|
9
9
|
export interface Image2CanvasConfig extends IBaseConfig {
|
|
@@ -12,14 +12,12 @@ export interface Image2CanvasConfig extends IBaseConfig {
|
|
|
12
12
|
scale?: number;
|
|
13
13
|
orientation?: number;
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export type ICompressConfig = Image2CanvasConfig & {
|
|
16
16
|
quality?: number;
|
|
17
17
|
type?: EImageType;
|
|
18
|
-
}
|
|
19
|
-
export
|
|
18
|
+
};
|
|
19
|
+
export type CompressAccuratelyConfig = Image2CanvasConfig & {
|
|
20
20
|
size?: number;
|
|
21
21
|
accuracy?: number;
|
|
22
22
|
type?: EImageType;
|
|
23
|
-
}
|
|
24
|
-
export declare function checkImageType(type: EImageType): boolean;
|
|
25
|
-
export {};
|
|
23
|
+
};
|
package/lib/use-image/type.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var EImageType = /* @__PURE__ */ ((EImageType2) => {
|
|
2
|
+
EImageType2["PNG"] = "image/png";
|
|
3
|
+
EImageType2["JPEG"] = "image/jpeg";
|
|
4
|
+
EImageType2["GIF"] = "image/gif";
|
|
5
|
+
return EImageType2;
|
|
6
|
+
})(EImageType || {});
|
|
5
7
|
export {
|
|
6
|
-
|
|
7
|
-
a as checkImageType
|
|
8
|
+
EImageType
|
|
8
9
|
};
|
|
@@ -1,45 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**自动排布配置 */
|
|
13
|
-
export type LayoutFlowNodeOption = {
|
|
14
|
-
/**节点宽度 */
|
|
15
|
-
node_w: number;
|
|
16
|
-
/**节点高度 */
|
|
17
|
-
node_h: number;
|
|
18
|
-
/**节点间距 */
|
|
19
|
-
nodesep: number;
|
|
20
|
-
/**层间距 */
|
|
21
|
-
ranksep: number;
|
|
22
|
-
};
|
|
23
|
-
/**自动排布类型 */
|
|
24
|
-
type LayoutDirection = 'LR' | 'RL' | 'TB' | 'BT';
|
|
25
|
-
type EventOption = {
|
|
26
|
-
[name: string]: (args: Record<string, any>) => void;
|
|
27
|
-
};
|
|
28
|
-
type CustomElementOption = {
|
|
29
|
-
nodes: Record<string, any>;
|
|
30
|
-
edges: Record<string, any>;
|
|
31
|
-
};
|
|
32
|
-
/**自动排布流程 */
|
|
33
|
-
export declare function useLayoutFlow(options: LayoutFlow): {
|
|
34
|
-
init: (container: HTMLElement) => void;
|
|
35
|
-
add_graph_elements: (elements: Cell[]) => void;
|
|
36
|
-
layout: (cfg: LayoutFlowNodeOption, dir?: LayoutDirection) => void;
|
|
1
|
+
import { LayoutFlow, LayoutFlowNodeOption, LayoutDirection, EventOption, CustomElementOption } from './type';
|
|
2
|
+
/**
|
|
3
|
+
* 自动排布流程
|
|
4
|
+
* @param options 布局配置选项
|
|
5
|
+
* @returns 流程布局方法
|
|
6
|
+
* @description 适用于需要自动布局的流程图场景,支持多种布局方向和自定义元素
|
|
7
|
+
*/
|
|
8
|
+
export declare function use_layout_flow(options: LayoutFlow): {
|
|
9
|
+
init: (container: HTMLElement) => Promise<boolean>;
|
|
10
|
+
add_graph_elements: (elements: any[]) => void;
|
|
11
|
+
layout: (cfg: LayoutFlowNodeOption, dir?: LayoutDirection) => Promise<boolean>;
|
|
37
12
|
center_content: (padding: number) => void;
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
create_nodes: (node_options: any[]) => any[];
|
|
14
|
+
create_edges: (edge_options: any[]) => any[];
|
|
40
15
|
register_event: (event_options: EventOption) => void;
|
|
41
|
-
register_custom_elements: (el_options: CustomElementOption) =>
|
|
16
|
+
register_custom_elements: (el_options: CustomElementOption) => Promise<boolean>;
|
|
42
17
|
get_nodes: () => any;
|
|
43
18
|
clear: () => void;
|
|
44
19
|
};
|
|
45
|
-
export {};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { deep_assign } from "@qy_better_lib/core";
|
|
2
|
+
function use_layout_flow(options) {
|
|
3
|
+
let graph = void 0;
|
|
4
|
+
let Graph;
|
|
5
|
+
let dagre;
|
|
6
|
+
async function load_dependencies() {
|
|
7
|
+
try {
|
|
8
|
+
if (!Graph || !dagre) {
|
|
9
|
+
const x6Module = await import("@antv/x6");
|
|
10
|
+
Graph = x6Module.Graph;
|
|
11
|
+
const dagreModule = await import("dagre");
|
|
12
|
+
dagre = dagreModule.default || dagreModule;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error("加载依赖失败:", error);
|
|
17
|
+
console.error("请安装必要的依赖: npm install @antv/x6 dagre");
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const default_option = {
|
|
22
|
+
panning: true,
|
|
23
|
+
// 画布可拖动
|
|
24
|
+
interacting: false,
|
|
25
|
+
// 边、节点是否可以拖动大小调整等
|
|
26
|
+
mousewheel: {
|
|
27
|
+
enabled: true
|
|
28
|
+
// 开启滚轮交互
|
|
29
|
+
},
|
|
30
|
+
scaling: { min: 0.1, max: 10 }
|
|
31
|
+
};
|
|
32
|
+
async function init(container) {
|
|
33
|
+
if (!container) {
|
|
34
|
+
throw new Error("init(): 容器元素不能为空");
|
|
35
|
+
}
|
|
36
|
+
if (!await load_dependencies()) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const flow_options = deep_assign(default_option, options);
|
|
40
|
+
try {
|
|
41
|
+
graph = new Graph({ container, ...flow_options });
|
|
42
|
+
return true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("初始化画布失败:", error);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function check_graph_initialized() {
|
|
49
|
+
if (!graph) {
|
|
50
|
+
console.error("错误:画布未初始化,请先调用 init() 方法");
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function add_graph_elements(elements) {
|
|
56
|
+
if (!check_graph_initialized()) return;
|
|
57
|
+
if (!Array.isArray(elements)) {
|
|
58
|
+
console.error("错误:elements 参数必须是数组");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
graph.resetCells(elements);
|
|
62
|
+
}
|
|
63
|
+
function build_dag_graph(nodes, edges, cfg, dir) {
|
|
64
|
+
const { node_w, node_h, nodesep, ranksep } = cfg;
|
|
65
|
+
const g = new dagre.graphlib.Graph();
|
|
66
|
+
g.setGraph({ rankdir: dir, nodesep, ranksep });
|
|
67
|
+
g.setDefaultEdgeLabel(() => ({}));
|
|
68
|
+
const width = node_w;
|
|
69
|
+
const height = node_h;
|
|
70
|
+
nodes.forEach((node) => {
|
|
71
|
+
g.setNode(node.id, { width, height });
|
|
72
|
+
});
|
|
73
|
+
edges.forEach((edge) => {
|
|
74
|
+
const source = edge.getSource();
|
|
75
|
+
const target = edge.getTarget();
|
|
76
|
+
if (source && target && source.cell && target.cell) {
|
|
77
|
+
g.setEdge(source.cell, target.cell);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return g;
|
|
81
|
+
}
|
|
82
|
+
function update_node_positions(g) {
|
|
83
|
+
g.nodes().forEach((id) => {
|
|
84
|
+
const node = graph.getCellById(id);
|
|
85
|
+
if (node) {
|
|
86
|
+
const pos = g.node(id);
|
|
87
|
+
node.position(pos.x, pos.y);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function update_edge_vertices(edges, dir) {
|
|
92
|
+
edges.forEach((edge) => {
|
|
93
|
+
try {
|
|
94
|
+
const source = edge.getSourceNode();
|
|
95
|
+
const target = edge.getTargetNode();
|
|
96
|
+
if (!source || !target) return;
|
|
97
|
+
const source_bbox = source.getBBox();
|
|
98
|
+
const target_bbox = target.getBBox();
|
|
99
|
+
if ((dir === "LR" || dir === "RL") && source_bbox.y !== target_bbox.y) {
|
|
100
|
+
const gap = dir === "LR" ? target_bbox.x - source_bbox.x - source_bbox.width : -source_bbox.x + target_bbox.x + target_bbox.width;
|
|
101
|
+
const fix = dir === "LR" ? source_bbox.width : 0;
|
|
102
|
+
const x = source_bbox.x + fix + gap / 2;
|
|
103
|
+
edge.setVertices([
|
|
104
|
+
{ x, y: source_bbox.center.y },
|
|
105
|
+
{ x, y: target_bbox.center.y }
|
|
106
|
+
]);
|
|
107
|
+
} else if ((dir === "TB" || dir === "BT") && source_bbox.x !== target_bbox.x) {
|
|
108
|
+
const gap = dir === "TB" ? target_bbox.y - source_bbox.y - source_bbox.height : -source_bbox.y + target_bbox.y + target_bbox.height;
|
|
109
|
+
const fix = dir === "TB" ? source_bbox.height : 0;
|
|
110
|
+
const y = source_bbox.y + fix + gap / 2;
|
|
111
|
+
edge.setVertices([
|
|
112
|
+
{ x: source_bbox.center.x, y },
|
|
113
|
+
{ x: target_bbox.center.x, y }
|
|
114
|
+
]);
|
|
115
|
+
} else {
|
|
116
|
+
edge.setVertices([]);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error("处理边布局时出错:", error);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function layout(cfg, dir = "LR") {
|
|
124
|
+
if (!check_graph_initialized()) return false;
|
|
125
|
+
if (!cfg) {
|
|
126
|
+
console.error("错误:布局配置不能为空");
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!await load_dependencies()) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const { node_w, node_h, nodesep, ranksep } = cfg;
|
|
133
|
+
if (typeof node_w !== "number" || node_w <= 0 || typeof node_h !== "number" || node_h <= 0 || typeof nodesep !== "number" || nodesep < 0 || typeof ranksep !== "number" || ranksep < 0) {
|
|
134
|
+
console.error("错误:布局配置参数必须为正数");
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const valid_directions = ["LR", "RL", "TB", "BT"];
|
|
138
|
+
if (!valid_directions.includes(dir)) {
|
|
139
|
+
console.error("错误:无效的布局方向,支持的方向:LR, RL, TB, BT");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const nodes = graph.getNodes();
|
|
144
|
+
const edges = graph.getEdges();
|
|
145
|
+
const g = build_dag_graph(nodes, edges, cfg, dir);
|
|
146
|
+
dagre.layout(g);
|
|
147
|
+
update_node_positions(g);
|
|
148
|
+
update_edge_vertices(edges, dir);
|
|
149
|
+
return true;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("执行布局时出错:", error);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function center_content(padding) {
|
|
156
|
+
if (!check_graph_initialized()) return;
|
|
157
|
+
if (typeof padding !== "number" || padding < 0) {
|
|
158
|
+
console.error("错误:padding 参数必须为非负数");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
graph.zoomToFit({ padding, maxScale: 1.2 });
|
|
162
|
+
graph.centerContent();
|
|
163
|
+
}
|
|
164
|
+
function create_nodes(node_options) {
|
|
165
|
+
if (!check_graph_initialized()) return [];
|
|
166
|
+
if (!Array.isArray(node_options)) {
|
|
167
|
+
console.error("错误:node_options 参数必须是数组");
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const nodes = [];
|
|
171
|
+
node_options.forEach((option) => {
|
|
172
|
+
if (option) {
|
|
173
|
+
try {
|
|
174
|
+
const node = graph.createNode(option);
|
|
175
|
+
nodes.push(node);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error("创建节点时出错:", error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
return nodes;
|
|
182
|
+
}
|
|
183
|
+
function create_edges(edge_options) {
|
|
184
|
+
if (!check_graph_initialized()) return [];
|
|
185
|
+
if (!Array.isArray(edge_options)) {
|
|
186
|
+
console.error("错误:edge_options 参数必须是数组");
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const edges = [];
|
|
190
|
+
edge_options.forEach((option) => {
|
|
191
|
+
if (option && option.source_id && option.target_id) {
|
|
192
|
+
try {
|
|
193
|
+
const edge = graph.createEdge({
|
|
194
|
+
shape: option.shape,
|
|
195
|
+
source: { cell: option.source_id },
|
|
196
|
+
target: { cell: option.target_id }
|
|
197
|
+
});
|
|
198
|
+
edges.push(edge);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("创建边时出错:", error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return edges;
|
|
205
|
+
}
|
|
206
|
+
function register_event(event_options) {
|
|
207
|
+
if (!check_graph_initialized()) return;
|
|
208
|
+
if (!event_options || typeof event_options !== "object") {
|
|
209
|
+
console.error("错误:event_options 参数必须是对象");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
Object.keys(event_options).forEach((key) => {
|
|
213
|
+
const option = event_options[key];
|
|
214
|
+
if (typeof option === "function") {
|
|
215
|
+
event_options[key] = add_event_param(option, "g", graph);
|
|
216
|
+
graph.on(key, event_options[key]);
|
|
217
|
+
} else {
|
|
218
|
+
console.error(`错误:事件 ${key} 的处理函数必须是函数`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
function add_event_param(original_func, add_param, param_val) {
|
|
222
|
+
return function(...args) {
|
|
223
|
+
args[0][add_param] = param_val;
|
|
224
|
+
return original_func.apply(this, args);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function register_custom_elements(el_options) {
|
|
229
|
+
if (!el_options || typeof el_options !== "object") {
|
|
230
|
+
console.error("错误:el_options 参数必须是对象");
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (!await load_dependencies()) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
if (el_options.nodes && typeof el_options.nodes === "object") {
|
|
237
|
+
Object.keys(el_options.nodes).forEach((key) => {
|
|
238
|
+
const option = el_options.nodes[key];
|
|
239
|
+
if (option) {
|
|
240
|
+
try {
|
|
241
|
+
Graph.registerNode(key, option, true);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`注册自定义节点 ${key} 时出错:`, error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (el_options.edges && typeof el_options.edges === "object") {
|
|
249
|
+
Object.keys(el_options.edges).forEach((key) => {
|
|
250
|
+
const option = el_options.edges[key];
|
|
251
|
+
if (option) {
|
|
252
|
+
try {
|
|
253
|
+
Graph.registerEdge(key, option, true);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error(`注册自定义边 ${key} 时出错:`, error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
function get_nodes() {
|
|
263
|
+
if (!check_graph_initialized()) return [];
|
|
264
|
+
return graph.getNodes();
|
|
265
|
+
}
|
|
266
|
+
function clear() {
|
|
267
|
+
graph && graph.clearCells();
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
init,
|
|
271
|
+
add_graph_elements,
|
|
272
|
+
layout,
|
|
273
|
+
center_content,
|
|
274
|
+
create_nodes,
|
|
275
|
+
create_edges,
|
|
276
|
+
register_event,
|
|
277
|
+
register_custom_elements,
|
|
278
|
+
get_nodes,
|
|
279
|
+
clear
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
use_layout_flow
|
|
284
|
+
};
|