创建完整的 Flutter 项目结构,包含 Android 和 iOS 平台配置、核心网络层、路由、状态管理及业务模块(订单、收益、认证),适配原 uni-app 项目功能逻辑
191 lines
5.0 KiB
Dart
191 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
import '../../core/theme/app_colors.dart';
|
||
import '../../core/theme/app_dimens.dart';
|
||
import '../../core/theme/app_typography.dart';
|
||
|
||
/// 页面骨架:固定顶栏 + 内容区。对应源「根容器固定、滚动只发生在内容区」的布局。
|
||
class XPageShell extends StatelessWidget {
|
||
const XPageShell({
|
||
super.key,
|
||
required this.topBar,
|
||
required this.child,
|
||
this.backgroundColor = AppColors.bgPage,
|
||
});
|
||
|
||
final Widget topBar;
|
||
final Widget child;
|
||
final Color backgroundColor;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
color: backgroundColor,
|
||
child: Column(
|
||
children: [topBar, Expanded(child: child)],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 页面大标题 + 副标题,对应 `.page-header` / `.section-title`(28px/w700)。
|
||
class XPageTitle extends StatelessWidget {
|
||
const XPageTitle({super.key, required this.title, this.subtitle});
|
||
|
||
final String title;
|
||
final String? subtitle;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: AppTypography.navLargeTitle.copyWith(
|
||
color: AppColors.onSurface,
|
||
),
|
||
),
|
||
if (subtitle != null) ...[
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
subtitle!,
|
||
style: AppTypography.bodyMd.copyWith(
|
||
color: AppColors.onSurfaceVariant,
|
||
),
|
||
),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 空态 / 加载中占位。
|
||
class XPlaceholder extends StatelessWidget {
|
||
const XPlaceholder({
|
||
super.key,
|
||
required this.text,
|
||
this.padding = const EdgeInsets.symmetric(vertical: 60),
|
||
});
|
||
|
||
final String text;
|
||
final EdgeInsetsGeometry padding;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: padding,
|
||
child: Center(
|
||
child: Text(
|
||
text,
|
||
textAlign: TextAlign.center,
|
||
style: AppTypography.bodyMd.copyWith(color: AppColors.textMuted),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 实心主按钮,对应 `.login-btn` / `.start-btn` 等。
|
||
class XPrimaryButton extends StatelessWidget {
|
||
const XPrimaryButton({
|
||
super.key,
|
||
required this.label,
|
||
required this.onPressed,
|
||
this.leading,
|
||
this.loading = false,
|
||
this.height = 48,
|
||
this.radius = AppRadius.lg,
|
||
this.background = AppColors.primary,
|
||
this.foreground = AppColors.onPrimary,
|
||
this.fontSize = 17,
|
||
this.shadows,
|
||
});
|
||
|
||
final String label;
|
||
final VoidCallback? onPressed;
|
||
final Widget? leading;
|
||
final bool loading;
|
||
final double height;
|
||
final double radius;
|
||
final Color background;
|
||
final Color foreground;
|
||
final double fontSize;
|
||
final List<BoxShadow>? shadows;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final disabled = onPressed == null || loading;
|
||
|
||
return Opacity(
|
||
opacity: disabled && !loading ? 0.55 : 1,
|
||
child: SizedBox(
|
||
height: height,
|
||
width: double.infinity,
|
||
child: Material(
|
||
color: background,
|
||
borderRadius: BorderRadius.circular(radius),
|
||
child: InkWell(
|
||
onTap: disabled ? null : onPressed,
|
||
borderRadius: BorderRadius.circular(radius),
|
||
child: Container(
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(radius),
|
||
boxShadow: shadows,
|
||
),
|
||
child: loading
|
||
? SizedBox(
|
||
width: 20,
|
||
height: 20,
|
||
child: CircularProgressIndicator(
|
||
strokeWidth: 2,
|
||
valueColor: AlwaysStoppedAnimation(foreground),
|
||
),
|
||
)
|
||
: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (leading != null) ...[
|
||
leading!,
|
||
const SizedBox(width: 8),
|
||
],
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.w600,
|
||
color: foreground,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 区块小标题(20/w600),对应 `.chart-title` / `.section-title`。
|
||
class XSectionTitle extends StatelessWidget {
|
||
const XSectionTitle(this.text, {super.key, this.trailing});
|
||
|
||
final String text;
|
||
final Widget? trailing;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final title = Text(
|
||
text,
|
||
style: AppTypography.headlineMd.copyWith(color: AppColors.onSurface),
|
||
);
|
||
if (trailing == null) return title;
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [title, trailing!],
|
||
);
|
||
}
|
||
}
|