@hlw-uni/mp-vue 2.0.4 → 2.0.7

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.
@@ -37,24 +37,6 @@ export interface AdCloseResult {
37
37
  /** 用户是否完整观看 */
38
38
  isEnded: boolean;
39
39
  }
40
- /** showReward 的 claim 回调返回契约 */
41
- export interface AdClaimResult {
42
- /** 本次结果:true=成功 / false=失败 */
43
- ok: boolean;
44
- /** 奖励数(用于 toast 显示 +N 积分;无则不 toast) */
45
- reward?: number;
46
- /** 失败提示语;不传不弹 toast */
47
- msg?: string;
48
- /** 仅 isEnded=false 时常用:true 让 mp-core 重新 show 一次(业务方挽留确认后用) */
49
- retry?: boolean;
50
- }
51
- /**
52
- * showReward 的 claim 回调签名 —— 业务方按场景实现(领积分、解锁、抽奖等)。
53
- * 每次广告关闭后被调用一次(无论是否完整看完),业务方根据 closeRes.isEnded 决定怎么走:
54
- * - isEnded=true → 调发奖接口 → return { ok: true, reward: N }
55
- * - isEnded=false → 业务自己决定挽留:return { ok: false, retry: true } 让重新 show
56
- */
57
- export type AdClaimFn = (closeRes: AdCloseResult) => Promise<AdClaimResult>;
58
40
  /**
59
41
  * 注入业务回调,应用启动时调用一次。
60
42
  * 不调用也不会崩,但 loadConfig / showReward 会无效。
@@ -79,23 +61,23 @@ export declare function useAd(): {
79
61
  loaded: Ref<boolean, boolean>;
80
62
  loadConfig: (force?: boolean) => Promise<void>;
81
63
  getUnitId: (type: AdType) => string;
82
- showReward: (claim?: AdClaimFn) => Promise<boolean>;
64
+ showReward: (onClose?: ((res: AdCloseResult) => void | Promise<void>) | undefined) => Promise<void>;
83
65
  showPopup: () => Promise<boolean>;
66
+ confirm: typeof confirmModal;
84
67
  };
85
68
  /**
86
- * 激励视频中途关闭挽留弹窗 —— 业务方在 claim 回调里也可以复用:
69
+ * 激励视频中途关闭挽留弹窗 —— 通过 useAd().confirm 暴露给业务方:
87
70
  *
88
- * showReward(async (closeRes) => {
89
- * if (!closeRes.isEnded) {
90
- * const goon = await confirmReward();
91
- * return { ok: false, retry: goon };
92
- * }
71
+ * const { showReward, confirm } = useAd();
72
+ * showReward(async ({ isEnded }) => {
73
+ * if (!isEnded) return { ok: false, retry: await confirm() };
93
74
  * const r = await claimAdReward();
94
75
  * return r.code === 1 ? { ok: true, reward: r.data?.reward } : { ok: false, msg: r.info };
95
76
  * });
96
77
  *
97
78
  * @returns true=用户选「继续观看」 / false=放弃
98
79
  */
99
- export declare function confirmReward(): Promise<boolean>;
80
+ declare function confirmModal(): Promise<boolean>;
100
81
  /** 销毁全部广告实例并清空缓存(业务一般不用,hot reload 时调) */
101
82
  export declare function destroyAds(): void;
83
+ export {};
@@ -10,7 +10,7 @@ export { usePageMeta } from './page-meta';
10
10
  export { useStorage, type StorageInstance } from './storage';
11
11
  export { useValidate } from './validate';
12
12
  export { useFormat } from './format';
