@hlw-uni/mp-vue 2.1.52 → 2.1.55

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 (81) hide show
  1. package/dist/app.d.ts +2 -20
  2. package/dist/composables/ad/index.d.ts +11 -73
  3. package/dist/composables/device/index.d.ts +5 -70
  4. package/dist/composables/index.d.ts +6 -14
  5. package/dist/composables/navigator/index.d.ts +15 -39
  6. package/dist/composables/request/client.d.ts +21 -0
  7. package/dist/composables/request/index.d.ts +6 -0
  8. package/dist/composables/request/service.d.ts +30 -0
  9. package/dist/composables/{http → request}/types.d.ts +0 -3
  10. package/dist/composables/share/index.d.ts +7 -66
  11. package/dist/composables/utils/index.d.ts +25 -32
  12. package/dist/hlw.d.ts +2 -4
  13. package/dist/index.d.ts +2 -3
  14. package/dist/index.js +614 -1253
  15. package/dist/index.mjs +613 -1252
  16. package/package.json +2 -1
  17. package/src/app.ts +3 -143
  18. package/src/components/hlw-ad/index.vue +6 -5
  19. package/src/components/hlw-page/index.vue +1 -1
  20. package/src/composables/ad/README.md +58 -0
  21. package/src/composables/ad/index.ts +117 -376
  22. package/src/composables/device/README.md +50 -0
  23. package/src/composables/device/index.ts +110 -83
  24. package/src/composables/index.ts +10 -39
  25. package/src/composables/msg/README.md +79 -0
  26. package/src/composables/navigator/README.md +71 -0
  27. package/src/composables/navigator/index.ts +77 -77
  28. package/src/composables/refs/README.md +40 -0
  29. package/src/composables/request/README.md +124 -0
  30. package/src/composables/{http → request}/adapters/oss.ts +1 -0
  31. package/src/composables/request/client.ts +204 -0
  32. package/src/composables/request/index.ts +15 -0
  33. package/src/composables/request/service.ts +54 -0
  34. package/src/composables/{http → request}/types.ts +0 -4
  35. package/src/composables/share/README.md +53 -0
  36. package/src/composables/share/index.ts +64 -168
  37. package/src/composables/theme/README.md +131 -0
  38. package/src/composables/theme/index.ts +4 -2
  39. package/src/composables/theme/palette.ts +22 -4
  40. package/src/composables/utils/README.md +81 -0
  41. package/src/composables/utils/index.ts +131 -95
  42. package/src/hlw.ts +6 -14
  43. package/src/index.ts +2 -3
  44. package/dist/composables/_internal/unwrap.d.ts +0 -15
  45. package/dist/composables/algo/index.d.ts +0 -7
  46. package/dist/composables/algo/uuid.d.ts +0 -17
  47. package/dist/composables/color/index.d.ts +0 -8
  48. package/dist/composables/contact/index.d.ts +0 -32
  49. package/dist/composables/format/index.d.ts +0 -9
  50. package/dist/composables/http/client.d.ts +0 -66
  51. package/dist/composables/http/index.d.ts +0 -8
  52. package/dist/composables/loading/index.d.ts +0 -7
  53. package/dist/composables/page-meta/index.d.ts +0 -18
  54. package/dist/composables/storage/index.d.ts +0 -16
  55. package/dist/composables/validate/index.d.ts +0 -12
  56. package/dist/error.d.ts +0 -1
  57. package/src/composables/_internal/unwrap.ts +0 -19
  58. package/src/composables/algo/index.ts +0 -7
  59. package/src/composables/algo/uuid.ts +0 -27
  60. package/src/composables/color/index.ts +0 -44
  61. package/src/composables/contact/index.ts +0 -92
  62. package/src/composables/format/index.ts +0 -48
  63. package/src/composables/http/client.ts +0 -237
  64. package/src/composables/http/index.ts +0 -8
  65. package/src/composables/http/useRequest.ts +0 -107
  66. package/src/composables/loading/index.ts +0 -23
  67. package/src/composables/page-meta/index.ts +0 -49
  68. package/src/composables/storage/index.ts +0 -76
  69. package/src/composables/validate/index.ts +0 -58
  70. package/src/error.ts +0 -5
  71. /package/dist/composables/{http → request}/adapters/alist.d.ts +0 -0
  72. /package/dist/composables/{http → request}/adapters/base.d.ts +0 -0
  73. /package/dist/composables/{http → request}/adapters/cos.d.ts +0 -0
  74. /package/dist/composables/{http → request}/adapters/index.d.ts +0 -0
  75. /package/dist/composables/{http → request}/adapters/oss.d.ts +0 -0
  76. /package/dist/composables/{http → request}/adapters/qiniu.d.ts +0 -0
  77. /package/src/composables/{http → request}/adapters/alist.ts +0 -0
  78. /package/src/composables/{http → request}/adapters/base.ts +0 -0
  79. /package/src/composables/{http → request}/adapters/cos.ts +0 -0
  80. /package/src/composables/{http → request}/adapters/index.ts +0 -0
  81. /package/src/composables/{http → request}/adapters/qiniu.ts +0 -0
