vhall-mini-sdk 0.2.1 → 0.3.1

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 (30) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/components/announcement/announ-banner/index.js +40 -0
  3. package/lib/components/announcement/announ-banner/index.json +4 -0
  4. package/lib/components/announcement/announ-banner/index.wxml +13 -0
  5. package/lib/components/announcement/announ-banner/index.wxss +1 -0
  6. package/lib/components/announcement/announ-icon/index.js +69 -0
  7. package/lib/components/announcement/announ-icon/index.json +3 -0
  8. package/lib/components/announcement/announ-icon/index.wxml +10 -0
  9. package/lib/components/announcement/announ-icon/index.wxss +1 -0
  10. package/lib/components/announcement/announ-popup/index.js +44 -0
  11. package/lib/components/announcement/announ-popup/index.json +7 -0
  12. package/lib/components/announcement/announ-popup/index.wxml +39 -0
  13. package/lib/components/announcement/announ-popup/index.wxss +1 -0
  14. package/lib/components/announcement/index.js +223 -0
  15. package/lib/components/announcement/index.json +9 -0
  16. package/lib/components/announcement/index.wxml +6 -0
  17. package/lib/components/announcement/index.wxss +0 -0
  18. package/lib/components/live-landscape/live-landscape.js +7 -1
  19. package/lib/components/live-landscape/live-landscape.json +2 -0
  20. package/lib/components/live-landscape/live-landscape.wxml +7 -4
  21. package/lib/components/live-portrait/live-portrait.js +10 -2
  22. package/lib/components/live-portrait/live-portrait.json +3 -1
  23. package/lib/components/live-portrait/live-portrait.wxml +8 -4
  24. package/lib/components/mall-reward/index.js +47 -29
  25. package/lib/components/product/product-card/product-card.js +5 -6
  26. package/lib/components/product/product-detail/product-detail.js +6 -6
  27. package/lib/components/product/product-list/product-list.js +2 -3
  28. package/lib/sdk/main/index.js +2 -2
  29. package/lib/sdk/plugins/MallRewardServer.js +1 -1
  30. package/package.json +8 -11
