vhall-mini-sdk 0.2.0 → 0.3.0
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/README.md +106 -11
- package/lib/components/announcement/announ-banner/index.js +40 -0
- package/lib/components/announcement/announ-banner/index.json +4 -0
- package/lib/components/announcement/announ-banner/index.wxml +13 -0
- package/lib/components/announcement/announ-banner/index.wxss +1 -0
- package/lib/components/announcement/announ-icon/index.js +69 -0
- package/lib/components/announcement/announ-icon/index.json +3 -0
- package/lib/components/announcement/announ-icon/index.wxml +10 -0
- package/lib/components/announcement/announ-icon/index.wxss +1 -0
- package/lib/components/announcement/announ-popup/index.js +44 -0
- package/lib/components/announcement/announ-popup/index.json +7 -0
- package/lib/components/announcement/announ-popup/index.wxml +39 -0
- package/lib/components/announcement/announ-popup/index.wxss +1 -0
- package/lib/components/announcement/index.js +228 -0
- package/lib/components/announcement/index.json +9 -0
- package/lib/components/announcement/index.wxml +6 -0
- package/lib/components/announcement/index.wxss +0 -0
- package/lib/components/chat/chat-item/chat-item.js +5 -5
- package/lib/components/live-landscape/live-landscape.js +7 -1
- package/lib/components/live-landscape/live-landscape.json +2 -0
- package/lib/components/live-landscape/live-landscape.wxml +7 -4
- package/lib/components/live-portrait/live-portrait.js +10 -2
- package/lib/components/live-portrait/live-portrait.json +3 -1
- package/lib/components/live-portrait/live-portrait.wxml +8 -4
- package/lib/components/mall-reward/index.js +47 -29
- package/lib/components/product/product-card/product-card.js +5 -6
- package/lib/components/product/product-detail/product-detail.js +6 -6
- package/lib/components/product/product-list/product-list.js +2 -3
- package/lib/sdk/main/index.js +2 -2
- package/lib/sdk/plugins/MallRewardServer.js +1 -1
- package/package.json +6 -10
package/README.md
CHANGED
|
@@ -8,43 +8,138 @@
|
|
|
8
8
|
|
|
9
9
|
## 快速开始
|
|
10
10
|
|
|
11
|
-
1. 安装
|
|
11
|
+
### 1. 安装 SDK 组件
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
# npm安装
|
|
14
|
+
# npm 安装
|
|
15
15
|
npm install vhall-mini-sdk
|
|
16
16
|
|
|
17
|
-
# yarn安装
|
|
17
|
+
# yarn 安装
|
|
18
18
|
yarn add vhall-mini-sdk
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
uni-app
|
|
21
|
+
> **uni-app 开发注意**:安装完成后,会在当前工程目录下自动生成 `wxcomponents/vhall-mini-sdk` 目录。
|
|
22
22
|
|
|
23
|
-
2. 构建npm
|
|
23
|
+
### 2. 构建 npm(仅原生微信小程序需要)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
在微信开发者工具菜单栏点击 **工具 → 构建 npm**,执行后生成 `miniprogram_npm/vhall-mini-sdk` 目录。
|
|
26
26
|
|
|
27
|
-
3. SDK
|
|
27
|
+
### 3. 引入 SDK
|
|
28
28
|
|
|
29
|
-
(1) 原生微信小程序开发
|
|
29
|
+
#### (1) 原生微信小程序开发
|
|
30
|
+
|
|
31
|
+
支持 `require` 和 `import` 两种方式引入:
|
|
30
32
|
|
|
31
33
|
```js
|
|
34
|
+
// require方式
|
|
32
35
|
const VhallMiniSdk = require('vhall-mini-sdk/sdk/main/index.js');
|
|
33
36
|
const CustomMenuServer = require('vhall-mini-sdk/sdk/plugins/CustomMenuServer.js');
|
|
34
37
|
const ChatServer = require('vhall-mini-sdk/sdk/plugins/ChatServer.js');
|
|
35
38
|
const ProductServer = require('vhall-mini-sdk/sdk/plugins/ProductServer.js');
|
|
36
39
|
const InteractServer = require('vhall-mini-sdk/sdk/plugins/InteractServer.js');
|
|
40
|
+
const LotteryServer = require('vhall-mini-sdk/sdk/plugins/LotteryServer.js');
|
|
41
|
+
const MallRewardServer = require('vhall-mini-sdk/sdk/plugins/MallRewardServer.js');
|
|
42
|
+
const InviteCardServer = require('vhall-mini-sdk/sdk/plugins/InviteCardServer.js');
|
|
43
|
+
|
|
44
|
+
// import方式
|
|
45
|
+
import VhallMiniSdk from 'vhall-mini-sdk/sdk/main/index.js';
|
|
46
|
+
import ChatServer from 'vhall-mini-sdk/sdk/plugins/ChatServer.js';
|
|
47
|
+
import CustomMenuServer from 'vhall-mini-sdk/sdk/plugins/CustomMenuServer.js';
|
|
48
|
+
import InteractServer from 'vhall-mini-sdk/sdk/plugins/InteractServer.js';
|
|
49
|
+
import InviteCardServer from 'vhall-mini-sdk/sdk/plugins/InviteCardServer.js';
|
|
50
|
+
import LotteryServer from 'vhall-mini-sdk/sdk/plugins/LotteryServer.js';
|
|
51
|
+
import MallRewardServer from 'vhall-mini-sdk/sdk/plugins/MallRewardServer.js';
|
|
52
|
+
import ProductServer from 'vhall-mini-sdk/sdk/plugins/ProductServer.js';
|
|
37
53
|
```
|
|
38
54
|
|
|
39
|
-
(2) uni-app小程序开发
|
|
55
|
+
#### (2) uni-app 小程序开发
|
|
56
|
+
|
|
57
|
+
> **⚠️ 重点:必须用 `require` 引入,不能用 `import`**
|
|
58
|
+
>
|
|
59
|
+
> `wxcomponents` 目录专门存放“原生小程序组件/库”,uni-app 为保证这些原生代码的纯净性与兼容性,会将其排除在编译流程之外。因此**不能用 Web 标准的 `import` 读取**(其 `export default` 未被编译成可执行代码),必须降级使用小程序标准的 `require` 加载。
|
|
40
60
|
|
|
41
61
|
```js
|
|
42
|
-
import { ref, onMounted } from 'vue';
|
|
62
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
63
|
+
// wxcomponents目录是专门用来存放“原生小程序组件/库”的,uni-app为了保证这些原生代码的纯净性和兼容性,特意把它们排除在编译流程之外。
|
|
64
|
+
// 所以,我们不能用 Web 标准的 import 去读取它(因为 export default 没被转换成任何可执行代码),你必须降级使用小程序标准的 require 来加载它。
|
|
43
65
|
const VhallMiniSdk = require('../../wxcomponents/vhall-mini-sdk/sdk/main/index.js');
|
|
66
|
+
// 下面的Server插件可按需加载
|
|
44
67
|
const CustomMenuServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/CustomMenuServer.js');
|
|
45
68
|
const ChatServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/ChatServer.js');
|
|
46
69
|
const ProductServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/ProductServer.js');
|
|
47
70
|
const InteractServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/InteractServer.js');
|
|
71
|
+
const LotteryServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/LotteryServer.js');
|
|
72
|
+
const MallRewardServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/MallRewardServer.js');
|
|
73
|
+
const InviteCardServer = require('../../wxcomponents/vhall-mini-sdk/sdk/plugins/InviteCardServer.js');
|
|
74
|
+
|
|
75
|
+
// 布尔值用ref包裹,.value修改值
|
|
76
|
+
const sdkReady = ref(false);
|
|
77
|
+
// SDK实例不需要响应式,普通变量即可
|
|
78
|
+
let sdkInstance = null;
|
|
79
|
+
let unListener =null;
|
|
80
|
+
async function initSdk() {
|
|
81
|
+
const options = {
|
|
82
|
+
app_key: '',
|
|
83
|
+
email: '',
|
|
84
|
+
sign: '',
|
|
85
|
+
signedat: ,
|
|
86
|
+
username: '',
|
|
87
|
+
webinar_id: '',
|
|
88
|
+
head: 'https://cnstatic01.e.vhall.com/common-static/images/default_avatar.png',
|
|
89
|
+
modules: [
|
|
90
|
+
CustomMenuServer,
|
|
91
|
+
ChatServer,
|
|
92
|
+
ProductServer,
|
|
93
|
+
InteractServer,
|
|
94
|
+
LotteryServer,
|
|
95
|
+
MallRewardServer,
|
|
96
|
+
InviteCardServer,
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// 创建实例
|
|
102
|
+
sdkInstance = new VhallMiniSdk(options);
|
|
103
|
+
|
|
104
|
+
// 事件监听
|
|
105
|
+
unListener = sdkInstance.on({
|
|
106
|
+
[VhallMiniSdk.EVENT.NET_STATE_CHANGE]: (state) => {
|
|
107
|
+
console.log('网络状态变化', state);
|
|
108
|
+
},
|
|
109
|
+
[VhallMiniSdk.EVENT.SDK_READY]: () => {
|
|
110
|
+
console.log('initLiveRoom is ready');
|
|
111
|
+
},
|
|
112
|
+
[VhallMiniSdk.EVENT.INIT_FAILED]: (error) => {
|
|
113
|
+
console.error('initLiveRoom is failed', error);
|
|
114
|
+
},
|
|
115
|
+
[VhallMiniSdk.EVENT.ERROR]: (err) => {
|
|
116
|
+
console.error('VhallMiniSdk error:', JSON.stringify(err));
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 初始化直播间
|
|
121
|
+
await sdkInstance.init();
|
|
122
|
+
|
|
123
|
+
if (sdkInstance.isReady()) {
|
|
124
|
+
// 关键:ref变量修改必须加 .value
|
|
125
|
+
sdkReady.value = true;
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.warn('VhallMiniSdk init failed', error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
initSdk();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
onUnmounted(() => {
|
|
137
|
+
unListener?.();
|
|
138
|
+
sdkInstance?.destroy();
|
|
139
|
+
sdkInstance = null;
|
|
140
|
+
});
|
|
48
141
|
```
|
|
49
142
|
|
|
50
|
-
4.
|
|
143
|
+
### 4. 更多接入支持
|
|
144
|
+
|
|
145
|
+
参考文档:<https://vhall.apifox.cn/dev/sdk/wmp/quick-start>
|
|
@@ -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,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">✕</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;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,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://cnstatic01.e.vhall.com/common-static/images/mp/announ_icon_2.png" mode="aspectFit" />
|
|
5
|
+
<image wx:else class="announ-icon-img" src="https://cnstatic01.e.vhall.com/common-static/images/mp/announ_icon_1.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,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,228 @@
|
|
|
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:记录该公告 id,同一条不再自动弹(新公告仍展示)
|
|
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
|
+
// 关闭顶部滚动条(用户手动关闭)
|
|
224
|
+
handleBannerClose() {
|
|
225
|
+
this.closeAnnouncement();
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
@@ -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
|