feat(miniprogram): 通用组件 + 页面接入 — Chunk 7
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 创建 EmptyState/ErrorState/Loading 三个通用组件
- 8个列表页面接入通用组件替换内联空状态/loading
- app.config.ts 添加 login 页面路由
This commit is contained in:
iven
2026-04-24 01:03:23 +08:00
parent 9ef65b9a9f
commit 0c73927450
14 changed files with 201 additions and 45 deletions

View File

@@ -1,6 +1,7 @@
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/login/index',
'pages/health/index',
'pages/health/input/index',
'pages/health/trend/index',
@@ -20,7 +21,6 @@ export default defineAppConfig({
'pages/profile/followups/index',
'pages/profile/medication/index',
'pages/profile/settings/index',
'pages/login/index',
],
tabBar: {
color: '#94A3B8',

View File

@@ -0,0 +1,37 @@
@import '../../styles/variables.scss';
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120px 40px;
}
.empty-state-icon {
font-size: 80px;
margin-bottom: 24px;
}
.empty-state-text {
font-size: 30px;
color: $tx2;
margin-bottom: 8px;
}
.empty-state-hint {
font-size: 24px;
color: $tx3;
margin-bottom: 32px;
}
.empty-state-action {
background: $pri;
border-radius: 40px;
padding: 16px 48px;
}
.empty-state-action-text {
font-size: 28px;
color: #fff;
}

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface EmptyStateProps {
icon?: string;
text: string;
hint?: string;
actionText?: string;
onAction?: () => void;
}
export default function EmptyState({
icon = '📭',
text,
hint,
actionText,
onAction,
}: EmptyStateProps) {
return (
<View className='empty-state'>
<Text className='empty-state-icon'>{icon}</Text>
<Text className='empty-state-text'>{text}</Text>
{hint && <Text className='empty-state-hint'>{hint}</Text>}
{actionText && onAction && (
<View className='empty-state-action' onClick={onAction}>
<Text className='empty-state-action-text'>{actionText}</Text>
</View>
)}
</View>
);
}

View File

@@ -0,0 +1,32 @@
@import '../../styles/variables.scss';
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120px 40px;
}
.error-state-icon {
font-size: 80px;
margin-bottom: 24px;
}
.error-state-text {
font-size: 28px;
color: $tx2;
margin-bottom: 32px;
text-align: center;
}
.error-state-retry {
background: $pri;
border-radius: 40px;
padding: 16px 48px;
}
.error-state-retry-text {
font-size: 28px;
color: #fff;
}

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface ErrorStateProps {
text?: string;
onRetry?: () => void;
}
export default function ErrorState({
text = '加载失败,请稍后重试',
onRetry,
}: ErrorStateProps) {
return (
<View className='error-state'>
<Text className='error-state-icon'></Text>
<Text className='error-state-text'>{text}</Text>
{onRetry && (
<View className='error-state-retry' onClick={onRetry}>
<Text className='error-state-retry-text'></Text>
</View>
)}
</View>
);
}

View File

