@djvlc/runtime-host-vue 1.0.3 → 1.0.4
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/dist/index.cjs +1 -1082
- package/dist/index.js +1 -1060
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,1060 +1 @@
|
|
|
1
|
-
// src/vue-runtime.ts
|
|
2
|
-
import { ref, shallowRef, readonly, computed } from "vue";
|
|
3
|
-
import {
|
|
4
|
-
createRuntime
|
|
5
|
-
} from "@djvlc/runtime-core";
|
|
6
|
-
function createVueRuntime(options) {
|
|
7
|
-
const runtime = shallowRef(null);
|
|
8
|
-
const loading = ref(true);
|
|
9
|
-
const phase = ref("idle");
|
|
10
|
-
const page = shallowRef(null);
|
|
11
|
-
const error = shallowRef(null);
|
|
12
|
-
const hostApi = shallowRef(null);
|
|
13
|
-
const isReady = computed(() => phase.value === "ready");
|
|
14
|
-
const hasError = computed(() => phase.value === "error" || error.value !== null);
|
|
15
|
-
const metrics = shallowRef({
|
|
16
|
-
initTime: 0,
|
|
17
|
-
loadTime: 0,
|
|
18
|
-
renderTime: 0,
|
|
19
|
-
totalTime: 0,
|
|
20
|
-
initTimestamp: null,
|
|
21
|
-
readyTimestamp: null
|
|
22
|
-
});
|
|
23
|
-
let startTime = 0;
|
|
24
|
-
let initStartTime = 0;
|
|
25
|
-
let loadStartTime = 0;
|
|
26
|
-
let renderStartTime = 0;
|
|
27
|
-
const init = async () => {
|
|
28
|
-
const container = options.containerRef?.value;
|
|
29
|
-
if (!container) {
|
|
30
|
-
throw new Error("Container element not found");
|
|
31
|
-
}
|
|
32
|
-
startTime = performance.now();
|
|
33
|
-
initStartTime = startTime;
|
|
34
|
-
if (options.enableMetrics) {
|
|
35
|
-
metrics.value = {
|
|
36
|
-
...metrics.value,
|
|
37
|
-
initTimestamp: Date.now()
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const runtimeInstance = createRuntime({
|
|
41
|
-
...options,
|
|
42
|
-
container,
|
|
43
|
-
onError: (err) => {
|
|
44
|
-
error.value = err;
|
|
45
|
-
options.onError?.(err);
|
|
46
|
-
},
|
|
47
|
-
onEvent: (event) => {
|
|
48
|
-
options.onEvent?.(event);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
runtime.value = runtimeInstance;
|
|
52
|
-
runtimeInstance.onStateChange((state) => {
|
|
53
|
-
phase.value = state.phase;
|
|
54
|
-
loading.value = state.phase !== "ready" && state.phase !== "error";
|
|
55
|
-
if (state.page) {
|
|
56
|
-
page.value = state.page;
|
|
57
|
-
}
|
|
58
|
-
if (state.error) {
|
|
59
|
-
error.value = state.error;
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
await runtimeInstance.init();
|
|
63
|
-
hostApi.value = runtimeInstance.getHostApi();
|
|
64
|
-
if (options.enableMetrics) {
|
|
65
|
-
metrics.value = {
|
|
66
|
-
...metrics.value,
|
|
67
|
-
initTime: performance.now() - initStartTime
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const load = async () => {
|
|
72
|
-
if (!runtime.value) {
|
|
73
|
-
throw new Error("Runtime not initialized");
|
|
74
|
-
}
|
|
75
|
-
loadStartTime = performance.now();
|
|
76
|
-
const result = await runtime.value.load();
|
|
77
|
-
page.value = result;
|
|
78
|
-
hostApi.value = runtime.value.getHostApi();
|
|
79
|
-
if (options.enableMetrics) {
|
|
80
|
-
metrics.value = {
|
|
81
|
-
...metrics.value,
|
|
82
|
-
loadTime: performance.now() - loadStartTime
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return result;
|
|
86
|
-
};
|
|
87
|
-
const render = async () => {
|
|
88
|
-
if (!runtime.value) {
|
|
89
|
-
throw new Error("Runtime not initialized");
|
|
90
|
-
}
|
|
91
|
-
renderStartTime = performance.now();
|
|
92
|
-
await runtime.value.render();
|
|
93
|
-
loading.value = false;
|
|
94
|
-
if (options.enableMetrics) {
|
|
95
|
-
const now = performance.now();
|
|
96
|
-
metrics.value = {
|
|
97
|
-
...metrics.value,
|
|
98
|
-
renderTime: now - renderStartTime,
|
|
99
|
-
totalTime: now - startTime,
|
|
100
|
-
readyTimestamp: Date.now()
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
const destroy = () => {
|
|
105
|
-
runtime.value?.destroy();
|
|
106
|
-
runtime.value = null;
|
|
107
|
-
hostApi.value = null;
|
|
108
|
-
page.value = null;
|
|
109
|
-
error.value = null;
|
|
110
|
-
phase.value = "idle";
|
|
111
|
-
loading.value = true;
|
|
112
|
-
};
|
|
113
|
-
const reload = async () => {
|
|
114
|
-
if (!runtime.value) {
|
|
115
|
-
throw new Error("Runtime not initialized");
|
|
116
|
-
}
|
|
117
|
-
error.value = null;
|
|
118
|
-
loading.value = true;
|
|
119
|
-
await load();
|
|
120
|
-
await render();
|
|
121
|
-
};
|
|
122
|
-
const setVariable = (key, value) => {
|
|
123
|
-
runtime.value?.setVariable(key, value);
|
|
124
|
-
};
|
|
125
|
-
const setVariables = (variables) => {
|
|
126
|
-
if (!runtime.value) return;
|
|
127
|
-
Object.entries(variables).forEach(([key, value]) => {
|
|
128
|
-
runtime.value?.setVariable(key, value);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
const getVariable = (key) => {
|
|
132
|
-
return runtime.value?.getState().variables[key];
|
|
133
|
-
};
|
|
134
|
-
const refreshData = async (queryId) => {
|
|
135
|
-
await runtime.value?.refreshData(queryId);
|
|
136
|
-
};
|
|
137
|
-
const executeAction = async (actionType, params) => {
|
|
138
|
-
const api = hostApi.value;
|
|
139
|
-
if (!api) {
|
|
140
|
-
throw new Error("HostAPI not available");
|
|
141
|
-
}
|
|
142
|
-
const response = await api.executeAction(actionType, params || {});
|
|
143
|
-
if (response.success) {
|
|
144
|
-
return response.data;
|
|
145
|
-
}
|
|
146
|
-
throw new Error(response.errorMessage || "Action failed");
|
|
147
|
-
};
|
|
148
|
-
return {
|
|
149
|
-
runtime,
|
|
150
|
-
loading: readonly(loading),
|
|
151
|
-
phase: readonly(phase),
|
|
152
|
-
page,
|
|
153
|
-
error,
|
|
154
|
-
hostApi,
|
|
155
|
-
isReady,
|
|
156
|
-
hasError,
|
|
157
|
-
metrics,
|
|
158
|
-
init,
|
|
159
|
-
load,
|
|
160
|
-
render,
|
|
161
|
-
destroy,
|
|
162
|
-
reload,
|
|
163
|
-
setVariable,
|
|
164
|
-
setVariables,
|
|
165
|
-
getVariable,
|
|
166
|
-
refreshData,
|
|
167
|
-
executeAction
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// src/components/DJVRenderer.ts
|
|
172
|
-
import {
|
|
173
|
-
defineComponent,
|
|
174
|
-
ref as ref3,
|
|
175
|
-
shallowRef as shallowRef3,
|
|
176
|
-
onMounted as onMounted2,
|
|
177
|
-
onUnmounted as onUnmounted2,
|
|
178
|
-
watch as watch2,
|
|
179
|
-
h
|
|
180
|
-
} from "vue";
|
|
181
|
-
|
|
182
|
-
// src/composables/useRuntime.ts
|
|
183
|
-
import {
|
|
184
|
-
inject,
|
|
185
|
-
provide,
|
|
186
|
-
readonly as readonly2,
|
|
187
|
-
ref as ref2,
|
|
188
|
-
shallowRef as shallowRef2,
|
|
189
|
-
computed as computed2,
|
|
190
|
-
watch,
|
|
191
|
-
onMounted,
|
|
192
|
-
onUnmounted
|
|
193
|
-
} from "vue";
|
|
194
|
-
var RuntimeContextKey = /* @__PURE__ */ Symbol("DJVRuntime");
|
|
195
|
-
function provideRuntime(value) {
|
|
196
|
-
provide(RuntimeContextKey, value);
|
|
197
|
-
}
|
|
198
|
-
function injectRuntime() {
|
|
199
|
-
const context = inject(RuntimeContextKey);
|
|
200
|
-
if (!context) {
|
|
201
|
-
throw new Error("useDJVRuntime must be used within a DJVProvider");
|
|
202
|
-
}
|
|
203
|
-
return context;
|
|
204
|
-
}
|
|
205
|
-
function useDJVRuntime() {
|
|
206
|
-
const context = injectRuntime();
|
|
207
|
-
const loading = computed2(() => {
|
|
208
|
-
const phase = context.value.state.phase;
|
|
209
|
-
return phase !== "ready" && phase !== "error";
|
|
210
|
-
});
|
|
211
|
-
const isReady = computed2(() => context.value.state.phase === "ready");
|
|
212
|
-
const hasError = computed2(() => context.value.state.phase === "error" || context.value.state.error !== null);
|
|
213
|
-
const reload = async () => {
|
|
214
|
-
const runtime = context.value.runtime;
|
|
215
|
-
if (!runtime) {
|
|
216
|
-
throw new Error("Runtime not available");
|
|
217
|
-
}
|
|
218
|
-
await runtime.load();
|
|
219
|
-
await runtime.render();
|
|
220
|
-
};
|
|
221
|
-
return {
|
|
222
|
-
runtime: computed2(() => context.value.runtime),
|
|
223
|
-
state: computed2(() => context.value.state),
|
|
224
|
-
loading,
|
|
225
|
-
phase: computed2(() => context.value.state.phase),
|
|
226
|
-
error: computed2(() => context.value.state.error),
|
|
227
|
-
page: computed2(() => context.value.state.page),
|
|
228
|
-
isReady,
|
|
229
|
-
hasError,
|
|
230
|
-
reload
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
function useHostApi() {
|
|
234
|
-
const context = injectRuntime();
|
|
235
|
-
const hostApi = context.value.hostApi;
|
|
236
|
-
if (!hostApi) {
|
|
237
|
-
throw new Error("HostAPI not available. Make sure runtime is initialized.");
|
|
238
|
-
}
|
|
239
|
-
return hostApi;
|
|
240
|
-
}
|
|
241
|
-
function useRuntimeState(key) {
|
|
242
|
-
const context = injectRuntime();
|
|
243
|
-
return computed2(() => {
|
|
244
|
-
return context.value.state.variables[key];
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
function useRuntimeStateWritable(key, defaultValue) {
|
|
248
|
-
const context = injectRuntime();
|
|
249
|
-
const value = computed2(() => {
|
|
250
|
-
return context.value.state.variables[key] ?? defaultValue;
|
|
251
|
-
});
|
|
252
|
-
const setValue = (newValue) => {
|
|
253
|
-
context.value.runtime?.setVariable(key, newValue);
|
|
254
|
-
};
|
|
255
|
-
return [value, setValue];
|
|
256
|
-
}
|
|
257
|
-
function useQuery(queryId, options) {
|
|
258
|
-
const context = injectRuntime();
|
|
259
|
-
const loading = ref2(false);
|
|
260
|
-
const error = ref2(null);
|
|
261
|
-
const lastUpdated = ref2(null);
|
|
262
|
-
let intervalTimer = null;
|
|
263
|
-
const data = computed2(() => {
|
|
264
|
-
return context.value.state.queries[queryId];
|
|
265
|
-
});
|
|
266
|
-
const refetch = async () => {
|
|
267
|
-
loading.value = true;
|
|
268
|
-
error.value = null;
|
|
269
|
-
try {
|
|
270
|
-
await context.value.runtime?.refreshData(queryId);
|
|
271
|
-
lastUpdated.value = Date.now();
|
|
272
|
-
} catch (e) {
|
|
273
|
-
error.value = e;
|
|
274
|
-
} finally {
|
|
275
|
-
loading.value = false;
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
onMounted(() => {
|
|
279
|
-
if (options?.refreshOnMount && context.value.runtime) {
|
|
280
|
-
refetch();
|
|
281
|
-
}
|
|
282
|
-
if (options?.refreshInterval && options.refreshInterval > 0) {
|
|
283
|
-
intervalTimer = setInterval(refetch, options.refreshInterval);
|
|
284
|
-
}
|
|
285
|
-
if (options?.refreshOnFocus) {
|
|
286
|
-
window.addEventListener("focus", refetch);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
onUnmounted(() => {
|
|
290
|
-
if (intervalTimer) {
|
|
291
|
-
clearInterval(intervalTimer);
|
|
292
|
-
}
|
|
293
|
-
if (options?.refreshOnFocus) {
|
|
294
|
-
window.removeEventListener("focus", refetch);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
return {
|
|
298
|
-
data,
|
|
299
|
-
loading,
|
|
300
|
-
error,
|
|
301
|
-
refetch,
|
|
302
|
-
lastUpdated
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function useAction(actionType, options) {
|
|
306
|
-
const context = injectRuntime();
|
|
307
|
-
const loading = ref2(false);
|
|
308
|
-
const result = shallowRef2();
|
|
309
|
-
const error = ref2(null);
|
|
310
|
-
const executionCount = ref2(0);
|
|
311
|
-
const reset = () => {
|
|
312
|
-
result.value = void 0;
|
|
313
|
-
error.value = null;
|
|
314
|
-
executionCount.value = 0;
|
|
315
|
-
};
|
|
316
|
-
const executeWithRetry = async (params, retriesLeft) => {
|
|
317
|
-
const hostApi = context.value.hostApi;
|
|
318
|
-
if (!hostApi) {
|
|
319
|
-
throw new Error("HostAPI not available");
|
|
320
|
-
}
|
|
321
|
-
try {
|
|
322
|
-
const response = await hostApi.executeAction(actionType, params);
|
|
323
|
-
if (response.success) {
|
|
324
|
-
return response.data;
|
|
325
|
-
} else {
|
|
326
|
-
throw new Error(response.errorMessage || "Action failed");
|
|
327
|
-
}
|
|
328
|
-
} catch (e) {
|
|
329
|
-
if (retriesLeft > 0) {
|
|
330
|
-
await new Promise((resolve) => setTimeout(resolve, options?.retryDelay || 1e3));
|
|
331
|
-
return executeWithRetry(params, retriesLeft - 1);
|
|
332
|
-
}
|
|
333
|
-
throw e;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
const execute = async (params) => {
|
|
337
|
-
loading.value = true;
|
|
338
|
-
error.value = null;
|
|
339
|
-
executionCount.value++;
|
|
340
|
-
try {
|
|
341
|
-
const data = await executeWithRetry(params, options?.retryCount || 0);
|
|
342
|
-
result.value = data;
|
|
343
|
-
options?.onSuccess?.(data);
|
|
344
|
-
return data;
|
|
345
|
-
} catch (e) {
|
|
346
|
-
const err = e;
|
|
347
|
-
error.value = err;
|
|
348
|
-
options?.onError?.(err);
|
|
349
|
-
throw e;
|
|
350
|
-
} finally {
|
|
351
|
-
loading.value = false;
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
return {
|
|
355
|
-
execute,
|
|
356
|
-
loading,
|
|
357
|
-
result,
|
|
358
|
-
error,
|
|
359
|
-
reset,
|
|
360
|
-
executionCount
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function useData(queryId, params, options) {
|
|
364
|
-
const context = injectRuntime();
|
|
365
|
-
const data = shallowRef2();
|
|
366
|
-
const loading = ref2(false);
|
|
367
|
-
const error = ref2(null);
|
|
368
|
-
const isFetched = ref2(false);
|
|
369
|
-
let intervalTimer = null;
|
|
370
|
-
const getCurrentParams = () => {
|
|
371
|
-
if (!params) return void 0;
|
|
372
|
-
if (typeof params === "object" && "value" in params) {
|
|
373
|
-
return params.value;
|
|
374
|
-
}
|
|
375
|
-
return params;
|
|
376
|
-
};
|
|
377
|
-
const refetch = async (newParams) => {
|
|
378
|
-
const hostApi = context.value.hostApi;
|
|
379
|
-
if (!hostApi) {
|
|
380
|
-
throw new Error("HostAPI not available");
|
|
381
|
-
}
|
|
382
|
-
loading.value = true;
|
|
383
|
-
error.value = null;
|
|
384
|
-
try {
|
|
385
|
-
const result = await hostApi.requestData(
|
|
386
|
-
queryId,
|
|
387
|
-
newParams || getCurrentParams()
|
|
388
|
-
);
|
|
389
|
-
data.value = result;
|
|
390
|
-
isFetched.value = true;
|
|
391
|
-
options?.onSuccess?.(result);
|
|
392
|
-
} catch (e) {
|
|
393
|
-
const err = e;
|
|
394
|
-
error.value = err;
|
|
395
|
-
options?.onError?.(err);
|
|
396
|
-
} finally {
|
|
397
|
-
loading.value = false;
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
if (params && typeof params === "object" && "value" in params && options?.refreshOnParamsChange !== false) {
|
|
401
|
-
watch(params, () => {
|
|
402
|
-
if (isFetched.value) {
|
|
403
|
-
refetch();
|
|
404
|
-
}
|
|
405
|
-
}, { deep: true });
|
|
406
|
-
}
|
|
407
|
-
onMounted(() => {
|
|
408
|
-
if (options?.immediate !== false && context.value.hostApi) {
|
|
409
|
-
refetch();
|
|
410
|
-
}
|
|
411
|
-
if (options?.refreshInterval && options.refreshInterval > 0) {
|
|
412
|
-
intervalTimer = setInterval(refetch, options.refreshInterval);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
onUnmounted(() => {
|
|
416
|
-
if (intervalTimer) {
|
|
417
|
-
clearInterval(intervalTimer);
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
return {
|
|
421
|
-
data,
|
|
422
|
-
loading,
|
|
423
|
-
error,
|
|
424
|
-
refetch,
|
|
425
|
-
isFetched
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
function useRuntimeEvent(eventType, handler) {
|
|
429
|
-
const context = injectRuntime();
|
|
430
|
-
const unsubscribe = context.value.runtime?.on(eventType, (event) => {
|
|
431
|
-
handler(event.data);
|
|
432
|
-
});
|
|
433
|
-
onUnmounted(() => {
|
|
434
|
-
unsubscribe?.();
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
function usePageInfo() {
|
|
438
|
-
const context = injectRuntime();
|
|
439
|
-
return {
|
|
440
|
-
/** 页面 UID */
|
|
441
|
-
pageUid: computed2(() => context.value.state.page?.pageUid),
|
|
442
|
-
/** 页面版本 ID */
|
|
443
|
-
pageVersionId: computed2(() => context.value.state.page?.pageVersionId),
|
|
444
|
-
/** Schema 版本 */
|
|
445
|
-
schemaVersion: computed2(() => context.value.state.page?.pageJson?.schemaVersion),
|
|
446
|
-
/** 页面标题 */
|
|
447
|
-
title: computed2(() => {
|
|
448
|
-
const page = context.value.state.page;
|
|
449
|
-
return page?.title;
|
|
450
|
-
}),
|
|
451
|
-
/** 页面配置 */
|
|
452
|
-
config: computed2(() => {
|
|
453
|
-
const page = context.value.state.page;
|
|
454
|
-
return page?.config;
|
|
455
|
-
}),
|
|
456
|
-
/** 页面是否已加载 */
|
|
457
|
-
isLoaded: computed2(() => context.value.state.page !== null)
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function useComponentState(componentId) {
|
|
461
|
-
const context = injectRuntime();
|
|
462
|
-
const componentStatus = computed2(() => {
|
|
463
|
-
return context.value.state.components.get(componentId);
|
|
464
|
-
});
|
|
465
|
-
return {
|
|
466
|
-
/** 组件是否已加载 */
|
|
467
|
-
isLoaded: computed2(() => componentStatus.value?.status === "loaded"),
|
|
468
|
-
/** 组件是否加载中 */
|
|
469
|
-
isLoading: computed2(() => componentStatus.value?.status === "loading"),
|
|
470
|
-
/** 组件是否加载失败 */
|
|
471
|
-
hasError: computed2(() => componentStatus.value?.status === "failed"),
|
|
472
|
-
/** 加载耗时 */
|
|
473
|
-
loadTime: computed2(() => componentStatus.value?.loadTime),
|
|
474
|
-
/** 组件信息 */
|
|
475
|
-
info: componentStatus
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function useLifecycle(options) {
|
|
479
|
-
const context = injectRuntime();
|
|
480
|
-
const hasMounted = ref2(false);
|
|
481
|
-
const hasReady = ref2(false);
|
|
482
|
-
watch(
|
|
483
|
-
() => context.value.state.phase,
|
|
484
|
-
async (newPhase, oldPhase) => {
|
|
485
|
-
options?.onPhaseChange?.(newPhase);
|
|
486
|
-
if (!hasMounted.value && newPhase !== "idle") {
|
|
487
|
-
hasMounted.value = true;
|
|
488
|
-
try {
|
|
489
|
-
await options?.onMounted?.();
|
|
490
|
-
} catch (error) {
|
|
491
|
-
options?.onError?.(error);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (!hasReady.value && newPhase === "ready") {
|
|
495
|
-
hasReady.value = true;
|
|
496
|
-
try {
|
|
497
|
-
await options?.onReady?.();
|
|
498
|
-
} catch (error) {
|
|
499
|
-
options?.onError?.(error);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (newPhase === "error" && oldPhase !== "error") {
|
|
503
|
-
options?.onError?.(context.value.state.error);
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
{ immediate: true }
|
|
507
|
-
);
|
|
508
|
-
return {
|
|
509
|
-
/** 当前阶段 */
|
|
510
|
-
phase: computed2(() => context.value.state.phase),
|
|
511
|
-
/** 是否已 mounted */
|
|
512
|
-
hasMounted: readonly2(hasMounted),
|
|
513
|
-
/** 是否已 ready */
|
|
514
|
-
hasReady: readonly2(hasReady)
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
function useWhen(condition, callback, options) {
|
|
518
|
-
const executed = ref2(false);
|
|
519
|
-
const { once = true, immediate = true } = options || {};
|
|
520
|
-
const stop = watch(
|
|
521
|
-
condition,
|
|
522
|
-
async (value) => {
|
|
523
|
-
if (value && (!once || !executed.value)) {
|
|
524
|
-
executed.value = true;
|
|
525
|
-
await callback();
|
|
526
|
-
if (once) {
|
|
527
|
-
stop();
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
{ immediate }
|
|
532
|
-
);
|
|
533
|
-
onUnmounted(() => {
|
|
534
|
-
stop();
|
|
535
|
-
});
|
|
536
|
-
return {
|
|
537
|
-
/** 是否已执行 */
|
|
538
|
-
executed: readonly2(executed),
|
|
539
|
-
/** 手动停止监听 */
|
|
540
|
-
stop
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
function useDebouncedAction(actionType, delay = 300) {
|
|
544
|
-
const { execute: rawExecute, loading, result, error } = useAction(actionType);
|
|
545
|
-
let timeoutId = null;
|
|
546
|
-
const execute = (params) => {
|
|
547
|
-
if (timeoutId) {
|
|
548
|
-
clearTimeout(timeoutId);
|
|
549
|
-
}
|
|
550
|
-
timeoutId = setTimeout(() => {
|
|
551
|
-
rawExecute(params).catch(() => {
|
|
552
|
-
});
|
|
553
|
-
}, delay);
|
|
554
|
-
};
|
|
555
|
-
const cancel = () => {
|
|
556
|
-
if (timeoutId) {
|
|
557
|
-
clearTimeout(timeoutId);
|
|
558
|
-
timeoutId = null;
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
onUnmounted(() => {
|
|
562
|
-
cancel();
|
|
563
|
-
});
|
|
564
|
-
return {
|
|
565
|
-
execute,
|
|
566
|
-
loading,
|
|
567
|
-
result,
|
|
568
|
-
error,
|
|
569
|
-
cancel
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function useGlobalConfig() {
|
|
573
|
-
const config = inject("djvlc-config", {});
|
|
574
|
-
return config;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// src/components/DJVRenderer.ts
|
|
578
|
-
var DJVRenderer = defineComponent({
|
|
579
|
-
name: "DJVRenderer",
|
|
580
|
-
props: {
|
|
581
|
-
pageUid: {
|
|
582
|
-
type: String,
|
|
583
|
-
required: true
|
|
584
|
-
},
|
|
585
|
-
apiBaseUrl: {
|
|
586
|
-
type: String,
|
|
587
|
-
required: true
|
|
588
|
-
},
|
|
589
|
-
cdnBaseUrl: {
|
|
590
|
-
type: String,
|
|
591
|
-
required: true
|
|
592
|
-
},
|
|
593
|
-
channel: {
|
|
594
|
-
type: String,
|
|
595
|
-
default: "prod"
|
|
596
|
-
},
|
|
597
|
-
userId: String,
|
|
598
|
-
deviceId: String,
|
|
599
|
-
authToken: String,
|
|
600
|
-
previewToken: String,
|
|
601
|
-
debug: {
|
|
602
|
-
type: Boolean,
|
|
603
|
-
default: false
|
|
604
|
-
},
|
|
605
|
-
enableSRI: {
|
|
606
|
-
type: Boolean,
|
|
607
|
-
default: true
|
|
608
|
-
},
|
|
609
|
-
retryCount: {
|
|
610
|
-
type: Number,
|
|
611
|
-
default: 3
|
|
612
|
-
},
|
|
613
|
-
retryDelay: {
|
|
614
|
-
type: Number,
|
|
615
|
-
default: 1e3
|
|
616
|
-
},
|
|
617
|
-
timeout: {
|
|
618
|
-
type: Number,
|
|
619
|
-
default: 3e4
|
|
620
|
-
}
|
|
621
|
-
},
|
|
622
|
-
emits: ["load", "error", "ready", "phase-change"],
|
|
623
|
-
setup(props, { emit, slots }) {
|
|
624
|
-
const containerRef = ref3(null);
|
|
625
|
-
const mounted = ref3(true);
|
|
626
|
-
const retryAttempt = ref3(0);
|
|
627
|
-
const phase = ref3("idle");
|
|
628
|
-
const contextValue = shallowRef3({
|
|
629
|
-
runtime: null,
|
|
630
|
-
state: {
|
|
631
|
-
phase: "idle",
|
|
632
|
-
page: null,
|
|
633
|
-
variables: {},
|
|
634
|
-
queries: {},
|
|
635
|
-
components: /* @__PURE__ */ new Map(),
|
|
636
|
-
error: null,
|
|
637
|
-
destroyed: false
|
|
638
|
-
},
|
|
639
|
-
hostApi: null
|
|
640
|
-
});
|
|
641
|
-
provideRuntime(contextValue);
|
|
642
|
-
let vueRuntime = null;
|
|
643
|
-
const withTimeout = (promise, ms) => {
|
|
644
|
-
return Promise.race([
|
|
645
|
-
promise,
|
|
646
|
-
new Promise(
|
|
647
|
-
(_, reject) => setTimeout(() => reject(new Error("\u52A0\u8F7D\u8D85\u65F6")), ms)
|
|
648
|
-
)
|
|
649
|
-
]);
|
|
650
|
-
};
|
|
651
|
-
const initWithRetry = async () => {
|
|
652
|
-
if (!containerRef.value || !mounted.value) return;
|
|
653
|
-
const options = {
|
|
654
|
-
pageUid: props.pageUid,
|
|
655
|
-
apiBaseUrl: props.apiBaseUrl,
|
|
656
|
-
cdnBaseUrl: props.cdnBaseUrl,
|
|
657
|
-
channel: props.channel,
|
|
658
|
-
userId: props.userId,
|
|
659
|
-
deviceId: props.deviceId,
|
|
660
|
-
authToken: props.authToken,
|
|
661
|
-
previewToken: props.previewToken,
|
|
662
|
-
debug: props.debug,
|
|
663
|
-
enableSRI: props.enableSRI,
|
|
664
|
-
containerRef,
|
|
665
|
-
onError: (error) => {
|
|
666
|
-
emit("error", error);
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
vueRuntime = createVueRuntime(options);
|
|
670
|
-
try {
|
|
671
|
-
await withTimeout(
|
|
672
|
-
(async () => {
|
|
673
|
-
await vueRuntime.init();
|
|
674
|
-
if (!mounted.value) return;
|
|
675
|
-
phase.value = "loading";
|
|
676
|
-
emit("phase-change", "loading");
|
|
677
|
-
contextValue.value = {
|
|
678
|
-
runtime: vueRuntime.runtime.value,
|
|
679
|
-
state: vueRuntime.runtime.value?.getState() || contextValue.value.state,
|
|
680
|
-
hostApi: vueRuntime.hostApi.value
|
|
681
|
-
};
|
|
682
|
-
const pageData = await vueRuntime.load();
|
|
683
|
-
if (!mounted.value) return;
|
|
684
|
-
emit("load", pageData);
|
|
685
|
-
contextValue.value = {
|
|
686
|
-
...contextValue.value,
|
|
687
|
-
state: vueRuntime.runtime.value?.getState() || contextValue.value.state,
|
|
688
|
-
hostApi: vueRuntime.hostApi.value
|
|
689
|
-
};
|
|
690
|
-
await vueRuntime.render();
|
|
691
|
-
if (!mounted.value) return;
|
|
692
|
-
phase.value = "ready";
|
|
693
|
-
emit("phase-change", "ready");
|
|
694
|
-
emit("ready");
|
|
695
|
-
retryAttempt.value = 0;
|
|
696
|
-
vueRuntime.runtime.value?.onStateChange((state) => {
|
|
697
|
-
if (!mounted.value) return;
|
|
698
|
-
contextValue.value = {
|
|
699
|
-
...contextValue.value,
|
|
700
|
-
state
|
|
701
|
-
};
|
|
702
|
-
phase.value = state.phase;
|
|
703
|
-
emit("phase-change", state.phase);
|
|
704
|
-
});
|
|
705
|
-
})(),
|
|
706
|
-
props.timeout
|
|
707
|
-
);
|
|
708
|
-
} catch (error) {
|
|
709
|
-
if (!mounted.value) return;
|
|
710
|
-
if (retryAttempt.value < props.retryCount) {
|
|
711
|
-
retryAttempt.value++;
|
|
712
|
-
if (props.debug) {
|
|
713
|
-
console.log(`[DJVRenderer] \u91CD\u8BD5 ${retryAttempt.value}/${props.retryCount}...`);
|
|
714
|
-
}
|
|
715
|
-
setTimeout(() => {
|
|
716
|
-
if (mounted.value) {
|
|
717
|
-
initWithRetry();
|
|
718
|
-
}
|
|
719
|
-
}, props.retryDelay);
|
|
720
|
-
} else {
|
|
721
|
-
phase.value = "error";
|
|
722
|
-
emit("phase-change", "error");
|
|
723
|
-
emit("error", error);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
const handleRetry = () => {
|
|
728
|
-
retryAttempt.value = 0;
|
|
729
|
-
initWithRetry();
|
|
730
|
-
};
|
|
731
|
-
onMounted2(() => {
|
|
732
|
-
mounted.value = true;
|
|
733
|
-
initWithRetry();
|
|
734
|
-
});
|
|
735
|
-
onUnmounted2(() => {
|
|
736
|
-
mounted.value = false;
|
|
737
|
-
vueRuntime?.destroy();
|
|
738
|
-
});
|
|
739
|
-
watch2(
|
|
740
|
-
() => props.pageUid,
|
|
741
|
-
() => {
|
|
742
|
-
vueRuntime?.destroy();
|
|
743
|
-
retryAttempt.value = 0;
|
|
744
|
-
initWithRetry();
|
|
745
|
-
}
|
|
746
|
-
);
|
|
747
|
-
const renderDefaultLoading = () => {
|
|
748
|
-
return h("div", { class: "djvlc-loading" }, [
|
|
749
|
-
h("div", { class: "djvlc-loading-spinner" }),
|
|
750
|
-
h("span", {}, "\u52A0\u8F7D\u4E2D..."),
|
|
751
|
-
retryAttempt.value > 0 && h("span", { class: "djvlc-loading-retry" }, `\u91CD\u8BD5 ${retryAttempt.value}/${props.retryCount}`)
|
|
752
|
-
]);
|
|
753
|
-
};
|
|
754
|
-
const renderDefaultError = (error) => {
|
|
755
|
-
return h("div", { class: "djvlc-error" }, [
|
|
756
|
-
h("div", { class: "djvlc-error-icon" }, "\u26A0\uFE0F"),
|
|
757
|
-
h("span", { class: "djvlc-error-message" }, `\u52A0\u8F7D\u5931\u8D25\uFF1A${error.message}`),
|
|
758
|
-
h(
|
|
759
|
-
"button",
|
|
760
|
-
{
|
|
761
|
-
class: "djvlc-error-retry-btn",
|
|
762
|
-
onClick: handleRetry,
|
|
763
|
-
type: "button"
|
|
764
|
-
},
|
|
765
|
-
"\u91CD\u8BD5"
|
|
766
|
-
)
|
|
767
|
-
]);
|
|
768
|
-
};
|
|
769
|
-
const renderDefaultEmpty = () => {
|
|
770
|
-
return h("div", { class: "djvlc-empty" }, [h("span", {}, "\u6682\u65E0\u5185\u5BB9")]);
|
|
771
|
-
};
|
|
772
|
-
return () => {
|
|
773
|
-
const isLoading = vueRuntime?.loading.value ?? false;
|
|
774
|
-
const currentError = vueRuntime?.error.value;
|
|
775
|
-
const currentPage = vueRuntime?.page.value;
|
|
776
|
-
return h(
|
|
777
|
-
"div",
|
|
778
|
-
{
|
|
779
|
-
ref: containerRef,
|
|
780
|
-
class: "djvlc-renderer",
|
|
781
|
-
"data-phase": phase.value,
|
|
782
|
-
"data-page-uid": props.pageUid
|
|
783
|
-
},
|
|
784
|
-
[
|
|
785
|
-
// 加载中插槽
|
|
786
|
-
isLoading && (slots.loading?.() || renderDefaultLoading()),
|
|
787
|
-
// 错误插槽
|
|
788
|
-
currentError && (slots.error?.({ error: currentError, retry: handleRetry }) || renderDefaultError(currentError)),
|
|
789
|
-
// 空状态
|
|
790
|
-
!isLoading && !currentError && !currentPage && phase.value === "ready" && (slots.empty?.() || renderDefaultEmpty()),
|
|
791
|
-
// 默认插槽(额外内容)
|
|
792
|
-
slots.default?.()
|
|
793
|
-
]
|
|
794
|
-
);
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
// src/components/DJVProvider.ts
|
|
800
|
-
import {
|
|
801
|
-
defineComponent as defineComponent2,
|
|
802
|
-
shallowRef as shallowRef4,
|
|
803
|
-
watch as watch3,
|
|
804
|
-
onUnmounted as onUnmounted3,
|
|
805
|
-
h as h2
|
|
806
|
-
} from "vue";
|
|
807
|
-
var defaultState = {
|
|
808
|
-
phase: "idle",
|
|
809
|
-
page: null,
|
|
810
|
-
variables: {},
|
|
811
|
-
queries: {},
|
|
812
|
-
components: /* @__PURE__ */ new Map(),
|
|
813
|
-
error: null,
|
|
814
|
-
destroyed: false
|
|
815
|
-
};
|
|
816
|
-
var DJVProvider = defineComponent2({
|
|
817
|
-
name: "DJVProvider",
|
|
818
|
-
props: {
|
|
819
|
-
runtime: {
|
|
820
|
-
type: Object,
|
|
821
|
-
default: null
|
|
822
|
-
},
|
|
823
|
-
hostApi: {
|
|
824
|
-
type: Object,
|
|
825
|
-
default: null
|
|
826
|
-
},
|
|
827
|
-
class: {
|
|
828
|
-
type: String,
|
|
829
|
-
default: ""
|
|
830
|
-
},
|
|
831
|
-
debug: {
|
|
832
|
-
type: Boolean,
|
|
833
|
-
default: false
|
|
834
|
-
}
|
|
835
|
-
},
|
|
836
|
-
emits: ["state-change", "phase-change", "error"],
|
|
837
|
-
setup(props, { slots, emit }) {
|
|
838
|
-
let unsubscribe = null;
|
|
839
|
-
const contextValue = shallowRef4({
|
|
840
|
-
runtime: props.runtime,
|
|
841
|
-
state: props.runtime?.getState() || defaultState,
|
|
842
|
-
hostApi: props.hostApi
|
|
843
|
-
});
|
|
844
|
-
provideRuntime(contextValue);
|
|
845
|
-
const subscribeToRuntime = (runtime) => {
|
|
846
|
-
if (unsubscribe) {
|
|
847
|
-
unsubscribe();
|
|
848
|
-
unsubscribe = null;
|
|
849
|
-
}
|
|
850
|
-
if (!runtime) return;
|
|
851
|
-
unsubscribe = runtime.onStateChange((state) => {
|
|
852
|
-
const prevPhase = contextValue.value.state.phase;
|
|
853
|
-
contextValue.value = {
|
|
854
|
-
...contextValue.value,
|
|
855
|
-
state
|
|
856
|
-
};
|
|
857
|
-
emit("state-change", state);
|
|
858
|
-
if (state.phase !== prevPhase) {
|
|
859
|
-
emit("phase-change", state.phase);
|
|
860
|
-
if (props.debug) {
|
|
861
|
-
console.log(`[DJVProvider] Phase changed: ${prevPhase} -> ${state.phase}`);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (state.error) {
|
|
865
|
-
emit("error", state.error);
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
};
|
|
869
|
-
subscribeToRuntime(props.runtime);
|
|
870
|
-
watch3(
|
|
871
|
-
() => props.runtime,
|
|
872
|
-
(newRuntime, oldRuntime) => {
|
|
873
|
-
if (newRuntime !== oldRuntime) {
|
|
874
|
-
contextValue.value = {
|
|
875
|
-
runtime: newRuntime,
|
|
876
|
-
state: newRuntime?.getState() || defaultState,
|
|
877
|
-
hostApi: props.hostApi
|
|
878
|
-
};
|
|
879
|
-
subscribeToRuntime(newRuntime);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
);
|
|
883
|
-
watch3(
|
|
884
|
-
() => props.hostApi,
|
|
885
|
-
(newHostApi) => {
|
|
886
|
-
contextValue.value = {
|
|
887
|
-
...contextValue.value,
|
|
888
|
-
hostApi: newHostApi
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
);
|
|
892
|
-
onUnmounted3(() => {
|
|
893
|
-
if (unsubscribe) {
|
|
894
|
-
unsubscribe();
|
|
895
|
-
unsubscribe = null;
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
return () => h2(
|
|
899
|
-
"div",
|
|
900
|
-
{
|
|
901
|
-
class: ["djvlc-provider", props.class].filter(Boolean).join(" "),
|
|
902
|
-
"data-phase": contextValue.value.state.phase
|
|
903
|
-
},
|
|
904
|
-
slots.default?.()
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
// src/plugin.ts
|
|
910
|
-
var DJVLC_CONFIG_KEY = "djvlc-config";
|
|
911
|
-
function createTrackDirective() {
|
|
912
|
-
const observedElements = /* @__PURE__ */ new WeakSet();
|
|
913
|
-
return {
|
|
914
|
-
mounted(el, binding) {
|
|
915
|
-
const { event, data = {}, trigger = "click" } = binding.value;
|
|
916
|
-
if (trigger === "click") {
|
|
917
|
-
el.addEventListener("click", () => {
|
|
918
|
-
dispatchTrackEvent(event, { ...data, element: el.tagName });
|
|
919
|
-
});
|
|
920
|
-
} else if (trigger === "view") {
|
|
921
|
-
if (!observedElements.has(el)) {
|
|
922
|
-
observedElements.add(el);
|
|
923
|
-
const observer = new IntersectionObserver(
|
|
924
|
-
(entries) => {
|
|
925
|
-
entries.forEach((entry) => {
|
|
926
|
-
if (entry.isIntersecting) {
|
|
927
|
-
dispatchTrackEvent(event, { ...data, element: el.tagName });
|
|
928
|
-
observer.unobserve(el);
|
|
929
|
-
}
|
|
930
|
-
});
|
|
931
|
-
},
|
|
932
|
-
{ threshold: 0.5 }
|
|
933
|
-
);
|
|
934
|
-
observer.observe(el);
|
|
935
|
-
}
|
|
936
|
-
} else if (trigger === "mounted") {
|
|
937
|
-
dispatchTrackEvent(event, { ...data, element: el.tagName });
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
function dispatchTrackEvent(event, data) {
|
|
943
|
-
window.dispatchEvent(
|
|
944
|
-
new CustomEvent("djvlc:track", {
|
|
945
|
-
detail: { event, data, timestamp: Date.now() }
|
|
946
|
-
})
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
function createVisibleDirective() {
|
|
950
|
-
return {
|
|
951
|
-
mounted(el, binding) {
|
|
952
|
-
updateVisibility(el, binding.value);
|
|
953
|
-
},
|
|
954
|
-
updated(el, binding) {
|
|
955
|
-
updateVisibility(el, binding.value);
|
|
956
|
-
}
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
function updateVisibility(el, visible) {
|
|
960
|
-
el.style.display = visible ? "" : "none";
|
|
961
|
-
}
|
|
962
|
-
function createLoadingDirective() {
|
|
963
|
-
const loadingOverlays = /* @__PURE__ */ new WeakMap();
|
|
964
|
-
return {
|
|
965
|
-
mounted(el, binding) {
|
|
966
|
-
el.style.position = "relative";
|
|
967
|
-
updateLoading(el, binding.value, loadingOverlays);
|
|
968
|
-
},
|
|
969
|
-
updated(el, binding) {
|
|
970
|
-
updateLoading(el, binding.value, loadingOverlays);
|
|
971
|
-
},
|
|
972
|
-
unmounted(el) {
|
|
973
|
-
const overlay = loadingOverlays.get(el);
|
|
974
|
-
if (overlay) {
|
|
975
|
-
overlay.remove();
|
|
976
|
-
loadingOverlays.delete(el);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
function updateLoading(el, loading, overlays) {
|
|
982
|
-
let overlay = overlays.get(el);
|
|
983
|
-
if (loading) {
|
|
984
|
-
if (!overlay) {
|
|
985
|
-
overlay = document.createElement("div");
|
|
986
|
-
overlay.className = "djvlc-loading-overlay";
|
|
987
|
-
overlay.innerHTML = `
|
|
988
|
-
<div class="djvlc-loading-spinner"></div>
|
|
989
|
-
`;
|
|
990
|
-
overlay.style.cssText = `
|
|
991
|
-
position: absolute;
|
|
992
|
-
inset: 0;
|
|
993
|
-
display: flex;
|
|
994
|
-
align-items: center;
|
|
995
|
-
justify-content: center;
|
|
996
|
-
background: rgba(255, 255, 255, 0.8);
|
|
997
|
-
z-index: 100;
|
|
998
|
-
`;
|
|
999
|
-
el.appendChild(overlay);
|
|
1000
|
-
overlays.set(el, overlay);
|
|
1001
|
-
}
|
|
1002
|
-
overlay.style.display = "flex";
|
|
1003
|
-
} else if (overlay) {
|
|
1004
|
-
overlay.style.display = "none";
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
var DJVPlugin = {
|
|
1008
|
-
install(app, options = {}) {
|
|
1009
|
-
const prefix = options.componentPrefix || "";
|
|
1010
|
-
if (options.registerComponents !== false) {
|
|
1011
|
-
app.component(`${prefix}DJVRenderer`, DJVRenderer);
|
|
1012
|
-
app.component(`${prefix}DJVProvider`, DJVProvider);
|
|
1013
|
-
}
|
|
1014
|
-
if (options.registerDirectives !== false) {
|
|
1015
|
-
app.directive("djv-track", createTrackDirective());
|
|
1016
|
-
app.directive("djv-visible", createVisibleDirective());
|
|
1017
|
-
app.directive("djv-loading", createLoadingDirective());
|
|
1018
|
-
}
|
|
1019
|
-
const globalConfig = {
|
|
1020
|
-
apiBaseUrl: options.apiBaseUrl,
|
|
1021
|
-
cdnBaseUrl: options.cdnBaseUrl,
|
|
1022
|
-
channel: options.channel,
|
|
1023
|
-
debug: options.debug,
|
|
1024
|
-
enableSRI: options.enableSRI,
|
|
1025
|
-
enableMetrics: options.enableMetrics,
|
|
1026
|
-
retryCount: options.retryCount,
|
|
1027
|
-
retryDelay: options.retryDelay,
|
|
1028
|
-
timeout: options.timeout
|
|
1029
|
-
};
|
|
1030
|
-
app.provide(DJVLC_CONFIG_KEY, globalConfig);
|
|
1031
|
-
app.config.globalProperties.$djvlc = {
|
|
1032
|
-
config: globalConfig,
|
|
1033
|
-
track: dispatchTrackEvent
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
export {
|
|
1038
|
-
DJVLC_CONFIG_KEY,
|
|
1039
|
-
DJVPlugin,
|
|
1040
|
-
DJVProvider,
|
|
1041
|
-
DJVRenderer,
|
|
1042
|
-
RuntimeContextKey,
|
|
1043
|
-
createVueRuntime,
|
|
1044
|
-
injectRuntime,
|
|
1045
|
-
provideRuntime,
|
|
1046
|
-
useAction,
|
|
1047
|
-
useComponentState,
|
|
1048
|
-
useDJVRuntime,
|
|
1049
|
-
useData,
|
|
1050
|
-
useDebouncedAction,
|
|
1051
|
-
useGlobalConfig,
|
|
1052
|
-
useHostApi,
|
|
1053
|
-
useLifecycle,
|
|
1054
|
-
usePageInfo,
|
|
1055
|
-
useQuery,
|
|
1056
|
-
useRuntimeEvent,
|
|
1057
|
-
useRuntimeState,
|
|
1058
|
-
useRuntimeStateWritable,
|
|
1059
|
-
useWhen
|
|
1060
|
-
};
|
|
1
|
+
import{defineComponent as e,ref as t,shallowRef as n,onMounted as r,onUnmounted as a,watch as o,h as i,provide as l,computed as s,readonly as c,inject as u}from"vue";import{createRuntime as d}from"@djvlc/runtime-core";function p(e){const r=n(null),a=t(!0),o=t("idle"),i=n(null),l=n(null),u=n(null),p=s(()=>"ready"===o.value),m=s(()=>"error"===o.value||null!==l.value),f=n({initTime:0,loadTime:0,renderTime:0,totalTime:0,initTimestamp:null,readyTimestamp:null});let y=0,h=0,v=0,w=0;const g=async()=>{if(!r.value)throw new Error("Runtime not initialized");v=performance.now();const t=await r.value.load();return i.value=t,u.value=r.value.getHostApi(),e.enableMetrics&&(f.value={...f.value,loadTime:performance.now()-v}),t},b=async()=>{if(!r.value)throw new Error("Runtime not initialized");if(w=performance.now(),await r.value.render(),a.value=!1,e.enableMetrics){const e=performance.now();f.value={...f.value,renderTime:e-w,totalTime:e-y,readyTimestamp:Date.now()}}};return{runtime:r,loading:c(a),phase:c(o),page:i,error:l,hostApi:u,isReady:p,hasError:m,metrics:f,init:async()=>{const t=e.containerRef?.value;if(!t)throw new Error("Container element not found");y=performance.now(),h=y,e.enableMetrics&&(f.value={...f.value,initTimestamp:Date.now()});const n=d({...e,container:t,onError:t=>{l.value=t,e.onError?.(t)},onEvent:t=>{e.onEvent?.(t)}});r.value=n,n.onStateChange(e=>{o.value=e.phase,a.value="ready"!==e.phase&&"error"!==e.phase,e.page&&(i.value=e.page),e.error&&(l.value=e.error)}),await n.init(),u.value=n.getHostApi(),e.enableMetrics&&(f.value={...f.value,initTime:performance.now()-h})},load:g,render:b,destroy:()=>{r.value?.destroy(),r.value=null,u.value=null,i.value=null,l.value=null,o.value="idle",a.value=!0},reload:async()=>{if(!r.value)throw new Error("Runtime not initialized");l.value=null,a.value=!0,await g(),await b()},setVariable:(e,t)=>{r.value?.setVariable(e,t)},setVariables:e=>{r.value&&Object.entries(e).forEach(([e,t])=>{r.value?.setVariable(e,t)})},getVariable:e=>r.value?.getState().variables[e],refreshData:async e=>{await(r.value?.refreshData(e))},executeAction:async(e,t)=>{const n=u.value;if(!n)throw new Error("HostAPI not available");const r=await n.executeAction(e,t||{});if(r.success)return r.data;throw new Error(r.errorMessage||"Action failed")}}}var m=Symbol("DJVRuntime");function f(e){l(m,e)}function y(){const e=u(m);if(!e)throw new Error("useDJVRuntime must be used within a DJVProvider");return e}function h(){const e=y(),t=s(()=>{const t=e.value.state.phase;return"ready"!==t&&"error"!==t}),n=s(()=>"ready"===e.value.state.phase),r=s(()=>"error"===e.value.state.phase||null!==e.value.state.error);return{runtime:s(()=>e.value.runtime),state:s(()=>e.value.state),loading:t,phase:s(()=>e.value.state.phase),error:s(()=>e.value.state.error),page:s(()=>e.value.state.page),isReady:n,hasError:r,reload:async()=>{const t=e.value.runtime;if(!t)throw new Error("Runtime not available");await t.load(),await t.render()}}}function v(){const e=y().value.hostApi;if(!e)throw new Error("HostAPI not available. Make sure runtime is initialized.");return e}function w(e){const t=y();return s(()=>t.value.state.variables[e])}function g(e,t){const n=y();return[s(()=>n.value.state.variables[e]??t),t=>{n.value.runtime?.setVariable(e,t)}]}function b(e,n){const o=y(),i=t(!1),l=t(null),c=t(null);let u=null;const d=s(()=>o.value.state.queries[e]),p=async()=>{i.value=!0,l.value=null;try{await(o.value.runtime?.refreshData(e)),c.value=Date.now()}catch(e){l.value=e}finally{i.value=!1}};return r(()=>{n?.refreshOnMount&&o.value.runtime&&p(),n?.refreshInterval&&n.refreshInterval>0&&(u=setInterval(p,n.refreshInterval)),n?.refreshOnFocus&&window.addEventListener("focus",p)}),a(()=>{u&&clearInterval(u),n?.refreshOnFocus&&window.removeEventListener("focus",p)}),{data:d,loading:i,error:l,refetch:p,lastUpdated:c}}function j(e,r){const a=y(),o=t(!1),i=n(),l=t(null),s=t(0),c=async(t,n)=>{const o=a.value.hostApi;if(!o)throw new Error("HostAPI not available");try{const n=await o.executeAction(e,t);if(n.success)return n.data;throw new Error(n.errorMessage||"Action failed")}catch(e){if(n>0)return await new Promise(e=>setTimeout(e,r?.retryDelay||1e3)),c(t,n-1);throw e}};return{execute:async e=>{o.value=!0,l.value=null,s.value++;try{const t=await c(e,r?.retryCount||0);return i.value=t,r?.onSuccess?.(t),t}catch(e){const t=e;throw l.value=t,r?.onError?.(t),e}finally{o.value=!1}},loading:o,result:i,error:l,reset:()=>{i.value=void 0,l.value=null,s.value=0},executionCount:s}}function T(e,i,l){const s=y(),c=n(),u=t(!1),d=t(null),p=t(!1);let m=null;const f=async t=>{const n=s.value.hostApi;if(!n)throw new Error("HostAPI not available");u.value=!0,d.value=null;try{const r=await n.requestData(e,t||(()=>{if(i)return"object"==typeof i&&"value"in i?i.value:i})());c.value=r,p.value=!0,l?.onSuccess?.(r)}catch(e){const t=e;d.value=t,l?.onError?.(t)}finally{u.value=!1}};return i&&"object"==typeof i&&"value"in i&&!1!==l?.refreshOnParamsChange&&o(i,()=>{p.value&&f()},{deep:!0}),r(()=>{!1!==l?.immediate&&s.value.hostApi&&f(),l?.refreshInterval&&l.refreshInterval>0&&(m=setInterval(f,l.refreshInterval))}),a(()=>{m&&clearInterval(m)}),{data:c,loading:u,error:d,refetch:f,isFetched:p}}function E(e,t){const n=y(),r=n.value.runtime?.on(e,e=>{t(e.data)});a(()=>{r?.()})}function I(){const e=y();return{pageUid:s(()=>e.value.state.page?.pageUid),pageVersionId:s(()=>e.value.state.page?.pageVersionId),schemaVersion:s(()=>e.value.state.page?.pageJson?.schemaVersion),title:s(()=>{const t=e.value.state.page;return t?.title}),config:s(()=>{const t=e.value.state.page;return t?.config}),isLoaded:s(()=>null!==e.value.state.page)}}function k(e){const t=y(),n=s(()=>t.value.state.components.get(e));return{isLoaded:s(()=>"loaded"===n.value?.status),isLoading:s(()=>"loading"===n.value?.status),hasError:s(()=>"failed"===n.value?.status),loadTime:s(()=>n.value?.loadTime),info:n}}function A(e){const n=y(),r=t(!1),a=t(!1);return o(()=>n.value.state.phase,async(t,o)=>{if(e?.onPhaseChange?.(t),!r.value&&"idle"!==t){r.value=!0;try{await(e?.onMounted?.())}catch(t){e?.onError?.(t)}}if(!a.value&&"ready"===t){a.value=!0;try{await(e?.onReady?.())}catch(t){e?.onError?.(t)}}"error"===t&&"error"!==o&&e?.onError?.(n.value.state.error)},{immediate:!0}),{phase:s(()=>n.value.state.phase),hasMounted:c(r),hasReady:c(a)}}function R(e,n,r){const i=t(!1),{once:l=!0,immediate:s=!0}=r||{},u=o(e,async e=>{!e||l&&i.value||(i.value=!0,await n(),l&&u())},{immediate:s});return a(()=>{u()}),{executed:c(i),stop:u}}function D(e,t=300){const{execute:n,loading:r,result:o,error:i}=j(e);let l=null;const s=()=>{l&&(clearTimeout(l),l=null)};return a(()=>{s()}),{execute:e=>{l&&clearTimeout(l),l=setTimeout(()=>{n(e).catch(()=>{})},t)},loading:r,result:o,error:i,cancel:s}}function S(){return u("djvlc-config",{})}var V=e({name:"DJVRenderer",props:{pageUid:{type:String,required:!0},apiBaseUrl:{type:String,required:!0},cdnBaseUrl:{type:String,required:!0},channel:{type:String,default:"prod"},userId:String,deviceId:String,authToken:String,previewToken:String,debug:{type:Boolean,default:!1},enableSRI:{type:Boolean,default:!0},retryCount:{type:Number,default:3},retryDelay:{type:Number,default:1e3},timeout:{type:Number,default:3e4}},emits:["load","error","ready","phase-change"],setup(e,{emit:l,slots:s}){const c=t(null),u=t(!0),d=t(0),m=t("idle"),y=n({runtime:null,state:{phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1},hostApi:null});f(y);let h=null;const v=async()=>{if(!c.value||!u.value)return;const t={pageUid:e.pageUid,apiBaseUrl:e.apiBaseUrl,cdnBaseUrl:e.cdnBaseUrl,channel:e.channel,userId:e.userId,deviceId:e.deviceId,authToken:e.authToken,previewToken:e.previewToken,debug:e.debug,enableSRI:e.enableSRI,containerRef:c,onError:e=>{l("error",e)}};h=p(t);try{await(n=(async()=>{if(await h.init(),!u.value)return;m.value="loading",l("phase-change","loading"),y.value={runtime:h.runtime.value,state:h.runtime.value?.getState()||y.value.state,hostApi:h.hostApi.value};const e=await h.load();u.value&&(l("load",e),y.value={...y.value,state:h.runtime.value?.getState()||y.value.state,hostApi:h.hostApi.value},await h.render(),u.value&&(m.value="ready",l("phase-change","ready"),l("ready"),d.value=0,h.runtime.value?.onStateChange(e=>{u.value&&(y.value={...y.value,state:e},m.value=e.phase,l("phase-change",e.phase))})))})(),r=e.timeout,Promise.race([n,new Promise((e,t)=>setTimeout(()=>t(new Error("加载超时")),r))]))}catch(t){if(!u.value)return;d.value<e.retryCount?(d.value++,e.debug,setTimeout(()=>{u.value&&v()},e.retryDelay)):(m.value="error",l("phase-change","error"),l("error",t))}var n,r},w=()=>{d.value=0,v()};return r(()=>{u.value=!0,v()}),a(()=>{u.value=!1,h?.destroy()}),o(()=>e.pageUid,()=>{h?.destroy(),d.value=0,v()}),()=>{const t=h?.loading.value??!1,n=h?.error.value,r=h?.page.value;return i("div",{ref:c,class:"djvlc-renderer","data-phase":m.value,"data-page-uid":e.pageUid},[t&&(s.loading?.()||i("div",{class:"djvlc-loading"},[i("div",{class:"djvlc-loading-spinner"}),i("span",{},"加载中..."),d.value>0&&i("span",{class:"djvlc-loading-retry"},`重试 ${d.value}/${e.retryCount}`)])),n&&(s.error?.({error:n,retry:w})||(a=n,i("div",{class:"djvlc-error"},[i("div",{class:"djvlc-error-icon"},"⚠️"),i("span",{class:"djvlc-error-message"},`加载失败:${a.message}`),i("button",{class:"djvlc-error-retry-btn",onClick:w,type:"button"},"重试")]))),!t&&!n&&!r&&"ready"===m.value&&(s.empty?.()||i("div",{class:"djvlc-empty"},[i("span",{},"暂无内容")])),s.default?.()]);var a}}}),x={phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1},B=e({name:"DJVProvider",props:{runtime:{type:Object,default:null},hostApi:{type:Object,default:null},class:{type:String,default:""},debug:{type:Boolean,default:!1}},emits:["state-change","phase-change","error"],setup(e,{slots:t,emit:r}){let l=null;const s=n({runtime:e.runtime,state:e.runtime?.getState()||x,hostApi:e.hostApi});f(s);const c=t=>{l&&(l(),l=null),t&&(l=t.onStateChange(t=>{const n=s.value.state.phase;s.value={...s.value,state:t},r("state-change",t),t.phase!==n&&(r("phase-change",t.phase),e.debug),t.error&&r("error",t.error)}))};return c(e.runtime),o(()=>e.runtime,(t,n)=>{t!==n&&(s.value={runtime:t,state:t?.getState()||x,hostApi:e.hostApi},c(t))}),o(()=>e.hostApi,e=>{s.value={...s.value,hostApi:e}}),a(()=>{l&&(l(),l=null)}),()=>i("div",{class:["djvlc-provider",e.class].filter(Boolean).join(" "),"data-phase":s.value.state.phase},t.default?.())}}),P="djvlc-config";function U(e,t){window.dispatchEvent(new CustomEvent("djvlc:track",{detail:{event:e,data:t,timestamp:Date.now()}}))}function J(e,t){e.style.display=t?"":"none"}function C(e,t,n){let r=n.get(e);t?(r||(r=document.createElement("div"),r.className="djvlc-loading-overlay",r.innerHTML='\n <div class="djvlc-loading-spinner"></div>\n ',r.style.cssText="\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.8);\n z-index: 100;\n ",e.appendChild(r),n.set(e,r)),r.style.display="flex"):r&&(r.style.display="none")}var M={install(e,t={}){const n=t.componentPrefix||"";!1!==t.registerComponents&&(e.component(`${n}DJVRenderer`,V),e.component(`${n}DJVProvider`,B)),!1!==t.registerDirectives&&(e.directive("djv-track",function(){const e=new WeakSet;return{mounted(t,n){const{event:r,data:a={},trigger:o="click"}=n.value;if("click"===o)t.addEventListener("click",()=>{U(r,{...a,element:t.tagName})});else if("view"===o){if(!e.has(t)){e.add(t);const n=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&(U(r,{...a,element:t.tagName}),n.unobserve(t))})},{threshold:.5});n.observe(t)}}else"mounted"===o&&U(r,{...a,element:t.tagName})}}}()),e.directive("djv-visible",{mounted(e,t){J(e,t.value)},updated(e,t){J(e,t.value)}}),e.directive("djv-loading",function(){const e=new WeakMap;return{mounted(t,n){t.style.position="relative",C(t,n.value,e)},updated(t,n){C(t,n.value,e)},unmounted(t){const n=e.get(t);n&&(n.remove(),e.delete(t))}}}()));const r={apiBaseUrl:t.apiBaseUrl,cdnBaseUrl:t.cdnBaseUrl,channel:t.channel,debug:t.debug,enableSRI:t.enableSRI,enableMetrics:t.enableMetrics,retryCount:t.retryCount,retryDelay:t.retryDelay,timeout:t.timeout};e.provide(P,r),e.config.globalProperties.$djvlc={config:r,track:U}}};export{P as DJVLC_CONFIG_KEY,M as DJVPlugin,B as DJVProvider,V as DJVRenderer,m as RuntimeContextKey,p as createVueRuntime,y as injectRuntime,f as provideRuntime,j as useAction,k as useComponentState,h as useDJVRuntime,T as useData,D as useDebouncedAction,S as useGlobalConfig,v as useHostApi,A as useLifecycle,I as usePageInfo,b as useQuery,E as useRuntimeEvent,w as useRuntimeState,g as useRuntimeStateWritable,R as useWhen};
|