vite-uni-dev-tool 1.1.0 → 1.2.0

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 (135) hide show
  1. package/README.md +8 -0
  2. package/dist/const.cjs +1 -1
  3. package/dist/const.d.ts +1 -0
  4. package/dist/const.d.ts.map +1 -1
  5. package/dist/const.js +1 -1
  6. package/dist/core.d.ts.map +1 -1
  7. package/dist/core.js +2 -2
  8. package/dist/i18n/locales/en.cjs +1 -1
  9. package/dist/i18n/locales/en.d.ts +5 -0
  10. package/dist/i18n/locales/en.d.ts.map +1 -1
  11. package/dist/i18n/locales/en.js +1 -1
  12. package/dist/i18n/locales/zh-Hans.cjs +1 -1
  13. package/dist/i18n/locales/zh-Hans.d.ts +5 -0
  14. package/dist/i18n/locales/zh-Hans.d.ts.map +1 -1
  15. package/dist/i18n/locales/zh-Hans.js +1 -1
  16. package/dist/modules/devIntercept/index.cjs +9 -9
  17. package/dist/modules/devIntercept/index.d.ts +1 -1
  18. package/dist/modules/devIntercept/index.d.ts.map +1 -1
  19. package/dist/modules/devIntercept/index.js +9 -9
  20. package/dist/modules/devStore/index.cjs +1 -1
  21. package/dist/modules/devStore/index.d.ts.map +1 -1
  22. package/dist/modules/devStore/index.js +1 -1
  23. package/dist/plugins/uniDevTool/transform/transformMain.cjs +3 -3
  24. package/dist/plugins/uniDevTool/transform/transformMain.d.ts +2 -1
  25. package/dist/plugins/uniDevTool/transform/transformMain.d.ts.map +1 -1
  26. package/dist/plugins/uniDevTool/transform/transformMain.js +3 -3
  27. package/dist/plugins/uniDevTool/transform/transformVue.cjs +31 -25
  28. package/dist/plugins/uniDevTool/transform/transformVue.d.ts +2 -1
  29. package/dist/plugins/uniDevTool/transform/transformVue.d.ts.map +1 -1
  30. package/dist/plugins/uniDevTool/transform/transformVue.js +30 -24
  31. package/dist/plugins/uniDevTool/uniDevTool.cjs +3 -3
  32. package/dist/plugins/uniDevTool/uniDevTool.d.ts +3 -1
  33. package/dist/plugins/uniDevTool/uniDevTool.d.ts.map +1 -1
  34. package/dist/plugins/uniDevTool/uniDevTool.js +3 -3
  35. package/dist/type.d.ts +3 -0
  36. package/dist/type.d.ts.map +1 -1
  37. package/dist/v3/DevTool/components/BluetoothList/BluetoothItem.vue +199 -199
  38. package/dist/v3/DevTool/components/BluetoothList/BluetoothTool.vue +730 -730
  39. package/dist/v3/DevTool/components/BluetoothList/index.vue +167 -167
  40. package/dist/v3/DevTool/components/CaptureScreen/index.vue +109 -109
  41. package/dist/v3/DevTool/components/ConsoleList/ConsoleItem.vue +230 -225
  42. package/dist/v3/DevTool/components/ConsoleList/RunJSInput.vue +247 -247
  43. package/dist/v3/DevTool/components/ConsoleList/index.vue +171 -171
  44. package/dist/v3/DevTool/components/ConsoleList/staticTips.ts +1145 -1145
  45. package/dist/v3/DevTool/components/DevToolTitle/index.vue +24 -24
  46. package/dist/v3/DevTool/components/DevToolWindow/DevToolOverlay.vue +197 -197
  47. package/dist/v3/DevTool/components/DevToolWindow/hooks/dataUtils.ts +48 -48
  48. package/dist/v3/DevTool/components/DevToolWindow/hooks/useDevToolData.ts +387 -387
  49. package/dist/v3/DevTool/components/DevToolWindow/hooks/useDevToolHandlers.ts +629 -629
  50. package/dist/v3/DevTool/components/DevToolWindow/hooks/useDevToolOverlay.ts +201 -197
  51. package/dist/v3/DevTool/components/ElEvent/ElEventItem.vue +105 -105
  52. package/dist/v3/DevTool/components/ElEvent/index.vue +106 -106
  53. package/dist/v3/DevTool/components/Instance/components/InstanceTreeNode.vue +265 -265
  54. package/dist/v3/DevTool/components/Instance/flatten.ts +226 -226
  55. package/dist/v3/DevTool/components/Instance/index.vue +94 -94
  56. package/dist/v3/DevTool/components/Instance/registry.ts +49 -49
  57. package/dist/v3/DevTool/components/Instance/transformTree.ts +375 -375
  58. package/dist/v3/DevTool/components/Instance/transformTreeCtx.ts +268 -268
  59. package/dist/v3/DevTool/components/Instance/typing.d.ts +43 -43
  60. package/dist/v3/DevTool/components/InstanceDetail/index.vue +485 -485
  61. package/dist/v3/DevTool/components/JsonDetail/index.vue +70 -70
  62. package/dist/v3/DevTool/components/NFCList/NFCItem.vue +112 -112
  63. package/dist/v3/DevTool/components/NFCList/NFCTool.vue +454 -454
  64. package/dist/v3/DevTool/components/NFCList/const.ts +56 -56
  65. package/dist/v3/DevTool/components/NFCList/index.vue +94 -94
  66. package/dist/v3/DevTool/components/NetworkList/InterceptConfig.vue +624 -624
  67. package/dist/v3/DevTool/components/NetworkList/InterceptItem.vue +140 -140
  68. package/dist/v3/DevTool/components/NetworkList/NetworkDetail.vue +287 -287
  69. package/dist/v3/DevTool/components/NetworkList/NetworkIntercept.vue +88 -88
  70. package/dist/v3/DevTool/components/NetworkList/NetworkItem.vue +163 -163
  71. package/dist/v3/DevTool/components/NetworkList/NetworkSend.vue +589 -589
  72. package/dist/v3/DevTool/components/NetworkList/const.ts +4 -4
  73. package/dist/v3/DevTool/components/NetworkList/hooks/useNetworkForm.ts +86 -86
  74. package/dist/v3/DevTool/components/NetworkList/index.vue +160 -160
  75. package/dist/v3/DevTool/components/NetworkList/utils.ts +101 -101
  76. package/dist/v3/DevTool/components/Performance/index.vue +498 -498
  77. package/dist/v3/DevTool/components/Performance/modules/PerformanceMetrics.vue +153 -153
  78. package/dist/v3/DevTool/components/Performance/modules/usePerformanceChart.ts +460 -460
  79. package/dist/v3/DevTool/components/Performance/modules/usePerformanceData.ts +258 -258
  80. package/dist/v3/DevTool/components/PiniaList/index.vue +93 -93
  81. package/dist/v3/DevTool/components/RunJS/index.vue +148 -148
  82. package/dist/v3/DevTool/components/ScanCodeList/ScanCodeItem.vue +97 -97
  83. package/dist/v3/DevTool/components/ScanCodeList/index.vue +100 -100
  84. package/dist/v3/DevTool/components/SettingButton/index.vue +45 -45
  85. package/dist/v3/DevTool/components/SettingList/index.vue +218 -218
  86. package/dist/v3/DevTool/components/SettingList/modules/SettingBarrage.vue +304 -304
  87. package/dist/v3/DevTool/components/SettingList/modules/SettingDevTool.vue +212 -212
  88. package/dist/v3/DevTool/components/SettingList/modules/SettingInfo.vue +157 -157
  89. package/dist/v3/DevTool/components/SettingList/modules/SettingLanguage.vue +74 -74
  90. package/dist/v3/DevTool/components/SettingList/modules/SettingLog.vue +230 -230
  91. package/dist/v3/DevTool/components/SettingList/typing.d.ts +2 -2
  92. package/dist/v3/DevTool/components/SourceCode/Line.vue +127 -127
  93. package/dist/v3/DevTool/components/SourceCode/parseCode.ts +609 -609
  94. package/dist/v3/DevTool/components/StorageList/index.vue +174 -174
  95. package/dist/v3/DevTool/components/TransferList/TransferDetail.vue +268 -268
  96. package/dist/v3/DevTool/components/VuexList/index.vue +84 -84
  97. package/dist/v3/DevTool/index.vue +1 -0
  98. package/dist/v3/components/AppTransition/index.vue +176 -176
  99. package/dist/v3/components/AutoSizer/index.vue +192 -192
  100. package/dist/v3/components/AutoSizer/index1.vue +184 -184
  101. package/dist/v3/components/AutoSizer/utils.ts +49 -49
  102. package/dist/v3/components/Barrage/BarrageItem.vue +137 -137
  103. package/dist/v3/components/Barrage/index.vue +202 -202
  104. package/dist/v3/components/CircularButton/index.vue +84 -84
  105. package/dist/v3/components/CustomSwiper/CustomSwiperItem.vue +49 -49
  106. package/dist/v3/components/CustomSwiper/index.vue +104 -104
  107. package/dist/v3/components/DevErrorBoundary/index.vue +380 -0
  108. package/dist/v3/components/Empty/index.vue +29 -29
  109. package/dist/v3/components/FilterSelect/index.vue +179 -179
  110. package/dist/v3/components/JsonPretty/components/Brackets/index.vue +27 -27
  111. package/dist/v3/components/JsonPretty/components/Carets/index.vue +59 -59
  112. package/dist/v3/components/JsonPretty/components/CheckController/index.vue +136 -136
  113. package/dist/v3/components/JsonPretty/components/TreeNode/index.vue +387 -387
  114. package/dist/v3/components/JsonPretty/hooks/useClipboard.ts +21 -21
  115. package/dist/v3/components/JsonPretty/hooks/useError.ts +21 -21
  116. package/dist/v3/components/JsonPretty/type.ts +127 -127
  117. package/dist/v3/components/JsonPretty/utils/index.ts +169 -169
  118. package/dist/v3/components/MovableContainer/index.vue +8 -4
  119. package/dist/v3/components/Pick/index.vue +322 -322
  120. package/dist/v3/components/Tag/index.vue +113 -113
  121. package/dist/v3/components/VirtualList/AutoSize.vue +40 -40
  122. package/dist/v3/components/VirtualList/index.vue +416 -416
  123. package/dist/v3/hooks/useBluetooth/index.ts +561 -561
  124. package/dist/v3/hooks/useContainerStyle.ts +153 -153
  125. package/dist/v3/hooks/useNFC/index.ts +107 -107
  126. package/dist/v3/hooks/useNFC/typing.d.ts +396 -396
  127. package/dist/v3/hooks/useNFC/useNFCAndroid.ts +966 -966
  128. package/dist/v3/hooks/useNFC/useNFCMpWeiXin.ts +812 -812
  129. package/dist/v3/hooks/useNFC/utils.ts +754 -754
  130. package/dist/v3/hooks/useRequest/index.ts +586 -586
  131. package/dist/v3/hooks/useRequest/utils.ts +267 -267
  132. package/dist/v3/hooks/useScanCode/index.ts +206 -206
  133. package/dist/v3/hooks/useWebsocket/index.ts +253 -253
  134. package/dist/v3/styles/theme.ts +12 -12
  135. package/package.json +9 -1
