fix(miniprogram): 多角色找茬模式发现并修复 16 个问题
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / security-audit (push) Has been cancelled

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:
iven
2026-05-08 16:07:06 +08:00
parent 22b8ac7ac6
commit 3dac6a9eda
17 changed files with 231 additions and 134 deletions

View File

@@ -88,5 +88,6 @@ export default defineAppConfig({
navigationBarBackgroundColor: '#FFFFFF',
navigationBarTitleText: '健康管理',
navigationBarTextStyle: 'black',
enablePullDownRefresh: true,
},
});

View File

@@ -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>
);
}

View File

@@ -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;

View File

@@ -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}` });
};

View File

@@ -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; }
}
}
}

View File

@@ -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>
);
}

View File

@@ -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));
}

View File

@@ -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 {

View File

@@ -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));
}

View File

@@ -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>

View File

@@ -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 {
const res = await notificationService.list<{ data: unknown[] }>({ page: 1, page_size: 20 });
setNotifications((res as { data?: unknown[] })?.data || []);
setSessions((prev) => [...prev, ...list]);
}
setTotal(res.total || 0);
} else {
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 (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);

View File

@@ -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;
}

View File

@@ -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'>

View File

@@ -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 = () => {

View File

@@ -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 {

View File

@@ -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>

View File

@@ -24,6 +24,7 @@ $wrn-l: #FFF3E0; // 警告浅
// ─── 圆角 ───
$r: 16px;
$r-sm: 12px;
$r-xs: 8px;
$r-lg: 20px;
$r-pill: 999px;