feat(mp+health): 小程序分包迁移 + 积分商城后台列表 API
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 小程序页面迁移到 pkg-health/pkg-mall/pkg-profile 分包目录
- 删除旧 pages/health/input、pages/mall/detail 等旧路径
- 导航路径更新为分包路径(/pages/pkg-mall/exchange/index 等)
- TrendChart 组件优化
- 后台添加 admin_list_products API(支持查看已下架商品)
- config/index.ts 添加 defineConstants 环境变量
- mp e2e check-readiness 路径修正
This commit is contained in:
iven
2026-04-29 07:29:49 +08:00
parent 9015a2b85e
commit cb6f5cc651
32 changed files with 229 additions and 516 deletions

View File

@@ -0,0 +1,260 @@
@import '../../../styles/variables.scss';
@mixin section-title {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 30px;
font-weight: bold;
color: $tx;
margin-bottom: 20px;
display: block;
}
@mixin serif-number {
font-family: 'Georgia', 'Times New Roman', serif;
font-variant-numeric: tabular-nums;
}
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.medication-page {
min-height: 100vh;
background: $bg;
padding: 32px 24px;
padding-bottom: 160px;
}
.page-title {
@include section-title;
padding-left: 4px;
}
.reminder-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.reminder-card {
display: flex;
align-items: center;
background: $card;
border-radius: $r;
padding: 24px;
box-shadow: $shadow-sm;
&.disabled {
opacity: 0.55;
}
}
.reminder-avatar {
@include flex-center;
width: 72px;
height: 72px;
border-radius: $r;
background: $acc-l;
flex-shrink: 0;
margin-right: 20px;
}
.reminder-avatar-text {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 32px;
font-weight: bold;
color: $acc;
}
.reminder-info {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.reminder-name {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 30px;
font-weight: bold;
color: $tx;
margin-bottom: 4px;
}
.reminder-dosage {
@include serif-number;
font-size: 24px;
color: $tx2;
}
.reminder-actions {
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
margin-left: 12px;
}
.toggle {
width: 80px;
height: 44px;
border-radius: $r-pill;
padding: 4px;
position: relative;
transition: background 0.3s;
&.on {
background: $pri;
}
&.off {
background: $bd;
}
}
.toggle-dot {
width: 36px;
height: 36px;
border-radius: 50%;
background: #fff;
position: absolute;
top: 4px;
transition: left 0.3s;
.toggle.on & {
left: 40px;
}
.toggle.off & {
left: 4px;
}
}
.delete-btn {
font-size: 24px;
color: $dan;
padding: 4px 12px;
}
.form-card {
background: $card;
border-radius: $r;
padding: 28px;
margin-top: 24px;
box-shadow: $shadow-sm;
}
.form-card-title {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 28px;
font-weight: bold;
color: $tx;
margin-bottom: 20px;
display: block;
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 0;
border-bottom: 1px solid $bd-l;
&:last-of-type {
border-bottom: none;
}
}
.form-label {
font-size: 28px;
color: $tx;
flex-shrink: 0;
width: 160px;
}
.form-input {
flex: 1;
font-size: 28px;
color: $tx;
text-align: right;
border: none;
background: transparent;
outline: none;
}
.form-placeholder {
color: $tx3;
}
.time-picker-wrap {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.time-value {
@include serif-number;
font-size: 28px;
color: $tx;
}
.time-modify {
font-size: 24px;
color: $pri;
}
.form-actions {
display: flex;
gap: 16px;
margin-top: 24px;
}
.form-cancel {
flex: 1;
background: $bd-l;
border-radius: $r-sm;
padding: 20px;
text-align: center;
}
.form-cancel-text {
font-size: 28px;
color: $tx2;
}
.form-confirm {
flex: 1;
background: $pri;
border-radius: $r-sm;
padding: 20px;
text-align: center;
}
.form-confirm-text {
font-size: 28px;
color: #fff;
font-weight: bold;
}
.add-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: $pri;
padding: 28px;
text-align: center;
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
}
.add-text {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 32px;
color: #fff;
font-weight: bold;
letter-spacing: 2px;
}

View File

@@ -0,0 +1,175 @@
import React, { useState, useEffect } from 'react';
import { View, Text, Input, Picker } from '@tarojs/components';
import Taro from '@tarojs/taro';
import EmptyState from '../../../components/EmptyState';
import './index.scss';
interface MedicationReminder {
id: string;
name: string;
dosage: string;
time: string;
enabled: boolean;
}
const STORAGE_KEY = 'medication_reminders';
function loadReminders(): MedicationReminder[] {
return Taro.getStorageSync(STORAGE_KEY) || [];
}
function saveReminders(reminders: MedicationReminder[]) {
Taro.setStorageSync(STORAGE_KEY, reminders);
}
export default function MedicationReminder() {
const [reminders, setReminders] = useState<MedicationReminder[]>([]);
const [showForm, setShowForm] = useState(false);
const [formName, setFormName] = useState('');
const [formDosage, setFormDosage] = useState('');
const [formTime, setFormTime] = useState('08:00');
useEffect(() => {
setReminders(loadReminders());
}, []);
const updateReminders = (updated: MedicationReminder[]) => {
setReminders(updated);
saveReminders(updated);
};
const handleToggle = (id: string) => {
const updated = reminders.map((r) =>
r.id === id ? { ...r, enabled: !r.enabled } : r
);
updateReminders(updated);
};
const handleDelete = (id: string) => {
Taro.showModal({
title: '确认删除',
content: '确定要删除这个提醒吗?',
}).then((res) => {
if (res.confirm) {
updateReminders(reminders.filter((r) => r.id !== id));
}
});
};
const handleAdd = () => {
if (!formName.trim()) {
Taro.showToast({ title: '请输入药品名称', icon: 'none' });
return;
}
const newReminder: MedicationReminder = {
id: Date.now().toString(),
name: formName.trim(),
dosage: formDosage.trim(),
time: formTime,
enabled: true,
};
updateReminders([...reminders, newReminder]);
setFormName('');
setFormDosage('');
setFormTime('08:00');
setShowForm(false);
Taro.showToast({ title: '添加成功', icon: 'success' });
};
const nameInitial = (name: string) => {
return name ? name.charAt(0) : '药';
};
return (
<View className='medication-page'>
<Text className='page-title'></Text>
<View className='reminder-list'>
{reminders.map((r) => (
<View className={`reminder-card ${!r.enabled ? 'disabled' : ''}`} key={r.id}>
<View className='reminder-avatar'>
<Text className='reminder-avatar-text'>{nameInitial(r.name)}</Text>
</View>
<View className='reminder-info'>
<Text className='reminder-name'>{r.name}</Text>
<Text className='reminder-dosage'>
{r.dosage} | {r.time}
</Text>
</View>
<View className='reminder-actions'>
<View
className={`toggle ${r.enabled ? 'on' : 'off'}`}
onClick={() => handleToggle(r.id)}
>
<View className='toggle-dot' />
</View>
<Text
className='delete-btn'
onClick={() => handleDelete(r.id)}
>
</Text>
</View>
</View>
))}
</View>
{reminders.length === 0 && (
<EmptyState text='暂无用药提醒' />
)}
{showForm && (
<View className='form-card'>
<Text className='form-card-title'></Text>
<View className='form-item'>
<Text className='form-label'></Text>
<Input
className='form-input'
placeholder='请输入药品名称'
placeholderClass='form-placeholder'
value={formName}
onInput={(e) => setFormName(e.detail.value)}
/>
</View>
<View className='form-item'>
<Text className='form-label'></Text>
<Input
className='form-input'
placeholder='如: 1片、10ml'
placeholderClass='form-placeholder'
value={formDosage}
onInput={(e) => setFormDosage(e.detail.value)}
/>
</View>
<View className='form-item'>
<Text className='form-label'></Text>
<Picker
mode='time'
value={formTime}
onChange={(e) => setFormTime(e.detail.value)}
>
<View className='time-picker-wrap'>
<Text className='time-value'>{formTime}</Text>
<Text className='time-modify'></Text>
</View>
</Picker>
</View>
<View className='form-actions'>
<View className='form-cancel' onClick={() => setShowForm(false)}>
<Text className='form-cancel-text'></Text>
</View>
<View className='form-confirm' onClick={handleAdd}>
<Text className='form-confirm-text'></Text>
</View>
</View>
</View>
)}
{!showForm && (
<View className='add-btn' onClick={() => setShowForm(true)}>
<Text className='add-text'></Text>
</View>
)}
</View>
);
}