@@ -1,460 +1,460 @@
1
- import { ref, computed, nextTick } from 'vue';
2
- import { raf, caf } from '../../../../../utils';
3
-
4
- export function usePerformanceChart(opts: {
5
- theme: any;
6
- isDark: any;
7
- instance: any;
8
- canvasIdFn: () => string;
9
- stepSize: number;
10
- data: any; // from usePerformanceData
11
- config: {
12
- showFps: any;
13
- showCpu: any;
14
- showMemory: any;
15
- };
16
- props: any;
17
- }) {
18
- const canvasWidth = ref(0);
19
- const canvasHeight = ref(0);
20
- let canvasCtx: UniApp.CanvasContext | null = null;
21
- let renderTimer: number | null = null;
22
- // 使用标志变量控制渲染循环,确保在 Android 平台能正确停止
23
- let isRunning = false;
24
-
25
- // Colors
26
- const textColor = computed(() => {
27
- if (opts.theme.value === 'eye') return '#586e75';
28
- return opts.isDark.value ? '#ffffff' : '#000000';
29
- });
30
- const chartBgColor = computed(() => {
31
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.05)';
32
- return opts.isDark.value
33
- ? 'rgba(255, 255, 255, 0.08)'
34
- : 'rgba(0, 0, 0, 0.05)';
35
- });
36
- const mainLineColor = computed(() => {
37
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.3)';
38
- return opts.isDark.value
39
- ? 'rgba(255, 255, 255, 0.4)'
40
- : 'rgba(0, 0, 0, 0.2)';
41
- });
42
- const subLineColor = computed(() => {
43
- if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.1)';
44
- return opts.isDark.value
45
- ? 'rgba(255, 255, 255, 0.15)'
46
- : 'rgba(0, 0, 0, 0.08)';
47
- });
48
-
49
- function initCanvas(onReady?: () => void) {
50
- const canvasId = opts.canvasIdFn();
51
- canvasCtx = uni.createCanvasContext(canvasId, opts.instance?.proxy);
52
-
53
- const queryCanvas = (retryCount = 0) => {
54
- uni
55
- .createSelectorQuery()
56
- .in(opts.instance?.proxy)
57
- .select(`#${canvasId}`)
58
- .boundingClientRect((rect) => {
59
- const r = rect as any;
60
- if (r && r.width > 0 && r.height > 0) {
61
- canvasWidth.value = r.width;
62
- canvasHeight.value = r.height;
63
-
64
- // Initialize data if needed (call initDate from usePerformanceData)
65
- const points = Math.ceil(canvasWidth.value / opts.stepSize) + 4;
66
- opts.data.initData(points);
67
-
68
- if (onReady) onReady();
69
- } else {
70
- // Android 平台在二次打开时可能需要多一点时间才能获取到正确的宽高
71
- // 增加重试机制:每 50ms 重试一次,最多重试 10 次 (500ms)
72
- if (retryCount < 10) {
73
- setTimeout(() => {
74
- queryCanvas(retryCount + 1);
75
- }, 50);
76
- } else {
77
- console.warn(
78
- `[UniDevTool] Failed to get canvas rect for ${canvasId}`,
79
- );
80
- }
81
- }
82
- })
83
- .exec();
84
- };
85
-
86
- nextTick(() => {
87
- // 首次尝试稍微延迟一点,避开极其短暂的中间状态
88
- setTimeout(() => queryCanvas(), 50);
89
- });
90
- }
91
-
92
- function startRenderLoop() {
93
- // 避免重复启动
94
- if (isRunning) return;
95
- isRunning = true;
96
-
97
- const render = () => {
98
- // 检查标志位,如果已停止则不再继续
99
- if (!isRunning) return;
100
- drawChart();
101
- renderTimer = raf(render);
102
- };
103
- render();
104
- }
105
-
106
- function stopRenderLoop() {
107
- isRunning = false;
108
- if (renderTimer) {
109
- caf(renderTimer);
110
- renderTimer = null;
111
- }
112
- }
113
-
114
- function resetCanvas() {
115
- stopRenderLoop();
116
- canvasCtx = null;
117
- canvasWidth.value = 0;
118
- canvasHeight.value = 0;
119
- }
120
-
121
- function drawChart() {
122
- if (!canvasCtx || !canvasWidth.value || !canvasHeight.value) return;
123
-
124
- const ctx = canvasCtx;
125
- const w = canvasWidth.value;
126
- const h = canvasHeight.value;
127
- const bottomPadding = 12;
128
- const drawH = h - bottomPadding;
129
-
130
- // Data aliases
131
- const {
132
- fpsHistory: fList,
133
- cpuHistory: cList,
134
- memoryHistory: mList,
135
- timeHistory: tList,
136
- displayMaxScale: currentScale,
137
- } = opts.data;
138
-
139
- // Props aliases
140
- const config = opts.config;
141
- const props = opts.props;
142
-
143
- // 计算平滑偏移量
144
- const now = Date.now();
145
-
146
- // 强制使用当前时间作为最右侧锚点
147
- // 这样数据永远不会往右移动,只会随着时间推移自然向左滚动
148
- const rightEdgeTime = now;
149
-
150
- const getX = (ts: number) => {
151
- if (!ts) return -100; // 兜底处理无效数据
152
- return w - ((rightEdgeTime - ts) / 1000) * opts.stepSize;
153
- };
154
-
155
- const activeMetrics = [
156
- { key: 'cpu', show: config.showCpu.value },
157
- { key: 'memory', show: config.showMemory.value },
158
- { key: 'fps', show: config.showFps.value },
159
- ].filter((m) => m.show);
160
-
161
- const count = activeMetrics.length;
162
- const isSplit = count > 1;
163
- const chartHeight = count > 0 ? drawH / count : drawH;
164
-
165
- let cpuY = 0;
166
- let memY = 0;
167
- let fpsY = 0;
168
-
169
- activeMetrics.forEach((m, i) => {
170
- if (m.key === 'cpu') cpuY = i * chartHeight;
171
- if (m.key === 'memory') memY = i * chartHeight;
172
- if (m.key === 'fps') fpsY = i * chartHeight;
173
- });
174
-
175
- // 1. 绘制背景网格
176
- const maxFps = Math.max(...fList.value, 0);
177
- let targetScale = 60;
178
- if (maxFps > 144) targetScale = 240;
179
- else if (maxFps > 120) targetScale = 144;
180
- else if (maxFps > 90) targetScale = 120;
181
-
182
- const getStandardPoints = (scale: number) => {
183
- return [0, 30, 60, 100, 144, 240].filter(
184
- (p) => p <= Math.max(scale, currentScale.value),
185
- );
186
- };
187
-
188
- ctx.setLineWidth(1);
189
- ctx.setFontSize(props.mode === 'widget' ? 10 : 12);
190
-
191
- // --- FPS ---
192
- if (config.showFps.value) {
193
- const fpsPoints = getStandardPoints(targetScale);
194
- fpsPoints.forEach((p) => {
195
- const yPos =
196
- fpsY + (chartHeight - (p / currentScale.value) * chartHeight);
197
- if (yPos < fpsY - 5 || yPos > fpsY + chartHeight + 5) return;
198
-
199
- ctx.beginPath();
200
- ctx.setStrokeStyle(
201
- p === 0 || p === 60 || p === targetScale
202
- ? mainLineColor.value
203
- : subLineColor.value,
204
- );
205
- ctx.setLineDash(
206
- p === 0 || p === 60 || p === targetScale ? [] : [2, 4],
207
- 0,
208
- );
209
- ctx.moveTo(0, yPos);
210
- ctx.lineTo(w, yPos);
211
- ctx.stroke();
212
-
213
- ctx.setFillStyle(textColor.value);
214
- if (p !== 0 && (p === 60 || p === targetScale)) {
215
- ctx.setTextBaseline(p === targetScale ? 'top' : 'bottom');
216
- ctx.fillText(`${p}${isSplit ? ' FPS' : ''}`, 0, yPos);
217
- }
218
- });
219
- }
220
-
221
- // --- CPU ---
222
- if (config.showCpu.value) {
223
- const cpuPoints = [0, 50, 100];
224
- cpuPoints.forEach((p) => {
225
- const yPos = cpuY + (chartHeight - (p / 100) * chartHeight);
226
- if (yPos < cpuY - 5 || yPos > cpuY + chartHeight + 5) return;
227
-
228
- if (!isSplit || p !== 0) {
229
- ctx.beginPath();
230
- ctx.setStrokeStyle(
231
- p === 0 || p === 100 ? mainLineColor.value : subLineColor.value,
232
- );
233
- ctx.setLineDash(p === 0 || p === 100 ? [] : [2, 4], 0);
234
- ctx.moveTo(0, yPos);
235
- ctx.lineTo(w, yPos);
236
- ctx.stroke();
237
- }
238
-
239
- ctx.setFillStyle(textColor.value);
240
- if (p !== 0) {
241
- ctx.setTextBaseline(p === 100 ? 'top' : 'middle');
242
- ctx.fillText(`${p}${isSplit ? '%' : ''}`, 0, yPos);
243
- }
244
- });
245
- }
246
-
247
- // --- Memory ---
248
- const maxMem = Math.max(...mList.value, 100);
249
- let targetMemScale = Math.ceil(maxMem / 100) * 100;
250
- if (targetMemScale === 0) targetMemScale = 100;
251
-
252
- if (config.showMemory.value) {
253
- const memPoints = [0, targetMemScale / 2, targetMemScale];
254
- memPoints.forEach((p) => {
255
- const yPos = memY + (chartHeight - (p / targetMemScale) * chartHeight);
256
- if (yPos < memY - 5 || yPos > memY + chartHeight + 5) return;
257
-
258
- if (!isSplit || p !== 0) {
259
- ctx.beginPath();
260
- ctx.setStrokeStyle(
261
- p === 0 || p === targetMemScale
262
- ? mainLineColor.value
263
- : subLineColor.value,
264
- );
265
- ctx.setLineDash(p === 0 || p === targetMemScale ? [] : [2, 4], 0);
266
- ctx.moveTo(0, yPos);
267
- ctx.lineTo(w, yPos);
268
- ctx.stroke();
269
- }
270
-
271
- ctx.setFillStyle(textColor.value);
272
- if (p !== 0) {
273
- ctx.setTextBaseline(p === targetMemScale ? 'top' : 'middle');
274
- ctx.fillText(`${p.toFixed(0)}${isSplit ? 'M' : ''}`, 0, yPos);
275
- }
276
- });
277
- }
278
-
279
- // Split Lines
280
- if (isSplit) {
281
- ctx.setStrokeStyle(mainLineColor.value);
282
- ctx.setLineDash([], 0);
283
- for (let i = 1; i < count; i++) {
284
- const y = chartHeight * i;
285
- ctx.beginPath();
286
- ctx.moveTo(0, y);
287
- ctx.lineTo(w, y);
288
- ctx.stroke();
289
- }
290
- }
291
-
292
- // Vertical Grid & Time Layout
293
- const timeList = tList.value;
294
-
295
- // Vertical Grid - 基于时间绘制,不再基于数组索引,保证网格绝对均匀
296
- ctx.setStrokeStyle(subLineColor.value);
297
- ctx.setLineDash([2, 4], 0);
298
-
299
- const startTimeOnScreen = rightEdgeTime - (w / opts.stepSize) * 1000;
300
- const startGridSec = Math.ceil(startTimeOnScreen / 5000) * 5000;
301
-
302
- for (let t = startGridSec; t <= rightEdgeTime; t += 5000) {
303
- const xPos = getX(t);
304
- if (xPos >= 0 && xPos <= w) {
305
- ctx.beginPath();
306
- ctx.moveTo(xPos, 0);
307
- ctx.lineTo(xPos, drawH);
308
- ctx.stroke();
309
- }
310
- }
311
-
312
- // Time Axis
313
- if (timeList.length > 0) {
314
- ctx.setLineDash([], 0);
315
- ctx.setStrokeStyle(mainLineColor.value);
316
- ctx.beginPath();
317
- ctx.moveTo(0, drawH);
318
- ctx.lineTo(w, drawH);
319
- ctx.stroke();
320
-
321
- ctx.setFillStyle(textColor.value);
322
- ctx.setFontSize(10);
323
- ctx.setTextAlign('center');
324
-
325
- const formatTime = (ts: number) => {
326
- const d = new Date(ts);
327
- return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
328
- };
329
-
330
- const startTimeLabel = Math.ceil(startTimeOnScreen / 15000) * 15000;
331
- for (let t = startTimeLabel; t <= rightEdgeTime; t += 15000) {
332
- const xPos = getX(t);
333
- if (xPos >= 0 && xPos <= w) {
334
- const timeStr = formatTime(t);
335
- ctx.setTextBaseline('bottom');
336
- ctx.fillText(timeStr, xPos, h);
337
- }
338
- }
339
- ctx.setTextAlign('left');
340
- }
341
-
342
- const drawAreaChart = (
343
- list: number[],
344
- startY: number,
345
- height: number,
346
- maxVal: number,
347
- color: string,
348
- gradientStart: string,
349
- gradientEnd: string,
350
- ) => {
351
- const bottomY = startY + height;
352
- if (list.length === 0) return;
353
-
354
- const getValY = (val: number) =>
355
- startY + (height - (val / maxVal) * height);
356
-
357
- // 1. 准备渐变
358
- const gradient = ctx.createLinearGradient(0, startY, 0, bottomY);
359
- gradient.addColorStop(0, gradientStart);
360
- gradient.addColorStop(1, gradientEnd);
361
- ctx.setFillStyle(gradient);
362
-
363
- // 2. 绘制填充面积
364
- ctx.beginPath();
365
- ctx.setLineDash([], 0);
366
-
367
- const x0 = getX(timeList[0]);
368
- const y0 = getValY(list[0]);
369
- ctx.moveTo(x0, bottomY);
370
- ctx.lineTo(x0, y0);
371
-
372
- let lastY = y0;
373
- for (let i = 1; i < list.length; i++) {
374
- const x = getX(timeList[i]);
375
- lastY = getValY(list[i]);
376
- ctx.lineTo(x, lastY);
377
- }
378
-
379
- // 延伸到右侧边缘
380
- ctx.lineTo(w, lastY);
381
- ctx.lineTo(w, bottomY);
382
- ctx.closePath();
383
- ctx.fill();
384
-
385
- // 3. 绘制顶部线条
386
- ctx.beginPath();
387
- ctx.setLineWidth(2);
388
- ctx.setStrokeStyle(color);
389
- ctx.moveTo(x0, y0);
390
- for (let i = 1; i < list.length; i++) {
391
- const x = getX(timeList[i]);
392
- ctx.lineTo(x, getValY(list[i]));
393
- }
394
- ctx.lineTo(w, lastY);
395
- ctx.stroke();
396
- };
397
-
398
- if (config.showCpu.value) {
399
- drawAreaChart(
400
- cList.value,
401
- cpuY,
402
- chartHeight,
403
- 100,
404
- '#9254de',
405
- 'rgba(146, 84, 222, 0.5)',
406
- 'rgba(146, 84, 222, 0.05)',
407
- );
408
- }
409
-
410
- if (config.showMemory.value) {
411
- drawAreaChart(
412
- mList.value,
413
- memY,
414
- chartHeight,
415
- targetMemScale,
416
- '#409eff',
417
- 'rgba(64, 158, 255, 0.5)',
418
- 'rgba(64, 158, 255, 0.05)',
419
- );
420
- }
421
-
422
- if (config.showFps.value) {
423
- const currentFps = fList.value[fList.value.length - 1] || 0;
424
- let colorStr = '#f56c6c';
425
- let gradStart = 'rgba(245, 108, 108, 0.5)';
426
- let gradEnd = 'rgba(245, 108, 108, 0.05)';
427
-
428
- if (currentFps >= 50) {
429
- colorStr = '#67c23a';
430
- gradStart = 'rgba(103, 194, 58, 0.5)';
431
- gradEnd = 'rgba(103, 194, 58, 0.05)';
432
- } else if (currentFps >= 30) {
433
- colorStr = '#e6a23c';
434
- gradStart = 'rgba(230, 162, 60, 0.5)';
435
- gradEnd = 'rgba(230, 162, 60, 0.05)';
436
- }
437
-
438
- drawAreaChart(
439
- fList.value,
440
- fpsY,
441
- chartHeight,
442
- currentScale.value,
443
- colorStr,
444
- gradStart,
445
- gradEnd,
446
- );
447
- }
448
-
449
- ctx.draw();
450
- }
451
-
452
- return {
453
- chartBgColor,
454
- canvasWidth,
455
- initCanvas,
456
- startRenderLoop,
457
- stopRenderLoop,
458
- resetCanvas,
459
- };
460
- }
1
+ import { ref, computed, nextTick } from 'vue';
2
+ import { raf, caf } from '../../../../../utils';
3
+
4
+ export function usePerformanceChart(opts: {
5
+ theme: any;
6
+ isDark: any;
7
+ instance: any;
8
+ canvasIdFn: () => string;
9
+ stepSize: number;
10
+ data: any; // from usePerformanceData
11
+ config: {
12
+ showFps: any;
13
+ showCpu: any;
14
+ showMemory: any;
15
+ };
16
+ props: any;
17
+ }) {
18
+ const canvasWidth = ref(0);
19
+ const canvasHeight = ref(0);
20
+ let canvasCtx: UniApp.CanvasContext | null = null;
21
+ let renderTimer: number | null = null;
22
+ // 使用标志变量控制渲染循环,确保在 Android 平台能正确停止
23
+ let isRunning = false;
24
+
25
+ // Colors
26
+ const textColor = computed(() => {
27
+ if (opts.theme.value === 'eye') return '#586e75';
28
+ return opts.isDark.value ? '#ffffff' : '#000000';
29
+ });
30
+ const chartBgColor = computed(() => {
31
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.05)';
32
+ return opts.isDark.value
33
+ ? 'rgba(255, 255, 255, 0.08)'
34
+ : 'rgba(0, 0, 0, 0.05)';
35
+ });
36
+ const mainLineColor = computed(() => {
37
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.3)';
38
+ return opts.isDark.value
39
+ ? 'rgba(255, 255, 255, 0.4)'
40
+ : 'rgba(0, 0, 0, 0.2)';
41
+ });
42
+ const subLineColor = computed(() => {
43
+ if (opts.theme.value === 'eye') return 'rgba(88, 110, 117, 0.1)';
44
+ return opts.isDark.value
45
+ ? 'rgba(255, 255, 255, 0.15)'
46
+ : 'rgba(0, 0, 0, 0.08)';
47
+ });
48
+
49
+ function initCanvas(onReady?: () => void) {
50
+ const canvasId = opts.canvasIdFn();
51
+ canvasCtx = uni.createCanvasContext(canvasId, opts.instance?.proxy);
52
+
53
+ const queryCanvas = (retryCount = 0) => {
54
+ uni
55
+ .createSelectorQuery()
56
+ .in(opts.instance?.proxy)
57
+ .select(`#${canvasId}`)
58
+ .boundingClientRect((rect) => {
59
+ const r = rect as any;
60
+ if (r && r.width > 0 && r.height > 0) {
61
+ canvasWidth.value = r.width;
62
+ canvasHeight.value = r.height;
63
+
64
+ // Initialize data if needed (call initDate from usePerformanceData)
65
+ const points = Math.ceil(canvasWidth.value / opts.stepSize) + 4;
66
+ opts.data.initData(points);
67
+
68
+ if (onReady) onReady();
69
+ } else {
70
+ // Android 平台在二次打开时可能需要多一点时间才能获取到正确的宽高
71
+ // 增加重试机制:每 50ms 重试一次,最多重试 10 次 (500ms)
72
+ if (retryCount < 10) {
73
+ setTimeout(() => {
74
+ queryCanvas(retryCount + 1);
75
+ }, 50);
76
+ } else {
77
+ console.warn(
78
+ `[UniDevTool] Failed to get canvas rect for ${canvasId}`,
79
+ );
80
+ }
81
+ }
82
+ })
83
+ .exec();
84
+ };
85
+
86
+ nextTick(() => {
87
+ // 首次尝试稍微延迟一点,避开极其短暂的中间状态
88
+ setTimeout(() => queryCanvas(), 50);
89
+ });
90
+ }
91
+
92
+ function startRenderLoop() {
93
+ // 避免重复启动
94
+ if (isRunning) return;
95
+ isRunning = true;
96
+
97
+ const render = () => {
98
+ // 检查标志位,如果已停止则不再继续
99
+ if (!isRunning) return;
100
+ drawChart();
101
+ renderTimer = raf(render);
102
+ };
103
+ render();
104
+ }
105
+
106
+ function stopRenderLoop() {
107
+ isRunning = false;
108
+ if (renderTimer) {
109
+ caf(renderTimer);
110
+ renderTimer = null;
111
+ }
112
+ }
113
+
114
+ function resetCanvas() {
115
+ stopRenderLoop();
116
+ canvasCtx = null;
117
+ canvasWidth.value = 0;
118
+ canvasHeight.value = 0;
119
+ }
120
+
121
+ function drawChart() {
122
+ if (!canvasCtx || !canvasWidth.value || !canvasHeight.value) return;
123
+
124
+ const ctx = canvasCtx;
125
+ const w = canvasWidth.value;
126
+ const h = canvasHeight.value;
127
+ const bottomPadding = 12;
128
+ const drawH = h - bottomPadding;
129
+
130
+ // Data aliases
131
+ const {
132
+ fpsHistory: fList,
133
+ cpuHistory: cList,
134
+ memoryHistory: mList,
135
+ timeHistory: tList,
136
+ displayMaxScale: currentScale,
137
+ } = opts.data;
138
+
139
+ // Props aliases
140
+ const config = opts.config;
141
+ const props = opts.props;
142
+
143
+ // 计算平滑偏移量
144
+ const now = Date.now();
145
+
146
+ // 强制使用当前时间作为最右侧锚点
147
+ // 这样数据永远不会往右移动,只会随着时间推移自然向左滚动
148
+ const rightEdgeTime = now;
149
+
150
+ const getX = (ts: number) => {
151
+ if (!ts) return -100; // 兜底处理无效数据
152
+ return w - ((rightEdgeTime - ts) / 1000) * opts.stepSize;
153
+ };
154
+
155
+ const activeMetrics = [
156
+ { key: 'cpu', show: config.showCpu.value },
157
+ { key: 'memory', show: config.showMemory.value },
158
+ { key: 'fps', show: config.showFps.value },
159
+ ].filter((m) => m.show);
160
+
161
+ const count = activeMetrics.length;
162
+ const isSplit = count > 1;
163
+ const chartHeight = count > 0 ? drawH / count : drawH;
164
+
165
+ let cpuY = 0;
166
+ let memY = 0;
167
+ let fpsY = 0;
168
+
169
+ activeMetrics.forEach((m, i) => {
170
+ if (m.key === 'cpu') cpuY = i * chartHeight;
171
+ if (m.key === 'memory') memY = i * chartHeight;
172
+ if (m.key === 'fps') fpsY = i * chartHeight;
173
+ });
174
+
175
+ // 1. 绘制背景网格
176
+ const maxFps = Math.max(...fList.value, 0);
177
+ let targetScale = 60;
178
+ if (maxFps > 144) targetScale = 240;
179
+ else if (maxFps > 120) targetScale = 144;
180
+ else if (maxFps > 90) targetScale = 120;
181
+
182
+ const getStandardPoints = (scale: number) => {
183
+ return [0, 30, 60, 100, 144, 240].filter(
184
+ (p) => p <= Math.max(scale, currentScale.value),
185
+ );
186
+ };
187
+
188
+ ctx.setLineWidth(1);
189
+ ctx.setFontSize(props.mode === 'widget' ? 10 : 12);
190
+
191
+ // --- FPS ---
192
+ if (config.showFps.value) {
193
+ const fpsPoints = getStandardPoints(targetScale);
194
+ fpsPoints.forEach((p) => {
195
+ const yPos =
196
+ fpsY + (chartHeight - (p / currentScale.value) * chartHeight);
197
+ if (yPos < fpsY - 5 || yPos > fpsY + chartHeight + 5) return;
198
+
199
+ ctx.beginPath();
200
+ ctx.setStrokeStyle(
201
+ p === 0 || p === 60 || p === targetScale
202
+ ? mainLineColor.value
203
+ : subLineColor.value,
204
+ );
205
+ ctx.setLineDash(
206
+ p === 0 || p === 60 || p === targetScale ? [] : [2, 4],
207
+ 0,
208
+ );
209
+ ctx.moveTo(0, yPos);
210
+ ctx.lineTo(w, yPos);
211
+ ctx.stroke();
212
+
213
+ ctx.setFillStyle(textColor.value);
214
+ if (p !== 0 && (p === 60 || p === targetScale)) {
215
+ ctx.setTextBaseline(p === targetScale ? 'top' : 'bottom');
216
+ ctx.fillText(`${p}${isSplit ? ' FPS' : ''}`, 0, yPos);
217
+ }
218
+ });
219
+ }
220
+
221
+ // --- CPU ---
222
+ if (config.showCpu.value) {
223
+ const cpuPoints = [0, 50, 100];
224
+ cpuPoints.forEach((p) => {
225
+ const yPos = cpuY + (chartHeight - (p / 100) * chartHeight);
226
+ if (yPos < cpuY - 5 || yPos > cpuY + chartHeight + 5) return;
227
+
228
+ if (!isSplit || p !== 0) {
229
+ ctx.beginPath();
230
+ ctx.setStrokeStyle(
231
+ p === 0 || p === 100 ? mainLineColor.value : subLineColor.value,
232
+ );
233
+ ctx.setLineDash(p === 0 || p === 100 ? [] : [2, 4], 0);
234
+ ctx.moveTo(0, yPos);
235
+ ctx.lineTo(w, yPos);
236
+ ctx.stroke();
237
+ }
238
+
239
+ ctx.setFillStyle(textColor.value);
240
+ if (p !== 0) {
241
+ ctx.setTextBaseline(p === 100 ? 'top' : 'middle');
242
+ ctx.fillText(`${p}${isSplit ? '%' : ''}`, 0, yPos);
243
+ }
244
+ });
245
+ }
246
+
247
+ // --- Memory ---
248
+ const maxMem = Math.max(...mList.value, 100);
249
+ let targetMemScale = Math.ceil(maxMem / 100) * 100;
250
+ if (targetMemScale === 0) targetMemScale = 100;
251
+
252
+ if (config.showMemory.value) {
253
+ const memPoints = [0, targetMemScale / 2, targetMemScale];
254
+ memPoints.forEach((p) => {
255
+ const yPos = memY + (chartHeight - (p / targetMemScale) * chartHeight);
256
+ if (yPos < memY - 5 || yPos > memY + chartHeight + 5) return;
257
+
258
+ if (!isSplit || p !== 0) {
259
+ ctx.beginPath();
260
+ ctx.setStrokeStyle(
261
+ p === 0 || p === targetMemScale
262
+ ? mainLineColor.value
263
+ : subLineColor.value,
264
+ );
265
+ ctx.setLineDash(p === 0 || p === targetMemScale ? [] : [2, 4], 0);
266
+ ctx.moveTo(0, yPos);
267
+ ctx.lineTo(w, yPos);
268
+ ctx.stroke();
269
+ }
270
+
271
+ ctx.setFillStyle(textColor.value);
272
+ if (p !== 0) {
273
+ ctx.setTextBaseline(p === targetMemScale ? 'top' : 'middle');
274
+ ctx.fillText(`${p.toFixed(0)}${isSplit ? 'M' : ''}`, 0, yPos);
275
+ }
276
+ });
277
+ }
278
+
279
+ // Split Lines
280
+ if (isSplit) {
281
+ ctx.setStrokeStyle(mainLineColor.value);
282
+ ctx.setLineDash([], 0);
283
+ for (let i = 1; i < count; i++) {
284
+ const y = chartHeight * i;
285
+ ctx.beginPath();
286
+ ctx.moveTo(0, y);
287
+ ctx.lineTo(w, y);
288
+ ctx.stroke();
289
+ }
290
+ }
291
+
292
+ // Vertical Grid & Time Layout
293
+ const timeList = tList.value;
294
+
295
+ // Vertical Grid - 基于时间绘制,不再基于数组索引,保证网格绝对均匀
296
+ ctx.setStrokeStyle(subLineColor.value);
297
+ ctx.setLineDash([2, 4], 0);
298
+
299
+ const startTimeOnScreen = rightEdgeTime - (w / opts.stepSize) * 1000;
300
+ const startGridSec = Math.ceil(startTimeOnScreen / 5000) * 5000;
301
+
302
+ for (let t = startGridSec; t <= rightEdgeTime; t += 5000) {
303
+ const xPos = getX(t);
304
+ if (xPos >= 0 && xPos <= w) {
305
+ ctx.beginPath();
306
+ ctx.moveTo(xPos, 0);
307
+ ctx.lineTo(xPos, drawH);
308
+ ctx.stroke();
309
+ }
310
+ }
311
+
312
+ // Time Axis
313
+ if (timeList.length > 0) {
314
+ ctx.setLineDash([], 0);
315
+ ctx.setStrokeStyle(mainLineColor.value);
316
+ ctx.beginPath();
317
+ ctx.moveTo(0, drawH);
318
+ ctx.lineTo(w, drawH);
319
+ ctx.stroke();
320
+
321
+ ctx.setFillStyle(textColor.value);
322
+ ctx.setFontSize(10);
323
+ ctx.setTextAlign('center');
324
+
325
+ const formatTime = (ts: number) => {
326
+ const d = new Date(ts);
327
+ return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
328
+ };
329
+
330
+ const startTimeLabel = Math.ceil(startTimeOnScreen / 15000) * 15000;
331
+ for (let t = startTimeLabel; t <= rightEdgeTime; t += 15000) {
332
+ const xPos = getX(t);
333
+ if (xPos >= 0 && xPos <= w) {
334
+ const timeStr = formatTime(t);
335
+ ctx.setTextBaseline('bottom');
336
+ ctx.fillText(timeStr, xPos, h);
337
+ }
338
+ }
339
+ ctx.setTextAlign('left');
340
+ }
341
+
342
+ const drawAreaChart = (
343
+ list: number[],
344
+ startY: number,
345
+ height: number,
346
+ maxVal: number,
347
+ color: string,
348
+ gradientStart: string,
349
+ gradientEnd: string,
350
+ ) => {
351
+ const bottomY = startY + height;
352
+ if (list.length === 0) return;
353
+
354
+ const getValY = (val: number) =>
355
+ startY + (height - (val / maxVal) * height);
356
+
357
+ // 1. 准备渐变
358
+ const gradient = ctx.createLinearGradient(0, startY, 0, bottomY);
359
+ gradient.addColorStop(0, gradientStart);
360
+ gradient.addColorStop(1, gradientEnd);
361
+ ctx.setFillStyle(gradient);
362
+
363
+ // 2. 绘制填充面积
364
+ ctx.beginPath();
365
+ ctx.setLineDash([], 0);
366
+
367
+ const x0 = getX(timeList[0]);
368
+ const y0 = getValY(list[0]);
369
+ ctx.moveTo(x0, bottomY);
370
+ ctx.lineTo(x0, y0);
371
+
372
+ let lastY = y0;
373
+ for (let i = 1; i < list.length; i++) {
374
+ const x = getX(timeList[i]);
375
+ lastY = getValY(list[i]);
376
+ ctx.lineTo(x, lastY);
377
+ }
378
+
379
+ // 延伸到右侧边缘
380
+ ctx.lineTo(w, lastY);
381
+ ctx.lineTo(w, bottomY);
382
+ ctx.closePath();
383
+ ctx.fill();
384
+
385
+ // 3. 绘制顶部线条
386
+ ctx.beginPath();
387
+ ctx.setLineWidth(2);
388
+ ctx.setStrokeStyle(color);
389
+ ctx.moveTo(x0, y0);
390
+ for (let i = 1; i < list.length; i++) {
391
+ const x = getX(timeList[i]);
392
+ ctx.lineTo(x, getValY(list[i]));
393
+ }
394
+ ctx.lineTo(w, lastY);
395
+ ctx.stroke();
396
+ };
397
+
398
+ if (config.showCpu.value) {
399
+ drawAreaChart(
400
+ cList.value,
401
+ cpuY,
402
+ chartHeight,
403
+ 100,
404
+ '#9254de',
405
+ 'rgba(146, 84, 222, 0.5)',
406
+ 'rgba(146, 84, 222, 0.05)',
407
+ );
408
+ }
409
+
410
+ if (config.showMemory.value) {
411
+ drawAreaChart(
412
+ mList.value,
413
+ memY,
414
+ chartHeight,
415
+ targetMemScale,
416
+ '#409eff',
417
+ 'rgba(64, 158, 255, 0.5)',
418
+ 'rgba(64, 158, 255, 0.05)',
419
+ );
420
+ }
421
+
422
+ if (config.showFps.value) {
423
+ const currentFps = fList.value[fList.value.length - 1] || 0;
424
+ let colorStr = '#f56c6c';
425
+ let gradStart = 'rgba(245, 108, 108, 0.5)';
426
+ let gradEnd = 'rgba(245, 108, 108, 0.05)';
427
+
428
+ if (currentFps >= 50) {
429
+ colorStr = '#67c23a';
430
+ gradStart = 'rgba(103, 194, 58, 0.5)';
431
+ gradEnd = 'rgba(103, 194, 58, 0.05)';
432
+ } else if (currentFps >= 30) {
433
+ colorStr = '#e6a23c';
434
+ gradStart = 'rgba(230, 162, 60, 0.5)';
435
+ gradEnd = 'rgba(230, 162, 60, 0.05)';
436
+ }
437
+
438
+ drawAreaChart(
439
+ fList.value,
440
+ fpsY,
441
+ chartHeight,
442
+ currentScale.value,
443
+ colorStr,
444
+ gradStart,
445
+ gradEnd,
446
+ );
447
+ }
448
+
449
+ ctx.draw();
450
+ }
451
+
452
+ return {
453
+ chartBgColor,
454
+ canvasWidth,
455
+ initCanvas,
456
+ startRenderLoop,
457
+ stopRenderLoop,
458
+ resetCanvas,
459
+ };
460
+ }