feat: 完成智能电网电工端 Flutter 应用初始化
创建完整的 Flutter 项目结构,包含 Android 和 iOS 平台配置、核心网络层、路由、状态管理及业务模块(订单、收益、认证),适配原 uni-app 项目功能逻辑
This commit is contained in:
commit
56830e94ec
108 files changed
+9272
No files matched your search
@@ -0,0 +1,190 @@
|
||||
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!],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user