@@ -1,412 +1,153 @@
1
1
  /**
2
- * useAd —— 完整的广告业务封装
3
- *
4
- * 内置了:
5
- * - 6 个 unit_id 配置(store 缓存,调 getConfig 拉一次)
6
- * - 激励视频:广告加载 loading → onShown 自动关闭 → 中途关闭挽留 modal → 看完调业务方的 claim 发奖
7
- * - 插屏:一行调用
8
- * - 底层 SDK 适配(onLoad / offClose / show 兜底 / 实例缓存)
9
- *
10
- * 业务侧使用方式:
11
- *
12
- * 1. 在 App.vue 注入「全局」回调(拿配置 + 校验登录):
13
- *
14
- * import { setConfigAd } from "@hlw-uni/mp-vue";
15
- * import { getAdConfig } from "@/api/ad";
16
- * import { useUserStore } from "@/store";
17
- *
18
- * setConfigAd({
19
- * getConfig: async () => {
20
- * const res = await getAdConfig();
21
- * return res.code === 1 && res.data ? res.data : null;
22
- * },
23
- * isAuth: () => !!useUserStore().token,
24
- * });
25
- *
26
- * 2. 任意页面拉配置 / 用 unit_id 渲染:
27
- *
28
- * const { config, loadConfig, showPopup } = useAd();
29
- * await loadConfig();
30
- * <hlw-ad type="banner" :unit-id="config.banner" />
31
- *
32
- * 3. 看广告领奖励(业务方按场景传不同的 claim 接口):
33
- *
34
- * const ok = await showReward(async () => {
35
- * const res = await claimAdReward();
36
- * return res.code === 1
37
- * ? { ok: true, reward: res.data?.reward }
38
- * : { ok: false, msg: res.info };
39
- * });
40
- *
41
- * // 不传 claim 就是纯展示,看完即返回 true
42
- * const ok = await showReward();
2
+ * 小程序广告工具。
43
3
  */
44
4
 
45
- import { defineStore, storeToRefs } from "pinia";
46
- import { ref, computed } from "vue";
47
- import { useMsg, type HlwMsg } from "../msg";
48
- import { unwrapPayload, type AdapterPayload } from "../_internal/unwrap";
5
+ declare const wx: any;
49
6
 
