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,257 @@
type GuideMenuItem = {
key: string;
label: string;
icon: string;
};
interface GuidePopupProperties {
show: boolean;
title: string;
subtitle: string;
images: string[];
amount: string;
}
interface GuidePopupData {
mounted: boolean;
visible: boolean;
displayName: string;
displaySubtitle: string;
avatarUrl: string;
avatarLetter: string;
balanceLabel: string;
withdrawText: string;
incomeText: string;
menuItems: GuideMenuItem[];
}
interface GuidePopupInstance
extends WechatMiniprogram.Component.TrivialInstance {
data: GuidePopupData;
properties: GuidePopupProperties;
_hideTimer?: number | null;
}
const DEFAULT_NAME = "当前用户";
const DEFAULT_ENTRY = "个人中心";
const DEFAULT_BALANCE_LABEL = "可提现金额";
const DEFAULT_WITHDRAW_TEXT = "立即提现";
const DEFAULT_INCOME_TEXT = "收益明细";
const HIDE_DELAY = 280;
const DEFAULT_MENU_ITEMS: GuideMenuItem[] = [
{
key: "orders",
label: "我的订单",
icon: "./icons/orders.svg",
},
{
key: "prize-records",
label: "中奖记录",
icon: "./icons/gift.svg",
},
{
key: "coupons",
label: "我的优惠券",
icon: "./icons/coupon.svg",
},
{
key: "comment-board",
label: "评论记录板",
icon: "./icons/comment.svg",
},
{
key: "nickname",
label: "修改昵称",
icon: "./icons/edit.svg",
},
{
key: "complaint",
label: "投诉",
icon: "./icons/warning.svg",
},
];
function normalizeImages(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value.filter(
(item): item is string => typeof item === "string" && item.length > 0,
);
}
function syncViewModel(
instance: GuidePopupInstance,
titleText: string,
subtitleText: string,
imageList: string[],
) {
const name = titleText || wx.getStorageSync("userName") || DEFAULT_NAME;
const subtitle = subtitleText || DEFAULT_ENTRY;
const avatarUrl = imageList.length ? imageList[0] : "";
const avatarLetter = String(name).trim().slice(0, 1).toUpperCase() || "U";
instance.setData({
displayName: name,
displaySubtitle: subtitle,
avatarUrl,
avatarLetter,
});
}
function clearHideTimer(instance: GuidePopupInstance) {
if (!instance._hideTimer) return;
clearTimeout(instance._hideTimer);
instance._hideTimer = null;
}
function togglePopup(instance: GuidePopupInstance, show: boolean) {
clearHideTimer(instance);
if (show) {
if (instance.data.mounted) {
wx.nextTick(() => {
instance.setData({ visible: true });
});
return;
}
instance.setData(
{
mounted: true,
visible: false,
},
() => {
wx.nextTick(() => {
instance.setData({ visible: true });
});
},
);
return;
}
if (!instance.data.mounted) return;
instance.setData({ visible: false });
instance._hideTimer = setTimeout(() => {
instance.setData({ mounted: false });
instance._hideTimer = null;
}, HIDE_DELAY);
}
Component({
properties: {
show: {
type: Boolean,
value: false,
observer(this: GuidePopupInstance, nextValue: boolean) {
togglePopup(this, !!nextValue);
},
},
title: {
type: String,
value: "",
observer(this: GuidePopupInstance, nextValue: string) {
syncViewModel(
this,
nextValue || "",
this.properties.subtitle || "",
normalizeImages(this.properties.images),
);
},
},
subtitle: {
type: String,
value: "",
observer(this: GuidePopupInstance, nextValue: string) {
syncViewModel(
this,
this.properties.title || "",
nextValue || "",
normalizeImages(this.properties.images),
);
},
},
images: {
type: Array,
value: [],
observer(this: GuidePopupInstance, nextValue: unknown) {
syncViewModel(
this,
this.properties.title || "",
this.properties.subtitle || "",
normalizeImages(nextValue),
);
},
},
amount: {
type: String,
value: "0.00",
},
},
data: {
mounted: false,
visible: false,
displayName: DEFAULT_NAME,
displaySubtitle: DEFAULT_ENTRY,
avatarUrl: "",
avatarLetter: "U",
balanceLabel: DEFAULT_BALANCE_LABEL,
withdrawText: DEFAULT_WITHDRAW_TEXT,
incomeText: DEFAULT_INCOME_TEXT,
menuItems: DEFAULT_MENU_ITEMS,
} as GuidePopupData,
lifetimes: {
attached(this: GuidePopupInstance) {
syncViewModel(
this,
this.properties.title || "",
this.properties.subtitle || "",
normalizeImages(this.properties.images),
);
togglePopup(this, !!this.properties.show);
},
detached(this: GuidePopupInstance) {
clearHideTimer(this);
},
},
methods: {
noop() {},
handleClose(this: GuidePopupInstance) {
this.triggerEvent("close");
},
handlePreviewAvatar(this: GuidePopupInstance) {
const images = normalizeImages(this.properties.images);
if (!images.length) return;
wx.previewImage({
current: images[0],
urls: images,
});
},
handleQuickAction(
this: GuidePopupInstance,
e: WechatMiniprogram.CustomEvent,
) {
const key = (e.currentTarget.dataset as { key?: string }).key || "";
if (!key) return;
this.triggerEvent("action", { key });
},
handleMenuTap(this: GuidePopupInstance, e: WechatMiniprogram.CustomEvent) {
const key = (e.currentTarget.dataset as { key?: string }).key || "";
if (!key) return;
this.triggerEvent("action", { key });
},
},
});