13
- export { useAd, setConfigAd, destroyAds, confirmReward, type AdType, type AdConfig, type AdError, type AdAdapter, type AdCloseResult, type AdClaimResult, type AdClaimFn, } from './ad';
13
+ export { useAd, setConfigAd, destroyAds, type AdType, type AdConfig, type AdError, type AdAdapter, type AdCloseResult, } from './ad';
14
14
  export { useShare, useShareConfig, setConfigShare, type ShareConfig, type ShareConfigResolver, type ShareFrom, type ShareAppMessageContent, type ShareTimelineContent, type ShareConfigMap, type ShareConfigAdapter, type PageShareItem, type PageShareFallback, type SharePayload, } from './share';
15
15
  export { useContact, setConfigContact, type ContactConfig, type ContactAdapter, type ContactBindProps, } from './contact';
16
16
  export { useUtils, type DownloadFileOptions, type DownloadFileResult, type TapEvent } from './utils';
@@ -18,6 +18,8 @@ export interface ModalOptions {
18
18
  cancelText?: string;
19
19
  confirmColor?: string;
20
20
  cancelColor?: string;
21
+ /** false=隐藏取消按钮(纯提示弹窗用) */
22
+ showCancel?: boolean;
21
23
  }
22
24
  export interface HlwMsg {
23
25
  toast(opts: ToastOptions | string): void;
package/dist/index.js CHANGED
@@ -352,7 +352,8 @@ var __publicField = (obj, key, value) => {
352
352
  confirmText = "确定",
353
353
  cancelText = "取消",
354
354
  confirmColor = "#3b82f6",
355
- cancelColor = "#999999"
355
+ cancelColor = "#999999",
356
+ showCancel = true
356
357
  } = opts;
357
358
  uni.showModal({
358
359
  title,
@@ -361,6 +362,7 @@ var __publicField = (obj, key, value) => {
361
362
  cancelText,
362
363
  confirmColor,
363
364
  cancelColor,
365
+ showCancel,
364
366
  success: (res) => resolve(res.confirm),
365
367
  fail: () => resolve(false)
366
368
  });
@@ -672,44 +674,19 @@ var __publicField = (obj, key, value) => {
672
674
  function getUnitId(type) {
673
675
  return store.config[`${type}_unit_id`] || "";
674
676
  }
675
- async function showReward(claim) {
677
+ async function showReward(onClose) {
676
678
  const unitId = getUnitId("reward");
677
679
  if (!unitId) {
678
680
  msg().toast("激励广告未配置");
679
- return false;
681
+ return;
680
682
  }
681
683
  if ((adapter$1 == null ? void 0 : adapter$1.isAuth) && !adapter$1.isAuth()) {
682
684
  msg().toast("请先登录");
683
- return false;
684
- }
685
- while (true) {
686
- const closeRes = await playRewardedOnce(unitId);
687
- if (!claim) {
688
- if (closeRes.isEnded)
689
- return true;
690
- const goon = await confirmReward();
691
- if (!goon)
692
- return false;
693
- continue;
694
- }
695
- try {
696
- const r = await claim(closeRes);
697
- if (r.retry)
698
- continue;
699
- if (!r.ok) {
700
- if (r.msg)
701
- msg().error(r.msg);
702
- return false;
703
- }
704
- if (r.reward && r.reward > 0)
705
- msg().success(`+${r.reward} 积分`);
706
- return true;
707
- } catch (e) {
708
- console.warn("[useAd] claim failed", e);
709
- msg().error("领取失败,请稍后再试");
710
- return false;
711
- }
685
+ return;
712
686
  }
687
+ const closeRes = await playRewardedOnce(unitId);
688
+ if (onClose)
689
+ await onClose(closeRes);
713
690
  }
714
691
  async function showPopup() {
715
692
  const unitId = getUnitId("popup");
@@ -725,7 +702,8 @@ var __publicField = (obj, key, value) => {
725
702
  loadConfig: loadConfig2,
726
703
  getUnitId,
727
704
  showReward,
728
- showPopup
705
+ showPopup,
706
+ confirm: confirmModal
729
707
  };
730
708
  }
731
709
  async function playRewardedOnce(unitId) {
@@ -744,7 +722,7 @@ var __publicField = (obj, key, value) => {
744
722
  hide();
745
723
  }
746
724
  }
747
- function confirmReward() {
725
+ function confirmModal() {
748
726
  return new Promise((resolve) => {
749
727
  uni.showModal({
750
728
  title: "提示",
@@ -824,6 +802,11 @@ var __publicField = (obj, key, value) => {
824
802
  var _a;
825
803
  let ad = interstitialCache.get(unitId);
826
804
  if (!ad) {
805
+ if (typeof uni.createInterstitialAd !== "function") {
806
+ console.warn("[useAd] 当前基础库不支持插屏广告");
807
+ resolve(false);
808
+ return;
809
+ }
827
810
  ad = uni.createInterstitialAd({ adUnitId: unitId });
828
811
  if (!ad) {
829
812
  resolve(false);
@@ -834,12 +817,9 @@ var __publicField = (obj, key, value) => {
834
817
  });
835
818
  interstitialCache.set(unitId, ad);
836
819
  }
837
- ad.show().then(() => resolve(true)).catch(() => {
838
- if (typeof ad.load !== "function") {
839
- resolve(false);
840
- return;
841
- }
842
- ad.load().then(() => ad.show()).then(() => resolve(true)).catch(() => resolve(false));
820
+ ad.show().then(() => resolve(true)).catch((err) => {
821
+ console.warn(`[useAd] popup show error (${unitId})`, err);
822
+ resolve(false);
843
823
  });
844
824
  });
845
825
  }
@@ -1900,7 +1880,6 @@ var __publicField = (obj, key, value) => {
1900
1880
  exports2.alistAdapter = alistAdapter;
1901
1881
  exports2.buildThemeStyle = buildThemeStyle;
1902
1882
  exports2.clearDeviceCache = clearDeviceCache;
1903
- exports2.confirmReward = confirmReward;
1904
1883
  exports2.cosAdapter = cosAdapter;
1905
1884
  exports2.destroyAds = destroyAds;
1906
1885
  exports2.deviceToQuery = deviceToQuery;
package/dist/index.mjs CHANGED
@@ -351,7 +351,8 @@ function useMsg() {
351
351
  confirmText = "确定",
352
352
  cancelText = "取消",
353
353
  confirmColor = "#3b82f6",
354
- cancelColor = "#999999"
354
+ cancelColor = "#999999",
355
+ showCancel = true
355
356
  } = opts;
356
357
  uni.showModal({
357
358
  title,
@@ -360,6 +361,7 @@ function useMsg() {
360
361
  cancelText,
361
362
  confirmColor,
362
363
  cancelColor,
364
+ showCancel,
363
365
  success: (res) => resolve(res.confirm),
364
366
  fail: () => resolve(false)
365
367
  });
@@ -671,44 +673,19 @@ function useAd() {
671
673
  function getUnitId(type) {
672
674
  return store.config[`${type}_unit_id`] || "";
673
675
  }
674
- async function showReward(claim) {
676
+ async function showReward(onClose) {
675
677
  const unitId = getUnitId("reward");
676
678
  if (!unitId) {
677
679
  msg().toast("激励广告未配置");
678
- return false;
680
+ return;
679
681
  }
680
682
  if ((adapter$1 == null ? void 0 : adapter$1.isAuth) && !adapter$1.isAuth()) {
681
683
  msg().toast("请先登录");
682
- return false;
683
- }
684
- while (true) {
685
- const closeRes = await playRewardedOnce(unitId);
686
- if (!claim) {
687
- if (closeRes.isEnded)
688
- return true;
689
- const goon = await confirmReward();
690
- if (!goon)
691
- return false;
692
- continue;
693
- }
694
- try {
695
- const r = await claim(closeRes);
696
- if (r.retry)
697
- continue;
698
- if (!r.ok) {
699
- if (r.msg)
700
- msg().error(r.msg);
701
- return false;
702
- }
703
- if (r.reward && r.reward > 0)
704
- msg().success(`+${r.reward} 积分`);
705
- return true;
706
- } catch (e) {
707
- console.warn("[useAd] claim failed", e);
708
- msg().error("领取失败,请稍后再试");
709
- return false;
710
- }
684
+ return;
711
685
  }
686
+ const closeRes = await playRewardedOnce(unitId);
687
+ if (onClose)
688
+ await onClose(closeRes);
712
689
  }
713
690
  async function showPopup() {
714
691
  const unitId = getUnitId("popup");
@@ -724,7 +701,8 @@ function useAd() {
724
701
  loadConfig: loadConfig2,
725
702
  getUnitId,
726
703
  showReward,
727
- showPopup
704
+ showPopup,
705
+ confirm: confirmModal
728
706
  };
729
707
  }
730
708
  async function playRewardedOnce(unitId) {
@@ -743,7 +721,7 @@ async function playRewardedOnce(unitId) {
743
721
  hide();
744
722
  }
745
723
  }
746
- function confirmReward() {
724
+ function confirmModal() {
747
725
  return new Promise((resolve) => {
748
726
  uni.showModal({
749
727
  title: "提示",
@@ -823,6 +801,11 @@ function showInterstitialAd(unitId) {
823
801
  var _a;
824
802
  let ad = interstitialCache.get(unitId);
825
803
  if (!ad) {
804
+ if (typeof uni.createInterstitialAd !== "function") {
805
+ console.warn("[useAd] 当前基础库不支持插屏广告");
806
+ resolve(false);
807
+ return;
808
+ }
826
809
  ad = uni.createInterstitialAd({ adUnitId: unitId });
827
810
  if (!ad) {
828
811
  resolve(false);
@@ -833,12 +816,9 @@ function showInterstitialAd(unitId) {
833
816
  });
834
817
  interstitialCache.set(unitId, ad);
835
818
  }
836
- ad.show().then(() => resolve(true)).catch(() => {
837
- if (typeof ad.load !== "function") {
838
- resolve(false);
839
- return;
840
- }
841
- ad.load().then(() => ad.show()).then(() => resolve(true)).catch(() => resolve(false));
819
+ ad.show().then(() => resolve(true)).catch((err) => {
820
+ console.warn(`[useAd] popup show error (${unitId})`, err);
821
+ resolve(false);
842
822
  });
843
823
  });
844
824
  }
@@ -1900,7 +1880,6 @@ export {
1900
1880
  alistAdapter,
1901
1881
  buildThemeStyle,
1902
1882
  clearDeviceCache,
1903
- confirmReward,
1904
1883
  cosAdapter,
1905
1884
  destroyAds,
1906
1885
  deviceToQuery,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "2.0.4",
3
+ "version": "2.0.7",
4
4
  "description": "hlw-uni 小程序运行时 — Vue 组件 + composables + theme + http + 工具集(合并自原 mp-core)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -0,0 +1,238 @@
1
+ <template>
2
+ <view v-if="mounted" class="hlw-notice-mask" :class="{ 'hlw-notice-mask--in': visible }" @tap.self>
3
+ <view class="hlw-notice-card" :class="{ 'hlw-notice-card--in': visible }" @tap.stop>
4
+ <view class="hlw-notice-head">
5
+ <view class="hlw-notice-icon" :style="{ background: iconTint }">
6
+ <view class="hlw-notice-icon-i" :class="iconClass" :style="{ color: iconColor }" />
7
+ </view>
8
+ <text class="hlw-notice-title">{{ title }}</text>
9
+ </view>
10
+
11
+ <scroll-view
12
+ class="hlw-notice-body"
13
+ scroll-y
14
+ :show-scrollbar="false"
15
+ :enhanced="true"
16
+ >
17
+ <rich-text class="hlw-notice-text" :nodes="contentNodes" />
18
+ </scroll-view>
19
+
20
+ <view
21
+ class="hlw-notice-btn"
22
+ :style="{ background: iconColor }"
23
+ hover-class="hlw-notice-btn--hover"
24
+ @tap="onConfirm"
25
+ >
26
+ <text class="hlw-notice-btn-text">{{ confirmText }}</text>
27
+ </view>
28
+
29
+ <view
30
+ v-if="showCancel"
31
+ class="hlw-notice-dismiss"
32
+ hover-class="hlw-notice-dismiss--hover"
33
+ @tap="onCancel"
34
+ >
35
+ <text class="hlw-notice-dismiss-text">{{ cancelText }}</text>
36
+ </view>
37
+ </view>
38
+ </view>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+ import { computed, nextTick, ref, watch } from "vue";
43
+
44
+ interface Props {
45
+ show?: boolean;
46
+ title?: string;
47
+ /** 内容:HTML 原样渲染,纯文本自动 \n -> <br/> */
48
+ content?: string;
49
+ /** iconify class,例:i-fa6-solid-bell */
50
+ iconClass?: string;
51
+ /** 图标 + 主按钮颜色 */
52
+ iconColor?: string;
53
+ /** 图标背景色 */
54
+ iconTint?: string;
55
+ confirmText?: string;
56
+ cancelText?: string;
57
+ showCancel?: boolean;
58
+ /** 进出场动画时长(ms),需与 CSS transition 一致 */
59
+ animMs?: number;
60
+ }
61
+
62
+ const props = withDefaults(defineProps<Props>(), {
63
+ show: false,
64
+ title: "",
65
+ content: "",
66
+ iconClass: "i-fa6-solid-bell",
67
+ iconColor: "#1C1C1E",
68
+ iconTint: "#F2F2F7",
69
+ confirmText: "我知道了",
70
+ cancelText: "稍后再看",
71
+ showCancel: false,
72
+ animMs: 260,
73
+ });
74
+
75
+ const emit = defineEmits<{ confirm: []; cancel: [] }>();
76
+
77
+ const mounted = ref(false);
78
+ const visible = ref(false);
79
+ let timer: ReturnType<typeof setTimeout> | null = null;
80
+
81
+ const contentNodes = computed(() => {
82
+ const raw = props.content;
83
+ return /<[a-z][\s\S]*>/i.test(raw) ? raw : raw.replace(/\n/g, "<br/>");
84
+ });
85
+
86
+ watch(
87
+ () => props.show,
88
+ async (next) => {
89
+ if (timer) {
90
+ clearTimeout(timer);
91
+ timer = null;
92
+ }
93
+ if (next) {
94
+ mounted.value = true;
95
+ await nextTick();
96
+ visible.value = true;
97
+ } else {
98
+ visible.value = false;
99
+ timer = setTimeout(() => {
100
+ mounted.value = false;
101
+ timer = null;
102
+ }, props.animMs);
103
+ }
104
+ },
105
+ { immediate: true },
106
+ );
107
+
108
+ function onConfirm() {
109
+ emit("confirm");
110
+ }
111
+
112
+ function onCancel() {
113
+ emit("cancel");
114
+ }
115
+ </script>
116
+
117
+ <style lang="scss" scoped>
118
+ .hlw-notice-mask {
119
+ position: fixed;
120
+ inset: 0;
121
+ z-index: 1000;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ padding: 40rpx;
126
+ background: rgba(0, 0, 0, 0);
127
+ transition: background 0.25s ease-out;
128
+
129
+ &--in {
130
+ background: rgba(0, 0, 0, 0.4);
131
+ }
132
+ }
133
+
134
+ .hlw-notice-card {
135
+ width: 100%;
136
+ max-width: 540rpx;
137
+ background: var(--surface-card, #ffffff);
138
+ border-radius: var(--radius-xl, 36rpx);
139
+ padding: 40rpx 36rpx 32rpx;
140
+ box-shadow: 0 40rpx 80rpx rgba(0, 0, 0, 0.1);
141
+ transform: scale(0.92);
142
+ opacity: 0;
143
+ transition: transform 0.28s cubic-bezier(0.34, 1.2, 0.64, 1), opacity 0.22s ease-out;
144
+
145
+ &--in {
146
+ transform: scale(1);
147
+ opacity: 1;
148
+ }
149
+ }
150
+
151
+ .hlw-notice-head {
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ gap: 14rpx;
156
+ margin-bottom: 24rpx;
157
+ }
158
+
159
+ .hlw-notice-icon {
160
+ width: 48rpx;
161
+ height: 48rpx;
162
+ border-radius: var(--radius-sm, 12rpx);
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ .hlw-notice-icon-i {
170
+ font-size: 24rpx;
171
+ }
172
+
173
+ .hlw-notice-title {
174
+ font-size: var(--font-md, 30rpx);
175
+ font-weight: 700;
176
+ color: var(--text-primary, #1c1c1e);
177
+ line-height: 1.3;
178
+ word-break: break-word;
179
+ }
180
+
181
+ .hlw-notice-body {
182
+ width: 100%;
183
+ max-height: 560rpx;
184
+ margin-bottom: 36rpx;
185
+ padding-right: 6rpx;
186
+ }
187
+
188
+ .hlw-notice-text {
189
+ display: block;
190
+ font-size: var(--font-sm, 26rpx);
191
+ line-height: 1.65;
192
+ color: var(--text-secondary, #5c5c5e);
193
+ text-align: left;
194
+ white-space: pre-wrap;
195
+ word-break: break-word;
196
+ }
197
+
198
+ .hlw-notice-btn {
199
+ width: 100%;
200
+ padding: 22rpx 0;
201
+ border-radius: 9999rpx;
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
206
+ transition: transform 0.15s ease-out, filter 0.15s ease-out;
207
+
208
+ &--hover {
209
+ transform: scale(0.97);
210
+ filter: brightness(0.92);
211
+ }
212
+ }
213
+
214
+ .hlw-notice-btn-text {
215
+ color: #ffffff;
216
+ font-size: var(--font-base, 28rpx);
217
+ font-weight: 600;
218
+ letter-spacing: 1rpx;
219
+ }
220
+
221
+ .hlw-notice-dismiss {
222
+ margin-top: 16rpx;
223
+ padding: 10rpx 0;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+
228
+ &--hover {
229
+ opacity: 0.5;
230
+ }
231
+ }
232
+
233
+ .hlw-notice-dismiss-text {
234
+ font-size: var(--font-xs, 24rpx);
235
+ color: var(--text-muted, #8e8e93);
236
+ font-weight: 500;
237
+ }
238
+ </style>
@@ -88,26 +88,6 @@ export interface AdCloseResult {
88
88
  isEnded: boolean;
89
89
  }
90
90
 
91
- /** showReward 的 claim 回调返回契约 */
92
- export interface AdClaimResult {
93
- /** 本次结果:true=成功 / false=失败 */
94
- ok: boolean;
95
- /** 奖励数(用于 toast 显示 +N 积分;无则不 toast) */
96
- reward?: number;
97
- /** 失败提示语;不传不弹 toast */
98
- msg?: string;
99
- /** 仅 isEnded=false 时常用:true 让 mp-core 重新 show 一次(业务方挽留确认后用) */
100
- retry?: boolean;
101
- }
102
-
103
- /**
104
- * showReward 的 claim 回调签名 —— 业务方按场景实现(领积分、解锁、抽奖等)。
105
- * 每次广告关闭后被调用一次(无论是否完整看完),业务方根据 closeRes.isEnded 决定怎么走:
106
- * - isEnded=true → 调发奖接口 → return { ok: true, reward: N }
107
- * - isEnded=false → 业务自己决定挽留:return { ok: false, retry: true } 让重新 show
108
- */
109
- export type AdClaimFn = (closeRes: AdCloseResult) => Promise<AdClaimResult>;
110
-
111
91
  const EMPTY: AdConfig = {
112
92
  banner_unit_id: "",
113
93
  grid_unit_id: "",
@@ -180,51 +160,27 @@ export function useAd() {
180
160
  }
181
161
 
182
162
  /**
183
- * 显示激励视频
163
+ * 显示激励视频 —— 播一次、关闭后调 onClose(带 isEnded),其它都交给业务。
184
164
  *
185
- * 流程:loading 广告 UI → 关闭 → 业务 claim 回调(按 closeRes.isEnded 决定怎么走)
165
+ * showReward(({ isEnded }) => {
166
+ * if (!isEnded) confirm().then(goon => goon && tapReward());
167
+ * else claimAdReward().then(...);
168
+ * });
186
169
  *
187
- * @param claim 关闭后业务回调;不传则纯展示,看完即返回 true、未看完返回 false
188
- * @returns true=最终成功;false=未看完且业务放弃 / 配置缺失 / claim 失败
170
+ * @param onClose 关闭回调;不传则只播。retry / toast / 发奖全在业务侧自己写。
189
171
  */
190
- async function showReward(claim?: AdClaimFn): Promise<boolean> {
172
+ async function showReward(onClose?: (res: AdCloseResult) => void | Promise<void>): Promise<void> {
191
173
  const unitId = getUnitId("reward");
192
174
  if (!unitId) {
193
175
  msg().toast("激励广告未配置");
194
- return false;
176
+ return;
195
177
  }
196
178
  if (adapter?.isAuth && !adapter.isAuth()) {
197
179
  msg().toast("请先登录");
198
- return false;
199
- }
200
-
201
- while (true) {
202
- const closeRes = await playRewardedOnce(unitId);
203
-
204
- // 没传 claim:默认行为 = 看完了 true / 没看完弹挽留 + 重试 / 放弃 false
205
- if (!claim) {
206
- if (closeRes.isEnded) return true;
207
- const goon = await confirmReward();
208
- if (!goon) return false;
209
- continue;
210
- }
211
-
212
- // 业务方决定怎么走(包括「未看完是否挽留」)
213
- try {
214
- const r = await claim(closeRes);
215
- if (r.retry) continue;
216
- if (!r.ok) {
217
- if (r.msg) msg().error(r.msg);
218
- return false;
219
- }
220
- if (r.reward && r.reward > 0) msg().success(`+${r.reward} 积分`);
221
- return true;
222
- } catch (e) {
223
- console.warn("[useAd] claim failed", e);
224
- msg().error("领取失败,请稍后再试");
225
- return false;
226
- }
180
+ return;
227
181
  }
182
+ const closeRes = await playRewardedOnce(unitId);
183
+ if (onClose) await onClose(closeRes);
228
184
  }
229
185
 
230
186
  /**
@@ -246,6 +202,7 @@ export function useAd() {
246
202
  getUnitId,
247
203
  showReward,
248
204
  showPopup,
205
+ confirm: confirmModal,
249
206
  };
250
207
  }
251
208
 
@@ -272,20 +229,18 @@ async function playRewardedOnce(unitId: string): Promise<AdCloseResult> {
272
229
  }
273
230
 
274
231
  /**
275
- * 激励视频中途关闭挽留弹窗 —— 业务方在 claim 回调里也可以复用:
232
+ * 激励视频中途关闭挽留弹窗 —— 通过 useAd().confirm 暴露给业务方:
276
233
  *
277
- * showReward(async (closeRes) => {
278
- * if (!closeRes.isEnded) {
279
- * const goon = await confirmReward();
280
- * return { ok: false, retry: goon };
281
- * }
234
+ * const { showReward, confirm } = useAd();
235
+ * showReward(async ({ isEnded }) => {
236
+ * if (!isEnded) return { ok: false, retry: await confirm() };
282
237
  * const r = await claimAdReward();
283
238
  * return r.code === 1 ? { ok: true, reward: r.data?.reward } : { ok: false, msg: r.info };
284
239
  * });
285
240
  *
286
241
  * @returns true=用户选「继续观看」 / false=放弃
287
242
  */
288
- export function confirmReward(): Promise<boolean> {
243
+ function confirmModal(): Promise<boolean> {
289
244
  return new Promise((resolve) => {
290
245
  uni.showModal({
291
246
  title: "提示",
@@ -361,11 +316,17 @@ function showRewardedAd(unitId: string, hooks?: { onShown?: () => void }): Promi
361
316
  });
362
317
  }
363
318
 
364
- /** 底层插屏广告包装:show 失败兜底 load show */
319
+ /** 底层插屏广告包装:实例按 unitId 缓存复用,show 失败 console.warn 不重试(频控/网络等失败重试也救不了) */
365
320
  function showInterstitialAd(unitId: string): Promise<boolean> {
366
321
  return new Promise((resolve) => {
367
322
  let ad: any = interstitialCache.get(unitId);
368
323
  if (!ad) {
324
+ // 老基础库可能没有这个 API(对应官方 if (wx.createInterstitialAd))
325
+ if (typeof uni.createInterstitialAd !== "function") {
326
+ console.warn("[useAd] 当前基础库不支持插屏广告");
327
+ resolve(false);
328
+ return;
329
+ }
369
330
  ad = uni.createInterstitialAd({ adUnitId: unitId });
370
331
  if (!ad) { resolve(false); return; }
371
332
  ad.onError?.((err: AdError) => {
@@ -373,15 +334,11 @@ function showInterstitialAd(unitId: string): Promise<boolean> {
373
334
  });
374
335
  interstitialCache.set(unitId, ad);
375
336
  }
376
-
377
337
  ad.show()
378
338
  .then(() => resolve(true))
379
- .catch(() => {
380
- if (typeof ad.load !== "function") { resolve(false); return; }
381
- ad.load()
382
- .then(() => ad.show())
383
- .then(() => resolve(true))
384
- .catch(() => resolve(false));
339
+ .catch((err: any) => {
340
+ console.warn(`[useAd] popup show error (${unitId})`, err);
341
+ resolve(false);
385
342
  });
386
343
  });
387
344
  }
@@ -14,14 +14,11 @@ export {
14
14
  useAd,
15
15
  setConfigAd,
16
16
  destroyAds,
17
- confirmReward,
18
17
  type AdType,
19
18
  type AdConfig,
20
19
  type AdError,
21
20
  type AdAdapter,
22
21
  type AdCloseResult,
23
- type AdClaimResult,
24
- type AdClaimFn,
25
22
  } from "./ad";
26
23
  export {
27
24
  useShare,
@@ -20,6 +20,8 @@ export interface ModalOptions {
20
20
  cancelText?: string;
21
21
  confirmColor?: string;
22
22
  cancelColor?: string;
23
+ /** false=隐藏取消按钮(纯提示弹窗用) */
24
+ showCancel?: boolean;
23
25
  }
24
26
 
25
27
  export interface HlwMsg {
@@ -87,6 +89,7 @@ export function useMsg(): HlwMsg {
87
89
  cancelText = "取消",
88
90
  confirmColor = "#3b82f6",
89
91
  cancelColor = "#999999",
92
+ showCancel = true,
90
93
  } = opts;
91
94
  uni.showModal({
92
95
  title,
@@ -95,6 +98,7 @@ export function useMsg(): HlwMsg {
95
98
  cancelText,
96
99
  confirmColor,
97
100
  cancelColor,
101
+ showCancel,
98
102
  success: (res) => resolve(res.confirm),
99
103
  fail: () => resolve(false),
100
104
  });