feat: UI 系统中心化 + 趋势图重构 + 法律文档 H5 上线
- UI 系统: 新增 design_tokens / module_visuals / app_buttons / app_status_badge / app_toast / ai_content 六个共享模块; 28 个页面接入, SnackBar 全部替换为 AppToast - 趋势图: 直线折线 + 渐变填充 + 阴影; 去网格线; X 轴日+月份分隔; Y 轴整数刻度最多 4 个; 点击数据点/录入记录显示上方浮层, 选中点变实心 - 法律文档 H5: 后端 wwwroot 托管隐私政策/服务协议/关于/个人信息收集清单/第三方 SDK 清单 + 索引页; Program.cs 启用 UseDefaultFiles + UseStaticFiles - 其他: auth_provider 登录流程调整; api_client IP 适配; 主页智能体栏间距优化
This commit is contained in:
242
health_app/lib/widgets/app_buttons.dart
Normal file
242
health_app/lib/widgets/app_buttons.dart
Normal file
@@ -0,0 +1,242 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/app_colors.dart';
|
||||
import '../core/app_design_tokens.dart';
|
||||
|
||||
enum AppButtonVariant { primary, secondary, outline, ghost, danger, gradientOutline }
|
||||
|
||||
class AppButton extends StatelessWidget {
|
||||
final String label;
|
||||
final VoidCallback? onPressed;
|
||||
final IconData? icon;
|
||||
final AppButtonVariant variant;
|
||||
final Color? color;
|
||||
final LinearGradient? gradient;
|
||||
final bool loading;
|
||||
final double height;
|
||||
final bool expand;
|
||||
|
||||
const AppButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.onPressed,
|
||||
this.icon,
|
||||
this.variant = AppButtonVariant.primary,
|
||||
this.color,
|
||||
this.gradient,
|
||||
this.loading = false,
|
||||
this.height = 50,
|
||||
this.expand = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enabled = onPressed != null && !loading;
|
||||
final baseColor = color ?? AppColors.primary;
|
||||
final child = _ButtonContent(
|
||||
label: label,
|
||||
icon: icon,
|
||||
loading: loading,
|
||||
foreground: _foreground(baseColor),
|
||||
);
|
||||
|
||||
if (variant == AppButtonVariant.gradientOutline) {
|
||||
return _GradientOutlineButton(
|
||||
label: label,
|
||||
icon: icon,
|
||||
loading: loading,
|
||||
enabled: enabled,
|
||||
height: height,
|
||||
expand: expand,
|
||||
gradient: gradient ?? AppColors.actionOutlineGradient,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
}
|
||||
|
||||
final decoration = _decoration(baseColor, enabled);
|
||||
final width = expand ? double.infinity : null;
|
||||
return SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: enabled ? onPressed : null,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
child: Ink(
|
||||
decoration: decoration,
|
||||
child: Center(child: child),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _foreground(Color baseColor) {
|
||||
return switch (variant) {
|
||||
AppButtonVariant.primary => Colors.white,
|
||||
AppButtonVariant.secondary => baseColor,
|
||||
AppButtonVariant.outline => baseColor,
|
||||
AppButtonVariant.ghost => baseColor,
|
||||
AppButtonVariant.danger => variant == AppButtonVariant.danger && color == null
|
||||
? AppColors.error
|
||||
: Colors.white,
|
||||
AppButtonVariant.gradientOutline => AppColors.textPrimary,
|
||||
};
|
||||
}
|
||||
|
||||
BoxDecoration _decoration(Color baseColor, bool enabled) {
|
||||
final disabledColor = const Color(0xFFE5E7EB);
|
||||
return switch (variant) {
|
||||
AppButtonVariant.primary => BoxDecoration(
|
||||
color: enabled ? baseColor : disabledColor,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
boxShadow: enabled ? AppShadows.soft : AppShadows.none,
|
||||
),
|
||||
AppButtonVariant.secondary => BoxDecoration(
|
||||
color: enabled ? baseColor.withValues(alpha: 0.10) : disabledColor,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
),
|
||||
AppButtonVariant.outline => BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
border: Border.all(color: enabled ? baseColor.withValues(alpha: 0.38) : disabledColor),
|
||||
),
|
||||
AppButtonVariant.ghost => BoxDecoration(
|
||||
color: enabled ? baseColor.withValues(alpha: 0.08) : disabledColor,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
),
|
||||
AppButtonVariant.danger => BoxDecoration(
|
||||
color: enabled ? (color ?? AppColors.error) : disabledColor,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
),
|
||||
AppButtonVariant.gradientOutline => const BoxDecoration(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _ButtonContent extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData? icon;
|
||||
final bool loading;
|
||||
final Color foreground;
|
||||
|
||||
const _ButtonContent({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.loading,
|
||||
required this.foreground,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (loading)
|
||||
SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: foreground),
|
||||
)
|
||||
else if (icon != null)
|
||||
Icon(icon, size: 19, color: foreground),
|
||||
if (loading || icon != null) const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppTextStyles.button.copyWith(color: foreground),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GradientOutlineButton extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData? icon;
|
||||
final bool loading;
|
||||
final bool enabled;
|
||||
final double height;
|
||||
final bool expand;
|
||||
final LinearGradient gradient;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
const _GradientOutlineButton({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.loading,
|
||||
required this.enabled,
|
||||
required this.height,
|
||||
required this.expand,
|
||||
required this.gradient,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: height,
|
||||
width: expand ? double.infinity : null,
|
||||
padding: const EdgeInsets.all(1.5),
|
||||
decoration: BoxDecoration(
|
||||
gradient: enabled ? gradient : null,
|
||||
color: enabled ? null : AppColors.borderLight,
|
||||
borderRadius: AppRadius.lgBorder,
|
||||
boxShadow: enabled ? AppShadows.soft : AppShadows.none,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg - 1.5),
|
||||
child: InkWell(
|
||||
onTap: enabled ? onPressed : null,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg - 1.5),
|
||||
child: Center(
|
||||
child: _ButtonContent(
|
||||
label: label,
|
||||
icon: icon,
|
||||
loading: loading,
|
||||
foreground: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppIconTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final Color backgroundColor;
|
||||
final double size;
|
||||
final double iconSize;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
const AppIconTile({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.backgroundColor,
|
||||
this.size = 44,
|
||||
this.iconSize = 22,
|
||||
this.borderRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: borderRadius ?? AppRadius.mdBorder,
|
||||
border: Border.all(color: color.withValues(alpha: 0.16)),
|
||||
),
|
||||
child: Icon(icon, size: iconSize, color: color),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user