feat(miniprogram): 通用组件 + 页面接入 — Chunk 7
- 创建 EmptyState/ErrorState/Loading 三个通用组件 - 8个列表页面接入通用组件替换内联空状态/loading - app.config.ts 添加 login 页面路由
This commit is contained in:
@@ -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',
|
||||
|
||||
37
apps/miniprogram/src/components/EmptyState/index.scss
Normal file
37
apps/miniprogram/src/components/EmptyState/index.scss
Normal 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;
|
||||
}
|
||||
32
apps/miniprogram/src/components/EmptyState/index.tsx
Normal file
32
apps/miniprogram/src/components/EmptyState/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
apps/miniprogram/src/components/ErrorState/index.scss
Normal file
32
apps/miniprogram/src/components/ErrorState/index.scss
Normal 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;
|
||||
}
|
||||
25
apps/miniprogram/src/components/ErrorState/index.tsx
Normal file
25
apps/miniprogram/src/components/ErrorState/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
30
apps/miniprogram/src/components/Loading/index.scss
Normal file
30
apps/miniprogram/src/components/Loading/index.scss
Normal 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;
|
||||
}
|
||||
16
apps/miniprogram/src/components/Loading/index.tsx
Normal file
16
apps/miniprogram/src/components/Loading/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user