@agions/taroviz 1.3.0 → 1.3.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/README.md +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +479 -489
- package/package.json +1 -1
- package/src/adapters/h5/index.ts +1 -3
- package/src/adapters/index.ts +32 -21
- package/src/charts/common/BaseChartWrapper.tsx +2 -8
- package/src/core/utils/chartInstances.ts +9 -0
- package/src/core/utils/codeGenerator/CodeGenerator.ts +19 -5
- package/src/core/utils/uuid.ts +9 -5
- package/src/hooks/index.ts +27 -9
package/package.json
CHANGED
package/src/adapters/h5/index.ts
CHANGED
|
@@ -76,9 +76,7 @@ class H5Adapter implements Adapter {
|
|
|
76
76
|
// 获取容器元素
|
|
77
77
|
const container = this.containerRef?.current || document.getElementById(this.canvasId);
|
|
78
78
|
if (!container) {
|
|
79
|
-
|
|
80
|
-
// 如果容器未找到,返回一个空对象
|
|
81
|
-
return {} as EChartsType;
|
|
79
|
+
throw new Error(`[TaroViz] H5Adapter: container not found (canvasId: ${this.canvasId})`);
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
// 初始化图表
|
package/src/adapters/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ const PLATFORM_CONFIGS: Record<PlatformType, PlatformConfig> = {
|
|
|
26
26
|
[PlatformType.DD]: { name: 'DingTalk', requireComponent: true },
|
|
27
27
|
[PlatformType.QYWX]: { name: 'QiyeWechat' },
|
|
28
28
|
[PlatformType.LARK]: { name: 'Lark' },
|
|
29
|
+
[PlatformType.KWAI]: { name: 'Kwai', requireComponent: true },
|
|
29
30
|
[PlatformType.HARMONY]: { name: 'HarmonyOS', requireComponent: true },
|
|
30
31
|
};
|
|
31
32
|
|
|
@@ -99,25 +100,40 @@ export function getEnv(): 'h5' | 'weapp' | 'unknown' {
|
|
|
99
100
|
|
|
100
101
|
/**
|
|
101
102
|
* 创建适配器实例
|
|
103
|
+
* 使用静态导入避免动态 require 的 ESM 问题
|
|
102
104
|
*/
|
|
103
105
|
function createAdapterInstance(platform: PlatformType, options: AdapterOptions): Adapter {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
// 预加载所有适配器(编译时解析,支持 tree-shaking)
|
|
107
|
+
switch (platform) {
|
|
108
|
+
case PlatformType.H5:
|
|
109
|
+
case PlatformType.ALIPAY:
|
|
110
|
+
case PlatformType.QQ:
|
|
111
|
+
case PlatformType.JD:
|
|
112
|
+
case PlatformType.DD:
|
|
113
|
+
case PlatformType.QYWX:
|
|
114
|
+
case PlatformType.LARK:
|
|
115
|
+
case PlatformType.KWAI:
|
|
116
|
+
return h5Adapter.create(options);
|
|
117
|
+
case PlatformType.WEAPP:
|
|
118
|
+
return weappAdapter.create(options);
|
|
119
|
+
case PlatformType.SWAN:
|
|
120
|
+
return swanAdapter.create(options);
|
|
121
|
+
case PlatformType.TT:
|
|
122
|
+
return ttAdapter.create(options);
|
|
123
|
+
case PlatformType.HARMONY:
|
|
124
|
+
return harmonyAdapter.create(options);
|
|
125
|
+
default:
|
|
126
|
+
return h5Adapter.create(options);
|
|
127
|
+
}
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
// 静态导入适配器
|
|
131
|
+
import h5Adapter from './h5';
|
|
132
|
+
import weappAdapter from './weapp';
|
|
133
|
+
import swanAdapter from './swan';
|
|
134
|
+
import ttAdapter from './tt';
|
|
135
|
+
import harmonyAdapter from './harmony';
|
|
136
|
+
|
|
121
137
|
/**
|
|
122
138
|
* 获取适配器
|
|
123
139
|
*/
|
|
@@ -136,7 +152,7 @@ export function getAdapter(options: AdapterOptions): Adapter {
|
|
|
136
152
|
return createAdapterInstance(platform, options);
|
|
137
153
|
} catch (error) {
|
|
138
154
|
console.error(`[TaroViz] Failed to load adapter for platform '${platform}':`, error);
|
|
139
|
-
return
|
|
155
|
+
return h5Adapter.create(options);
|
|
140
156
|
}
|
|
141
157
|
}
|
|
142
158
|
|
|
@@ -158,9 +174,4 @@ export default {
|
|
|
158
174
|
getAdapter,
|
|
159
175
|
getEnv,
|
|
160
176
|
detectPlatform,
|
|
161
|
-
h5: require('./h5').default,
|
|
162
|
-
weapp: require('./weapp').default,
|
|
163
|
-
swan: require('./swan').default,
|
|
164
|
-
tt: require('./tt').default,
|
|
165
|
-
harmony: require('./harmony').default,
|
|
166
177
|
};
|
|
@@ -8,7 +8,7 @@ import React, { useEffect, useRef, useMemo } from 'react';
|
|
|
8
8
|
import { getAdapter } from '../../adapters';
|
|
9
9
|
import { uuid } from '../../core/utils';
|
|
10
10
|
import { BaseChartProps } from '../types';
|
|
11
|
-
import { processAdapterConfig
|
|
11
|
+
import { processAdapterConfig } from '../utils';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 基础图表包装组件
|
|
@@ -118,18 +118,12 @@ const BaseChartWrapper: React.FC<BaseChartProps & { chartType: string }> = ({
|
|
|
118
118
|
...style,
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
// 使用带有处理的适配器配置创建适配器实例
|
|
122
|
-
const adapter = getAdapter(adapterConfig);
|
|
123
|
-
|
|
124
121
|
return (
|
|
125
122
|
<div
|
|
126
123
|
className={`taroviz-${chartType} ${className}`}
|
|
127
124
|
style={mergedStyle}
|
|
128
125
|
ref={containerRef as React.RefObject<HTMLDivElement>}
|
|
129
|
-
|
|
130
|
-
{/* 安全地渲染适配器 */}
|
|
131
|
-
{safeRenderAdapter(adapter)}
|
|
132
|
-
</div>
|
|
126
|
+
/>
|
|
133
127
|
);
|
|
134
128
|
};
|
|
135
129
|
|
|
@@ -11,6 +11,15 @@ export const CHART_INSTANCES: Record<string, EChartsType> = {};
|
|
|
11
11
|
* @param instance 图表实例
|
|
12
12
|
*/
|
|
13
13
|
export function registerChart(id: string, instance: EChartsType): void {
|
|
14
|
+
// 如果已存在同名ID,先释放旧实例防止内存泄漏
|
|
15
|
+
if (CHART_INSTANCES[id]) {
|
|
16
|
+
try {
|
|
17
|
+
console.warn(`[TaroViz] Chart instance '${id}' already exists, replacing and disposing old instance`);
|
|
18
|
+
CHART_INSTANCES[id].dispose();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.warn(`Failed to dispose old chart instance: ${id}`, e);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
CHART_INSTANCES[id] = instance;
|
|
15
24
|
}
|
|
16
25
|
|
|
@@ -79,6 +79,20 @@ export class CodeGenerator {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* 转义代码生成用的字符串,防止 XSS
|
|
84
|
+
*/
|
|
85
|
+
private escapeForCode(str: string): string {
|
|
86
|
+
if (!str) return '';
|
|
87
|
+
// 转义特殊字符防止代码注入
|
|
88
|
+
return str
|
|
89
|
+
.replace(/\\/g, '\\\\')
|
|
90
|
+
.replace(/`/g, '\\`')
|
|
91
|
+
.replace(/\$/g, '\\$')
|
|
92
|
+
.replace(/\{/g, '\\{')
|
|
93
|
+
.replace(/\}/g, '\\}');
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
/**
|
|
83
97
|
* 初始化内置模板
|
|
84
98
|
*/
|
|
@@ -511,15 +525,15 @@ export default chart;`,
|
|
|
511
525
|
// 替换模板变量
|
|
512
526
|
let code = template.content;
|
|
513
527
|
|
|
514
|
-
//
|
|
515
|
-
const componentName = options.componentName || 'ChartComponent';
|
|
528
|
+
// 替换组件名称(转义防止XSS)
|
|
529
|
+
const componentName = this.escapeForCode(options.componentName || 'ChartComponent');
|
|
516
530
|
code = code.replace(/\{componentName\}/g, componentName);
|
|
517
531
|
|
|
518
|
-
// 替换图表ID
|
|
519
|
-
const chartId = options.chartId || 'chart';
|
|
532
|
+
// 替换图表ID(转义防止XSS)
|
|
533
|
+
const chartId = this.escapeForCode(options.chartId || 'chart');
|
|
520
534
|
code = code.replace(/\{chartId\}/g, chartId);
|
|
521
535
|
|
|
522
|
-
//
|
|
536
|
+
// 替换选项(JSON.stringify已处理转义,但模板变量要转义)
|
|
523
537
|
const optionStr = JSON.stringify(option, null, 2);
|
|
524
538
|
code = code.replace(/\{\s*option\s*\}/g, optionStr);
|
|
525
539
|
|
package/src/core/utils/uuid.ts
CHANGED
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* @returns 唯一标识符字符串
|
|
5
5
|
*/
|
|
6
6
|
export function uuid(): string {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
// 使用 crypto API 生成加密安全的 UUID
|
|
8
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
9
|
+
return globalThis.crypto.randomUUID();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 回退:使用 Math.random() + 时间戳混合
|
|
13
|
+
const timestamp = Date.now().toString(36);
|
|
14
|
+
const randomPart = Math.random().toString(36).substring(2, 15);
|
|
15
|
+
return `${timestamp}-${randomPart}-${Math.random().toString(36).substring(2, 10)}`;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/**
|
package/src/hooks/index.ts
CHANGED
|
@@ -410,23 +410,35 @@ export function useDataPolling<T>(
|
|
|
410
410
|
const [data, setData] = useState<T | null>(null);
|
|
411
411
|
const [loading, setLoading] = useState(autoStart);
|
|
412
412
|
const [error, setError] = useState<Error | null>(null);
|
|
413
|
-
|
|
413
|
+
|
|
414
|
+
// 用于取消进行中的请求
|
|
415
|
+
const abortRef = useRef<{ cancelled: boolean }>({ cancelled: false });
|
|
414
416
|
|
|
415
417
|
const fetchData = useCallback(async () => {
|
|
418
|
+
// 取消之前的请求
|
|
419
|
+
abortRef.current.cancelled = true;
|
|
420
|
+
// 创建新的取消标记
|
|
421
|
+
abortRef.current = { cancelled: false };
|
|
422
|
+
const currentAbort = abortRef.current;
|
|
423
|
+
|
|
416
424
|
let retries = retryCount;
|
|
417
425
|
setLoading(true);
|
|
418
426
|
setError(null);
|
|
419
427
|
|
|
420
|
-
while (retries >= 0) {
|
|
428
|
+
while (retries >= 0 && !currentAbort.cancelled) {
|
|
421
429
|
try {
|
|
422
430
|
const result = await fetchFn();
|
|
423
|
-
|
|
424
|
-
|
|
431
|
+
if (!currentAbort.cancelled) {
|
|
432
|
+
setData(result);
|
|
433
|
+
setLoading(false);
|
|
434
|
+
}
|
|
425
435
|
return;
|
|
426
436
|
} catch (e) {
|
|
427
437
|
retries--;
|
|
428
|
-
if (retries < 0) {
|
|
429
|
-
|
|
438
|
+
if (retries < 0 || currentAbort.cancelled) {
|
|
439
|
+
if (!currentAbort.cancelled) {
|
|
440
|
+
setError(e as Error);
|
|
441
|
+
}
|
|
430
442
|
setLoading(false);
|
|
431
443
|
} else {
|
|
432
444
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
@@ -442,12 +454,18 @@ export function useDataPolling<T>(
|
|
|
442
454
|
|
|
443
455
|
if (interval > 0) {
|
|
444
456
|
const timer = setInterval(fetchData, interval);
|
|
445
|
-
return () =>
|
|
457
|
+
return () => {
|
|
458
|
+
clearInterval(timer);
|
|
459
|
+
abortRef.current.cancelled = true;
|
|
460
|
+
};
|
|
446
461
|
}
|
|
447
|
-
|
|
462
|
+
|
|
463
|
+
return () => {
|
|
464
|
+
abortRef.current.cancelled = true;
|
|
465
|
+
};
|
|
466
|
+
}, [interval, autoStart, fetchData]);
|
|
448
467
|
|
|
449
468
|
const refresh = useCallback(() => {
|
|
450
|
-
setRefreshIndex((prev) => prev + 1);
|
|
451
469
|
fetchData();
|
|
452
470
|
}, [fetchData]);
|
|
453
471
|
|