50
- /** showPopup 默认延迟(ms):避免一进页面就弹打扰用户 */
51
- const DEFAULT_POPUP_DELAY_MS = 3000;
52
-
53
- /** 6 种广告类型 */
54
- export type AdType = "banner" | "grid" | "custom" | "video" | "reward" | "popup";
55
-
56
- /** 广告配置 —— 字段名跟后端表列名对齐(plugin_qz_mp.{type}_unit_id) */
57
- export interface AdConfig {
58
- banner_unit_id: string;
59
- grid_unit_id: string;
60
- custom_unit_id: string;
61
- video_unit_id: string;
62
- reward_unit_id: string;
63
- popup_unit_id: string;
64
- /** VIP 屏蔽展示型广告(reward 除外):0=否,1=屏蔽 banner/grid/custom/video/popup */
65
- vip_no_ad?: 0 | 1;
66
- }
67
-
68
- /** 广告错误对象(onError 回调参数) */
69
- export interface AdError {
70
- errCode: number;
71
- errMsg: string;
72
- }
73
-
74
- /**
75
- * 业务回调注入接口 —— setConfigAd 时由项目提供。
76
- *
77
- * getConfig 支持两种返回:
78
- * - 已解包:直接返回 AdConfig 或 null
79
- * - ThinkAdmin envelope:返回 { code, data } 对象,库自动按 code===1 解包
80
- *
81
- * 业务方可以直接传 envelope-returning 的接口函数引用:
82
- * setConfigAd({ getConfig: getAdConfig })
83
- */
84
- export interface AdAdapter {
85
- getConfig: () => Promise<AdapterPayload<AdConfig>>;
86
- /** 是否已登录;不传 = 不校验(showReward 调用前会问一次) */
87
- isAuth?: () => boolean;
88
- /** 是否 VIP;配合 config.vip_no_ad=1 时屏蔽展示型广告(reward 不受影响) */
89
- isVip?: () => boolean;
90
- /**
91
- * 用户级强制屏蔽:返回 true 直接屏蔽展示型广告(reward 不受影响),
92
- * 优先级高于 mp.vip_no_ad + isVip 的联合判断;不传 = 不强制。
93
- */
94
- userNoAd?: () => boolean;
7
+ export interface AdRes {
8
+ ok: boolean;
9
+ isEnded?: boolean;
10
+ err?: unknown;
95
11
  }
96
12
 
97
- /** 激励视频关闭回调返回 */
98
- export interface AdCloseResult {
99
- /** 用户是否完整观看 */
100
- isEnded: boolean;
101
- }
102
-
103
- const EMPTY: AdConfig = {
104
- banner_unit_id: "",
105
- grid_unit_id: "",
106
- custom_unit_id: "",
107
- video_unit_id: "",
108
- reward_unit_id: "",
109
- popup_unit_id: "",
110
- vip_no_ad: 0,
111
- };
13
+ type AdDone = (res: AdRes) => void;
112
14
 
113
- let adapter: AdAdapter | null = null;
15
+ let popupAd: any = null;
16
+ let popupDone: ((ok: boolean) => void) | undefined;
17
+ let popupClose: (() => void) | null = null;
18
+ let popupError: ((err: unknown) => void) | null = null;
114
19
 
115
- /** 进行中的 loadConfig promise;并发调用时复用,避免冷启动多发请求 */
116
- let pending: Promise<void> | null = null;
20
+ let rewardAd: any = null;
21
+ let rewardDone: AdDone | undefined;
22
+ let rewardPromise: Promise<AdRes> | null = null;
23
+ let rewardResolve: ((res: AdRes) => void) | null = null;
24
+ let rewardClose: ((res: { isEnded?: boolean }) => void) | null = null;
25
+ let rewardError: ((err: unknown) => void) | null = null;
117
26
 
118
- /**
119
- * 注入业务回调,应用启动时调用一次。
120
- * 不调用也不会崩,但 loadConfig / showReward 会无效。
121
- */
122
- export function setConfigAd(a: AdAdapter): void {
123
- adapter = a;
27
+ function api() {
28
+ return typeof wx === "undefined" ? null : wx;
124
29
  }
125
30
 
126
- const useAdStore = defineStore("hlw_ad", () => {
127
- const config = ref<AdConfig>({ ...EMPTY });
128
- const loaded = ref(false);
129
- return { config, loaded };
130
- });
131
-
132
- /* ============================================================
133
- * 内部状态:消息单例 + 广告实例缓存
134
- * ============================================================ */
135
-
136
- let _msg: HlwMsg | null = null;
137
- const msg = (): HlwMsg => (_msg ??= useMsg());
138
-
139
- interface RewardedEntry {
140
- ad: any;
141
- /** 同一 unit_id 是否在 load 中,避免并发 */
142
- loading: boolean;
31
+ function finish(res: AdRes) {
32
+ rewardDone?.(res);
33
+ rewardResolve?.(res);
34
+ rewardResolve = null;
35
+ rewardPromise = null;
143
36
  }
144
- const rewardedCache = new Map<string, RewardedEntry>();
145
- const interstitialCache = new Map<string, any>();
146
-
147
- /* ============================================================
148
- * 公共 composable
149
- * ============================================================ */
150
37
 
151
- export function useAd() {
152
- const store = useAdStore();
153
- const { loaded } = storeToRefs(store);
154
-
155
- /**
156
- * 真实生效的广告配置;以下任一命中即屏蔽展示型 unit_id(reward 保留):
157
- * 1. adapter.userNoAd() === true —— 用户级强制屏蔽,优先级最高
158
- * 2. raw.vip_no_ad === 1 && adapter.isVip() —— 小程序级 VIP 隐藏开关
159
- */
160
- const config = computed<AdConfig>(() => {
161
- const raw = store.config;
162
- const userForce = adapter?.userNoAd?.() === true;
163
- const vipHide = raw.vip_no_ad === 1 && adapter?.isVip?.() === true;
164
- if (userForce || vipHide) {
165
- return { ...EMPTY, reward_unit_id: raw.reward_unit_id, vip_no_ad: 1 };
166
- }
167
- return raw;
168
- });
38
+ function clearReward() {
39
+ if (!rewardAd) return;
40
+ if (rewardClose) rewardAd.offClose?.(rewardClose);
41
+ if (rewardError) rewardAd.offError?.(rewardError);
42
+ rewardClose = null;
43
+ rewardError = null;
44
+ }
169
45
 
170
- /**
171
- * 拉取广告配置(小程序冷启动后调一次即可)。
172
- * 并发调用复用同一次请求;`force=true` 跳过 loaded/pending 短路重新拉。
173
- */
174
- async function loadConfig(force = false): Promise<void> {
175
- if (loaded.value && !force) return;
176
- if (pending && !force) return pending;
177
- if (!adapter?.getConfig) {
178
- console.warn("[useAd] adapter.getConfig 未注入;先调用 setConfigAd()");
179
- return;
180
- }
181
- const fn = adapter.getConfig;
182
- const flight = (async () => {
183
- try {
184
- const cfg = unwrapPayload(await fn());
185
- if (cfg) {
186
- store.config = cfg;
187
- store.loaded = true;
188
- }
189
- } catch (e) {
190
- console.warn("[useAd] load config failed", e);
191
- } finally {
192
- if (pending === flight) pending = null;
193
- }
194
- })();
195
- pending = flight;
196
- return flight;
46
+ export function useHlwAd() {
47
+ function clearPopup() {
48
+ if (!popupAd) return;
49
+ if (popupClose) popupAd.offClose?.(popupClose);
50
+ if (popupError) popupAd.offError?.(popupError);
51
+ popupClose = null;
52
+ popupError = null;
197
53
  }
198
54
 
199
- /** 取指定类型的 unit_id(hlw-ad 组件 / 业务直接调时用),自动应用 VIP 屏蔽 */
200
- function getUnitId(type: AdType): string {
201
- return (config.value[`${type}_unit_id` as keyof AdConfig] as string) || "";
55
+ function setAdPopup(adId: string, done?: (ok: boolean) => void): boolean {
56
+ popupDone = done;
57
+ const wxApi = api();
58
+ if (!adId || !wxApi?.createInterstitialAd) return false;
59
+
60
+ clearPopup();
61
+ popupAd = wxApi.createInterstitialAd({ adUnitId: adId });
62
+ popupAd.onLoad?.(() => {});
63
+ popupError = (err: unknown) => {
64
+ console.error("插屏广告加载失败", err);
65
+ popupDone?.(false);
66
+ };
67
+ popupClose = () => {
68
+ popupDone?.(true);
69
+ };
70
+ popupAd.onError?.(popupError);
71
+ popupAd.onClose?.(popupClose);
72
+ return true;
202
73
  }
203
74
 
204
- /**
205
- * 显示激励视频 —— 播一次、关闭后调 onClose(带 isEnded),其它都交给业务。
206
- *
207
- * showReward(({ isEnded }) => {
208
- * if (!isEnded) confirm().then(goon => goon && tapReward());
209
- * else claimAdReward().then(...);
210
- * });
211
- *
212
- * @param onClose 关闭回调;不传则只播。retry / toast / 发奖全在业务侧自己写。
213
- */
214
- async function showReward(onClose?: (res: AdCloseResult) => void | Promise<void>): Promise<void> {
215
- const unitId = getUnitId("reward");
216
- if (!unitId) {
217
- msg().toast("激励广告未配置");
218
- return;
219
- }
220
- if (adapter?.isAuth && !adapter.isAuth()) {
221
- msg().toast("请先登录");
222
- return;
223
- }
224
- const closeRes = await playRewardedOnce(unitId);
225
- if (onClose) await onClose(closeRes);
226
- }
75
+ function showAdPopup(delay = 3000): Promise<boolean> {
76
+ return new Promise((resolve) => {
77
+ if (!popupAd) {
78
+ resolve(false);
79
+ return;
80
+ }
81
+
82
+ const done = popupDone;
83
+ popupDone = (ok: boolean) => {
84
+ done?.(ok);
85
+ resolve(ok);
86
+ };
227
87
 
228
- /**
229
- * 显示插屏广告
230
- * @returns true=展示成功;false=配置缺失 / show 失败(如近期已展示过)
231
- */
232
- /**
233
- * 显示插屏广告,默认延迟 3000ms 避免一进页面就弹打扰;传 0 立即弹。
234
- * @returns true=展示成功;false=配置缺失 / show 失败(如近期已展示过)
235
- *
236
- * 实现注意:用 setTimeout + .then() 串接而非 async/await,保持 WX 插屏
237
- * ad.show() 的调用栈跟 setTimeout 回调一致 —— 小程序引擎对插屏的合法
238
- * 触发时机敏感,async/await 多插的 microtask 会被判为"非合法触发"导致
239
- * 静默不弹。
240
- */
241
- function showPopup(delayMs: number = DEFAULT_POPUP_DELAY_MS): Promise<boolean> {
242
- return new Promise<boolean>((resolve) => {
243
88
  setTimeout(() => {
244
- loadConfig().then(() => {
245
- const unitId = getUnitId("popup");
246
- if (!unitId) {
247
- resolve(false);
248
- return;
249
- }
250
- showInterstitialAd(unitId).then(resolve);
89
+ popupAd.show().catch((err: unknown) => {
90
+ console.error("插屏广告显示失败", err);
91
+ popupDone?.(false);
251
92
  });
252
- }, delayMs);
93
+ }, Math.max(0, delay));
253
94
  });
254
95
  }
255
96
 
256
- return {
257
- // 状态
258
- config,
259
- loaded,
260
- // 方法
261
- loadConfig,
262
- getUnitId,
263
- showReward,
264
- showPopup,
265
- confirm: confirmModal,
266
- };
267
- }
268
-
269
- /* ============================================================
270
- * 内部 helper:业务流程层
271
- * ============================================================ */
272
-
273
- /** 播放一次激励视频,期间显示加载提示,广告 UI 弹出后自动 hide */
274
- async function playRewardedOnce(unitId: string): Promise<AdCloseResult> {
275
- msg().showLoading("广告加载中");
276
- let hidden = false;
277
- const hide = () => {
278
- if (hidden) return;
279
- hidden = true;
280
- msg().hideLoading();
281
- };
282
- try {
283
- const isEnded = await showRewardedAd(unitId, { onShown: hide });
284
- return { isEnded };
285
- } finally {
286
- // 兜底:如果 onShown 没触发(show 直接 reject 等),最终也得关 loading
287
- hide();
288
- }
289
- }
290
97
 
291
- /**
292
- * 激励视频中途关闭挽留弹窗 —— 通过 useAd().confirm 暴露给业务方:
293
- *
294
- * const { showReward, confirm } = useAd();
295
- * showReward(async ({ isEnded }) => {
296
- * if (!isEnded) return { ok: false, retry: await confirm() };
297
- * const r = await claimAdReward();
298
- * return r.code === 1 ? { ok: true, reward: r.data?.reward } : { ok: false, msg: r.info };
299
- * });
300
- *
301
- * @returns true=用户选「继续观看」 / false=放弃
302
- */
303
- function confirmModal(): Promise<boolean> {
304
- return new Promise((resolve) => {
305
- uni.showModal({
306
- title: "提示",
307
- content: "看完广告才可以领取奖励哦,要继续观看吗?",
308
- confirmText: "继续观看",
309
- cancelText: "放弃",
310
- success: (r) => resolve(!!r.confirm),
311
- fail: () => resolve(false),
98
+ function setAdReward(adId: string, done?: AdDone): Promise<AdRes> {
99
+ rewardDone = done;
100
+ rewardPromise = new Promise((resolve) => {
101
+ rewardResolve = resolve;
312
102
  });
313
- });
314
- }
315
103
 
316
- /* ============================================================
317
- * 内部 helper:底层 SDK 适配
318
- * ============================================================ */
319
-
320
- /**
321
- * 底层激励视频包装:onClose / onError 解绑 + load + show 兜底
322
- *
323
- * @param hooks.onShown 广告 UI 已渲染时回调(业务用来关 loading)
324
- */
325
- function showRewardedAd(unitId: string, hooks?: { onShown?: () => void }): Promise<boolean> {
326
- return new Promise((resolve) => {
327
- let entry = rewardedCache.get(unitId);
328
- if (!entry) {
329
- const ad: any = uni.createRewardedVideoAd({ adUnitId: unitId });
330
- if (!ad) { resolve(false); return; }
331
- ad.onError?.((err: AdError) => {
332
- console.warn(`[useAd] reward onError (${unitId})`, err);
333
- });
334
- entry = { ad, loading: false };
335
- rewardedCache.set(unitId, entry);
104
+ const wxApi = api();
105
+ if (!adId || !wxApi?.createRewardedVideoAd) {
106
+ finish({ ok: false });
107
+ return rewardPromise;
336
108
  }
337
- const { ad } = entry;
338
109
 
339
- let closeHandler: ((res: { isEnded: boolean }) => void) | null = null;
340
- let errorHandler: ((err: AdError) => void) | null = null;
341
- const cleanup = () => {
342
- if (closeHandler) { ad.offClose?.(closeHandler); closeHandler = null; }
343
- if (errorHandler) { ad.offError?.(errorHandler); errorHandler = null; }
344
- };
345
- closeHandler = (res) => {
346
- cleanup();
347
- resolve(!!(res && res.isEnded));
110
+ clearReward();
111
+ rewardAd = wxApi.createRewardedVideoAd({ adUnitId: adId });
112
+ rewardAd.onLoad?.(() => {});
113
+ rewardClose = (res: { isEnded?: boolean }) => {
114
+ finish({ ok: !!res?.isEnded, isEnded: !!res?.isEnded });
348
115
  };
349
- errorHandler = (err) => {
350
- console.warn(`[useAd] reward show error (${unitId})`, err);
351
- cleanup();
352
- resolve(false);
116
+ rewardError = (err: unknown) => {
117
+ console.error("激励视频广告加载失败", err);
118
+ finish({ ok: false, err });
353
119
  };
354
- ad.onClose(closeHandler);
355
- ad.onError(errorHandler);
356
-
357
- const doShow = () =>
358
- ad.show()
359
- .then(() => {
360
- try { hooks?.onShown?.(); } catch (e) { console.warn("[useAd] onShown error", e); }
361
- })
362
- .catch(() => {
363
- cleanup();
364
- resolve(false);
365
- });
120
+ rewardAd.onClose?.(rewardClose);
121
+ rewardAd.onError?.(rewardError);
122
+ return rewardPromise;
123
+ }
366
124
 
367
- if (entry.loading) {
368
- Promise.resolve().then(doShow);
369
- } else {
370
- entry.loading = true;
371
- ad.load()
372
- .then(doShow)
373
- .catch(doShow)
374
- .finally(() => { if (entry) entry.loading = false; });
125
+ function showAdReward(): Promise<AdRes> {
126
+ if (!rewardAd) {
127
+ return Promise.resolve({ ok: false });
375
128
  }
376
- });
377
- }
378
129
 
379
- /** 底层插屏广告包装:实例按 unitId 缓存复用,show 失败 console.warn 不重试(频控/网络等失败重试也救不了) */
380
- function showInterstitialAd(unitId: string): Promise<boolean> {
381
- return new Promise((resolve) => {
382
- let ad: any = interstitialCache.get(unitId);
383
- if (!ad) {
384
- // 老基础库可能没有这个 API(对应官方 if (wx.createInterstitialAd)
385
- if (typeof uni.createInterstitialAd !== "function") {
386
- console.warn("[useAd] 当前基础库不支持插屏广告");
387
- resolve(false);
388
- return;
389
- }
390
- ad = uni.createInterstitialAd({ adUnitId: unitId });
391
- if (!ad) { resolve(false); return; }
392
- ad.onError?.((err: AdError) => {
393
- console.warn(`[useAd] popup onError (${unitId})`, err);
394
- });
395
- interstitialCache.set(unitId, ad);
396
- }
397
- ad.show()
398
- .then(() => resolve(true))
399
- .catch((err: any) => {
400
- console.warn(`[useAd] popup show error (${unitId})`, err);
401
- resolve(false);
402
- });
403
- });
404
- }
130
+ const current = rewardPromise || new Promise<AdRes>((resolve) => {
131
+ rewardResolve = resolve;
132
+ });
133
+ rewardPromise = current;
134
+
135
+ rewardAd.show().catch(() => {
136
+ rewardAd.load()
137
+ .then(() => rewardAd.show())
138
+ .catch((err: unknown) => {
139
+ console.error("激励视频广告显示失败", err);
140
+ finish({ ok: false, err });
141
+ });
142
+ });
405
143
 
406
- /** 销毁全部广告实例并清空缓存(业务一般不用,hot reload 时调) */
407
- export function destroyAds(): void {
408
- rewardedCache.forEach((e) => e.ad?.destroy?.());
409
- rewardedCache.clear();
410
- interstitialCache.forEach((ad) => ad?.destroy?.());
411
- interstitialCache.clear();
144
+ return current;
145
+ }
146
+
147
+ return {
148
+ setAdPopup,
149
+ showAdPopup,
150
+ setAdReward,
151
+ showAdReward,
152
+ };
412
153
  }
@@ -0,0 +1,50 @@
1
+ # device 调用文档
2
+
3
+ `useDevice` 采集并缓存设备、窗口、宿主和小程序信息,同时提供常用设备字段组成的 query 字符串。
4
+
5
+ ## 引入
6
+
7
+ ```ts
8
+ import { useDevice, clearDeviceCache } from "@hlw-uni/mp-vue";
9
+ ```
10
+
11
+ ## 基础用法
12
+
13
+ ```ts
14
+ const { info, query } = useDevice();
15
+
16
+ console.log(info.appid);
17
+ console.log(info.device_model);
18
+ console.log(query);
19
+ ```
20
+
21
+ `useDevice` 内部会优先读取 `uni.getDeviceInfo`、`uni.getWindowInfo`、`uni.getAppBaseInfo`,旧环境会回退到 `uni.getSystemInfoSync`。
22
+
23
+ ## 清除缓存
24
+
25
+ ```ts
26
+ clearDeviceCache();
27
+ ```
28
+
29
+ 切换账号、切换宿主环境或需要重新采集设备信息时可以手动清理缓存。
30
+
31
+ ## 返回值
32
+
33
+ | 字段 | 说明 |
34
+ | --- | --- |
35
+ | `info` | 完整 `DeviceInfo` |
36
+ | `query` | 常用设备字段组成的 URL query 字符串 |
37
+
38
+ ## 常用字段
39
+
40
+ | 字段 | 说明 |
41
+ | --- | --- |
42
+ | `appid` | 小程序 appId |
43
+ | `device_brand` | 设备品牌 |
44
+ | `device_model` | 设备型号 |
45
+ | `device_id` | 设备 ID |
46
+ | `platform` | 平台类型 |
47
+ | `system` | 系统版本 |
48
+ | `sdk_version` | 基础库版本 |
49
+ | `screen_width` | 屏幕宽度 |
50
+ | `screen_height` | 屏幕高度 |