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
+262
View File
@@ -0,0 +1,262 @@
// components/my-video/index.ts
import { AES_KEY, AES_IV } from "../../env";
import { request } from "../../utils/request";
import { aesEncrypt } from "../../utils/crypto";
interface VideoData {
screen: boolean;
playing: boolean;
currentTime: number;
isReady: boolean;
sendDurationTimer: number | null;
lastUpdateTime: number;
isConnected: boolean; // 新增网络连接状态字段
}
interface MyVideoComponent {
data: VideoData;
videoContext: WechatMiniprogram.VideoContext | null;
pauseVideoHandler?: () => void;
resumeVideoHandler?: () => void;
setData: (d: Partial<VideoData>, cb?: () => void) => void;
initNetworkListener: () => void;
startSendProgress: () => void;
clearSendProgressTimer: () => void;
sendProgress: () => Promise<void>;
}
Component({
properties: {
src: String,
backgroundUrl: String,
},
data: {
screen: false,
playing: false,
currentTime: 0,
isReady: false,
isConnected: false,
sendDurationTimer: 0,
lastUpdateTime: 0,
} as VideoData,
lifetimes: {
attached(this: MyVideoComponent) {
this.initNetworkListener();
// 获取 videoContext 并初始化
this.videoContext = wx.createVideoContext("videoPlayer", this as any);
const pauseVideo = () => {
if (this.data.playing) {
wx.setStorageSync("interruptedTime", this.data.currentTime);
this.videoContext?.pause();
this.setData({ playing: false });
this.clearSendProgressTimer();
}
};
const resumeVideo = () => {
if (!this.videoContext) {
this.videoContext = wx.createVideoContext("videoPlayer", this as any);
}
this.videoContext.pause();
};
this.pauseVideoHandler = pauseVideo;
this.resumeVideoHandler = resumeVideo;
wx.onAudioInterruptionBegin(this.pauseVideoHandler);
wx.onAudioInterruptionEnd(this.resumeVideoHandler);
wx.onAppHide(this.pauseVideoHandler);
wx.onAppShow(this.resumeVideoHandler);
},
detached(this: MyVideoComponent) {
if (this.pauseVideoHandler) {
wx.offAudioInterruptionBegin(this.pauseVideoHandler);
wx.offAppHide(this.pauseVideoHandler);
}
if (this.resumeVideoHandler) {
wx.offAudioInterruptionEnd(this.resumeVideoHandler);
wx.offAppShow(this.resumeVideoHandler);
}
this.clearSendProgressTimer();
this.videoContext = null;
},
},
methods: {
initNetworkListener(this: MyVideoComponent) {
// 首先获取当前网络状态
wx.getNetworkType({
success: (res) => {
this.setData({
isConnected: res.networkType !== "none",
});
},
});
wx.onNetworkStatusChange(
(res: WechatMiniprogram.OnNetworkStatusChangeCallbackResult) => {
this.setData({
isConnected: res.isConnected,
});
if (!res.isConnected && this.data.playing) {
this.videoContext?.pause();
this.setData({ playing: false });
wx.showToast({
title: "网络连接已断开",
icon: "none",
duration: 2000,
});
}
if (res.isConnected) {
wx.showToast({
title: "网络已连接",
icon: "success",
duration: 1500,
});
}
}
);
},
handleLoadedMetadata(this: MyVideoComponent) {
this.setData({ isReady: true });
},
handlePosterTap(this: MyVideoComponent) {
if (!this.videoContext || !this.data.isReady) return;
if (!this.data.isConnected) {
wx.showToast({
title: "网络连接异常,请检查网络设置",
icon: "none",
duration: 2000,
});
return;
}
const newPlaying = !this.data.playing;
const resumeTime =
(wx.getStorageSync("interruptedTime") as number) ||
this.data.currentTime ||
(wx.getStorageSync("currentTime") as number) ||
0;
if (newPlaying) {
if (resumeTime > 0) {
this.videoContext.seek(resumeTime);
}
this.videoContext.play();
if (!this.data.sendDurationTimer) {
this.startSendProgress();
}
} else {
this.videoContext.pause();
this.clearSendProgressTimer();
}
this.setData({ playing: newPlaying });
wx.removeStorageSync("interruptedTime");
},
handlePause(this: MyVideoComponent) {
wx.setStorageSync("interruptedTime", this.data.currentTime);
this.setData({ playing: false });
this.clearSendProgressTimer();
},
handleTimeUpdate(
this: MyVideoComponent,
e: WechatMiniprogram.VideoTimeUpdate
) {
const now = Date.now();
const currentTime = Math.floor(e.detail.currentTime ?? 0);
if (now - this.data.lastUpdateTime >= 1000) {
this.setData({ currentTime });
(this.data as VideoData).lastUpdateTime = now;
wx.setStorageSync("currentTime", currentTime);
}
},
handleEnded(this: MyVideoComponent) {
this.sendProgress();
wx.setStorageSync("ended", true);
this.setData({ playing: false, currentTime: 0 });
wx.removeStorageSync("currentTime");
wx.removeStorageSync("interruptedTime");
this.clearSendProgressTimer();
},
handleFullscreenChange(this: MyVideoComponent) {
const newScreen = !this.data.screen;
if (newScreen) {
this.videoContext?.requestFullScreen({ direction: 90 });
} else {
this.videoContext?.exitFullScreen();
}
this.setData({ screen: newScreen });
},
handleError(this: MyVideoComponent, e: WechatMiniprogram.VideoError) {
console.error("播放错误:", e.detail.errMsg);
this.setData({ playing: false });
},
startSendProgress(this: MyVideoComponent) {
this.clearSendProgressTimer();
const timer = setInterval(() => {
this.sendProgress();
}, 15000) as unknown as number;
this.setData({ sendDurationTimer: timer });
},
clearSendProgressTimer(this: MyVideoComponent) {
const t = this.data.sendDurationTimer;
if (t) {
clearInterval(t);
this.setData({ sendDurationTimer: 0 });
}
},
async sendProgress(this: MyVideoComponent) {
try {
const openId = wx.getStorageSync("openId");
const code = wx.getStorageSync("code");
const state = wx.getStorageSync("state");
const seeId = wx.getStorageSync("seeId");
const duration = this.data.currentTime + 1;
if (code) {
const params = {
openId,
code,
duration
};
await request({
options: {
url: `/app/video-watch/inspect`,
data: aesEncrypt(JSON.stringify(params), AES_KEY, AES_IV),
},
isLoading: false,
});
} else {
const params = {
openId,
state,
seeId,
duration
};
await request({
options: {
url: `/app/watch/inspect`,
data: aesEncrypt(JSON.stringify(params), AES_KEY, AES_IV),
},
isLoading: false,
});
}
} catch (err) {
console.error("上报观看进度失败:", err);
}
},
},
});