@hlw-uni/mp-vue 2.0.6 → 2.0.8

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.js CHANGED
@@ -639,6 +639,7 @@ var __publicField = (obj, key, value) => {
639
639
  popup_unit_id: ""
640
640
  };
641
641
  let adapter$1 = null;
642
+ let pending$1 = null;
642
643
  function setConfigAd(a) {
643
644
  adapter$1 = a;
644
645
  }
@@ -657,24 +658,35 @@ var __publicField = (obj, key, value) => {
657
658
  async function loadConfig2(force = false) {
658
659
  if (loaded.value && !force)
659
660
  return;
661
+ if (pending$1 && !force)
662
+ return pending$1;
660
663
  if (!(adapter$1 == null ? void 0 : adapter$1.getConfig)) {
661
664
  console.warn("[useAd] adapter.getConfig 未注入;先调用 setConfigAd()");
662
665
  return;
663
666
  }
664
- try {
665
- const cfg = unwrapPayload(await adapter$1.getConfig());
666
- if (cfg) {
667
- store.config = cfg;
668
- store.loaded = true;
667
+ const fn = adapter$1.getConfig;
668
+ const flight = (async () => {
669
+ try {
670
+ const cfg = unwrapPayload(await fn());
671
+ if (cfg) {
672
+ store.config = cfg;
673
+ store.loaded = true;
674
+ }
675
+ } catch (e) {
676
+ console.warn("[useAd] load config failed", e);
677
+ } finally {
678
+ if (pending$1 === flight)
679
+ pending$1 = null;
669
680
  }
670
- } catch (e) {
671
- console.warn("[useAd] load config failed", e);
672
- }
681
+ })();
682
+ pending$1 = flight;
683
+ return flight;
673
684
  }
674
685
  function getUnitId(type) {
675
686
  return store.config[`${type}_unit_id`] || "";
676
687
  }
677
688
  async function showReward(onClose) {
689
+ await loadConfig2();
678
690
  const unitId = getUnitId("reward");
679
691
  if (!unitId) {
680
692
  msg().toast("激励广告未配置");
@@ -689,6 +701,7 @@ var __publicField = (obj, key, value) => {
689
701
  await onClose(closeRes);
690
702
  }
691
703
  async function showPopup() {
704
+ await loadConfig2();
692
705
  const unitId = getUnitId("popup");
693
706
  if (!unitId)
694
707
  return false;
@@ -802,6 +815,11 @@ var __publicField = (obj, key, value) => {
802
815
  var _a;
803
816
  let ad = interstitialCache.get(unitId);
804
817
  if (!ad) {
818
+ if (typeof uni.createInterstitialAd !== "function") {
819
+ console.warn("[useAd] 当前基础库不支持插屏广告");
820
+ resolve(false);
821
+ return;
822
+ }
805
823
  ad = uni.createInterstitialAd({ adUnitId: unitId });
806
824
  if (!ad) {
807
825
  resolve(false);
@@ -812,12 +830,9 @@ var __publicField = (obj, key, value) => {
812
830
  });
813
831
  interstitialCache.set(unitId, ad);
814
832
  }
815
- ad.show().then(() => resolve(true)).catch(() => {
816
- if (typeof ad.load !== "function") {
817
- resolve(false);
818
- return;
819
- }
820
- ad.load().then(() => ad.show()).then(() => resolve(true)).catch(() => resolve(false));
833
+ ad.show().then(() => resolve(true)).catch((err) => {
834
+ console.warn(`[useAd] popup show error (${unitId})`, err);
835
+ resolve(false);
821
836
  });
822
837
  });
823
838
  }
package/dist/index.mjs CHANGED
@@ -638,6 +638,7 @@ const EMPTY = {
638
638
  popup_unit_id: ""
639
639
  };
640
640
  let adapter$1 = null;
641
+ let pending$1 = null;
641
642
  function setConfigAd(a) {
642
643
  adapter$1 = a;
643
644
  }
@@ -656,24 +657,35 @@ function useAd() {
656
657
  async function loadConfig2(force = false) {
657
658
  if (loaded.value && !force)
658
659
  return;
660
+ if (pending$1 && !force)
661
+ return pending$1;
659
662
  if (!(adapter$1 == null ? void 0 : adapter$1.getConfig)) {
660
663
  console.warn("[useAd] adapter.getConfig 未注入;先调用 setConfigAd()");
661
664
  return;
662
665
  }
663
- try {
664
- const cfg = unwrapPayload(await adapter$1.getConfig());
665
- if (cfg) {
666
- store.config = cfg;
667
- store.loaded = true;
666
+ const fn = adapter$1.getConfig;
667
+ const flight = (async () => {
668
+ try {
669
+ const cfg = unwrapPayload(await fn());
670
+ if (cfg) {
671
+ store.config = cfg;
672
+ store.loaded = true;
673
+ }
674
+ } catch (e) {
675
+ console.warn("[useAd] load config failed", e);
676
+ } finally {
677
+ if (pending$1 === flight)
678
+ pending$1 = null;
668
679
  }
669
- } catch (e) {
670
- console.warn("[useAd] load config failed", e);
671
- }
680
+ })();
681
+ pending$1 = flight;
682
+ return flight;
672
683
  }
673
684
  function getUnitId(type) {
674
685
  return store.config[`${type}_unit_id`] || "";
675
686
  }
676
687
  async function showReward(onClose) {
688
+ await loadConfig2();
677
689
  const unitId = getUnitId("reward");
678
690
  if (!unitId) {
679
691
  msg().toast("激励广告未配置");
@@ -688,6 +700,7 @@ function useAd() {
688
700
  await onClose(closeRes);
689
701
  }
690
702
  async function showPopup() {
703
+ await loadConfig2();
691
704
  const unitId = getUnitId("popup");
692
705
  if (!unitId)
693
706
  return false;
@@ -801,6 +814,11 @@ function showInterstitialAd(unitId) {
801
814
  var _a;
802
815
  let ad = interstitialCache.get(unitId);
803
816
  if (!ad) {
817
+ if (typeof uni.createInterstitialAd !== "function") {
818
+ console.warn("[useAd] 当前基础库不支持插屏广告");
819
+ resolve(false);
820
+ return;
821
+ }
804
822
  ad = uni.createInterstitialAd({ adUnitId: unitId });
805
823
  if (!ad) {
806
824
  resolve(false);
@@ -811,12 +829,9 @@ function showInterstitialAd(unitId) {
811
829
  });
812
830
  interstitialCache.set(unitId, ad);
813
831
  }
814
- ad.show().then(() => resolve(true)).catch(() => {
815
- if (typeof ad.load !== "function") {
816
- resolve(false);
817
- return;
818
- }
819
- ad.load().then(() => ad.show()).then(() => resolve(true)).catch(() => resolve(false));
832
+ ad.show().then(() => resolve(true)).catch((err) => {
833
+ console.warn(`[useAd] popup show error (${unitId})`, err);
834
+ resolve(false);
820
835
  });
821
836
  });
822
837
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
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>
@@ -99,6 +99,9 @@ const EMPTY: AdConfig = {
99
99
 
100
100
  let adapter: AdAdapter | null = null;
101
101
 
102
+ /** 进行中的 loadConfig promise;并发调用时复用,避免冷启动多发请求 */
103
+ let pending: Promise<void> | null = null;
104
+
102
105
  /**
103
106
  * 注入业务回调,应用启动时调用一次。
104
107
  * 不调用也不会崩,但 loadConfig / showReward 会无效。
@@ -136,22 +139,33 @@ export function useAd() {
136
139
  const store = useAdStore();
137
140
  const { config, loaded } = storeToRefs(store);
138
141
 
139
- /** 拉取广告配置(小程序冷启动后调一次即可) */
142
+ /**
143
+ * 拉取广告配置(小程序冷启动后调一次即可)。
144
+ * 并发调用复用同一次请求;`force=true` 跳过 loaded/pending 短路重新拉。
145
+ */
140
146
  async function loadConfig(force = false): Promise<void> {
141
147
  if (loaded.value && !force) return;
148
+ if (pending && !force) return pending;
142
149
  if (!adapter?.getConfig) {
143
150
  console.warn("[useAd] adapter.getConfig 未注入;先调用 setConfigAd()");
144
151
  return;
145
152
  }
146
- try {
147
- const cfg = unwrapPayload(await adapter.getConfig());
148
- if (cfg) {
149
- store.config = cfg;
150
- store.loaded = true;
153
+ const fn = adapter.getConfig;
154
+ const flight = (async () => {
155
+ try {
156
+ const cfg = unwrapPayload(await fn());
157
+ if (cfg) {
158
+ store.config = cfg;
159
+ store.loaded = true;
160
+ }
161
+ } catch (e) {
162
+ console.warn("[useAd] load config failed", e);
163
+ } finally {
164
+ if (pending === flight) pending = null;
151
165
  }
152
- } catch (e) {
153
- console.warn("[useAd] load config failed", e);
154
- }
166
+ })();
167
+ pending = flight;
168
+ return flight;
155
169
  }
156
170
 
157
171
  /** 取指定类型的 unit_id(hlw-ad 组件 / 业务直接调时用) */
@@ -170,6 +184,7 @@ export function useAd() {
170
184
  * @param onClose 关闭回调;不传则只播。retry / toast / 发奖全在业务侧自己写。
171
185
  */
172
186
  async function showReward(onClose?: (res: AdCloseResult) => void | Promise<void>): Promise<void> {
187
+ await loadConfig();
173
188
  const unitId = getUnitId("reward");
174
189
  if (!unitId) {
175
190
  msg().toast("激励广告未配置");
@@ -188,6 +203,7 @@ export function useAd() {
188
203
  * @returns true=展示成功;false=配置缺失 / show 失败(如近期已展示过)
189
204
  */
190
205
  async function showPopup(): Promise<boolean> {
206
+ await loadConfig();
191
207
  const unitId = getUnitId("popup");
192
208
  if (!unitId) return false;
193
209
  return await showInterstitialAd(unitId);
@@ -316,11 +332,17 @@ function showRewardedAd(unitId: string, hooks?: { onShown?: () => void }): Promi
316
332
  });
317
333
  }
318
334
 
319
- /** 底层插屏广告包装:show 失败兜底 load show */
335
+ /** 底层插屏广告包装:实例按 unitId 缓存复用,show 失败 console.warn 不重试(频控/网络等失败重试也救不了) */
320
336
  function showInterstitialAd(unitId: string): Promise<boolean> {
321
337
  return new Promise((resolve) => {
322
338
  let ad: any = interstitialCache.get(unitId);
323
339
  if (!ad) {
340
+ // 老基础库可能没有这个 API(对应官方 if (wx.createInterstitialAd))
341
+ if (typeof uni.createInterstitialAd !== "function") {
342
+ console.warn("[useAd] 当前基础库不支持插屏广告");
343
+ resolve(false);
344
+ return;
345
+ }
324
346
  ad = uni.createInterstitialAd({ adUnitId: unitId });
325
347
  if (!ad) { resolve(false); return; }
326
348
  ad.onError?.((err: AdError) => {
@@ -328,15 +350,11 @@ function showInterstitialAd(unitId: string): Promise<boolean> {
328
350
  });
329
351
  interstitialCache.set(unitId, ad);
330
352
  }
331
-
332
353
  ad.show()
333
354
  .then(() => resolve(true))
334
- .catch(() => {
335
- if (typeof ad.load !== "function") { resolve(false); return; }
336
- ad.load()
337
- .then(() => ad.show())
338
- .then(() => resolve(true))
339
- .catch(() => resolve(false));
355
+ .catch((err: any) => {
356
+ console.warn(`[useAd] popup show error (${unitId})`, err);
357
+ resolve(false);
340
358
  });
341
359
  });
342
360
  }