Prompt file imported from lxKylin/yucheng-mini (
.codex/prompts/M6.prompt.md). Fill in{{item_name}},{{item_level}},{{doneBtnMod}},{{idx}}before use. Copyright stays with the author.
M6 · 提醒详情模块开发
任务概述
你是一名熟练的 Taro 4 + React 18 + TypeScript + Zustand 专家。请按照以下规范,逐步完成 M6 提醒详情模块的全部开发内容。完成每个子任务后自行验证,确认无误再进入下一个。
当前项目状态
- M1–M3 已完成:全局样式、数据层、首页(含 AppBar、MedicineCard、StatusTag、ProgressBar)
- M5 已完成:
src/components/BottomSheet/index.tsx:接收open、title、onClose、onAfterOpen、onAfterClose、children,使用 taroifyPopup实现底部弹层src/components/ReminderForm/index.tsx:表单组件,支持新增(reminderId不传)和编辑(reminderId传入)两种模式src/pages/home/index.tsx:已接入 BottomSheet + ReminderForm(新增模式),FAB 可正常打开表单
- 本模块需实现:
src/components/ReminderDetail/index.tsx(目录为空,需新建)src/components/ReminderDetail/index.scss(目录为空,需新建)- 修改
src/pages/home/index.tsx,接入详情面板(handleDetail当前为 Toast 占位)
src/pages/home/index.config.ts不修改
当前 src/pages/home/index.tsx 关键片段
// 已有 state
const [formOpen, setFormOpen] = useState(false);
const [formKey, setFormKey] = useState(0);
// 以下是 M6 需要替换的 Toast 占位
// TODO: M6 接入后替换为打开详情 BottomSheet。
const handleDetail = (_id: string) => {
Taro.showToast({ title: '详情开发中', icon: 'none', duration: 1200 });
};
// 已有关闭函数
const closeFormSheet = () => {
setFormOpen(false);
};
// 已有 TabBar 回调
const handleSheetEntered = () => {
Taro.hideTabBar({ animation: true });
};
const handleSheetExited = () => {
Taro.showTabBar({ animation: true });
};
{
/* 已有 FAB,仅在 formOpen===false 时显示 */
}
{
!formOpen ? (
<View className="home-fab" onClick={handleAddNew}>
<Text className="home-fab__icon">+</Text>
</View>
) : null;
}
{
/* 已有新增表单 BottomSheet */
}
<BottomSheet
open={formOpen}
title="新增提醒"
onClose={closeFormSheet}
onAfterOpen={handleSheetEntered}
onAfterClose={handleSheetExited}
>
<ReminderForm
key={formKey}
onSuccess={closeFormSheet}
onCancel={closeFormSheet}
/>
</BottomSheet>;
taroify 组件使用规范
项目已安装 @taroify/core,且通过 vite-plugin-style-import 配置了样式自动导入(引入组件时无需手动 import CSS 文件)。
本模块适用的 taroify 组件:
| 用途 | taroify 组件 | import 路径 | 说明 |
|---|---|---|---|
| 操作按钮 | Button |
@taroify/core/button |
通过 className 覆盖颜色匹配设计规范 soft 色板 |
复用已有组件(直接 import,无需重新实现):
| 组件 | import 路径 |
|---|---|
StatusTag |
@/components/StatusTag |
ProgressBar |
@/components/ProgressBar |
UI 设计参考
UI 参考 ui5.0/index.html 中 detail() 函数渲染的 Sheet 内容:
Sheet 整体结构:
BottomSheet(底部弹层,title="提醒详情")
└── ReminderDetail(详情内容区)
├── 药物名称区(药物名 + 规格 + StatusTag)
├── 倒计时大数字(逾期/今日/N天后,paused 时展示暂停提示)
├── ProgressBar(进度条)
├── 主操作按钮行("本次已开药" + "暂停提醒/重新启用")
├── 信息格(2 列 × 2 行:最近开药日期 / 开药间隔 / 下次开药日期 / 提醒设置)
├── 推荐操作提示(绿色软色背景,引导用户点击"本次已开药")
├── 次级操作按钮行("编辑提醒" + "删除提醒")
└── 开药历史记录(标题 + 条目列表,history 为空时不渲染)
子任务 1:新建 ReminderDetail 组件
1.1 新建 src/components/ReminderDetail/index.tsx
完整内容如下:
import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro'
import Button from '@taroify/core/button'
import { useDerivedById, useReminderActions } from '@/hooks/useReminders'
import ProgressBar from '@/components/ProgressBar'
import StatusTag from '@/components/StatusTag'
import './index.scss'
interface ReminderDetailProps {
/** 要展示的提醒 ID */
reminderId: string
/** 关闭详情面板 */
onClose: () => void
/** 点击"编辑提醒"后触发,传入 id,由父组件负责打开编辑表单 */
onEdit: (id: string) => void
}
export default function ReminderDetail({ reminderId, onClose, onEdit }: ReminderDetailProps) {
const item = useDerivedById(reminderId)
const { markDone, togglePause, deleteReminder } = useReminderActions()
if (!item) return null
// 倒计时显示逻辑
const daysNumber = Math.abs(item.daysLeft)
const daysText =
item.daysLeft < 0
? `天 · 已逾期`
: item.daysLeft === 0
? `今日需要开药`
: `天后预计需要重新开药`
// 进度条 level:paused 状态复用 good(不突出显示)
const progressLevel = item.status === 'paused' ? 'good' : item.level
// "本次已开药" 按钮颜色:danger/today 用危险色,其余用成功色
const doneBtnMod = item.level === 'danger' ? 'danger' : 'success'
const handleDone = () => {
markDone(item.id)
Taro.showToast({ title: `{{item_name}} 已进入下一轮周期`, icon: 'success', duration: 1500 })
onClose()
}
const handleTogglePause = () => {
togglePause(item.id)
const msg = item.status === 'paused' ? '提醒已重新启用' : '提醒已暂停'
Taro.showToast({ title: msg, icon: 'none', duration: 1500 })
onClose()
}
const handleDelete = () => {
Taro.showModal({
title: '删除提醒',
content: `确定要删除「{{item_name}}」的开药提醒吗?此操作不可撤销。`,
confirmText: '删除',
cancelText: '取消',
confirmColor: '#ca4e41',
success: (res) => {
if (res.confirm) {
deleteReminder(item.id)
Taro.showToast({ title: `{{item_name}} 已删除`, icon: 'none', duration: 1500 })
onClose()
}
},
})
}
return (
<View className='reminder-detail'>
{/* ─── 药物名称区 ─── */}
<View className='reminder-detail__header'>
<View className='reminder-detail__header-main'>
<Text className='reminder-detail__name'>{item.name}</Text>
{item.spec ? (
<Text className='reminder-detail__spec'>{item.spec}</Text>
) : null}
{item.note ? (
<Text className='reminder-detail__note'>{item.note}</Text>
) : null}
</View>
<StatusTag level={item.level} label={item.levelLabel} />
</View>
{/* ─── 倒计时大数字 / 暂停提示 ─── */}
{item.status !== 'paused' ? (
<View className='reminder-detail__days'>
<Text className={`reminder-detail__days-number reminder-detail__days-number--{{item_level}}`}>
{item.daysLeft === 0 ? '' : daysNumber}
</Text>
<Text className='reminder-detail__days-text'>{daysText}</Text>
</View>
) : (
<View className='reminder-detail__paused-notice'>
<Text className='reminder-detail__paused-text'>
提醒已暂停,点击下方「重新启用」恢复跟踪。
</Text>
</View>
)}
{/* ─── 进度条 ─── */}
<View className='reminder-detail__progress'>
<ProgressBar progress={item.progress} level={progressLevel} />
</View>
{/* ─── 主操作按钮 ─── */}
<View className='reminder-detail__actions'>
<Button
className={`reminder-detail__btn reminder-detail__btn--done reminder-detail__btn--{{doneBtnMod}}`}
onClick={handleDone}
>
本次已开药
</Button>
<Button
className='reminder-detail__btn reminder-detail__btn--pause'
onClick={handleTogglePause}
>
{item.status === 'paused' ? '重新启用' : '暂停提醒'}
</Button>
</View>
{/* ─── 信息格 ─── */}
<View className='reminder-detail__info-grid'>
<View className='reminder-detail__info'>
<Text className='reminder-detail__info-label'>最近开药日期</Text>
<Text className='reminder-detail__info-value'>{item.lastDate}</Text>
</View>
<View className='reminder-detail__info'>
<Text className='reminder-detail__info-label'>开药间隔</Text>
<Text className='reminder-detail__info-value'>{item.interval} 天</Text>
</View>
<View className='reminder-detail__info'>
<Text className='reminder-detail__info-label'>下次开药日期</Text>
<Text className='reminder-detail__info-value'>{item.nextDate}</Text>
</View>
<View className='reminder-detail__info'>
<Text className='reminder-detail__info-label'>提醒设置</Text>
<Text className='reminder-detail__info-value'>
提前 {item.before} 天 {item.time}
</Text>
</View>
</View>
{/* ─── 推荐操作提示 ─── */}
<View className='reminder-detail__notice'>
<Text className='reminder-detail__notice-title'>推荐操作</Text>
<Text className='reminder-detail__notice-desc'>
如果今天已经完成挂号或续方,点击「本次已开药」,系统会更新最近开药日期并推算下一次提醒。
</Text>
</View>
{/* ─── 次级操作(编辑 / 删除)─── */}
<View className='reminder-detail__footer'>
<Button
className='reminder-detail__btn reminder-detail__btn--edit'
onClick={() => onEdit(item.id)}
>
编辑提醒
</Button>
<Button
className='reminder-detail__btn reminder-detail__btn--delete'
onClick={handleDelete}
>
删除提醒
</Button>
</View>
{/* ─── 开药历史记录 ─── */}
{item.history.length > 0 ? (
<View className='reminder-detail__history'>
<View className='reminder-detail__history-head'>
<Text className='reminder-detail__history-title'>开药历史记录</Text>
<Text className='reminder-detail__history-count'>
最近 {item.history.length} 次
</Text>
</View>
{item.history.map((date, idx) => (
<View key={`hist-{{idx}}`} className='reminder-detail__history-item'>
<Text className='reminder-detail__history-date'>{date}</Text>
<Text className='reminder-detail__history-desc'>
周期 {item.interval} 天 · 提前 {item.before} 天提醒
</Text>
</View>
))}
</View>
) : null}
</View>
)
}
1.2 新建 src/components/ReminderDetail/index.scss
完整内容如下:
@use '@/assets/styles/variables' as *;
.reminder-detail {
display: grid;
gap: $spacing-4;
// ─── 药物名称区 ────────────────────────────────────────────────
&__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: $spacing-3;
}
&__header-main {
display: grid;
gap: 4px;
flex: 1;
min-width: 0;
}
&__name {
color: $ink-strong;
font-size: $text-xl;
font-weight: 900;
line-height: 1.2;
}
&__spec {
color: $muted;
font-size: $text-sm;
}
&__note {
color: $quiet;
font-size: $text-xs;
line-height: 1.5;
}
// ─── 倒计时大数字 ──────────────────────────────────────────────
&__days {
display: flex;
align-items: baseline;
gap: $spacing-2;
flex-wrap: wrap;
}
&__days-number {
font-size: $text-hero;
line-height: 1;
font-weight: 900;
font-variant-numeric: tabular-nums;
&--danger {
color: $danger;
}
&--warning {
color: $warning;
}
&--good {
color: $success;
}
&--paused {
color: $muted;
}
}
&__days-text {
color: $muted;
font-size: $text-base;
font-weight: 700;
line-height: 1.3;
}
// ─── 暂停状态提示 ──────────────────────────────────────────────
&__paused-notice {
padding: $spacing-4;
border-radius: $radius;
background: $violet-soft;
}
&__paused-text {
color: $violet;
font-size: $text-sm;
line-height: 1.6;
}
// ─── 进度条(仅做间距,ProgressBar 组件自带样式) ─────────────
&__progress {
// intentionally empty
}
// ─── 主操作按钮行 ──────────────────────────────────────────────
&__actions {
display: flex;
gap: $spacing-3;
}
// ─── 信息格 ────────────────────────────────────────────────────
&__info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $spacing-3;
}
&__info {
display: grid;
gap: 4px;
padding: $spacing-3;
border-radius: $radius;
background: $surface-soft;
}
&__info-label {
color: $muted;
font-size: $text-xs;
}
&__info-value {
color: $ink-strong;
font-size: $text-sm;
font-weight: 900;
}
// ─── 推荐操作提示 ──────────────────────────────────────────────
&__notice {
padding: $spacing-4;
border-radius: $radius;
background: $primary-soft;
border: 1px solid rgba(21, 122, 102, 0.12);
}
&__notice-title {
display: block;
color: $primary-deep;
font-size: $text-sm;
font-weight: 900;
}
&__notice-desc {
display: block;
margin-top: 4px;
color: $primary;
font-size: $text-xs;
line-height: 1.6;
}
// ─── 次级操作按钮行 ────────────────────────────────────────────
&__footer {
display: flex;
gap: $spacing-3;
}
// ─── 历史记录 ──────────────────────────────────────────────────
&__history {
display: grid;
gap: $spacing-3;
padding-top: $spacing-4;
border-top: 1px solid $line;
}
&__history-head {
display: flex;
align-items: center;
justify-content: space-between;
}
&__history-title {
color: $ink-strong;
font-size: $text-sm;
font-weight: 900;
}
&__history-count {
color: $muted;
font-size: $text-xs;
}
&__history-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-3;
border-radius: $radius;
background: $surface-soft;
}
&__history-date {
color: $ink;
font-size: $text-sm;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
&__history-desc {
color: $muted;
font-size: $text-xs;
}
// ─── 按钮覆盖(taroify Button 默认为 solid 色板,覆盖为 soft)─
&__btn {
flex: 1;
min-height: 80px !important;
border-radius: $radius !important;
font-size: $text-sm !important;
font-weight: 900 !important;
border: none !important;
// "本次已开药"——danger 状态:红色软色
&--done.reminder-detail__btn--danger {
background: $danger-soft !important;
color: $danger !important;
}
// "本次已开药"——非紧急状态:绿色软色
&--done.reminder-detail__btn--success {
background: $success-soft !important;
color: $success !important;
}
// "暂停提醒 / 重新启用"——中性色
&--pause {
background: $surface-soft !important;
color: $ink !important;
}
// "编辑提醒"——主色填充
&--edit {
background: $primary !important;
color: #fff !important;
}
// "删除提醒"——危险软色
&--delete {
background: $danger-soft !important;
color: $danger !important;
}
}
}
子任务 2:更新首页,接入详情面板
修改 src/pages/home/index.tsx。本任务只修改以下四处,不重写整个文件。
2.1 替换 import 区域
将文件顶部已有的 import 块:
import AppBar from '@/components/AppBar';
import BottomSheet from '@/components/BottomSheet';
import MedicineCard from '@/components/MedicineCard';
import ReminderForm from '@/components/ReminderForm';
替换为:
import AppBar from '@/components/AppBar';
import BottomSheet from '@/components/BottomSheet';
import MedicineCard from '@/components/MedicineCard';
import ReminderDetail from '@/components/ReminderDetail';
import ReminderForm from '@/components/ReminderForm';
2.2 在 Home 函数内补充 detail 相关状态
在已有的:
const [formOpen, setFormOpen] = useState(false);
const [formKey, setFormKey] = useState(0);
之后,紧接着添加:
// detailOpen:控制详情面板显隐;detailId:当前查看的提醒 ID
const [detailOpen, setDetailOpen] = useState(false);
const [detailId, setDetailId] = useState('');
// editReminderId:编辑模式时传给 ReminderForm;undefined 表示新增模式
const [editReminderId, setEditReminderId] = useState<string | undefined>(
undefined
);
2.3 替换三个函数
将:
const closeFormSheet = () => {
setFormOpen(false);
};
替换为:
const closeFormSheet = () => {
setFormOpen(false);
setEditReminderId(undefined);
};
const closeDetailSheet = () => {
setDetailOpen(false);
setDetailId('');
};
/** 从详情面板跳转到编辑表单 */
const handleEditFromDetail = (id: string) => {
// 先关闭详情(TabBar 状态由各自 onAfterClose 管理)
setDetailOpen(false);
setDetailId('');
// 再打开编辑表单(Taro 会在下一帧 hideTabBar)
setEditReminderId(id);
setFormKey((k) => k + 1);
setFormOpen(true);
};
将:
// TODO: M6 接入后替换为打开详情 BottomSheet。
const handleDetail = (_id: string) => {
Taro.showToast({ title: '详情开发中', icon: 'none', duration: 1200 });
};
替换为:
const handleDetail = (id: string) => {
setDetailId(id);
Taro.hideTabBar({ animation: true });
setDetailOpen(true);
};
2.4 替换 JSX 末尾(FAB + 两个 BottomSheet)
将:
{!formOpen ? (
<View className="home-fab" onClick={handleAddNew}>
<Text className="home-fab__icon">+</Text>
</View>
) : null}
<BottomSheet
open={formOpen}
title="新增提醒"
onClose={closeFormSheet}
onAfterOpen={handleSheetEntered}
onAfterClose={handleSheetExited}
>
<ReminderForm
key={formKey}
onSuccess={closeFormSheet}
onCancel={closeFormSheet}
/>
</BottomSheet>
</View>
);
}
替换为:
{/* FAB:两个 Sheet 均关闭时才显示 */}
{!formOpen && !detailOpen ? (
<View className="home-fab" onClick={handleAddNew}>
<Text className="home-fab__icon">+</Text>
</View>
) : null}
{/* 新增 / 编辑提醒 BottomSheet(M5 + M6 编辑入口) */}
<BottomSheet
open={formOpen}
title={editReminderId ? "编辑提醒" : "新增提醒"}
onClose={closeFormSheet}
onAfterOpen={handleSheetEntered}
onAfterClose={handleSheetExited}
>
<ReminderForm
key={formKey}
reminderId={editReminderId}
onSuccess={closeFormSheet}
onCancel={closeFormSheet}
/>
</BottomSheet>
{/* 提醒详情 BottomSheet(M6) */}
<BottomSheet
open={detailOpen}
title="提醒详情"
onClose={closeDetailSheet}
onAfterOpen={handleSheetEntered}
onAfterClose={handleSheetExited}
>
{detailId ? (
<ReminderDetail
reminderId={detailId}
onClose={closeDetailSheet}
onEdit={handleEditFromDetail}
/>
) : null}
</BottomSheet>
</View>
);
}
子任务 3:验证
pnpm dev:weapp 启动后在微信开发者工具中验证:
- 首页药物卡片点击「查看详情」,详情 BottomSheet 从底部滑入
- 详情面板标题显示「提醒详情」,Sheet 标题区显示关闭按钮
- 药物名称、规格、备注、StatusTag 颜色正确
- 逾期药物显示红色大数字 + "天 · 已逾期";今日药物显示"今日需要开药";未来药物显示 N + "天后预计需要重新开药"
- ProgressBar 颜色与状态对应(danger=红、warning=橙、good=绿、paused=绿)
- 点击「本次已开药」→ Toast 提示 "XX 已进入下一轮周期",详情面板关闭,首页列表刷新
- 点击「暂停提醒」→ Toast 提示,面板关闭,卡片变为紫色 paused 状态
- 对已暂停药物点击「重新启用」→ Toast 提示,恢复 active 状态
- 点击「编辑提醒」→ 详情面板关闭,编辑表单 BottomSheet 弹出,标题为"编辑提醒",已回填原有数据;提交后数据更新
- 点击「删除提醒」→ 弹出系统 Modal 确认框,点取消不操作;点删除 → Toast 提示,面板关闭,首页列表移除该条目
- 信息格:最近开药日期、开药间隔、下次开药日期、提醒设置(提前 N 天 HH:mm)正确显示
- 推荐操作提示:绿色软色背景,文字正确
- 历史记录:有历史时显示列表,history 为空时不渲染该区域
- FAB 在详情面板打开时隐藏,关闭后恢复
验收检查清单
- ReminderDetail 组件:接收
reminderId、onClose、onEdit,使用useDerivedById获取派生数据,reminderId对应记录不存在时返回null - 药物名称区:显示 name、spec(可选)、note(可选)和正确颜色的 StatusTag
- 倒计时大数字:逾期/今日/N天后,颜色与 level 对应;paused 状态改为展示暂停提示块(紫色软色背景)
- ProgressBar:paused 状态复用 good level(绿色,不突出)
- "本次已开药"按钮:danger 状态为红色软色,其余为绿色软色;点击后调用
markDone,Toast 提示,关闭面板,首页刷新 - "暂停提醒/重新启用"按钮:文字随 status 切换;点击后调用
togglePause,Toast 提示,关闭面板 - 信息格(2×2):四项数据全部正确显示,样式为 soft 背景圆角卡片
- 推荐操作提示:绿色软色背景(
$primary-soft),标题加粗,文字正确 - "编辑提醒"按钮:主色背景(
$primary)白字;点击后关闭详情,打开编辑表单 BottomSheet(标题变为"编辑提醒"),表单回填已有数据 - "删除提醒"按钮:危险软色;点击后弹出
Taro.showModal确认框,确认后调用deleteReminder,Toast 提示,关闭面板,列表移除 - 历史记录:有数据时渲染,条目显示日期 + 周期描述;history 为空时不渲染
- FAB 隐藏:详情面板或表单面板打开时 FAB 不显示
- TabBar 管理:详情面板打开时 TabBar 隐藏,关闭后 TabBar 恢复;编辑表单同理
- 首页 "查看详情" 联通:点击 MedicineCard 的详情按钮 → 详情 BottomSheet 弹出 → 展示正确的 ReminderDetail
- 编辑入口联通:详情面板 → 编辑提醒 → 表单以编辑模式弹出,提交后数据更新,首页刷新
-
pnpm dev:weapp无 TypeScript / SCSS 编译报错
