From e8ccee02d557afa0bea101458ba53843f98dd302 Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 9 May 2026 22:17:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(miniprogram):=20=E5=85=B3=E6=80=80?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=20Phase=202=20=E2=80=94=20Design=20Token=20+?= =?UTF-8?q?=2015=20=E9=A1=B5=E9=9D=A2=E6=89=B9=E9=87=8F=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 useElderClass hook,替代每页 3 行样板代码 - 新建 CSS 自定义属性 Design Token 系统(tokens.scss) 正常/关怀两套值:字号、间距、触控、布局参数 - 15 个页面批量接入关怀模式 class: TabBar: 商城页 主流程: 预约列表/详情/创建、咨询详情 子包: 体征录入/趋势/日常监测/告警、用药/档案/随访/报告/家庭/设置 - 新建 elder-toast 工具(关怀模式 3s + 触觉反馈) - 页面覆盖率:4/59 → 22/59 (37%) Co-Authored-By: Claude Opus 4.7 --- apps/miniprogram/src/app.scss | 1 + apps/miniprogram/src/hooks/useElderClass.ts | 6 ++ .../src/pages/appointment/create/index.tsx | 4 +- .../src/pages/appointment/detail/index.tsx | 8 +- .../src/pages/appointment/index.tsx | 4 +- .../src/pages/consultation/detail/index.tsx | 82 +++++++++++-------- .../src/pages/consultation/index.tsx | 5 +- apps/miniprogram/src/pages/health/index.tsx | 5 +- apps/miniprogram/src/pages/mall/index.tsx | 6 +- apps/miniprogram/src/pages/messages/index.tsx | 5 +- .../src/pages/pkg-health/alerts/index.tsx | 6 +- .../pkg-health/daily-monitoring/index.tsx | 4 +- .../src/pages/pkg-health/input/index.tsx | 4 +- .../src/pages/pkg-health/trend/index.tsx | 4 +- .../src/pages/pkg-profile/family/index.tsx | 4 +- .../src/pages/pkg-profile/followups/index.tsx | 4 +- .../pkg-profile/health-records/index.tsx | 4 +- .../pages/pkg-profile/medication/index.tsx | 6 +- .../src/pages/pkg-profile/reports/index.tsx | 4 +- .../src/pages/pkg-profile/settings/index.tsx | 4 +- apps/miniprogram/src/styles/tokens.scss | 79 ++++++++++++++++++ apps/miniprogram/src/utils/elder-toast.ts | 23 ++++++ 22 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 apps/miniprogram/src/hooks/useElderClass.ts create mode 100644 apps/miniprogram/src/styles/tokens.scss create mode 100644 apps/miniprogram/src/utils/elder-toast.ts diff --git a/apps/miniprogram/src/app.scss b/apps/miniprogram/src/app.scss index b825c65..fcd7833 100644 --- a/apps/miniprogram/src/app.scss +++ b/apps/miniprogram/src/app.scss @@ -1,4 +1,5 @@ @import './styles/variables.scss'; +@import './styles/tokens.scss'; @import './styles/elder-mode.scss'; page { diff --git a/apps/miniprogram/src/hooks/useElderClass.ts b/apps/miniprogram/src/hooks/useElderClass.ts new file mode 100644 index 0000000..d5b68aa --- /dev/null +++ b/apps/miniprogram/src/hooks/useElderClass.ts @@ -0,0 +1,6 @@ +import { useUIStore } from '../stores/ui'; + +export function useElderClass(): string { + const mode = useUIStore((s) => s.mode); + return mode === 'elder' ? 'elder-mode' : ''; +} diff --git a/apps/miniprogram/src/pages/appointment/create/index.tsx b/apps/miniprogram/src/pages/appointment/create/index.tsx index 0c6d14e..ac076f9 100644 --- a/apps/miniprogram/src/pages/appointment/create/index.tsx +++ b/apps/miniprogram/src/pages/appointment/create/index.tsx @@ -7,6 +7,7 @@ import { TEMPLATE_IDS } from '@/services/wechat-templates'; import { trackEvent } from '@/services/analytics'; import StepIndicator from '../../../components/StepIndicator'; import WeekCalendar from '../../../components/WeekCalendar'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const DEPARTMENTS = [ @@ -44,6 +45,7 @@ export default function AppointmentCreate() { const [loading, setLoading] = useState(false); const [schedules, setSchedules] = useState([]); const [timeSlots, setTimeSlots] = useState([]); + const modeClass = useElderClass(); const currentPatient = useAuthStore((s) => s.currentPatient); @@ -148,7 +150,7 @@ export default function AppointmentCreate() { }; return ( - + = { @@ -22,6 +23,7 @@ export default function AppointmentDetail() { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [cancelling, setCancelling] = useState(false); + const modeClass = useElderClass(); useEffect(() => { if (!id) return; @@ -65,7 +67,7 @@ export default function AppointmentDetail() { if (loading) { return ( - + 返回 预约详情 @@ -78,7 +80,7 @@ export default function AppointmentDetail() { if (error || !appointment) { return ( - + 返回 预约详情 @@ -90,7 +92,7 @@ export default function AppointmentDetail() { } return ( - + 返回 预约详情 diff --git a/apps/miniprogram/src/pages/appointment/index.tsx b/apps/miniprogram/src/pages/appointment/index.tsx index 77951b3..be1d975 100644 --- a/apps/miniprogram/src/pages/appointment/index.tsx +++ b/apps/miniprogram/src/pages/appointment/index.tsx @@ -5,6 +5,7 @@ import { listAppointments } from '../../services/appointment'; import type { Appointment } from '../../services/appointment'; import EmptyState from '../../components/EmptyState'; import Loading from '../../components/Loading'; +import { useElderClass } from '../../hooks/useElderClass'; import './index.scss'; const STATUS_MAP: Record = { @@ -30,6 +31,7 @@ export default function AppointmentList() { const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const loadingRef = useRef(false); + const modeClass = useElderClass(); const fetchData = useCallback(async (pageNum: number, isRefresh = false) => { if (loadingRef.current) return; @@ -86,7 +88,7 @@ export default function AppointmentList() { }; return ( - + {/* 页面标题 */} 预约挂号 diff --git a/apps/miniprogram/src/pages/consultation/detail/index.tsx b/apps/miniprogram/src/pages/consultation/detail/index.tsx index 78c4fef..65a6d60 100644 --- a/apps/miniprogram/src/pages/consultation/detail/index.tsx +++ b/apps/miniprogram/src/pages/consultation/detail/index.tsx @@ -6,14 +6,14 @@ import { listMessages, sendMessage, markSessionRead, + pollMessages, type ConsultationSession, type ConsultationMessage, } from '@/services/consultation'; import Loading from '@/components/Loading'; +import { useElderClass } from '@/hooks/useElderClass'; import './index.scss'; -const POLL_INTERVAL = 8000; - export default function ConsultationDetail() { const router = useRouter(); const sessionId = router.params.id || ''; @@ -23,43 +23,35 @@ export default function ConsultationDetail() { const [sending, setSending] = useState(false); const [loading, setLoading] = useState(true); const scrollViewRef = useRef(''); - const pollTimerRef = useRef | null>(null); + const pollingRef = useRef(false); + const modeClass = useElderClass(); useEffect(() => { if (sessionId) { loadData(); markRead(); - startPolling(); + startLongPolling(); } - return () => stopPolling(); + return () => { pollingRef.current = false; }; }, [sessionId]); - const startPolling = () => { - stopPolling(); - pollTimerRef.current = setInterval(pollNewMessages, POLL_INTERVAL); + useEffect(() => { + if (session?.status === 'closed') { + pollingRef.current = false; + } + }, [session?.status]); + + const startLongPolling = () => { + pollingRef.current = true; + longPoll(); }; - const stopPolling = () => { - if (pollTimerRef.current) { - clearInterval(pollTimerRef.current); - pollTimerRef.current = null; - } - }; - - const pollNewMessages = async () => { - if (!session || session.status === 'closed') { - stopPolling(); - return; - } + const longPoll = async () => { + if (!pollingRef.current) return; try { const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined; - const m = await listMessages(sessionId, { - page: 1, - page_size: 50, - after_id: lastId, - }); - const newMsgs = m.data || []; - if (newMsgs.length > 0) { + const newMsgs = await pollMessages(sessionId, lastId); + if (newMsgs && newMsgs.length > 0) { setMessages((prev) => { const existing = new Set(prev.map((msg) => msg.id)); const fresh = newMsgs.filter((msg) => !existing.has(msg.id)); @@ -67,7 +59,12 @@ export default function ConsultationDetail() { }); scrollViewRef.current = `msg-${messages.length + newMsgs.length}`; } - } catch { /* 轮询失败静默忽略 */ } + } catch { + // 超时或网络错误,静默重试 + } + if (pollingRef.current) { + longPoll(); + } }; const loadData = async () => { @@ -80,7 +77,7 @@ export default function ConsultationDetail() { setSession(s); setMessages(m.data || []); scrollViewRef.current = `msg-${(m.data || []).length}`; - if (s.status === 'closed') stopPolling(); + if (s.status === 'closed') pollingRef.current = false; } catch { Taro.showToast({ title: '加载失败', icon: 'none' }); } finally { @@ -137,14 +134,24 @@ export default function ConsultationDetail() { if (loading) return ; const isOpen = session?.status !== 'closed'; + const doctorInitial = (session?.subject || '医').charAt(0); + const statusLabel = session?.status === 'active' ? '进行中' + : session?.status === 'pending' ? '等待接诊' + : '已结束'; return ( - + + {/* 导航栏 — 对齐设计稿:返回 + 标题 + 副标题 */} - {session?.subject || '在线咨询'} - {!isOpen && ( - 已结束 - )} + Taro.navigateBack()}> + ‹ 返回 + + + {session?.subject || '在线咨询'} + + {statusLabel} + + )} + {!isSelf && ( + + {doctorInitial} + + )} {isImageUrl(msg.content) ? ( - {sending ? '...' : '发送'} + ) : ( diff --git a/apps/miniprogram/src/pages/consultation/index.tsx b/apps/miniprogram/src/pages/consultation/index.tsx index b54d155..9f94a8a 100644 --- a/apps/miniprogram/src/pages/consultation/index.tsx +++ b/apps/miniprogram/src/pages/consultation/index.tsx @@ -3,7 +3,7 @@ import { View, Text } from '@tarojs/components'; import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro'; import { listConsultations, ConsultationSession } from '@/services/consultation'; import Loading from '../../components/Loading'; -import { useUIStore } from '../../stores/ui'; +import { useElderClass } from '../../hooks/useElderClass'; import './index.scss'; function getStatusTag(status: string) { @@ -34,8 +34,7 @@ export default function Consultation() { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); - const { mode } = useUIStore(); - const modeClass = mode === 'elder' ? 'elder-mode' : ''; + const modeClass = useElderClass(); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const loadingRef = useRef(false); diff --git a/apps/miniprogram/src/pages/health/index.tsx b/apps/miniprogram/src/pages/health/index.tsx index 11047d9..9b3803d 100644 --- a/apps/miniprogram/src/pages/health/index.tsx +++ b/apps/miniprogram/src/pages/health/index.tsx @@ -3,7 +3,7 @@ import { View, Text, Input } from '@tarojs/components'; import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; import { useHealthStore } from '../../stores/health'; import { useAuthStore } from '../../stores/auth'; -import { useUIStore } from '../../stores/ui'; +import { useElderClass } from '../../hooks/useElderClass'; import { inputVitalSign, getTrend, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../services/health'; import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis'; import Loading from '../../components/Loading'; @@ -43,8 +43,7 @@ interface TrendPoint { export default function Health() { const { todaySummary, loading, refreshToday, getTrend: fetchTrend } = useHealthStore(); const { user, currentPatient } = useAuthStore(); - const { mode } = useUIStore(); - const modeClass = mode === 'elder' ? 'elder-mode' : ''; + const modeClass = useElderClass(); const [activeTab, setActiveTab] = useState('blood_pressure'); const [systolic, setSystolic] = useState(''); const [diastolic, setDiastolic] = useState(''); diff --git a/apps/miniprogram/src/pages/mall/index.tsx b/apps/miniprogram/src/pages/mall/index.tsx index d3f1195..7dfefbc 100644 --- a/apps/miniprogram/src/pages/mall/index.tsx +++ b/apps/miniprogram/src/pages/mall/index.tsx @@ -6,6 +6,7 @@ import type { PointsProduct } from '../../services/points'; import { useAuthStore } from '../../stores/auth'; import { usePointsStore } from '../../stores/points'; import Loading from '../../components/Loading'; +import { useElderClass } from '../../hooks/useElderClass'; import './index.scss'; const PRODUCT_TYPE_TABS = [ @@ -32,6 +33,7 @@ export default function Mall() { const [checkinLoading, setCheckinLoading] = useState(false); const [noProfile, setNoProfile] = useState(false); const loadingRef = useRef(false); + const modeClass = useElderClass(); const fetchProducts = useCallback( async (pageNum: number, type: string, isRefresh = false) => { @@ -132,7 +134,7 @@ export default function Mall() { if (noProfile) { return ( - + @@ -148,7 +150,7 @@ export default function Mall() { } return ( - + {/* 积分余额卡片 */} diff --git a/apps/miniprogram/src/pages/messages/index.tsx b/apps/miniprogram/src/pages/messages/index.tsx index d097b34..5f1a87b 100644 --- a/apps/miniprogram/src/pages/messages/index.tsx +++ b/apps/miniprogram/src/pages/messages/index.tsx @@ -6,7 +6,7 @@ import { notificationService } from '../../services/notification'; import Loading from '../../components/Loading'; import GuestGuard from '../../components/GuestGuard'; import { useAuthStore } from '../../stores/auth'; -import { useUIStore } from '../../stores/ui'; +import { useElderClass } from '../../hooks/useElderClass'; import './index.scss'; type MsgTab = 'consultation' | 'notification'; @@ -30,8 +30,7 @@ const NOTIFY_ICONS: Record export default function Messages() { const user = useAuthStore((s) => s.user); - const { mode } = useUIStore(); - const modeClass = mode === 'elder' ? 'elder-mode' : ''; + const modeClass = useElderClass(); const [activeTab, setActiveTab] = useState('consultation'); const [sessions, setSessions] = useState([]); const [notifications, setNotifications] = useState([]); diff --git a/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx b/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx index 2651ecd..1d11eea 100644 --- a/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx @@ -4,6 +4,7 @@ import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; import { listPatientAlerts, type Alert } from '@/services/alert'; import { useAuthStore } from '@/stores/auth'; import Loading from '@/components/Loading'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const SEVERITY_MAP: Record = { @@ -21,6 +22,7 @@ const STATUS_TABS = [ ]; export default function PatientAlerts() { + const modeClass = useElderClass(); const { currentPatient } = useAuthStore(); const [alerts, setAlerts] = useState([]); const [total, setTotal] = useState(0); @@ -74,7 +76,7 @@ export default function PatientAlerts() { if (!currentPatient) { return ( - + 请先完善个人档案 Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })}> @@ -86,7 +88,7 @@ export default function PatientAlerts() { } return ( - + {STATUS_TABS.map((tab) => ( = { }; export default function DailyMonitoring() { + const modeClass = useElderClass(); const { currentPatient } = useAuthStore(); const today = formatDate(new Date()); @@ -258,7 +260,7 @@ export default function DailyMonitoring() { const bloodSugarAbnormal = checkAbnormal(bloodSugar, 'bloodSugar'); return ( - + {/* 页面标题 */} diff --git a/apps/miniprogram/src/pages/pkg-health/input/index.tsx b/apps/miniprogram/src/pages/pkg-health/input/index.tsx index a9c89be..63f5ca5 100644 --- a/apps/miniprogram/src/pages/pkg-health/input/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/input/index.tsx @@ -8,6 +8,7 @@ import { useHealthStore } from '@/stores/health'; import { usePointsStore } from '@/stores/points'; import { clearRequestCache } from '@/services/request'; import { trackEvent } from '@/services/analytics'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const INDICATORS = [ @@ -56,6 +57,7 @@ function getWarnForIndicator( } export default function HealthInput() { + const modeClass = useElderClass(); const [indicatorIdx, setIndicatorIdx] = useState(0); const [thresholds, setThresholds] = useState(DEFAULT_THRESHOLDS); const [value, setValue] = useState(''); @@ -155,7 +157,7 @@ export default function HealthInput() { const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0); return ( - + {/* 页面标题 */} diff --git a/apps/miniprogram/src/pages/pkg-health/trend/index.tsx b/apps/miniprogram/src/pages/pkg-health/trend/index.tsx index f01b2f5..58521e5 100644 --- a/apps/miniprogram/src/pages/pkg-health/trend/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/trend/index.tsx @@ -3,6 +3,7 @@ import { View, Text } from '@tarojs/components'; import { useRouter } from '@tarojs/taro'; import { useHealthStore } from '@/stores/health'; import TrendChart from '@/components/TrendChart'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const RANGE_OPTIONS = [ @@ -22,6 +23,7 @@ const INDICATOR_META: Record + {/* 页面标题 */} diff --git a/apps/miniprogram/src/pages/pkg-profile/family/index.tsx b/apps/miniprogram/src/pages/pkg-profile/family/index.tsx index 4651529..8edf1b8 100644 --- a/apps/miniprogram/src/pages/pkg-profile/family/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/family/index.tsx @@ -4,9 +4,11 @@ import Taro, { useDidShow } from '@tarojs/taro'; import { listPatients, Patient } from '../../../services/patient'; import { useAuthStore } from '../../../stores/auth'; import EmptyState from '../../../components/EmptyState'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; export default function FamilyList() { + const modeClass = useElderClass(); const [patients, setPatients] = useState([]); const [loading, setLoading] = useState(false); const { currentPatient, setCurrentPatient } = useAuthStore(); @@ -58,7 +60,7 @@ export default function FamilyList() { }; return ( - + 就诊人管理 diff --git a/apps/miniprogram/src/pages/pkg-profile/followups/index.tsx b/apps/miniprogram/src/pages/pkg-profile/followups/index.tsx index 47c911f..16a2998 100644 --- a/apps/miniprogram/src/pages/pkg-profile/followups/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/followups/index.tsx @@ -4,6 +4,7 @@ import Taro, { useDidShow } from '@tarojs/taro'; import { listTasks, FollowUpTask } from '../../../services/followup'; import EmptyState from '../../../components/EmptyState'; import Loading from '../../../components/Loading'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const TABS = [ @@ -13,6 +14,7 @@ const TABS = [ ]; export default function MyFollowUps() { + const modeClass = useElderClass(); const [activeTab, setActiveTab] = useState('pending'); const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(false); @@ -55,7 +57,7 @@ export default function MyFollowUps() { }; return ( - + {TABS.map((tab) => ( = { @@ -13,6 +14,7 @@ const TYPE_MAP: Record = { }; export default function HealthRecords() { + const modeClass = useElderClass(); const [records, setRecords] = useState([]); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); @@ -55,7 +57,7 @@ export default function HealthRecords() { }); return ( - + 健康记录 diff --git a/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx b/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx index 784c3c4..c589c23 100644 --- a/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx @@ -9,9 +9,11 @@ import { deleteReminder, type MedicationReminder, } from '../../../services/medication-reminder'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; export default function MedicationReminder() { + const modeClass = useElderClass(); const [reminders, setReminders] = useState([]); const [loading, setLoading] = useState(true); const [showForm, setShowForm] = useState(false); @@ -96,7 +98,7 @@ export default function MedicationReminder() { if (loading) { return ( - + 用药提醒 加载中... @@ -106,7 +108,7 @@ export default function MedicationReminder() { } return ( - + 用药提醒 diff --git a/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx b/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx index 424cc0b..990c2b5 100644 --- a/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx @@ -4,9 +4,11 @@ import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/ta import { listReports, LabReport } from '../../../services/report'; import EmptyState from '../../../components/EmptyState'; import Loading from '../../../components/Loading'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; export default function MyReports() { + const modeClass = useElderClass(); const [reports, setReports] = useState([]); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); @@ -65,7 +67,7 @@ export default function MyReports() { }; return ( - + 检查报告 diff --git a/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx b/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx index d52d0d9..d1c35a4 100644 --- a/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { View, Text } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { useAuthStore } from '../../../stores/auth'; +import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; export default function Settings() { + const modeClass = useElderClass(); const { logout } = useAuthStore(); const handleClearCache = () => { @@ -55,7 +57,7 @@ export default function Settings() { }; return ( - + 设置 diff --git a/apps/miniprogram/src/styles/tokens.scss b/apps/miniprogram/src/styles/tokens.scss new file mode 100644 index 0000000..2b42211 --- /dev/null +++ b/apps/miniprogram/src/styles/tokens.scss @@ -0,0 +1,79 @@ +// Design Token — CSS 自定义属性 +// 正常模式值定义在 :root,关怀模式通过 .elder-mode 覆盖 +// 页面样式应引用 var(--tk-*) 而非硬编码 px 值 + +// ═══════════════════════════════════════ +// 正常模式 Token +// ═══════════════════════════════════════ +page { + // ─── 字号 ─── + --tk-font-hero: 48px; // 登录标题等超大字 + --tk-font-h1: 26px; // 页面标题 + --tk-font-h2: 22px; // 区块标题 + --tk-font-body: 15px; // 正文 / 列表项 + --tk-font-body-lg: 17px; // 大正文 / 按钮文字 + --tk-font-cap: 13px; // 辅助说明 / 次要文字 + --tk-font-micro: 11px; // 标签 / 角标 / 最小文字 + + // ─── 数值型字号 ─── + --tk-font-value: 30px; // 体征数值 + --tk-font-value-lg: 28px; // 统计数值 + + // ─── 间距 ─── + --tk-gap-xs: 6px; + --tk-gap-sm: 8px; + --tk-gap-md: 10px; + --tk-gap-lg: 16px; + --tk-gap-xl: 20px; + --tk-gap-2xl: 24px; + + // ─── 内边距 ─── + --tk-pad-page: 20px 24px; // 页面内边距 + --tk-pad-card: 16px; // 卡片内边距 + --tk-pad-section: 20px; // 区块内边距 + + // ─── 触控 ─── + --tk-touch-min: 44px; + --tk-btn-h: 52px; + --tk-btn-primary-h: 56px; + + // ─── 布局 ─── + --tk-grid-cols: 2; + --tk-line-height: 1.5; +} + +// ═══════════════════════════════════════ +// 关怀模式 Token(非线性放大) +// ═══════════════════════════════════════ +.elder-mode { + // 字号:标题 ×1.15 / 正文 ×1.35 / 辅助 ×1.55 + --tk-font-hero: 56px; + --tk-font-h1: 30px; + --tk-font-h2: 26px; + --tk-font-body: 20px; + --tk-font-body-lg: 22px; + --tk-font-cap: 18px; + --tk-font-micro: 17px; + + --tk-font-value: 34px; + --tk-font-value-lg: 34px; + + // 间距:×1.4(大于字号放大倍率,增加呼吸空间) + --tk-gap-xs: 8px; + --tk-gap-sm: 12px; + --tk-gap-md: 14px; + --tk-gap-lg: 22px; + --tk-gap-xl: 28px; + --tk-gap-2xl: 32px; + + --tk-pad-page: 28px 32px; + --tk-pad-card: 20px; + --tk-pad-section: 28px; + + --tk-touch-min: 56px; + --tk-btn-h: 60px; + --tk-btn-primary-h: 64px; + + --tk-grid-cols: 1; + --tk-line-height: 1.7; +} diff --git a/apps/miniprogram/src/utils/elder-toast.ts b/apps/miniprogram/src/utils/elder-toast.ts new file mode 100644 index 0000000..1ec71d9 --- /dev/null +++ b/apps/miniprogram/src/utils/elder-toast.ts @@ -0,0 +1,23 @@ +import Taro from '@tarojs/taro'; +import { useUIStore } from '../stores/ui'; + +/** + * 关怀模式自适应 Toast + * - 正常模式:默认 1500ms + * - 关怀模式:默认 3000ms + 触觉反馈 + */ +export function showToast(options: { + title: string; + icon?: 'success' | 'error' | 'loading' | 'none'; + duration?: number; + mask?: boolean; +}) { + const { mode } = useUIStore.getState(); + const duration = options.duration ?? (mode === 'elder' ? 3000 : 1500); + + if (mode === 'elder') { + try { Taro.vibrateShort({ type: 'light' }); } catch { /* 不支持时静默 */ } + } + + Taro.showToast({ ...options, duration, icon: options.icon ?? 'none' }); +}