feat: 电子药箱小程序 - 4Tab药品管理
- 电子药箱: 药品库存管理 + 手动录入 + 今日用药打卡 - 药品百科: 搜索 + 分类筛选 + 20种药品静态数据 - 惠教中心: 4条药品使用指南课程 - 我的: 家庭成员信息管理 - 自定义TabBar + navigation-bar组件 - SVG药品分类插图
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744786271399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M832 192H192c-35.2 0-64 28.8-64 64v512c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64V256c0-35.2-28.8-64-64-64z" fill="#F56C6C"/><path d="M192 192h640L512 448 192 192z" fill="#F78989"/><path d="M512 512m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" fill="#FFD700"/></svg>
|
||||
|
After Width: | Height: | Size: 545 B |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar",
|
||||
"van-tabs": "@vant/weapp/tabs/index",
|
||||
"van-tab": "@vant/weapp/tab/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
.page-wrap {
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
height: calc(100vh - 162rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ===== 汇总卡片 ===== */
|
||||
.summary-section {
|
||||
padding: 24rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.total-card {
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
|
||||
margin-bottom: 16rpx;
|
||||
padding: 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.total-card .card-label {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
color: #fff !important;
|
||||
font-size: 48rpx !important;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.sub-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
padding: 24rpx 10rpx;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.card-amount {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card-amount.received {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.card-amount.pending {
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.card-amount.expired {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tabs-wrap {
|
||||
margin: 16rpx;
|
||||
}
|
||||
|
||||
/* ===== 红包列表 ===== */
|
||||
.red-history {
|
||||
flex: 1;
|
||||
background: #f5f5f5;
|
||||
margin-top: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-scroll {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.record {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin: 0 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.record:first-child {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.red-icon {
|
||||
width: 70rpx;
|
||||
height: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.state-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.receive-btn {
|
||||
margin-top: 12rpx;
|
||||
padding: 8rpx 28rpx;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
background: #ff4d4f;
|
||||
border-radius: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0.9;
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { request } from "../../utils/request";
|
||||
import { appId } from "../../env";
|
||||
import { receiveRedPacket } from "../../utils/util";
|
||||
|
||||
const app = getApp<IAppOption>();
|
||||
const formatDate = (isoString: string): string => {
|
||||
const date = new Date(isoString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const RED_STATE_MAP: Record<number, string> = {
|
||||
10: "未发送",
|
||||
11: "等待领取",
|
||||
20: "正在发送",
|
||||
21: "等待确认",
|
||||
30: "发送失败",
|
||||
31: "个数不够",
|
||||
32: "账户余额不足",
|
||||
33: "客户未实名",
|
||||
34: "其它原因",
|
||||
40: "已领取",
|
||||
50: "已过期",
|
||||
};
|
||||
|
||||
const getStateText = (state: number) => {
|
||||
return RED_STATE_MAP[state] || "发放中";
|
||||
};
|
||||
|
||||
const FAILED_STATES = [30, 31, 32, 33, 34];
|
||||
|
||||
const canReceive = (state: number) => {
|
||||
return state === 11 || state === 21 || FAILED_STATES.includes(state);
|
||||
};
|
||||
|
||||
const isRetry = (state: number) => {
|
||||
return FAILED_STATES.includes(state);
|
||||
};
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [] as any[],
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
current: 1,
|
||||
pageSize: 15,
|
||||
activeTab: 0,
|
||||
tabs: [
|
||||
{ label: '待领取', value: 0, type: 1 },
|
||||
{ label: '已领取', value: 1, type: 2 },
|
||||
{ label: '已过期', value: 2, type: 3 },
|
||||
],
|
||||
summary: {
|
||||
totalAmount: '0.00',
|
||||
receivedAmount: '0.00',
|
||||
pendingAmount: '0.00',
|
||||
expiredAmount: '0.00',
|
||||
},
|
||||
store: {},
|
||||
safeBottom: app.globalData.safeBottom,
|
||||
safeTop: app.globalData.safeTop,
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
await this.fetchRecords(1, false, this.data.tabs[0].type);
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.loading || !this.data.hasMore) return;
|
||||
const nextPage = this.data.current + 1;
|
||||
this.fetchRecords(nextPage, true, this.data.tabs[this.data.activeTab].type);
|
||||
},
|
||||
|
||||
onTabChange(e: any) {
|
||||
const index = e.detail.index;
|
||||
const tab = this.data.tabs[index];
|
||||
this.setData({ activeTab: index });
|
||||
this.fetchRecords(1, false, tab.type);
|
||||
},
|
||||
|
||||
async handleReceive(e: any) {
|
||||
const { id, outrewardsid } = e.currentTarget.dataset;
|
||||
const params: Record<string, any> = {
|
||||
appId: appId,
|
||||
openId: wx.getStorageSync("openId"),
|
||||
};
|
||||
if (outrewardsid) params.outRewardsId = outrewardsid;
|
||||
if (id) params.redId = id;
|
||||
try {
|
||||
await receiveRedPacket(
|
||||
params,
|
||||
'/app/general/receive-red',
|
||||
appId
|
||||
);
|
||||
// 领取成功,刷新当前列表
|
||||
this.fetchRecords(1, false, this.data.tabs[this.data.activeTab].type);
|
||||
} catch (err) {
|
||||
console.error('领取红包失败:', err);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchRecords(page: number, append = false, type: number) {
|
||||
const app = getApp<IAppOption>();
|
||||
const openId = wx.getStorageSync("openId");
|
||||
|
||||
this.setData({
|
||||
store: app.globalData.store,
|
||||
});
|
||||
if (!openId) {
|
||||
wx.showToast({ title: "请先登录", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
this.setData({ loading: true });
|
||||
} else {
|
||||
wx.showLoading({ title: "加载中" });
|
||||
}
|
||||
|
||||
try {
|
||||
const params: Record<string, any> = {};
|
||||
params.openId = openId;
|
||||
params.appId = appId;
|
||||
params.current = page;
|
||||
params.pageSize = 15;
|
||||
params.type = type;
|
||||
|
||||
const res = await request({
|
||||
options: {
|
||||
url: "/app/saas/query-red",
|
||||
data: params,
|
||||
},
|
||||
isLoading: !append,
|
||||
// needLogin: false,
|
||||
});
|
||||
|
||||
const records = res.data?.records || res.records || [];
|
||||
const items = records.map((item: any) => ({
|
||||
...item,
|
||||
date: formatDate(item.createdTime),
|
||||
stateText: getStateText(item.state),
|
||||
amountText: `${(item.amount / 100).toFixed(2)}`,
|
||||
canReceive: canReceive(item.state),
|
||||
isRetry: isRetry(item.state),
|
||||
}));
|
||||
|
||||
this.setData({
|
||||
list: append ? [...this.data.list, ...items] : items,
|
||||
current: page,
|
||||
hasMore: records.length >= this.data.pageSize,
|
||||
});
|
||||
|
||||
// 汇总数据在 res.data 根层级,单位是分,转为元
|
||||
const d = res.data;
|
||||
if (d && d.totalAmount !== undefined) {
|
||||
this.setData({
|
||||
summary: {
|
||||
totalAmount: (d.totalAmount / 100).toFixed(2),
|
||||
receivedAmount: (d.receivedAmount / 100).toFixed(2),
|
||||
pendingAmount: (d.pendingAmount / 100).toFixed(2),
|
||||
expiredAmount: (d.expiredAmount / 100).toFixed(2),
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("加载红包记录失败:", err);
|
||||
} finally {
|
||||
if (append) {
|
||||
this.setData({ loading: false });
|
||||
} else {
|
||||
wx.hideLoading();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
<navigation-bar title="红包记录" back="{{true}}" color="black" background="#FFF" />
|
||||
|
||||
<view class="page-wrap">
|
||||
<view class="summary-section">
|
||||
<view class="summary-card total-card">
|
||||
<text class="card-label">总领取金额</text>
|
||||
<text class="card-amount total-amount">¥{{summary.totalAmount}}</text>
|
||||
</view>
|
||||
<view class="summary-row">
|
||||
<view class="summary-card sub-card">
|
||||
<text class="card-label">待领取金额</text>
|
||||
<text class="card-amount pending">¥{{summary.pendingAmount}}</text>
|
||||
</view>
|
||||
<view class="summary-card sub-card">
|
||||
<text class="card-label">已领取金额</text>
|
||||
<text class="card-amount received">¥{{summary.receivedAmount}}</text>
|
||||
</view>
|
||||
<view class="summary-card sub-card">
|
||||
<text class="card-label">已过期金额</text>
|
||||
<text class="card-amount expired">¥{{summary.expiredAmount}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabs-wrap">
|
||||
<van-tabs active="{{activeTab}}" bind:change="onTabChange" animated>
|
||||
<van-tab wx:for="{{tabs}}" wx:key="value" title="{{item.label}}" />
|
||||
</van-tabs>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{list.length}}" class="red-history">
|
||||
<scroll-view
|
||||
class="list-scroll"
|
||||
scroll-y
|
||||
bindscrolltolower="onReachBottom"
|
||||
lower-threshold="200"
|
||||
>
|
||||
<view class="record" wx:for="{{list}}" wx:key="id">
|
||||
<image class="red-icon" src="./assets/red.svg" mode="aspectFill"/>
|
||||
<view class="content">
|
||||
<text class="name">{{item.description}}</text>
|
||||
<text class="time">{{item.date}}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="amount">¥{{item.amountText}}</text>
|
||||
<view wx:if="{{item.canReceive}}" class="receive-btn" data-id="{{item.id}}" data-outRewardsId="{{item.outRewardsId}}" bind:tap="handleReceive">{{item.isRetry ? '重试' : '领取'}}</view>
|
||||
<text wx:else class="state-text">{{item.stateText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!hasMore}}" class="no-more">没有更多数据了</view>
|
||||
<view wx:if="{{loading}}" class="loading-tip">加载中...</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="no-data">暂无记录</view>
|
||||
</view>
|
||||
Reference in New Issue
Block a user