@@ -0,0 +1,30 @@
@import '../../styles/variables.scss';
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 40px;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid $bd;
border-top-color: $pri;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-state-text {
font-size: 26px;
color: $tx3;
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface LoadingProps {
text?: string;
}
export default function Loading({ text = '加载中...' }: LoadingProps) {
return (
<View className='loading-state'>
<View className='loading-spinner' />
<Text className='loading-state-text'>{text}</Text>
</View>
);
}

View File

@@ -3,6 +3,8 @@ import { View, Text } from '@tarojs/components';
import Taro, { useDidShow, useReachBottom, usePullDownRefresh } from '@tarojs/taro';
import { listAppointments, cancelAppointment } from '../../services/appointment';
import type { Appointment } from '../../services/appointment';
import EmptyState from '../../components/EmptyState';
import Loading from '../../components/Loading';
import './index.scss';
const STATUS_MAP: Record<string, { label: string; className: string }> = {
@@ -82,11 +84,7 @@ export default function AppointmentList() {
{/* 预约列表 */}
{appointments.length === 0 && !loading ? (
<View className='empty-state'>
<Text className='empty-icon'>📋</Text>
<Text className='empty-text'></Text>
<Text className='empty-hint'></Text>
</View>
<EmptyState icon='📋' text='暂无预约记录' hint='点击下方按钮新建预约' />
) : (
<View className='appointment-list'>
{appointments.map((item) => {
@@ -120,14 +118,10 @@ export default function AppointmentList() {
);
})}
{loading && (
<View className='loading-tip'>
<Text className='loading-text'>...</Text>
</View>
<Loading />
)}
{!loading && appointments.length >= total && total > 0 && (
<View className='loading-tip'>
<Text className='loading-text'></Text>
</View>
<Loading text='没有更多了' />
)}
</View>
)}

View File

@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
import { listArticles, Article } from '../../services/article';
import EmptyState from '../../components/EmptyState';
import Loading from '../../components/Loading';
import './index.scss';
export default function ArticleList() {
@@ -80,15 +82,11 @@ export default function ArticleList() {
</View>
{articles.length === 0 && !loading && (
<View className='empty-state'>
<Text className='empty-text'></Text>
</View>
<EmptyState text='暂无资讯文章' />
)}
{loading && (
<View className='loading-hint'>
<Text className='loading-text'>...</Text>
</View>
<Loading />
)}
</View>
);

View File

@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import { listTasks, FollowUpTask } from '../../services/followup';
import EmptyState from '../../components/EmptyState';
import Loading from '../../components/Loading';
import './index.scss';
const TABS = [
@@ -89,18 +91,14 @@ export default function FollowUpList() {
</View>
{tasks.length === 0 && !loading && (
<View className='empty-state'>
<Text className='empty-text'>{(() => {
const tab = TABS.find((t) => t.key === activeTab);
return tab ? tab.label : '';
})()}</Text>
</View>
<EmptyState text={`暂无${(() => {
const tab = TABS.find((t) => t.key === activeTab);
return tab ? tab.label : '';
})()}任务`} />
)}
{loading && (
<View className='loading-hint'>
<Text className='loading-text'>...</Text>
</View>
<Loading />
)}
</View>
);

View File

@@ -1,6 +1,7 @@
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import { useAuthStore } from '../../stores/auth';
import EmptyState from '../../components/EmptyState';
import './index.scss';
export default function Index() {
@@ -76,9 +77,7 @@ export default function Index() {
{/* 即将到来 */}
<View className='upcoming'>
<Text className='section-title'></Text>
<View className='empty-hint'>
<Text className='empty-text'></Text>
</View>
<EmptyState text='暂无即将到来的预约' />
</View>
</View>
);

View File

@@ -3,6 +3,7 @@ import { View, Text } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import { listPatients, Patient } from '../../../services/patient';
import { useAuthStore } from '../../../stores/auth';
import EmptyState from '../../../components/EmptyState';
import './index.scss';
export default function FamilyList() {
@@ -72,9 +73,7 @@ export default function FamilyList() {
</View>
{patients.length === 0 && !loading && (
<View className='empty-state'>
<Text className='empty-text'></Text>
</View>
<EmptyState text='暂无就诊人' />
)}
<View className='family-add-btn' onClick={goToAdd}>

View File

@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
import { listReports, LabReport } from '../../../services/report';
import EmptyState from '../../../components/EmptyState';
import Loading from '../../../components/Loading';
import './index.scss';
const PAGE_SIZE = 20;
@@ -79,15 +81,11 @@ export default function MyReports() {
</View>
{reports.length === 0 && !loading && (
<View className='empty-state'>
<Text className='empty-text'></Text>
</View>
<EmptyState text='暂无报告记录' />
)}
{loading && (
<View className='loading-hint'>
<Text className='loading-text'>...</Text>
</View>
<Loading />
)}
</View>
);

View File

@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
import { listReports, LabReport } from '../../services/report';
import EmptyState from '../../components/EmptyState';
import Loading from '../../components/Loading';
import './index.scss';
const PAGE_SIZE = 20;
@@ -82,15 +84,11 @@ export default function ReportList() {
</View>
{reports.length === 0 && !loading && (
<View className='empty-state'>
<Text className='empty-text'></Text>
</View>
<EmptyState text='暂无报告记录' />
)}
{loading && (
<View className='loading-hint'>
<Text className='loading-text'>...</Text>
</View>
<Loading />
)}
</View>
);