feat: 电子药箱小程序 - 4Tab药品管理
- 电子药箱: 药品库存管理 + 手动录入 + 今日用药打卡 - 药品百科: 搜索 + 分类筛选 + 20种药品静态数据 - 惠教中心: 4条药品使用指南课程 - 我的: 家庭成员信息管理 - 自定义TabBar + navigation-bar组件 - SVG药品分类插图
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"backgroundColor": "#0a1220",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"volc-live-portrait": "../volc-mini-sdk/components/volc-live-portrait/volc-live-portrait",
|
||||
"volc-live-landscape": "../volc-mini-sdk/components/volc-live-landscape/volc-live-landscape"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/* pages/live/wxroom/index.wxss */
|
||||
page {
|
||||
height: 100%;
|
||||
background: #081120;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wxroom {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 18% 12%, rgba(83, 214, 255, 0.14), transparent 28%),
|
||||
radial-gradient(circle at 88% 18%, rgba(47, 140, 255, 0.2), transparent 24%),
|
||||
linear-gradient(180deg, #0d1b31 0%, #09121f 52%, #050b15 100%);
|
||||
}
|
||||
|
||||
.wxroom__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
background: rgba(4, 10, 18, 0.72);
|
||||
}
|
||||
|
||||
.wxroom__panel {
|
||||
width: 100%;
|
||||
max-width: 620rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
border-radius: 28rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(19, 31, 54, 0.96) 0%,
|
||||
rgba(10, 18, 33, 0.98) 100%
|
||||
);
|
||||
border: 1rpx solid rgba(115, 191, 255, 0.18);
|
||||
box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.28);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wxroom__panel--error {
|
||||
border-color: rgba(255, 138, 138, 0.24);
|
||||
box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
.wxroom__spinner {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
margin: 0 auto 28rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: #71c1ff;
|
||||
animation: wxroom-spin 0.9s linear infinite;
|
||||
}
|
||||
|
||||
.wxroom__title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
color: #f4f8ff;
|
||||
}
|
||||
|
||||
.wxroom__message {
|
||||
margin-top: 20rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.7;
|
||||
color: rgba(233, 242, 255, 0.8);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.wxroom__actions {
|
||||
display: flex;
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.wxroom__button {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #f4f8ff;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.wxroom__button + .wxroom__button {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.wxroom__button--primary {
|
||||
background: linear-gradient(135deg, #53d6ff 0%, #2f8cff 100%);
|
||||
box-shadow: 0 16rpx 36rpx rgba(47, 140, 255, 0.24);
|
||||
}
|
||||
|
||||
.wxroom__button--secondary {
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
@keyframes wxroom-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// package-live/wxroom/index.ts
|
||||
import VolcMiniSdk, { EVENTS } from "../volc-mini-sdk/index";
|
||||
import { request } from "../../utils/request";
|
||||
import { appId } from "../../env";
|
||||
import { aesDecrypt, aesEncrypt } from "../../utils/crypto";
|
||||
import { AES_KEY, AES_IV } from "../../env";
|
||||
import { receiveRedPacket, lockPromise } from "../../utils/util";
|
||||
const WXROOM_REDIRECT_PATH_KEY = "wxroomRedirectPath";
|
||||
const WXROOM_PAGE_PATH = "/package-live/wxroom/index";
|
||||
|
||||
const isMobileEnvironment = (): boolean => {
|
||||
const platform = wx.getSystemInfoSync().platform?.toLowerCase() || '';
|
||||
return platform !== 'windows' && platform !== 'mac';
|
||||
};
|
||||
|
||||
let pageOptionsCache: Record<string, string | undefined> = {};
|
||||
Page({
|
||||
data: {
|
||||
sdk: null as any,
|
||||
activityId: 0,
|
||||
loading: false,
|
||||
loadingText: "",
|
||||
showError: false,
|
||||
errorTitle: "",
|
||||
errorMessage: "",
|
||||
inviterToken: "",
|
||||
msg: "",
|
||||
userId: "",
|
||||
isLandscape: false,
|
||||
},
|
||||
|
||||
async onLoad(options: Record<string, string | undefined>) {
|
||||
pageOptionsCache = options || {};
|
||||
const activityId = this.getActivityId(options);
|
||||
if (!activityId) {
|
||||
this.handleEnterError(
|
||||
"缺少直播间ID,请重新从正确的直播链接进入。",
|
||||
"无法进入直播间",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMobileEnvironment()) {
|
||||
this.handleEnterError(
|
||||
"当前环境不支持观看直播,请使用手机观看。",
|
||||
"环境不支持",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cacheRedirectPath(options);
|
||||
this.setData({ activityId, inviterToken: options.inviterToken, userId: options.userId, isLandscape: String(options.isLandscape) === "true", });
|
||||
const app = getApp<IAppOption>();
|
||||
this.setLoading("正在校验登录状态...");
|
||||
const isLogin = app.checkLoginState();
|
||||
if (!isLogin) {
|
||||
wx.reLaunch({ url: `/package-live/login/login?userId=${options?.userId}&activityId=${activityId}` });
|
||||
return;
|
||||
};
|
||||
},
|
||||
onReady() {
|
||||
// onReady在页面生命周期中只执行一次,将初始化放在此处避免
|
||||
// onHide销毁SDK后onShow重复初始化导致声音重复播放
|
||||
this.initSdk(this.data.activityId, pageOptionsCache);
|
||||
},
|
||||
onShow() {
|
||||
if (!this.data.sdk && this.data.activityId) {
|
||||
this.initSdk(this.data.activityId, pageOptionsCache);
|
||||
}
|
||||
},
|
||||
|
||||
async initSdk(
|
||||
activityId: number,
|
||||
options?: Record<string, string | undefined>,
|
||||
) {
|
||||
try {
|
||||
this.clearError();
|
||||
this.setLoading("正在获取直播凭证...");
|
||||
|
||||
const signToken = await this.fetchSignToken();
|
||||
if (!signToken) {
|
||||
wx.redirectTo({
|
||||
url: `/package-live/login/login?userId=${options?.userId}&activityId=${activityId}&show=${true}&msg=${this.data.msg}`
|
||||
})
|
||||
throw new Error("未获取到直播凭证,请稍后重试");
|
||||
}
|
||||
|
||||
this.destroySdk();
|
||||
this.setLoading("正在进入直播间...");
|
||||
|
||||
const sdk = VolcMiniSdk({
|
||||
activityId,
|
||||
signToken,
|
||||
mode: Number(options?.mode || 2) || 2,
|
||||
} as any);
|
||||
|
||||
sdk.on(EVENTS.error, (err: any) => {
|
||||
console.log("sdk error", err?.code, err?.message);
|
||||
this.handleEnterError(
|
||||
err?.message || "直播间加载失败,请稍后重试",
|
||||
"直播间加载失败",
|
||||
);
|
||||
});
|
||||
const debugInfo = sdk.getDebugInfo()
|
||||
console.log('debugInfo:', debugInfo)
|
||||
sdk.on(EVENTS.card.click, (payload: any) => {
|
||||
wx.navigateTo({
|
||||
url: payload?.url
|
||||
})
|
||||
});
|
||||
// 监听浮窗商品卡片点击事件。
|
||||
sdk.on('floatingCard.click', (payload: any) => {
|
||||
wx.navigateTo({
|
||||
url: payload?.url
|
||||
})
|
||||
})
|
||||
|
||||
// 按钮红包功能
|
||||
sdk.on(EVENTS.feature.click, (payload: any) => {
|
||||
console.log('feature.click', payload)
|
||||
})
|
||||
|
||||
|
||||
sdk.on(EVENTS.luckymoney.withdrawal, (payload: any) => {
|
||||
this.handleReceive(payload);
|
||||
});
|
||||
|
||||
this.setData({
|
||||
sdk,
|
||||
loading: false,
|
||||
loadingText: "",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("init wxroom sdk failed:", error);
|
||||
this.handleEnterError(
|
||||
error?.message || "进入直播间失败,请稍后重试",
|
||||
"进入直播间失败",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchSignToken() {
|
||||
const nickName = wx.getStorageSync("userName") || "微信用户";
|
||||
const userIdStr = String(wx.getStorageSync("mbuId") || "");
|
||||
const avatarUrl = wx.getStorageSync("avatarUrl") || "";
|
||||
|
||||
if (!userIdStr) {
|
||||
throw new Error("登录信息缺失,请重新登录后再进入直播间");
|
||||
}
|
||||
|
||||
const res = await request<any>({
|
||||
options: {
|
||||
url: "/app/saas/activity-login",
|
||||
method: "POST",
|
||||
data: aesEncrypt(JSON.stringify({
|
||||
nickName,
|
||||
userIdStr,
|
||||
avatarUrl,
|
||||
userId: this.data.userId,
|
||||
inviterToken: this.data.inviterToken,
|
||||
activityId: this.data.activityId,
|
||||
mpUserId: wx.getStorageSync("userId"),
|
||||
}), AES_KEY, AES_IV)
|
||||
},
|
||||
loadingTitle: "进入直播间",
|
||||
isLoading: false,
|
||||
});
|
||||
const data = JSON.parse(aesDecrypt(res?.data, AES_KEY, AES_IV))
|
||||
this.setData({ msg: data?.msg ?? "暂无活动" });
|
||||
return (
|
||||
data?.signToken || ""
|
||||
);
|
||||
},
|
||||
handleReceive: lockPromise(async function(data: any) {
|
||||
const params = {
|
||||
appId: appId,
|
||||
outRewardsId: data.redPacketId,
|
||||
openId: wx.getStorageSync("openId"),
|
||||
}
|
||||
try {
|
||||
await receiveRedPacket(
|
||||
params,
|
||||
'/app/general/receive-red',
|
||||
appId
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('领取红包失败:', err);
|
||||
}
|
||||
}),
|
||||
getActivityId(options: Record<string, string | undefined>) {
|
||||
const rawActivityId = options.activityId || options.roomId || "";
|
||||
const activityId = Number(rawActivityId);
|
||||
return Number.isFinite(activityId) ? activityId : 0;
|
||||
},
|
||||
|
||||
buildQueryString(params: Record<string, string | undefined>) {
|
||||
return Object.entries(params)
|
||||
.filter(([, value]) => value !== undefined && value !== "")
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
|
||||
)
|
||||
.join("&");
|
||||
},
|
||||
|
||||
cacheRedirectPath(options: Record<string, string | undefined>) {
|
||||
const query = this.buildQueryString(options);
|
||||
const path = query ? `${WXROOM_PAGE_PATH}?${query}` : WXROOM_PAGE_PATH;
|
||||
wx.setStorageSync(WXROOM_REDIRECT_PATH_KEY, path);
|
||||
},
|
||||
|
||||
setLoading(text: string) {
|
||||
this.setData({
|
||||
loading: true,
|
||||
loadingText: text,
|
||||
showError: false,
|
||||
errorTitle: "",
|
||||
errorMessage: "",
|
||||
});
|
||||
},
|
||||
|
||||
clearError() {
|
||||
this.setData({
|
||||
showError: false,
|
||||
errorTitle: "",
|
||||
errorMessage: "",
|
||||
});
|
||||
},
|
||||
|
||||
handleEnterError(message: string, title = "无法进入直播间") {
|
||||
this.destroySdk();
|
||||
this.setData({
|
||||
loading: false,
|
||||
loadingText: "",
|
||||
showError: true,
|
||||
errorTitle: title,
|
||||
errorMessage: message,
|
||||
});
|
||||
},
|
||||
|
||||
retryEnter() {
|
||||
const activityId =
|
||||
this.data.activityId || this.getActivityId(pageOptionsCache);
|
||||
if (!activityId) {
|
||||
this.handleEnterError(
|
||||
"缺少直播间ID,请重新从正确的直播链接进入。",
|
||||
"无法进入直播间",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.initSdk(activityId, pageOptionsCache);
|
||||
},
|
||||
|
||||
goBack() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
wx.switchTab({
|
||||
url: "/pages/tab-bar/medicine-box/index",
|
||||
});
|
||||
},
|
||||
|
||||
destroySdk() {
|
||||
this.data.sdk?.destroy?.();
|
||||
if (this.data.sdk) {
|
||||
this.setData({
|
||||
sdk: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
commentCheck() { },
|
||||
|
||||
onUnload() {
|
||||
this.destroySdk();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
<view class="wxroom">
|
||||
|
||||
|
||||
<volc-live-portrait
|
||||
wx:if="{{!isLandscape}}"
|
||||
sdkInstance="{{sdk}}"
|
||||
bind:commentCheck="commentCheck"
|
||||
></volc-live-portrait>
|
||||
|
||||
<volc-live-landscape
|
||||
wx:else
|
||||
sdkInstance="{{sdk}}"
|
||||
bind:commentCheck="commentCheck"
|
||||
></volc-live-landscape>
|
||||
|
||||
<view>
|
||||
|
||||
</view>
|
||||
<view class="wxroom__overlay" wx:if="{{loading}}">
|
||||
<view class="wxroom__panel">
|
||||
<view class="wxroom__spinner"></view>
|
||||
<view class="wxroom__title">正在进入直播间</view>
|
||||
<view class="wxroom__message">{{loadingText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="wxroom__overlay" wx:if="{{showError}}">
|
||||
<view class="wxroom__panel wxroom__panel--error">
|
||||
<view class="wxroom__title">{{errorTitle}}</view>
|
||||
<view class="wxroom__message">{{errorMessage}}</view>
|
||||
<view class="wxroom__actions">
|
||||
<view
|
||||
class="wxroom__button wxroom__button--primary"
|
||||
bindtap="retryEnter"
|
||||
>
|
||||
重试
|
||||
</view>
|
||||
<view
|
||||
class="wxroom__button wxroom__button--secondary"
|
||||
bindtap="goBack"
|
||||
>
|
||||
返回
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
Reference in New Issue
Block a user