package/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ 本项目所有重要变更均记录于此文件。
4
+
5
+ 格式遵循 [Keep a Changelog](https://keepachangelog.com/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
6
+
7
+ ## [0.3.1] - 2026-08-13
8
+
9
+ ### 修复
10
+
11
+ - 修复公告关闭事件定义重复问题。
12
+
13
+ ## [0.3.0] - 2026-08-13
14
+
15
+ ### 新增
16
+
17
+ - 直播和回放场景下新增 <span style="color:red">公告</span> 能力。
18
+
19
+ - VhallMiniSdk增加 <span style="color:red">VERSION</span> 属性,实例增加 <span style="color:red">version</span> 属性, 方便获取当前SDK版本号。
20
+
21
+ ### 变更
22
+
23
+ - SDK 错误页支持直播模式(mode,0:竖屏,1:横屏)初始0或1,不用空值,业务侧无需额外调用 setData 即可正常展示。
24
+
25
+ ### 修复
26
+
27
+ - 修复某些场景下商城奖励计时不动以及计时不准确的问题。
28
+
29
+ ## [0.2.1] - 2026-08-06
30
+
31
+ ### 变更
32
+
33
+ - SDK和几个插件都去掉了具名导出,避免默认导出和具名导出混用带来的问题。
34
+
35
+ ```js
36
+ // 旧
37
+ import { MallRewardServer } from ...
38
+ // 新
39
+ import MallRewardServer from ...
40
+ ```
41
+
42
+ ### 修复
43
+
44
+ - 修复某些场景下收不到消息的问题。
@@ -0,0 +1,40 @@
1
+ Component({
2
+ properties: {
3
+ text: {
4
+ type: String,
5
+ value: '',
6
+ },
7
+ show: {
8
+ type: Boolean,
9
+ value: false,
10
+ },
11
+ static: {
12
+ type: Boolean,
13
+ value: false,
14
+ },
15
+ // 直播间模式:0=竖屏,1=横屏(样式区分)
16
+ mode: {
17
+ type: Number,
18
+ value: 0,
19
+ },
20
+ },
21
+
22
+ data: {
23
+ duration: 6, // 滚动动画时长(秒),按文字长度动态计算
24
+ },
25
+
26
+ observers: {
27
+ // 文字越长滚动时长越大,保持匀速观感
28
+ text(val) {
29
+ const len = (val || '').length;
30
+ const duration = Math.max(4, Math.ceil(len * 0.2));
31
+ this.setData({ duration });
32
+ },
33
+ },
34
+
35
+ methods: {
36
+ onClose() {
37
+ this.triggerEvent('close');
38
+ },
39
+ },
40
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "shared"
4
+ }
@@ -0,0 +1,13 @@
1
+ <view wx:if="{{show }}" class="announ-banner {{static ? 'announ-banner--static' : ''}} mode-{{mode}}">
2
+ <view class="announ-banner-icon">
3
+ <view class="announ-banner-icon__box"></view>
4
+ <view class="announ-banner-icon__body"></view>
5
+ <view class="announ-banner-icon__wave announ-banner-icon__wave--top"></view>
6
+ <view class="announ-banner-icon__wave announ-banner-icon__wave--mid"></view>
7
+ <view class="announ-banner-icon__wave announ-banner-icon__wave--bottom"></view>
8
+ </view>
9
+ <view class="announ-banner-text-wrap">
10
+ <view class="announ-banner-text" style="animation-duration:{{duration}}s">{{text}}</view>
11
+ </view>
12
+ <view class="announ-banner-close" catchtap="onClose">&#10005;</view>
13
+ </view>
@@ -0,0 +1 @@
1
+ .announ-banner{align-items:center;background:rgba(0,0,0,.5);border-radius:40rpx;box-sizing:border-box;color:#fff;display:flex;font-size:24rpx;left:12rpx;padding:0 12rpx;position:fixed;right:12rpx;top:180rpx;z-index:99}.announ-banner-icon{color:#fff;flex-shrink:0;height:32rpx;margin-right:16rpx;position:relative;width:34rpx}.mode-1 .announ-banner-icon{color:#fb3a32}.announ-banner-icon__box{background:currentColor;border-bottom-left-radius:4rpx;border-top-left-radius:4rpx;height:14rpx;left:3rpx;position:absolute;top:9rpx;width:7rpx;z-index:2}.announ-banner-icon__body{background:currentColor;border-radius:4rpx 6rpx 6rpx 4rpx;-webkit-clip-path:polygon(0 20%,0 80%,78% 100%,78% 0);clip-path:polygon(0 20%,0 80%,78% 100%,78% 0);height:28rpx;left:8rpx;position:absolute;top:2rpx;width:15rpx}.announ-banner-icon__wave{background:currentColor;border-radius:4rpx;position:absolute}.announ-banner-icon__wave--top{height:4rpx;right:4rpx;top:5rpx;transform:rotate(-32deg);width:9rpx}.announ-banner-icon__wave--mid{height:4rpx;right:1rpx;top:14rpx;width:11rpx}.announ-banner-icon__wave--bottom{bottom:5rpx;height:4rpx;right:4rpx;transform:rotate(32deg);width:9rpx}.announ-banner-text-wrap{align-items:center;display:flex;flex:1;height:100%;overflow:hidden;padding:6rpx 0}.announ-banner-text{animation:announ-banner-scroll linear infinite;display:inline-block;padding-left:100%;white-space:nowrap}.announ-banner--static .announ-banner-text{animation:none;padding-left:0}.announ-banner-close{flex-shrink:0;font-size:32rpx;margin-left:16rpx}.announ-banner.mode-1{background:linear-gradient(59.32deg,#ffebe9,#edecff 107.06%);border-radius:0;color:#262626;font-size:28rpx;left:0;max-width:none;padding:16rpx 12rpx;right:0;top:calc(56.25vw + 85rpx);transform:none;width:100%}@keyframes announ-banner-scroll{0%{transform:translateX(0)}to{transform:translateX(-100%)}}
@@ -0,0 +1,69 @@
1
+ Component({
2
+ properties: {
3
+ // 是否显示公告图标
4
+ showIcon: {
5
+ type: Boolean,
6
+ value: false,
7
+ },
8
+ // 未读角标数字
9
+ badge: {
10
+ type: Number,
11
+ value: 0,
12
+ },
13
+ // 直播间模式:0=竖屏,1=横屏(样式区分)
14
+ mode: {
15
+ type: Number,
16
+ value: 0,
17
+ },
18
+ },
19
+ data: {
20
+ x: 20,
21
+ y: 40,
22
+ startX: 0,
23
+ startY: 0,
24
+ hasMoved: false,
25
+ },
26
+ lifetimes: {
27
+ attached() {
28
+ // 初始定位:竖屏在左上角;横屏 70rpx≈35px,在右下角抽奖历史图标上方
29
+ try {
30
+ const info = wx.getSystemInfoSync();
31
+ const iconSize = this.data.mode === 1 ? 35 : 42;
32
+ if (this.data.mode === 1) {
33
+ // 横屏:距右 40rpx≈20px;历史图标 bottom:200rpx + 高 70rpx + 间隔 36rpx ≈ 306rpx
34
+ const rightGap = 20;
35
+ const bottomGap = 160;
36
+ const x = Math.max(0, info.windowWidth - iconSize - rightGap);
37
+ const y = Math.max(0, info.windowHeight - iconSize - bottomGap);
38
+ this.setData({ x, y });
39
+ } else {
40
+ // 竖屏:左上角,留 12rpx≈6px 边距
41
+ const x = 6;
42
+ const y = 40;
43
+ this.setData({ x, y });
44
+ }
45
+ } catch (e) {
46
+ // 获取失败则保持默认左上角
47
+ }
48
+ },
49
+ },
50
+ methods: {
51
+ onTouchStart(e) {
52
+ this.setData({ hasMoved: false });
53
+ },
54
+
55
+ onTouchEnd(e) {
56
+ this.data.hasMoved = false;
57
+ },
58
+
59
+ onDragChange(e) {
60
+ const { x, y } = e.detail;
61
+ this.setData({ x, y, hasMoved: true });
62
+ },
63
+
64
+ onTapIcon() {
65
+ if (this.data.hasMoved) return;
66
+ this.triggerEvent('onTapIcon');
67
+ },
68
+ },
69
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "component": true
3
+ }
@@ -0,0 +1,10 @@
1
+ <movable-area class="drag-area">
2
+ <movable-view wx:if="{{showIcon}}" class="drag-view" direction="all" x="{{x}}" y="{{y}}" damping="{{40}}" out-of-bounds="{{false}}" bindchange="onDragChange" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd">
3
+ <view class="announ-icon mode-{{mode}}" bindtap="onTapIcon">
4
+ <image wx:if="{{mode === 1}}" class="announ-icon-img" src="https://t-alistatic01.e.vhall.com/common-static/saas-wap/static/img/notice-icon.a86f1993.png" mode="aspectFit" />
5
+ <image wx:else class="announ-icon-img" src="https://t-alistatic01.e.vhall.com/common-static/images/mp/announ_icon.png" mode="aspectFit" />
6
+ <text wx:if="{{mode !== 1}}" class="announ-icon-text">公告</text>
7
+ <view wx:if="{{badge > 0}}" class="announ-icon-badge">{{badge > 99 ? '99+' : badge}}</view>
8
+ </view>
9
+ </movable-view>
10
+ </movable-area>
@@ -0,0 +1 @@
1
+ .drag-area{height:100vh;left:0;pointer-events:none;position:fixed;top:0;width:100vw;z-index:100}.drag-view{height:96rpx;pointer-events:auto;top:10rpx;width:96rpx}.announ-icon{align-items:center;background:rgba(0,0,0,.4);border-radius:50%;display:flex;flex-direction:column;height:84rpx;justify-content:center;position:relative;width:84rpx}.announ-icon.mode-1{height:70rpx;width:70rpx}.announ-icon-img{height:64rpx;width:64rpx}.mode-1 .announ-icon-img{height:70rpx;width:70rpx}.announ-icon-text{color:#fff;font-size:24rpx;margin-top:-20rpx}.announ-icon-badge{align-items:center;background:#ff3b30;border:2rpx solid #fff;border-radius:16rpx;box-sizing:border-box;color:#fff;display:flex;font-size:20rpx;font-weight:700;height:32rpx;justify-content:center;min-width:32rpx;padding:0 8rpx;position:absolute;right:-8rpx;top:-8rpx}
@@ -0,0 +1,44 @@
1
+ Component({
2
+ properties: {
3
+ show: { type: Boolean, value: false },
4
+ // 公告信息(store.announcement)
5
+ info: { type: Object, value: null },
6
+ },
7
+
8
+ data: {
9
+ list: [],
10
+ },
11
+
12
+ observers: {
13
+ info: function (info) {
14
+ const rawList = info?.list || [];
15
+ this.setData({
16
+ list: rawList.map((item) => ({
17
+ ...item,
18
+ _text: this.getContentText(item.content),
19
+ _time: this.formatTime(item.created_at),
20
+ })),
21
+ });
22
+ },
23
+ },
24
+
25
+ methods: {
26
+ getContentText(content) {
27
+ if (!content) return '';
28
+ if (typeof content === 'string') return content;
29
+ return content.content || content.text || '';
30
+ },
31
+
32
+ formatTime(time) {
33
+ if (!time) return '';
34
+ if (typeof time !== 'string') return '';
35
+ // 将 "2026-08-07 16:47:13" 截断为 "08-07 16:47"
36
+ const matched = time.match(/\d{4}-(\d{2}-\d{2})\s+(\d{2}:\d{2}):\d{2}/);
37
+ return matched ? `${matched[1]} ${matched[2]}` : time;
38
+ },
39
+
40
+ onClose() {
41
+ this.triggerEvent('close');
42
+ },
43
+ },
44
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "shared",
4
+ "usingComponents": {
5
+ "vh-popup": "../../../pubComponents/popup/popup"
6
+ }
7
+ }
@@ -0,0 +1,39 @@
1
+ <vh-popup
2
+ wx:if="{{show}}"
3
+ visible="{{show}}"
4
+ showClose="{{true}}"
5
+ mode="bottom"
6
+ hasMask="{{true}}"
7
+ maskClosable="{{true}}"
8
+ bgColor="#f7f7f7"
9
+ bind:close="onClose"
10
+ >
11
+ <view class="announ-popup">
12
+ <view class="announ-popup-header">
13
+ <text class="announ-popup-star left">✦</text>
14
+ <text class="announ-popup-title">公告</text>
15
+ <text class="announ-popup-star right">✦</text>
16
+ </view>
17
+ <scroll-view class="announ-popup-list" scroll-y>
18
+ <view class="announ-items">
19
+ <view
20
+ wx:for="{{list}}"
21
+ wx:key="id"
22
+ class="announ-item {{index === list.length - 1 ? 'last' : ''}} {{index === 0 ? 'first' : ''}}"
23
+ >
24
+ <view class="announ-item-timeline">
25
+ <view class="announ-item-line announ-item-line-top"></view>
26
+ <view class="announ-item-dot-outer">
27
+ <view class="announ-item-dot-inner"></view>
28
+ </view>
29
+ <view class="announ-item-line announ-item-line-bottom"></view>
30
+ </view>
31
+ <view class="announ-item-content">
32
+ <view class="announ-item-text">{{item._text}}</view>
33
+ <view class="announ-item-time">{{item._time}}</view>
34
+ </view>
35
+ </view>
36
+ </view>
37
+ </scroll-view>
38
+ </view>
39
+ </vh-popup>
@@ -0,0 +1 @@
1
+ .popup-body{display:flex}.announ-popup{background:#f7f7f7;box-sizing:border-box;display:flex;flex-direction:column;height:50vh;width:100%}.announ-popup-header{align-items:center;display:flex;justify-content:center;margin-bottom:32rpx;position:relative}.announ-popup-title{color:#1a1a1a;font-size:36rpx;font-weight:600;position:relative}.announ-popup-title:after{background:#ff4d4f;border-radius:4rpx;bottom:-4rpx;content:"";height:8rpx;left:50%;opacity:.6;position:absolute;transform:translateX(-50%);width:60%}.announ-popup-star{color:#ffcd00;font-size:24rpx;margin:0 12rpx}.announ-popup-star.left{transform:rotate(-15deg)}.announ-popup-star.right{transform:rotate(15deg)}.announ-popup-list{box-sizing:border-box;flex:1;min-height:0;overflow:hidden}.announ-popup-items{position:relative}.announ-popup-line{border-left:1rpx dashed #bfbfbf;left:50%;position:absolute;transform:translateX(-50%);width:0}.announ-item{display:flex;padding-bottom:32rpx;padding-left:48rpx;position:relative}.announ-item-timeline{border-left:1rpx dashed #bfbfbf;bottom:0;left:16rpx;position:absolute;top:0;width:24rpx}.announ-item-dot-outer{align-items:center;background:#fff;border:3rpx solid #ccc;border-radius:50%;box-sizing:border-box;display:flex;flex-shrink:0;height:28rpx;justify-content:center;left:-14rpx;position:absolute;top:0;width:28rpx;z-index:1}.announ-item-dot-inner{background:#ccc;border-radius:50%;box-sizing:border-box;height:12rpx;width:12rpx}.announ-item.first .announ-item-dot-outer{background:#fff;border-color:#fb2626}.announ-item.first .announ-item-dot-inner{background:#fb2626}.announ-item.last{padding-bottom:0}.announ-item.last .announ-item-timeline{border:none}.announ-item-content{background:#fff;border-radius:12rpx;flex:1;padding:20rpx 24rpx}.announ-item-text{color:#1a1a1a;font-size:28rpx;line-height:1.5;margin-bottom:12rpx;word-break:break-all}.announ-item-time{color:#999;font-size:24rpx}
@@ -0,0 +1,223 @@
1
+ Component({
2
+ properties: {
3
+ sdkInstance: {
4
+ type: Object,
5
+ value: null,
6
+ },
7
+ // 直播间模式:0=竖屏,1=横屏(样式区分)
8
+ mode: {
9
+ type: Number,
10
+ value: 0,
11
+ },
12
+ },
13
+
14
+ data: {
15
+ announInfo: null, // 公告信息
16
+ showIcon: false, // 是否显示公告小图标
17
+ showPopup: false, // 是否显示公告弹窗
18
+ showBanner: false, // 是否显示公告banner
19
+ bannerText: '', // 公告banner文本
20
+ isStatic: false, // 公告banner内容是否静止不滚动(display_type==1)
21
+ curNoticeId: '', // 当前展示的公告 id
22
+ badge: 0, // 公告未读角标数字(收到推送 +1,打开弹窗清零)
23
+ },
24
+
25
+ observers: {
26
+ sdkInstance: function sdkInstance(_sdkInstance) {
27
+ if (!_sdkInstance) return;
28
+ this.sdkInstance = _sdkInstance;
29
+
30
+ this.unsub = this.sdkInstance.store.get({
31
+ announcement: (val) => {
32
+ // 初始化 / 配置刷新时,按公告数据统一处理展示逻辑
33
+ const watchInitData = this.sdkInstance.store.get('watchInitData');
34
+ const webinarType = watchInitData?.webinar?.type;
35
+ // 不是直播也不是回放,不处理
36
+ if (webinarType != 1 && webinarType != 5) return;
37
+
38
+ this.applyAnnouncement(val);
39
+ },
40
+ });
41
+
42
+ this.initEventListener();
43
+ },
44
+ },
45
+
46
+ lifetimes: {
47
+ detached() {
48
+ this.unsub && this.unsub();
49
+ this.unlistener?.();
50
+ this.clearTimer();
51
+ },
52
+ },
53
+
54
+ methods: {
55
+ initEventListener() {
56
+ this.unlistener?.();
57
+ const EVENT = this.sdkInstance.EVENT;
58
+ const obj = {};
59
+ // 推送公告:把消息字段转成与接口列表一致的格式,插入到列表首位(去重同 id),保留已有公告
60
+ obj[EVENT.ROOM_ANNOUNCEMENT] = (msg) => {
61
+ const data = msg?.data || msg;
62
+ if (!data) return;
63
+ // display_type: 0
64
+ // duration: 600
65
+ // event_type: "room_announcement"
66
+ // id: "364"
67
+ // push_time: "2026-08-10 16:39:55"
68
+ // record_display: 1
69
+ // room_announcement_text: "zxcfzxcxzc"
70
+ // room_id: "lss_1d63a879"
71
+ // room_join_id: "101643"
72
+ // set_type: 1
73
+ // type: "room_announcement"
74
+
75
+ const item = {
76
+ id: data.id,
77
+ content: data.room_announcement_text ? { content: data.room_announcement_text } : data.content || '',
78
+ duration: data.duration,
79
+ display_type: data.display_type,
80
+ create_at: data.push_time,
81
+ record_display: data.record_display,
82
+ set_type: data.set_type
83
+ };
84
+ const prev = this.sdkInstance.store.get('announcement') || {};
85
+ const list = (prev.list || []).filter((it) => it.id !== item.id);
86
+ list.unshift(item);
87
+
88
+ this.sdkInstance.store.set('announcement', { list, total: list.length });
89
+ // 收到推送公告,未读角标 +1
90
+ this.setData({ badge: this.data.badge + 1 });
91
+ };
92
+
93
+ // 公告删除:从列表中移除该公告,store 变化会自动触发 applyAnnouncement 重新渲染并关闭 banner
94
+ obj[EVENT.DEL_ROOM_ANNOUNCEMENT] = (msg) => {
95
+ const delId = msg?.data?.id ?? msg?.id;
96
+ if (!delId) return;
97
+ const prev = this.sdkInstance.store.get('announcement') || {};
98
+ const list = (prev.list || []).filter((it) => it.id !== delId);
99
+ this.sdkInstance.store.set('announcement', { list, total: list.length });
100
+ };
101
+
102
+ // 直播结束:自动关闭公告
103
+ obj[EVENT.LIVE_END] = () => {
104
+ this.closeAnnouncement();
105
+ };
106
+
107
+ this.unlistener = this.sdkInstance.on(obj);
108
+ },
109
+
110
+ /**
111
+ * 根据公告数据统一处理展示逻辑(store / common-config 回调)
112
+ * - 列表为空:关闭 banner
113
+ * - 否则取第一条公告 item 交给统一调度
114
+ */
115
+ applyAnnouncement(announ) {
116
+ this.setData({ announInfo: announ });
117
+
118
+ const list = announ?.list || [];
119
+ const total = announ?.total || 0;
120
+
121
+ if (total < 1) {
122
+ this.clearTimer();
123
+ this.setData({
124
+ showIcon: false,
125
+ showBanner: false,
126
+ isStatic: false,
127
+ curNoticeId: '',
128
+ bannerText: '',
129
+ });
130
+ return;
131
+ }
132
+
133
+ // 取第一条公告 item 交给统一调度
134
+ this.scheduleBanner(list[0] || {});
135
+ },
136
+
137
+ /**
138
+ * 统一调度 banner 展示与定时关闭
139
+ * - content: {content: "sdfdsfsdfsdfdsfdsfdsf"}
140
+ * - created_at: "2026-08-10 16:22:52" // 公告创建四时间
141
+ * - display_type:0 // 0 滚动, 1:不滚动
142
+ * - duration: 300 剩余时长,0:永久展示 ; >0 按剩余时长展示,超时自动关闭
143
+ * - id: "363" //公告id
144
+ * - set_type: 1
145
+ */
146
+ scheduleBanner(item = {}) {
147
+ const id = item.id;
148
+ const duration = Number(item.duration) || 0;
149
+ const displayType = Number(item.display_type);
150
+ const createAt = item.created_at;
151
+ const text = typeof item.content === 'string' ? item.content : item.content?.content || item.content?.text || '';
152
+
153
+ this.clearTimer();
154
+
155
+ this.setData({
156
+ showIcon: true,
157
+ isStatic: displayType === 1,
158
+ curNoticeId: id,
159
+ bannerText: text,
160
+ });
161
+
162
+ // duration < 1:永久展示
163
+ if (duration < 1) {
164
+ this.setData({ showBanner: true });
165
+ return;
166
+ }
167
+
168
+ // 计算剩余展示时长
169
+ const now = Date.now();
170
+ const createTs = this.parseTime(createAt);
171
+ // pushTime 解析失败则视为从当前时刻开始计时,避免 NaN 导致不展示
172
+ const remain = isNaN(createTs) ? duration * 1000 : duration * 1000 - (now - createTs);
173
+
174
+ if (remain > 0) {
175
+ this.setData({ showBanner: true });
176
+ this.announTimer = setTimeout(() => {
177
+ this.setData({ showBanner: false });
178
+ this.announTimer = null;
179
+ }, remain);
180
+ } else {
181
+ this.setData({ showBanner: false });
182
+ }
183
+ },
184
+
185
+ // 解析时间字符串为时间戳(兼容 iOS:将 "2026-08-07 16:47:13" 转为 "2026/08/07 16:47:13")
186
+ parseTime(str) {
187
+ if (!str) return NaN;
188
+ const s = String(str).replace(/-/g, '/');
189
+ const t = new Date(s).getTime();
190
+ return isNaN(t) ? NaN : t;
191
+ },
192
+
193
+ // 清除展示定时器
194
+ clearTimer() {
195
+ if (this.announTimer) {
196
+ clearTimeout(this.announTimer);
197
+ this.announTimer = null;
198
+ }
199
+ },
200
+
201
+ // 被动关闭(删除/直播结束):关闭当前 banner
202
+ closeAnnouncement() {
203
+ this.clearTimer();
204
+ this.setData({ showBanner: false });
205
+ },
206
+
207
+ // 用户手动关闭当前 banner
208
+ handleBannerClose() {
209
+ this.clearTimer();
210
+ this.setData({ showBanner: false });
211
+ },
212
+
213
+ // 点击公告图标:打开弹窗并清零未读角标
214
+ handleIconClick() {
215
+ this.setData({ showPopup: true, badge: 0 });
216
+ },
217
+
218
+ // 关闭公告弹窗
219
+ handlePopupClose() {
220
+ this.setData({ showPopup: false });
221
+ },
222
+ },
223
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "shared",
4
+ "usingComponents": {
5
+ "announ-icon": "./announ-icon/index",
6
+ "announ-popup": "./announ-popup/index",
7
+ "announ-banner": "./announ-banner/index"
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ <!-- 公告顶部滚动条 -->
2
+ <announ-banner show="{{showBanner}}" text="{{bannerText}}" static="{{isStatic}}" mode="{{mode}}" bind:close="handleBannerClose" />
3
+ <!-- 公告浮动小图标 -->
4
+ <announ-icon wx:if="{{showIcon}}" showIcon="{{showIcon}}" badge="{{badge}}" mode="{{mode}}" bind:onTapIcon="handleIconClick" />
5
+ <!-- 公告弹窗 -->
6
+ <announ-popup show="{{showPopup}}" info="{{announInfo}}" bind:close="handlePopupClose" />
File without changes
@@ -1,3 +1,5 @@
1
+ import { reloadCurrentPage } from '../../shared/mpUtils.js';
2
+
1
3
  Component({
2
4
  properties: {
3
5
  sdkInstance: {
@@ -9,6 +11,7 @@ Component({
9
11
  tabList: [],
10
12
  activeTab: 0,
11
13
  liveInfo: null,
14
+ isReady: false, // SDK 是否已初始化完成
12
15
  showStartDialog: false, // 是否显示开始直播弹窗
13
16
 
14
17
  showRecordWatchExpireDialog: false, // 是否显示回放视频已过观看有效期提示框
@@ -31,6 +34,9 @@ Component({
31
34
  liveInfo: val,
32
35
  });
33
36
  },
37
+ isReady: (val) => {
38
+ this.setData({ isReady: !!val });
39
+ },
34
40
  webinarStatus: (val) => {
35
41
  this.setData({
36
42
  webinarStatus: val,
@@ -124,7 +130,7 @@ Component({
124
130
  this.setData({
125
131
  showStartDialog: false,
126
132
  });
127
- this.sdkInstance.init();
133
+ reloadCurrentPage();
128
134
  },
129
135
  handleExprie() {
130
136
  this.sdkInstance._handleBizErrorCode(512912)
@@ -13,6 +13,7 @@
13
13
  "vhall-product-order": "../product/product-order/product-order",
14
14
  "vhall-lottery": "../lottery/lottery",
15
15
  "vhall-mall-reward":"../mall-reward/index",
16
+ "vhall-announcement":"../announcement/index",
16
17
  "start-countdown": "../../pubComponents/countdown/countdown",
17
18
  "signin-dialog": "../signin/signin-dialog",
18
19
  "my-invite-detail": "../invite-rank/my-invite-detail",
@@ -31,6 +32,7 @@
31
32
  "vhall-gift-sender": "view",
32
33
  "vhall-lottery": "view",
33
34
  "vhall-mall-reward":"view",
35
+ "vhall-announcement":"view",
34
36
  "vhall-invite-rank": "view"
35
37
  },
36
38
  "permissiion": {
@@ -1,4 +1,5 @@
1
- <view class="live_landscape" wx:if="{{!errorResp || !errorResp.code}}">
1
+ <error-page wx:if="{{errorResp && errorResp.code}}" errorResp="{{errorResp}}"></error-page>
2
+ <view class="live_landscape" wx:elif="{{isReady}}">
2
3
  <view class="live_landscape_player">
3
4
  <vhall-player sdkInstance="{{sdkInstance}}"></vhall-player>
4
5
  </view>
@@ -66,8 +67,10 @@
66
67
  <vh-popup visible="{{showOrder}}" mode="bottom" maskClosable="true" bind:close="closeOrder">
67
68
  <vhall-product-order show="{{showOrder}}" product="{{selectedProduct}}" sdkInstance="{{sdkInstance}}" bind:close="closeOrder" bind:paySuccess="onPaySuccess" />
68
69
  </vh-popup>
69
- <vhall-lottery sdkInstance="{{sdkInstance}}" />
70
+ <!-- 抽奖组件 -->
71
+ <vhall-lottery wx:if="{{webinarStatus==1}}" sdkInstance="{{sdkInstance}}" />
70
72
  <!-- 商城奖励 -->
71
- <vhall-mall-reward sdkInstance="{{sdkInstance}}" />
73
+ <vhall-mall-reward wx:if="{{webinarStatus==1||webinarStatus==5}}" sdkInstance="{{sdkInstance}}" />
74
+ <!-- 公告组件 -->
75
+ <vhall-announcement wx:if="{{webinarStatus==1||webinarStatus==5}}" sdkInstance="{{sdkInstance}}" mode="{{1}}" />
72
76
  </view>
73
- <error-page wx:else errorResp="{{errorResp}}"></error-page>
@@ -1,3 +1,5 @@
1
+ import { reloadCurrentPage } from '../../shared/mpUtils.js';
2
+
1
3
  Component({
2
4
  properties: {
3
5
  sdkInstance: {
@@ -9,6 +11,7 @@ Component({
9
11
  tabList: [],
10
12
  activeTab: 0,
11
13
  liveInfo: null,
14
+ isReady: false, // SDK 是否已初始化完成
12
15
  showStartDialog: false, // 是否显示开始直播弹窗
13
16
 
14
17
  showRecordWatchExpireDialog: false, // 是否显示回放视频已过观看有效期提示框
@@ -42,6 +45,9 @@ Component({
42
45
  liveInfo: val,
43
46
  });
44
47
  },
48
+ isReady: (val) => {
49
+ this.setData({ isReady: !!val });
50
+ },
45
51
  webinarStatus: (val) => {
46
52
  this.setData({
47
53
  webinarStatus: val,
@@ -148,7 +154,8 @@ Component({
148
154
  this.setData({
149
155
  showStartDialog: false,
150
156
  });
151
- this.sdkInstance.init();
157
+ reloadCurrentPage();
158
+
152
159
  },
153
160
  handleExprie() {
154
161
  this.sdkInstance._handleBizErrorCode(512912)
@@ -279,4 +286,5 @@ Component({
279
286
  // 支付成功后的处理
280
287
  },
281
288
  },
282
- });
289
+ });
290
+
@@ -13,6 +13,7 @@
13
13
  "vhall-product-order": "../product/product-order/product-order",
14
14
  "vhall-lottery": "../lottery/lottery",
15
15
  "vhall-mall-reward": "../mall-reward/index",
16
+ "vhall-announcement": "../announcement/index",
16
17
  "product-detail": "../product/product-detail/product-detail",
17
18
  "start-countdown": "../../pubComponents/countdown/countdown",
18
19
  "signin-dialog": "../signin/signin-dialog",
@@ -33,6 +34,7 @@
33
34
  "signin-dialog": "view",
34
35
  "vhall-gift-sender": "view",
35
36
  "vhall-lottery": "view",
36
- "vhall-invite-rank": "view"
37
+ "vhall-invite-rank": "view",
38
+ "vhall-announcement": "view"
37
39
  }
38
40
  }