feat: UI 清新风全面改造 — 朝露主题 + shadcn_ui + 全局字号放大
- 全新"朝露"主题:深青绿主色(#2D6A4F),暖白底,完整设计Token - 引入 shadcn_ui 组件库,MaterialApp 注入 ShadTheme - 新增 4 个公共组件:AppCard、AppMenuItem、AppEmptyState、AppTabChip - 全面消除硬编码颜色,统一使用 AppTheme Token - 全局字号 +3~4pt,图标等比放大 +3px - home/medication/profile/settings/login 等核心页面重写 - 路由和功能逻辑零改动
This commit is contained in:
46
health_app/lib/widgets/app_card.dart
Normal file
46
health_app/lib/widgets/app_card.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../core/app_theme.dart';
|
||||
|
||||
/// 统一样式的清新卡片
|
||||
class AppCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final EdgeInsetsGeometry? margin;
|
||||
final VoidCallback? onTap;
|
||||
final Color? backgroundColor;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
const AppCard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding = const EdgeInsets.all(AppTheme.sLg),
|
||||
this.margin,
|
||||
this.onTap,
|
||||
this.backgroundColor,
|
||||
this.borderRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final card = Container(
|
||||
margin: margin ?? const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sSm),
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? theme.colorScheme.card,
|
||||
borderRadius: borderRadius ?? BorderRadius.circular(AppTheme.rMd),
|
||||
boxShadow: [AppTheme.shadowLight],
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (onTap != null) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: card,
|
||||
);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
}
|
||||
52
health_app/lib/widgets/app_empty_state.dart
Normal file
52
health_app/lib/widgets/app_empty_state.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
/// 统一空状态占位组件
|
||||
class AppEmptyState extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final Widget? action;
|
||||
|
||||
const AppEmptyState({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.action,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(48),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.muted,
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Icon(icon, size: 36, color: theme.colorScheme.mutedForeground),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(title, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: theme.colorScheme.foreground)),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(subtitle!, style: TextStyle(fontSize: 17, color: theme.colorScheme.mutedForeground), textAlign: TextAlign.center),
|
||||
],
|
||||
if (action != null) ...[
|
||||
const SizedBox(height: 20),
|
||||
action!,
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
67
health_app/lib/widgets/app_menu_item.dart
Normal file
67
health_app/lib/widgets/app_menu_item.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../core/app_theme.dart';
|
||||
|
||||
/// 统一菜单项,profile_page 和 settings_pages 都共用这个
|
||||
class AppMenuItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? trailing;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const AppMenuItem({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.card,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rSm),
|
||||
),
|
||||
child: Row(children: [
|
||||
Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryLight,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rSm),
|
||||
),
|
||||
child: Icon(icon, size: 22, color: AppTheme.primary),
|
||||
),
|
||||
const SizedBox(width: AppTheme.sLg),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500, color: theme.colorScheme.foreground)),
|
||||
if (subtitle != null && subtitle!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(subtitle!, style: TextStyle(fontSize: 16, color: theme.colorScheme.mutedForeground)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (trailing != null && trailing!.isNotEmpty)
|
||||
Text(trailing!, style: TextStyle(fontSize: 17, color: theme.colorScheme.mutedForeground)),
|
||||
const SizedBox(width: 4),
|
||||
Icon(LucideIcons.chevronRight, size: 21, color: theme.colorScheme.mutedForeground),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
39
health_app/lib/widgets/app_tab_chip.dart
Normal file
39
health_app/lib/widgets/app_tab_chip.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../core/app_theme.dart';
|
||||
|
||||
/// 统一样式的标签/筛选 Chip
|
||||
class AppTabChip extends StatelessWidget {
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const AppTabChip({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: AppTheme.sSm),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: AppTheme.sSm),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? AppTheme.primary : AppTheme.primaryLight,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rPill),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: selected ? Colors.white : AppTheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../core/app_theme.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../core/navigation_provider.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
@@ -46,12 +47,12 @@ class HealthDrawer extends ConsumerWidget {
|
||||
Expanded(child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(user?.name ?? '未设置昵称', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
Text(user?.name ?? '未设置昵称', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
const SizedBox(height: 2),
|
||||
Text(user?.phone ?? '未登录', style: TextStyle(fontSize: 12, color: Colors.white70)),
|
||||
Text(user?.phone ?? '未登录', style: TextStyle(fontSize: 15, color: Colors.white70)),
|
||||
],
|
||||
)),
|
||||
Icon(Icons.chevron_right, size: 18, color: Colors.white54),
|
||||
Icon(Icons.chevron_right, size: 21, color: Colors.white54),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -72,19 +73,19 @@ class HealthDrawer extends ConsumerWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF6C5CE7).withAlpha(15),
|
||||
color: AppTheme.primary.withAlpha(15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.monitor_heart_rounded, size: 13, color: Color(0xFF6C5CE7)),
|
||||
Icon(Icons.monitor_heart_rounded, size: 16, color: AppTheme.primary),
|
||||
SizedBox(width: 4),
|
||||
Text('健康概览', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF6C5CE7))),
|
||||
Text('健康概览', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: AppTheme.primary)),
|
||||
]),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'trend'),
|
||||
child: const Padding(padding: EdgeInsets.all(4), child: Text('详情', style: TextStyle(fontSize: 11, color: Color(0xFF888888)))),
|
||||
child: const Padding(padding: EdgeInsets.all(4), child: Text('详情', style: TextStyle(fontSize: 14, color: Color(0xFF888888)))),
|
||||
),
|
||||
]),
|
||||
),
|
||||
@@ -99,7 +100,7 @@ class HealthDrawer extends ConsumerWidget {
|
||||
_MiniMetric(icon: Icons.monitor_weight_outlined, label: '体重', value: _metricVal(data['Weight']), onTap: () => pushRoute(ref, 'trend', params: {'type': 'weight'})),
|
||||
]),
|
||||
),
|
||||
loading: () => const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF6C5CE7))))),
|
||||
loading: () => const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.primary)))),
|
||||
error: (_, __) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 12, 14),
|
||||
child: Row(children: [
|
||||
@@ -134,9 +135,9 @@ class HealthDrawer extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.apps_rounded, size: 13, color: Color(0xFFF0A060)),
|
||||
Icon(Icons.apps_rounded, size: 16, color: Color(0xFFF0A060)),
|
||||
SizedBox(width: 4),
|
||||
Text('功能', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))),
|
||||
Text('功能', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
@@ -179,11 +180,11 @@ class HealthDrawer extends ConsumerWidget {
|
||||
color: const Color(0xFFEEEEEE),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(Icons.settings_outlined, size: 18, color: Color(0xFF666666)),
|
||||
child: const Icon(Icons.settings_outlined, size: 21, color: Color(0xFF666666)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(child: Text('设置', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: Color(0xFF333333)))),
|
||||
const Icon(Icons.chevron_right, size: 16, color: Color(0xFFCCCCCC)),
|
||||
const Expanded(child: Text('设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Color(0xFF333333)))),
|
||||
const Icon(Icons.chevron_right, size: 19, color: Color(0xFFCCCCCC)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -269,7 +270,7 @@ class _MiniMetric extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
const _MiniMetric({required this.icon, required this.label, required this.value, this.onTap});
|
||||
|
||||
static const _c = Color(0xFF6C5CE7);
|
||||
static const _c = AppTheme.primary;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -284,11 +285,11 @@ class _MiniMetric extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(icon, size: 18, color: _c),
|
||||
Icon(icon, size: 21, color: _c),
|
||||
const SizedBox(height: 6),
|
||||
Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A)), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF1A1A1A)), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 2),
|
||||
Text(label, style: TextStyle(fontSize: 10, color: Colors.grey[500])),
|
||||
Text(label, style: TextStyle(fontSize: 13, color: Colors.grey[500])),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -311,7 +312,7 @@ class _FeatureChip extends StatelessWidget {
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
static const _c = Color(0xFF6C5CE7);
|
||||
static const _c = AppTheme.primary;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -326,9 +327,9 @@ class _FeatureChip extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(icon, size: 18, color: _c),
|
||||
Icon(icon, size: 21, color: _c),
|
||||
const SizedBox(height: 6),
|
||||
Text(label, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF666666))),
|
||||
Text(label, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF666666))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -185,11 +185,11 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
color: Color(0xFFFFF8EE),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(item.icon, size: 20, color: const Color(0xFFF5A623)),
|
||||
child: Icon(item.icon, size: 23, color: const Color(0xFFF5A623)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(item.label,
|
||||
style: const TextStyle(fontSize: 11, color: AppTheme.textSub),
|
||||
style: const TextStyle(fontSize: 14, color: AppTheme.textSub),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
@@ -212,14 +212,14 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
children: [
|
||||
// 标题行 + 详情入口
|
||||
Row(children: [
|
||||
Text(package.title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.text)),
|
||||
Text(package.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.text)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('详情', style: TextStyle(fontSize: 13, color: AppTheme.textSub)),
|
||||
Text('详情', style: TextStyle(fontSize: 16, color: AppTheme.textSub)),
|
||||
const SizedBox(width: 2),
|
||||
Icon(Icons.chevron_right, size: 18, color: AppTheme.textHint),
|
||||
Icon(Icons.chevron_right, size: 21, color: AppTheme.textHint),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
@@ -232,9 +232,9 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('VIP', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
Text('VIP', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
SizedBox(width: 4),
|
||||
Text('VIP 产品权益', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||
Text('VIP 产品权益', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
@@ -246,8 +246,8 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
child: GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('查看更多服务包', style: TextStyle(fontSize: 13, color: AppTheme.primary, fontWeight: FontWeight.w500)),
|
||||
Icon(Icons.chevron_right, size: 16, color: AppTheme.primary),
|
||||
Text('查看更多服务包', style: TextStyle(fontSize: 16, color: AppTheme.primary, fontWeight: FontWeight.w500)),
|
||||
Icon(Icons.chevron_right, size: 19, color: AppTheme.primary),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user