feat: 电子药箱小程序 - 4Tab药品管理

- 电子药箱: 药品库存管理 + 手动录入 + 今日用药打卡
- 药品百科: 搜索 + 分类筛选 + 20种药品静态数据
- 惠教中心: 4条药品使用指南课程
- 我的: 家庭成员信息管理
- 自定义TabBar + navigation-bar组件
- SVG药品分类插图
This commit is contained in:
陈誉午
2026-07-29 11:20:52 +08:00
commit 994b267f5c
683 changed files with 43849 additions and 0 deletions
@@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"player": "/components/player/player",
"banner": "/components/banner/banner",
"comment": "/components/static/comment/comment",
"product-popup": "/components/static/product-popup/product-popup"
}
}
@@ -0,0 +1,94 @@
/* components/liveroom/liveroom.wxss */
.liveroom-wrap {
position: relative;
width: 100%;
height: 100%;
color: #fcfcfd;
background-image: linear-gradient(90deg, #2f1d17 0%, #1e2326 100%);
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.countdown {
margin: 30rpx 0;
}
.countdown-text {
color: white;
font-size: 28rpx;
}
.countdown-time {
color: white;
font-size: 36rpx;
font-weight: bold;
}
.enter-room-btn {
background: #ff6b6b;
width: 400rpx;
height: 80rpx;
line-height: 80rpx;
color: #fff;
font-size: 32rpx;
border-radius: 40rpx;
border: none;
}
.live-ended {
position: fixed;
top: 35%;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
}
.ended-content {
border-radius: 16rpx;
padding: 48rpx;
text-align: center;
width: 600rpx;
}
.ended-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 24rpx;
}
.ended-text {
font-size: 36rpx;
color: #fff;
font-weight: 700;
display: block;
margin-bottom: 16rpx;
}
.ended-time {
font-size: 28rpx;
color: #999;
display: block;
margin-bottom: 48rpx;
}
.replay-btn {
width: 400rpx;
height: 80rpx;
line-height: 80rpx;
background: #ff6b35;
color: #fff;
font-size: 32rpx;
border-radius: 40rpx;
border: none;
}
.replay-btn:active {
background: #ff8555;
}
+216
View File
@@ -0,0 +1,216 @@
import { request } from "../../utils/request";
import { appId } from "../../env";
import {
join,
leave,
addCustomEventListener,
removeCustomEventListener,
} from "../../utils/server-sent-events";
interface LiveroomData {
isPlayback: boolean;
pluginVisible: boolean;
roomInfo: any;
pullUrlInfo: any;
vodInfo: any;
watchNum: number;
productNum: number;
chatEnhancementApproved: boolean;
}
interface LiveroomProperties {
joinedGroupId: string;
}
interface LiveroomComponent
extends WechatMiniprogram.Component.TrivialInstance {
data: LiveroomData;
properties: LiveroomProperties;
_listener?: (data: any) => void;
onLiveMessage: (data: any) => void;
init: () => Promise<void>;
cleanup: () => void;
handleError: (error?: unknown) => void;
setChatEnhancementApproval: (approved: boolean) => void;
}
const statusId = "roomId-1";
// components/liveroom/liveroom.ts
Component({
/**
* 组件的属性列表
*/
properties: {
joinedGroupId: {
type: String,
value: "",
observer: function (
this: LiveroomComponent,
newVal: string,
oldVal: string,
) {
if (newVal && newVal !== oldVal) {
this.init();
}
},
},
},
lifetimes: {
attached(this: LiveroomComponent) {},
detached(this: LiveroomComponent) {
this.cleanup();
},
},
pageLifetimes: {
show(this: LiveroomComponent) {
this._listener = this.onLiveMessage.bind(this);
addCustomEventListener("pushMsg", this._listener);
join(statusId);
},
hide() {
// @ts-ignore
removeCustomEventListener("pushMsg", this._listener);
leave(statusId);
},
},
/**
* 组件的初始数据
*/
data: {
isPlayback: false,
pluginVisible: true,
roomInfo: null,
watchNum: 0,
productNum: 0,
durationInterval: null,
},
/**
* 组件的方法列表
*/
methods: {
onLiveMessage(data: any) {
console.log("收到通知:", data);
const that = this as any;
if (data.type == "watchNum") {
that.setData({ watchNum: data.count });
}
if (data.type == "productNum") {
that.setData({ productNum: data.count });
}
},
updateIsPlayback(this: any, bool: boolean) {
this.isPlayback = bool;
},
controlstoggle(this: LiveroomComponent, e: WechatMiniprogram.CustomEvent) {
const { state } = e.detail;
if (state && this.data.roomInfo) {
this.setData({
roomInfo: {
...this.data.roomInfo,
state,
},
});
}
},
async init(this: LiveroomComponent) {
const app = getApp<IAppOption>();
if (!app.globalData.isLogin) return;
const roomId = this.properties.joinedGroupId;
const params = {
roomId: roomId,
appId: appId,
openId: wx.getStorageSync("openId"),
};
try {
const data = await request({
options: {
url: `/app/video-live/details`,
data: params,
method: "GET",
},
});
const bool = this.compareTime(data.data.onlineDatetime);
console.log(bool, "12313");
this.setData({
roomInfo: data.data,
pullUrlInfo: data.data,
vodInfo: data.data,
bool,
});
} catch (error) {
this.handleError(error);
}
},
handleError(this: LiveroomComponent) {
wx.showToast({
title: "获取直播信息失败",
icon: "none",
duration: 2000,
});
},
cleanup(this: LiveroomComponent) {
this.setData({
isPlayback: false,
pluginVisible: true,
roomInfo: null,
watchNum: 0,
productNum: 0,
chatEnhancementApproved: false,
});
},
compareTime(targetTimeStr: string) {
const targetTime = new Date(targetTimeStr);
const currentTime = new Date();
return targetTime > currentTime;
},
startDurationTimer(this: LiveroomComponent) {
this.durationInterval = setInterval(() => {
const now = new Date().getTime();
const startTime = new Date(this.data.roomInfo.onlineDatetime).getTime();
if (now < startTime) {
const remainingTime = Math.floor((startTime - now) / 1000);
const days = Math.floor(remainingTime / (24 * 3600));
const hours = Math.floor((remainingTime % (24 * 3600)) / 3600);
const minutes = Math.floor((remainingTime % 3600) / 60);
const seconds = remainingTime % 60;
this.setData({
isLiveStarted: false,
countdown:
days > 0
? `${days}${hours.toString().padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`
: `${hours.toString().padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`,
});
} else {
const duration = Math.floor((now - startTime) / 1000);
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = duration % 60;
this.setData({
isLiveStarted: true,
duration: `${hours.toString().padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`,
});
}
}, 1000);
},
stopDurationTimer(this: LiveroomComponent) {
if (this.durationInterval) {
clearInterval(this.durationInterval);
this.durationInterval = null;
}
},
},
});
@@ -0,0 +1,39 @@
<!--components/liveroom/liveroom.wxml-->
<view class="liveroom-wrap">
<banner room-info="{{roomInfo}}" watchNum="{{watchNum}}"/>
<!-- 直播未开始提示
<view class="live-ended" wx:if="{{bool}}" >
<view class="ended-content">
<text class="ended-text">直播未开始</text>
<view class="countdown">
<text class="countdown-text">距离开始还有:</text>
<text class="countdown-time">{{roomInfo.onlineDatetime}}</text>
</view>
<button class="enter-room-btn" bind:tap="enterRoom">进入房间</button>
</view>
</view> -->
<!-- 直播结束提示 -->
<view class="live-ended" wx:if="{{roomInfo.state == 2}}">
<view class="ended-content">
<text class="ended-text">直播已结束</text>
<text class="ended-time">结束时间:{{roomInfo.offlineDatetime}}</text>
<button class="replay-btn">观看回放</button>
</view>
</view>
<banner room-info="{{roomInfo}}"/>
<player
is-playback="{{isPlayback}}"
is-living="{{isLiving}}"
pull-url-info="{{pullUrlInfo}}"
vod-info="{{vodInfo}}"
bind:controlstoggle="controlstoggle"
/>
<comment wx:if="{{roomInfo.chat}}"/>
<product-popup joinedGroupId="{{joinedGroupId}}" productNum="{{productNum}}"/>
</view>