fix(miniprogram): 多角色找茬模式发现并修复 16 个问题
P0 Bug: - 健康 AI 建议幽灵路径 pkg-appointment → appointment/create - 血糖 indicator_type 始终 blood_sugar,不区分空腹/餐后 - 商城订单页 switchTab 跳转非 TabBar 页面 P1 设计系统: - Profile/Index 页 emoji 图标替换为衬线首字 - Profile 硬编码颜色替换为 SCSS 变量 class - alerts/action-inbox 两个页面全面接入设计系统 - ai-report/detail 删除重复 mixin 定义 - ErrorBoundary 添加重试按钮移除 emoji - 新增 $r-xs: 8px 圆角变量 P1 导航/交互: - Profile 补充 4 个缺失菜单(透析/知情同意/用药/活动) - Settings 隐私政策改为跳转实际页面 - 全局启用 enablePullDownRefresh - 首页/健康页添加下拉刷新 - 咨询/消息列表添加分页加载更多 - 医生端患者列表改为上拉加载 - 首页/健康页间距统一为 24px
This commit is contained in:
@@ -88,5 +88,6 @@ export default defineAppConfig({
|
||||
navigationBarBackgroundColor: '#FFFFFF',
|
||||
navigationBarTitleText: '健康管理',
|
||||
navigationBarTextStyle: 'black',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,13 +23,25 @@ export default class ErrorBoundary extends Component<Props, State> {
|
||||
console.error('[ErrorBoundary]', error, info.componentStack);
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
this.setState({ hasError: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '60vh', padding: '40px' }}>
|
||||
<Text style={{ fontSize: '48px', marginBottom: '20px' }}>😵</Text>
|
||||
<Text style={{ fontSize: '32px', color: '#134E4A', marginBottom: '12px' }}>页面出了点问题</Text>
|
||||
<Text style={{ fontSize: '24px', color: '#94A3B8', marginBottom: '24px' }}>请返回重试</Text>
|
||||
<View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '60vh', padding: '40px 24px' }}>
|
||||
<View style={{ width: '64px', height: '64px', borderRadius: '32px', background: '#F0DDD4', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '20px' }}>
|
||||
<Text style={{ fontFamily: 'Georgia, serif', fontSize: '28px', fontWeight: 600, color: '#8B3E1F' }}>!</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: '32px', color: '#2D2A26', marginBottom: '12px', fontWeight: 600 }}>页面出了点问题</Text>
|
||||
<Text style={{ fontSize: '24px', color: '#78716C', marginBottom: '32px' }}>请返回重试</Text>
|
||||
<View
|
||||
onClick={this.handleRetry}
|
||||
style={{ background: '#C4623A', borderRadius: '12px', padding: '14px 48px' }}
|
||||
>
|
||||
<Text style={{ color: '#FFFFFF', fontSize: '28px' }}>重新加载</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
@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 tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { listConsultations, ConsultationSession } from '@/services/consultation';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
@@ -33,32 +33,51 @@ export default function Consultation() {
|
||||
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
const loadSessions = async () => {
|
||||
setLoading(true);
|
||||
const loadSessions = async (pageNum: number, isRefresh = false) => {
|
||||
if (loadingRef.current) return;
|
||||
loadingRef.current = true;
|
||||
if (isRefresh) setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const resp = await listConsultations({ page: 1, page_size: 20 });
|
||||
setSessions(resp.data || []);
|
||||
const resp = await listConsultations({ page: pageNum, page_size: 20 });
|
||||
const list = resp.data || [];
|
||||
if (isRefresh) {
|
||||
setSessions(list);
|
||||
} else {
|
||||
setSessions((prev) => [...prev, ...list]);
|
||||
}
|
||||
setTotal(resp.total || 0);
|
||||
setPage(pageNum);
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : '加载失败';
|
||||
setError(msg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
useDidShow(() => {
|
||||
Taro.setNavigationBarTitle({ title: '在线咨询' });
|
||||
loadSessions();
|
||||
loadSessions(1, true);
|
||||
});
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
loadSessions().finally(() => {
|
||||
loadSessions(1, true).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!loading && sessions.length < total) {
|
||||
loadSessions(page + 1);
|
||||
}
|
||||
});
|
||||
|
||||
const handleTapSession = (session: ConsultationSession) => {
|
||||
Taro.navigateTo({ url: `/pages/consultation/detail/index?id=${session.id}` });
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.action-inbox-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.inbox-tabs {
|
||||
display: flex;
|
||||
background: white;
|
||||
background: $card;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px solid $bd;
|
||||
|
||||
.inbox-tab {
|
||||
flex: 1;
|
||||
@@ -16,7 +19,7 @@
|
||||
|
||||
&.active {
|
||||
.inbox-tab-text {
|
||||
color: #C4623A;
|
||||
color: $pri;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
|
||||
@@ -27,7 +30,7 @@
|
||||
left: 30%;
|
||||
right: 30%;
|
||||
height: 3px;
|
||||
background: #C4623A;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +39,7 @@
|
||||
|
||||
.inbox-tab-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +49,11 @@
|
||||
}
|
||||
|
||||
.inbox-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
.inbox-card-header {
|
||||
display: flex;
|
||||
@@ -59,7 +63,7 @@
|
||||
}
|
||||
|
||||
.inbox-type-tag {
|
||||
color: white;
|
||||
color: $card;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
@@ -69,11 +73,12 @@
|
||||
.inbox-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.inbox-card-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,37 +88,38 @@
|
||||
|
||||
.inbox-empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
// 半屏弹窗
|
||||
.half-screen-dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 70vh;
|
||||
background: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
background: $card;
|
||||
border-radius: $r-lg $r-lg 0 0;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
box-shadow: $shadow-lg;
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
.dialog-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +129,7 @@
|
||||
|
||||
.dialog-patient {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -151,12 +157,13 @@
|
||||
.thread-content {
|
||||
.thread-label {
|
||||
font-size: 13px;
|
||||
color: $tx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thread-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,19 +171,19 @@
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 20px 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid $bd-l;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
&.primary { background: #C4623A; color: white; }
|
||||
&.danger { background: #ff4d4f; color: white; }
|
||||
&.default { background: #f5f5f5; color: #666; }
|
||||
&.primary { background: $pri; color: $card; }
|
||||
&.danger { background: $dan; color: $card; }
|
||||
&.default { background: $surface-alt; color: $tx2; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import * as doctorApi from '@/services/doctor';
|
||||
import Loading from '@/components/Loading';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
@@ -14,14 +14,15 @@ export default function PatientList() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadTags();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadPatients();
|
||||
}, [page, activeTag]);
|
||||
loadPatients(1, true);
|
||||
}, [activeTag]);
|
||||
|
||||
const loadTags = async () => {
|
||||
try {
|
||||
@@ -30,32 +31,51 @@ export default function PatientList() {
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
|
||||
const loadPatients = async () => {
|
||||
setLoading(true);
|
||||
const loadPatients = async (pageNum: number, isRefresh = false) => {
|
||||
if (loadingRef.current) return;
|
||||
loadingRef.current = true;
|
||||
if (isRefresh) setLoading(true);
|
||||
try {
|
||||
const res = await doctorApi.listPatients({
|
||||
page,
|
||||
page: pageNum,
|
||||
page_size: 20,
|
||||
search: search || undefined,
|
||||
tag_id: activeTag || undefined,
|
||||
});
|
||||
setPatients(res.data || []);
|
||||
const list = res.data || [];
|
||||
if (isRefresh) {
|
||||
setPatients(list);
|
||||
} else {
|
||||
setPatients((prev) => [...prev, ...list]);
|
||||
}
|
||||
setTotal(res.total || 0);
|
||||
setPage(pageNum);
|
||||
} catch {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
loadPatients(1, true).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!loading && patients.length < total) {
|
||||
loadPatients(page + 1);
|
||||
}
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
setPage(1);
|
||||
loadPatients();
|
||||
loadPatients(1, true);
|
||||
};
|
||||
|
||||
const handleTagFilter = (tagId: string) => {
|
||||
setActiveTag(tagId === activeTag ? '' : tagId);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const getGenderLabel = (gender?: string) => {
|
||||
@@ -156,23 +176,12 @@ export default function PatientList() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{total > 20 && (
|
||||
<View className='pagination'>
|
||||
<Text
|
||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
||||
onClick={() => page > 1 && setPage(page - 1)}
|
||||
>
|
||||
上一页
|
||||
</Text>
|
||||
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||
<Text
|
||||
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||
onClick={() => page < totalPages && setPage(page + 1)}
|
||||
>
|
||||
下一页
|
||||
</Text>
|
||||
{!loading && patients.length >= total && total > 0 && (
|
||||
<View style={{ textAlign: 'center', padding: '20px' }}>
|
||||
<Text style={{ fontSize: '24px', color: '#78716C' }}>没有更多了</Text>
|
||||
</View>
|
||||
)}
|
||||
{loading && patients.length > 0 && <Loading />}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
.health-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 20px 16px 100px;
|
||||
padding: 20px 24px 100px;
|
||||
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { View, Text, Input } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||
import { useHealthStore } from '../../stores/health';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { inputVitalSign, getTrend, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../services/health';
|
||||
@@ -61,6 +61,12 @@ export default function Health() {
|
||||
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); });
|
||||
});
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
Promise.all([refreshToday(true), loadTrend(activeTab), loadAiSuggestions()]).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
const loadAiSuggestions = async () => {
|
||||
try {
|
||||
const items = await listPendingSuggestions();
|
||||
@@ -164,7 +170,8 @@ export default function Health() {
|
||||
case 'blood_sugar': {
|
||||
const val = parseFloat(sugarVal);
|
||||
if (!val) { Taro.showToast({ title: '请填写血糖值', icon: 'none' }); return; }
|
||||
await inputVitalSign(patientId, { indicator_type: 'blood_sugar', value: val });
|
||||
const bsType = sugarPeriod === 'fasting' ? 'blood_sugar_fasting' : 'blood_sugar_postprandial';
|
||||
await inputVitalSign(patientId, { indicator_type: bsType, value: val });
|
||||
setSugarVal('');
|
||||
break;
|
||||
}
|
||||
@@ -201,7 +208,7 @@ export default function Health() {
|
||||
<View className='ai-suggestion-card' onClick={() => {
|
||||
const first = aiSuggestions[0];
|
||||
if (first?.suggestion_type === 'appointment') {
|
||||
Taro.navigateTo({ url: `/pages/pkg-appointment/create/index?patientId=${first.patient_id}` });
|
||||
Taro.navigateTo({ url: `/pages/appointment/create/index` });
|
||||
} else if (first?.suggestion_type === 'followup') {
|
||||
Taro.navigateTo({ url: '/pages/pkg-profile/followups/index' });
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 20px 16px 100px;
|
||||
padding: 20px 24px 100px;
|
||||
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { useState } from 'react';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { useHealthStore } from '../../stores/health';
|
||||
import ProgressRing from '../../components/ProgressRing';
|
||||
@@ -30,6 +30,12 @@ export default function Index() {
|
||||
trackPageView('home');
|
||||
});
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
Promise.all([refreshToday(true), loadUpcoming()]).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
const loadUpcoming = async () => {
|
||||
const patientId = useAuthStore.getState().currentPatient?.id;
|
||||
if (!patientId) return;
|
||||
@@ -121,7 +127,7 @@ export default function Index() {
|
||||
className='greeting-bell'
|
||||
onClick={() => Taro.switchTab({ url: '/pages/messages/index' })}
|
||||
>
|
||||
<Text className='greeting-bell-icon'>🔔</Text>
|
||||
<Text className='greeting-bell-icon'>消</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro';
|
||||
import { listConsultations, ConsultationSession } from '../../services/consultation';
|
||||
import { notificationService } from '../../services/notification';
|
||||
import Loading from '../../components/Loading';
|
||||
@@ -21,34 +21,62 @@ export default function Messages() {
|
||||
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
||||
const [notifications, setNotifications] = useState<NotificationItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
useDidShow(() => {
|
||||
loadData(activeTab);
|
||||
loadData(activeTab, 1, true);
|
||||
});
|
||||
|
||||
const loadData = async (tab: MsgTab) => {
|
||||
const loadData = async (tab: MsgTab, pageNum: number = 1, isRefresh = false) => {
|
||||
if (loadingRef.current) return;
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
try {
|
||||
if (tab === 'consultation') {
|
||||
const res = await listConsultations({ page: 1, page_size: 20 });
|
||||
setSessions(res.data || []);
|
||||
const res = await listConsultations({ page: pageNum, page_size: 20 });
|
||||
const list = res.data || [];
|
||||
if (isRefresh) {
|
||||
setSessions(list);
|
||||
} else {
|
||||
setSessions((prev) => [...prev, ...list]);
|
||||
}
|
||||
setTotal(res.total || 0);
|
||||
} else {
|
||||
const res = await notificationService.list<{ data: unknown[] }>({ page: 1, page_size: 20 });
|
||||
setNotifications((res as { data?: unknown[] })?.data || []);
|
||||
const res = await notificationService.list<{ data: unknown[]; total?: number }>({ page: pageNum, page_size: 20 });
|
||||
const list = (res as { data?: unknown[] })?.data || [];
|
||||
if (isRefresh) {
|
||||
setNotifications(list as NotificationItem[]);
|
||||
} else {
|
||||
setNotifications((prev) => [...prev, ...(list as NotificationItem[])]);
|
||||
}
|
||||
setTotal((res as { total?: number })?.total || 0);
|
||||
}
|
||||
setPage(pageNum);
|
||||
} catch {
|
||||
if (tab === 'consultation') setSessions([]);
|
||||
else setNotifications([]);
|
||||
if (isRefresh) {
|
||||
if (tab === 'consultation') setSessions([]);
|
||||
else setNotifications([]);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (tab: MsgTab) => {
|
||||
setActiveTab(tab);
|
||||
loadData(tab);
|
||||
loadData(tab, 1, true);
|
||||
};
|
||||
|
||||
useReachBottom(() => {
|
||||
const currentList = activeTab === 'consultation' ? sessions : notifications;
|
||||
if (!loading && currentList.length < total) {
|
||||
loadData(activeTab, page + 1);
|
||||
}
|
||||
});
|
||||
|
||||
const formatTime = (dateStr: string | null) => {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.alerts-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
background: $bg;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.alerts-tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
background: $card;
|
||||
padding: 20px 16px;
|
||||
gap: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px solid $bd;
|
||||
}
|
||||
|
||||
.alerts-tab {
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: $r-pill;
|
||||
background: $surface-alt;
|
||||
}
|
||||
|
||||
.alerts-tab.active {
|
||||
background: #C4623A;
|
||||
background: $pri;
|
||||
}
|
||||
|
||||
.alerts-tab-text {
|
||||
font-size: 26px;
|
||||
color: #666;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.alerts-tab-text.active {
|
||||
color: #fff;
|
||||
color: $card;
|
||||
}
|
||||
|
||||
.alerts-list {
|
||||
@@ -36,10 +39,11 @@
|
||||
}
|
||||
|
||||
.alert-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
@@ -51,7 +55,7 @@
|
||||
|
||||
.alert-badge {
|
||||
padding: 4px 16px;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
}
|
||||
|
||||
.alert-badge.sev-info {
|
||||
@@ -88,17 +92,17 @@
|
||||
}
|
||||
|
||||
.alert-badge.sev-urgent .alert-badge-text {
|
||||
color: #fff;
|
||||
color: $card;
|
||||
}
|
||||
|
||||
.alert-time {
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
color: $tx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -112,23 +116,23 @@
|
||||
|
||||
.alerts-empty-text {
|
||||
font-size: 30px;
|
||||
color: #999;
|
||||
color: $tx3;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.alerts-empty-hint {
|
||||
font-size: 26px;
|
||||
color: #bbb;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.alerts-empty-action {
|
||||
margin-top: 24px;
|
||||
padding: 16px 48px;
|
||||
background: #C4623A;
|
||||
border-radius: 32px;
|
||||
background: $pri;
|
||||
border-radius: $r-pill;
|
||||
}
|
||||
|
||||
.alerts-empty-action-text {
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function MallOrders() {
|
||||
text='暂无订单'
|
||||
hint='去商城兑换心仪商品吧'
|
||||
actionText='去商城'
|
||||
onAction={() => Taro.switchTab({ url: '/pages/mall/index' })}
|
||||
onAction={() => Taro.redirectTo({ url: '/pages/mall/index' })}
|
||||
/>
|
||||
) : (
|
||||
<View className='order-list'>
|
||||
|
||||
@@ -40,11 +40,7 @@ export default function Settings() {
|
||||
};
|
||||
|
||||
const handlePrivacy = () => {
|
||||
Taro.showModal({
|
||||
title: '隐私政策',
|
||||
content: '我们重视您的隐私保护。所有健康数据均经过加密存储,仅用于为您提供健康管理服务,不会向第三方分享。',
|
||||
showCancel: false,
|
||||
});
|
||||
Taro.navigateTo({ url: '/pages/legal/privacy-policy' });
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
|
||||
@@ -118,10 +118,25 @@
|
||||
border-radius: 12px;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.menu-icon--pri-l {
|
||||
background: $pri-l;
|
||||
}
|
||||
|
||||
&.menu-icon--acc-l {
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
&.menu-icon--surface-alt {
|
||||
background: $surface-alt;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon-text {
|
||||
font-size: 22px;
|
||||
font-family: Georgia, Times New Roman, serif;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
|
||||
@@ -6,14 +6,18 @@ import { usePointsStore } from '../../stores/points';
|
||||
import './index.scss';
|
||||
|
||||
const MENU_ITEMS = [
|
||||
{ label: '就诊人管理', icon: '👨👩👧', bg: '#F0DDD4' },
|
||||
{ label: '我的报告', icon: '📄', bg: '#E8F0E8' },
|
||||
{ label: '健康记录', icon: '📝', bg: '#E8F0F8' },
|
||||
{ label: '诊断记录', icon: '📋', bg: '#E8F0E8' },
|
||||
{ label: '我的随访', icon: '🏥', bg: '#F3E8F8' },
|
||||
{ label: '我的预约', icon: '📅', bg: '#E8F0F8' },
|
||||
{ label: '在线咨询', icon: '💬', bg: '#E8F0E8' },
|
||||
{ label: '设置', icon: '⚙️', bg: '#f0f0f0' },
|
||||
{ label: '就诊人管理', icon: '家', bg: 'pri-l' },
|
||||
{ label: '我的报告', icon: '报', bg: 'acc-l' },
|
||||
{ label: '健康记录', icon: '健', bg: 'pri-l' },
|
||||
{ label: '诊断记录', icon: '诊', bg: 'acc-l' },
|
||||
{ label: '我的随访', icon: '随', bg: 'pri-l' },
|
||||
{ label: '我的预约', icon: '约', bg: 'acc-l' },
|
||||
{ label: '用药记录', icon: '药', bg: 'pri-l' },
|
||||
{ label: '透析记录', icon: '透', bg: 'acc-l' },
|
||||
{ label: '知情同意', icon: '知', bg: 'pri-l' },
|
||||
{ label: '线下活动', icon: '活', bg: 'acc-l' },
|
||||
{ label: '在线咨询', icon: '问', bg: 'pri-l' },
|
||||
{ label: '设置', icon: '设', bg: 'surface-alt' },
|
||||
];
|
||||
|
||||
const MENU_PATHS: Record<string, string> = {
|
||||
@@ -23,6 +27,10 @@ const MENU_PATHS: Record<string, string> = {
|
||||
'诊断记录': '/pages/pkg-profile/diagnoses/index',
|
||||
'我的随访': '/pages/pkg-profile/followups/index',
|
||||
'我的预约': '/pages/appointment/index',
|
||||
'用药记录': '/pages/pkg-profile/medication/index',
|
||||
'透析记录': '/pages/pkg-profile/dialysis-records/index',
|
||||
'知情同意': '/pages/pkg-profile/consents/index',
|
||||
'线下活动': '/pages/events/index',
|
||||
'在线咨询': '/pages/consultation/index',
|
||||
'设置': '/pages/pkg-profile/settings/index',
|
||||
};
|
||||
@@ -56,7 +64,9 @@ export default function Profile() {
|
||||
{/* 用户信息卡片 */}
|
||||
<View className='profile-user-card'>
|
||||
<View className='profile-avatar'>
|
||||
<Text className='profile-avatar-icon'>👤</Text>
|
||||
<Text className='profile-avatar-icon'>
|
||||
{(user?.display_name || '访').charAt(0)}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='profile-user-info'>
|
||||
<Text className='profile-name'>{user?.display_name || '未登录'}</Text>
|
||||
@@ -84,7 +94,7 @@ export default function Profile() {
|
||||
key={item.label}
|
||||
onClick={() => handleMenuClick(item.label)}
|
||||
>
|
||||
<View className='menu-icon' style={`background:${item.bg};`}>
|
||||
<View className={`menu-icon menu-icon--${item.bg}`}>
|
||||
<Text className='menu-icon-text'>{item.icon}</Text>
|
||||
</View>
|
||||
<Text className='menu-label'>{item.label}</Text>
|
||||
|
||||
@@ -24,6 +24,7 @@ $wrn-l: #FFF3E0; // 警告浅
|
||||
// ─── 圆角 ───
|
||||
$r: 16px;
|
||||
$r-sm: 12px;
|
||||
$r-xs: 8px;
|
||||
$r-lg: 20px;
|
||||
$r-pill: 999px;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user