refactor(mp): 迁移医护+健康页面 — 使用 PageShell + ContentCard 统一组件库

行动收件箱、医护工作台、健康趋势、患者告警、体征录入、
日常监测、设备同步共 7 个页面迁移:
- 最外层容器 → PageShell
- 卡片元素 → ContentCard
- SCSS 删除 min-height/background/box-shadow 通用样式
This commit is contained in:
iven
2026-05-16 01:33:24 +08:00
parent 4dd5a1b4d9
commit 37327a4da4
14 changed files with 86 additions and 124 deletions

View File

@@ -1,10 +1,8 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
@import '../../styles/variables.scss';
@import '../../styles/mixins.scss';
.action-inbox-page {
min-height: 100vh;
background: $bg;
}
// PageShell 已接管min-height, background
// ContentCard 已接管inbox-card 背景/圆角/阴影/触摸反馈
.inbox-list {
height: calc(100vh - 50px);
@@ -12,11 +10,7 @@
}
.inbox-card {
background: $card;
border-radius: $r-sm;
padding: 14px 16px;
margin-bottom: 10px;
box-shadow: $shadow-sm;
.inbox-card-header {
display: flex;

View File

@@ -13,6 +13,8 @@ import Loading from '@/components/Loading';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import SegmentTabs from '@/components/SegmentTabs';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../hooks/useElderClass';
import './index.scss';
@@ -119,7 +121,7 @@ export default function ActionInboxPage() {
};
return (
<View className={`action-inbox-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
<SegmentTabs tabs={STATUS_TABS} activeKey={activeTab} onChange={handleTabChange} variant="underline" />
{error ? (
@@ -129,10 +131,11 @@ export default function ActionInboxPage() {
) : (
<ScrollView scrollY className="inbox-list">
{items.map((item) => (
<View
<ContentCard
key={item.id}
className="inbox-card"
onClick={() => handleItemClick(item)}
activeFeedback="opacity"
onPress={() => handleItemClick(item)}
>
<View className="inbox-card-header">
<Text
@@ -146,7 +149,7 @@ export default function ActionInboxPage() {
{item.patient_name} ·{' '}
{new Date(item.created_at).toLocaleDateString('zh-CN')}
</Text>
</View>
</ContentCard>
))}
{loading && <Loading />}
{!loading && items.length >= total && total > 0 && (
@@ -208,6 +211,6 @@ export default function ActionInboxPage() {
)}
</View>
)}
</View>
</PageShell>
);
}

View File

@@ -1,11 +1,9 @@
@import '../../styles/variables.scss';
@import '../../styles/mixins.scss';
// PageShell 已接管min-height, background, padding
.doctor-home {
min-height: 100vh;
background: $bg;
padding: 32px;
padding-bottom: calc(160px + env(safe-area-inset-bottom));
&__header {
margin-bottom: 40px;

View File

@@ -1,11 +1,12 @@
import { useState, useMemo, useCallback } from 'react';
import { View, Text, Input, ScrollView } from '@tarojs/components';
import { View, Text, Input } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useAuthStore } from '@/stores/auth';
import { useElderClass } from '../../hooks/useElderClass';
import { usePageData } from '@/hooks/usePageData';
import { getDashboard, type DoctorDashboard } from '@/services/doctor/dashboard';
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import './index.scss';
interface CardConfig {
@@ -107,7 +108,7 @@ export default function DoctorHome() {
if (loading) return <Loading />;
return (
<ScrollView scrollY className={`doctor-home ${modeClass}`}>
<PageShell safeBottom={false} className={`doctor-home ${modeClass}`}>
<View className='doctor-home__header'>
<Text className='doctor-home__title'></Text>
<Text className='doctor-home__greeting'>
@@ -192,6 +193,6 @@ export default function DoctorHome() {
<View className='doctor-home__footer'>
<Text className='doctor-home__logout' onClick={handleLogout}>退</Text>
</View>
</ScrollView>
</PageShell>
);
}

View File

@@ -1,11 +1,8 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.alerts-page {
min-height: 100vh;
background: $bg;
padding-bottom: env(safe-area-inset-bottom);
}
// PageShell 已接管min-height, background, safe-bottom
// ContentCard 已接管alert-card 背景/圆角/阴影
.alerts-tabs {
display: flex;
@@ -39,11 +36,7 @@
}
.alert-card {
background: $card;
border-radius: $r;
padding: 24px;
margin-bottom: 16px;
box-shadow: $shadow-sm;
}
.alert-header {

View File

@@ -8,6 +8,8 @@ import Loading from '@/components/Loading';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import SegmentTabs from '@/components/SegmentTabs';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../hooks/useElderClass';
import './index.scss';
@@ -79,23 +81,23 @@ export default function PatientAlerts() {
if (!currentPatient) {
return (
<View className={`alerts-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
<ErrorState text='请先完善个人档案' onRetry={() => Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })} />
</View>
</PageShell>
);
}
if (error) {
return (
<View className={`alerts-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
<SegmentTabs tabs={STATUS_TABS} activeKey={status} onChange={handleTabChange} variant="pill" />
<ErrorState onRetry={() => fetchAlerts(1, status, true)} />
</View>
</PageShell>
);
}
return (
<View className={`alerts-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
<SegmentTabs tabs={STATUS_TABS} activeKey={status} onChange={handleTabChange} variant="pill" />
{alerts.length === 0 && !loading ? (
@@ -105,7 +107,7 @@ export default function PatientAlerts() {
{alerts.map((item) => {
const sev = SEVERITY_MAP[item.severity] || SEVERITY_MAP.warning;
return (
<View className='alert-card' key={item.id}>
<ContentCard className='alert-card' key={item.id}>
<View className='alert-header'>
<View className={`alert-badge ${sev.className}`}>
<Text className='alert-badge-text'>{sev.label}</Text>
@@ -115,7 +117,7 @@ export default function PatientAlerts() {
</Text>
</View>
<Text className='alert-title'>{item.title}</Text>
</View>
</ContentCard>
);
})}
{loading && <Loading />}
@@ -124,6 +126,6 @@ export default function PatientAlerts() {
)}
</View>
)}
</View>
</PageShell>
);
}

View File

@@ -1,11 +1,8 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.dm-page {
min-height: 100vh;
background: $bg;
padding: 0 0 60px;
}
// PageShell 已接管min-height, background
// ContentCard 已接管dm-card/dm-group 背景/圆角/阴影
/* ── hero ── */
.dm-hero {
@@ -44,10 +41,6 @@
/* ── card (standalone, used for date picker) ── */
.dm-card {
background: $card;
border-radius: $r;
box-shadow: $shadow-md;
padding: 28px;
margin: 0 24px 20px;
}
@@ -98,9 +91,6 @@
/* ── collapsible group ── */
.dm-group {
background: $card;
border-radius: $r;
box-shadow: $shadow-md;
margin: 0 24px 20px;
overflow: hidden;
}

View File

@@ -10,6 +10,8 @@ import { clearRequestCache } from '@/services/request';
import { trackEvent } from '@/services/analytics';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import { useElderClass } from '@/hooks/useElderClass';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import {
BP_RANGE, WEIGHT_RANGE, SUGAR_RANGE, VOLUME_RANGE,
checkAbnormal, formatDate, FIELD_LABELS,
@@ -218,7 +220,7 @@ export default function DailyMonitoring() {
const bloodSugarAbnormal = checkAbnormal(bloodSugar, 'bloodSugar');
return (
<View className={`dm-page ${modeClass}`}>
<PageShell padding="none" safeBottom className={modeClass}>
{/* 页面标题 */}
<View className='dm-hero'>
<View className='dm-hero-icon'>
@@ -229,7 +231,7 @@ export default function DailyMonitoring() {
</View>
{/* 日期选择 (standalone card) */}
<View className='dm-card'>
<ContentCard className='dm-card'>
<View className='dm-card-header'>
<Text className='dm-card-title'></Text>
{isToday && (
@@ -247,10 +249,10 @@ export default function DailyMonitoring() {
<Text className='dm-date-arrow'>V</Text>
</View>
</Picker>
</View>
</ContentCard>
{/* ── Group 1: 晨间体征 (default open) ── */}
<View className={`dm-group${collapsed.morning ? ' dm-group-collapsed' : ''}`}>
<ContentCard className={`dm-group${collapsed.morning ? ' dm-group-collapsed' : ''}`}>
<View className='dm-group-header' onClick={() => toggleSection('morning')}>
<Text className='dm-group-title'></Text>
<Text className={`dm-group-arrow${collapsed.morning ? '' : ' dm-group-arrow-open'}`}>&#9656;</Text>
@@ -295,10 +297,10 @@ export default function DailyMonitoring() {
</View>
<Text className='dm-field-unit'>mmHg</Text>
</View>
</View>
</ContentCard>
{/* ── Group 2: 晚间体征 (default open) ── */}
<View className={`dm-group${collapsed.evening ? ' dm-group-collapsed' : ''}`}>
<ContentCard className={`dm-group${collapsed.evening ? ' dm-group-collapsed' : ''}`}>
<View className='dm-group-header' onClick={() => toggleSection('evening')}>
<Text className='dm-group-title'></Text>
<Text className={`dm-group-arrow${collapsed.evening ? '' : ' dm-group-arrow-open'}`}>&#9656;</Text>
@@ -343,10 +345,10 @@ export default function DailyMonitoring() {
</View>
<Text className='dm-field-unit'>mmHg</Text>
</View>
</View>
</ContentCard>
{/* ── Group 3: 其他指标 (default collapsed) ── */}
<View className={`dm-group${collapsed.other ? ' dm-group-collapsed' : ''}`}>
<ContentCard className={`dm-group${collapsed.other ? ' dm-group-collapsed' : ''}`}>
<View className='dm-group-header' onClick={() => toggleSection('other')}>
<Text className='dm-group-title'></Text>
<Text className={`dm-group-arrow${collapsed.other ? '' : ' dm-group-arrow-open'}`}>&#9656;</Text>
@@ -428,7 +430,7 @@ export default function DailyMonitoring() {
/>
</View>
</View>
</View>
</ContentCard>
{/* 提交 */}
<View
@@ -442,6 +444,6 @@ export default function DailyMonitoring() {
<View className='dm-reset' onClick={resetForm}>
<Text className='dm-reset-text'></Text>
</View>
</View>
</PageShell>
);
}

View File

@@ -1,11 +1,8 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.device-sync-page {
min-height: 100vh;
background: $bg;
padding-bottom: env(safe-area-inset-bottom);
}
// PageShell 已接管min-height, background, safe-bottom
// ContentCard 已接管sync-status-card/sync-result-card 背景/圆角/阴影
.sync-header {
background: $pri;
@@ -130,11 +127,7 @@
.sync-status-card {
display: flex;
align-items: center;
background: $card;
border-radius: $r-sm;
padding: 24px;
margin-bottom: 16px;
box-shadow: $shadow-sm;
}
.sync-status-dot {
@@ -225,9 +218,6 @@
display: flex;
flex-direction: column;
align-items: center;
background: $card;
border-radius: $r;
padding: 48px 24px;
margin-bottom: 24px;
box-shadow: $shadow-sm;
}

View File

@@ -12,6 +12,8 @@ import { uploadReadings } from '@/services/device-sync';
import { useAuthStore } from '@/stores/auth';
import type { BLEDevice, NormalizedReading } from '@/services/ble/types';
import { useElderClass } from '@/hooks/useElderClass';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import './index.scss';
/** liveReadings 最大保留条数,防止内存无限增长 */
@@ -232,10 +234,10 @@ export default function DeviceSync() {
const renderConnected = () => (
<View className="sync-section">
<View className="sync-status-card">
<ContentCard className="sync-status-card">
<Text className="sync-status-dot sync-status-dot--connected" />
<Text className="sync-status-text">: {selectedDevice?.name}</Text>
</View>
</ContentCard>
{liveReadings.length > 0 && (
<View className="sync-readings-panel">
@@ -276,11 +278,11 @@ export default function DeviceSync() {
const renderDone = () => (
<View className="sync-section">
<View className="sync-result-card">
<ContentCard className="sync-result-card">
<Text className="sync-result-icon">V</Text>
<Text className="sync-result-title"></Text>
<Text className="sync-result-count"> {syncCount} </Text>
</View>
</ContentCard>
<View className="sync-action" onClick={() => {
handleDisconnect();
if (returnTo === 'input') {
@@ -293,7 +295,7 @@ export default function DeviceSync() {
);
return (
<View className={`device-sync-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
<View className="sync-header">
<Text className="sync-header-title"></Text>
</View>
@@ -317,6 +319,6 @@ export default function DeviceSync() {
{(pageState === 'idle' || pageState === 'error') && renderIdle()}
{pageState === 'connected' && renderConnected()}
{pageState === 'done' && renderDone()}
</View>
</PageShell>
);
}

View File

@@ -1,11 +1,8 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.input-page {
min-height: 100vh;
background: $bg;
padding: 0 0 60px;
}
// PageShell 已接管min-height, background
// ContentCard 已接管input-card 背景/圆角/阴影
/* ── hero ── */
.input-hero {
@@ -47,10 +44,6 @@
display: flex;
align-items: center;
justify-content: space-between;
background: $card;
border-radius: $r;
box-shadow: $shadow-sm;
padding: 24px 28px;
margin: 0 24px 20px;
border: 1px dashed $pri;
@@ -72,10 +65,6 @@
/* ── card ── */
.input-card {
background: $card;
border-radius: $r;
box-shadow: $shadow-md;
padding: 28px;
margin: 0 24px 20px;
}

View File

@@ -12,6 +12,8 @@ import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import { trackEvent } from '@/services/analytics';
import { useElderClass } from '../../../hooks/useElderClass';
import Loading from '../../../components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import './index.scss';
const INDICATORS = [
@@ -176,7 +178,7 @@ export default function HealthInput() {
const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0);
return (
<View className={`input-page ${modeClass}`}>
<PageShell padding="none" safeBottom className={modeClass}>
{loadingThresholds && <Loading />}
{!loadingThresholds && (
@@ -197,7 +199,7 @@ export default function HealthInput() {
</View>
{/* 指标类型选择 */}
<View className='input-card'>
<ContentCard className='input-card'>
<View className='input-card-header'>
<View className='input-card-indicator'>
<Text className='input-card-indicator-char'>{indicatorInitial}</Text>
@@ -215,11 +217,11 @@ export default function HealthInput() {
<Text className='input-picker-arrow'>V</Text>
</View>
</Picker>
</View>
</ContentCard>
{/* 数值输入 */}
{BP_INDICATORS.includes(INDICATORS[indicatorIdx].value) ? (
<View className='input-card'>
<ContentCard className='input-card'>
<Text className='input-section-title'></Text>
<View className='input-bp-group'>
<View className='input-bp-field'>
@@ -249,9 +251,9 @@ export default function HealthInput() {
</View>
</View>
<Text className='input-field-unit'>mmHg</Text>
</View>
</ContentCard>
) : (
<View className='input-card'>
<ContentCard className='input-card'>
<Text className='input-section-title'></Text>
<Input
type='digit'
@@ -263,11 +265,11 @@ export default function HealthInput() {
<Text className='input-field-unit'>
{INDICATORS[indicatorIdx].label.match(/\((.+)\)/)?.[1] || ''}
</Text>
</View>
</ContentCard>
)}
{/* 备注 */}
<View className='input-card'>
<ContentCard className='input-card'>
<Text className='input-section-title'></Text>
<Input
className='input-field-box input-field-full'
@@ -275,7 +277,7 @@ export default function HealthInput() {
value={note}
onInput={(e) => setNote(e.detail.value)}
/>
</View>
</ContentCard>
{/* 提交 */}
<View
@@ -286,6 +288,6 @@ export default function HealthInput() {
</View>
</>
)}
</View>
</PageShell>
);
}

View File

@@ -1,9 +1,10 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.trend-page {
min-height: 100vh;
background: $bg;
// PageShell 已接管min-height, background
// ContentCard 已接管trend-chart-card 背景/圆角/阴影
.trend-page-shell {
padding-bottom: 60px;
}
@@ -48,10 +49,6 @@
/* ── chart card ── */
.trend-chart-card {
margin: 0 24px 20px;
background: $card;
border-radius: $r;
box-shadow: $shadow-md;
padding: 24px;
min-height: 300px;
overflow: hidden;
}
@@ -59,9 +56,6 @@
/* ── reference card ── */
.trend-ref-card {
margin: 0 24px 20px;
background: $acc-l;
border-radius: $r;
padding: 20px 24px;
display: flex;
align-items: center;
gap: 16px;

View File

@@ -8,6 +8,8 @@ import Loading from '@/components/Loading';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import SegmentTabs from '@/components/SegmentTabs';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../hooks/useElderClass';
import './index.scss';
@@ -67,7 +69,7 @@ export default function Trend() {
};
return (
<View className={`trend-page ${modeClass}`}>
<PageShell padding="none" className={modeClass}>
{/* 页面标题 */}
<View className='trend-hero'>
<View className='trend-hero-icon'>
@@ -81,36 +83,36 @@ export default function Trend() {
{/* ECharts 折线图 */}
{loading ? (
<View className='trend-chart-card'>
<ContentCard className='trend-chart-card'>
<Loading />
</View>
</ContentCard>
) : error ? (
<View className='trend-chart-card'>
<ContentCard className='trend-chart-card'>
<ErrorState onRetry={fetchTrend} />
</View>
</ContentCard>
) : points.length === 0 ? (
<View className='trend-chart-card'>
<ContentCard className='trend-chart-card'>
<EmptyState text='暂无趋势数据' />
</View>
</ContentCard>
) : (
<View className='trend-chart-card'>
<ContentCard className='trend-chart-card'>
<TrendChart
data={points}
referenceMin={meta.refMin}
referenceMax={meta.refMax}
unit={meta.unit}
/>
</View>
</ContentCard>
)}
{/* 参考区间 */}
{!loading && points.length > 0 && meta.refMin !== undefined && meta.refMax !== undefined && (
<View className='trend-ref-card'>
<ContentCard variant="outlined" className='trend-ref-card'>
<Text className='trend-ref-label'></Text>
<Text className='trend-ref-value'>
{meta.refMin} ~ {meta.refMax} {meta.unit}
</Text>
</View>
</ContentCard>
)}
{/* 数据列表 */}
@@ -135,6 +137,6 @@ export default function Trend() {
})}
</View>
)}
</View>
</PageShell>
);
}