feat: 完成 Flutter 版智能电网 IoT 管理应用初始化搭建

This commit is contained in:
andyesfly committed 2026-10-02 17:21:53 +08:00
commit f61b7bf90b
120 files changed
+15229

No files matched your search

@@ -0,0 +1,73 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/api_response.dart';
import '../../../core/providers.dart';
import 'project_models.dart';
final projectApiProvider = Provider<ProjectApi>(
(ref) => ProjectApi(ref.watch(apiClientProvider)),
);
/// 智电改造方案接口,对应后端 `/api/project-plans`。
///
/// 说明:uni-app 版项目模块使用本地 mock(`api/mock.ts`),字段与后端契约完全不一致;
/// Flutter 版按用户确认改为直连后端真实接口。
class ProjectApi {
ProjectApi(this._client);
final ApiClient _client;
Future<ApiResponse<ProjectPlanPage>> list({
int page = 1,
int pageSize = 100,
String? keyword,
int? customerId,
int? status,
String? registerStartDate,
String? registerEndDate,
}) => _client.get<ProjectPlanPage>(
'/project-plans',
query: {
'page': page,
'pageSize': pageSize,
'keyword': keyword,
'customerId': customerId,
'status': status,
'registerStartDate': registerStartDate,
'registerEndDate': registerEndDate,
},
parse: (d) {
final m = (d as Map).cast<String, dynamic>();
return ProjectPlanPage(
list: (m['list'] as List? ?? [])
.map((e) => ProjectPlan.fromJson((e as Map).cast<String, dynamic>()))
.toList(),
total: (m['total'] as num?)?.toInt() ?? 0,
page: (m['page'] as num?)?.toInt() ?? 1,
pageSize: (m['pageSize'] as num?)?.toInt() ?? pageSize,
);
},
);
Future<ApiResponse<ProjectPlan>> detail(int id) => _client.get<ProjectPlan>(
'/project-plans/$id',
parse: (d) => ProjectPlan.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<ProjectPlan>> create(Map<String, dynamic> body) =>
_client.post<ProjectPlan>(
'/project-plans',
body: body,
parse: (d) => ProjectPlan.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<ProjectPlan>> update(int id, Map<String, dynamic> body) =>
_client.put<ProjectPlan>(
'/project-plans/$id',
body: body,
parse: (d) => ProjectPlan.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<void>> delete(int id) => _client.delete<void>('/project-plans/$id');
}
@@ -0,0 +1,150 @@
import 'package:flutter/painting.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/utils/id_util.dart';
/// 方案状态:0 草稿 1 已确认。
abstract final class ProjectPlanStatus {
static const draft = 0;
static const confirmed = 1;
}
const projectStatusMap = <int, ({String text, ChipTone tone})>{
0: (text: '草稿', tone: ChipTone.warn),
1: (text: '已确认', tone: ChipTone.primary),
};
String projectStatusText(int? s) => projectStatusMap[s]?.text ?? '未知';
ChipTone projectStatusTone(int? s) => projectStatusMap[s]?.tone ?? ChipTone.muted;
/// 列表卡片左侧色条颜色。
Color projectStatusBarColor(int? status) => switch (status) {
0 => AppColors.statusWarn,
1 => AppColors.primary,
_ => AppColors.borderRule,
};
/// 项目方案设备明细,对应后端 `project_plan_detail` 表。
class ProjectPlanDetail {
const ProjectPlanDetail({
required this.id,
required this.planId,
required this.customerId,
required this.equipName,
required this.quantity,
this.equipId,
this.smartDeviceId,
this.remark,
});
final int id;
final int planId;
final int customerId;
final String equipName;
final num quantity;
final int? equipId;
final int? smartDeviceId;
final String? remark;
factory ProjectPlanDetail.fromJson(Map<String, dynamic> json) => ProjectPlanDetail(
id: toIntOr(json['id'], 0),
planId: toIntOr(json['planId'], 0),
customerId: toIntOr(json['customerId'], 0),
equipName: json['equipName'] as String? ?? '',
quantity: (json['quantity'] as num?) ?? 1,
equipId: toIntOrNull(json['equipId']),
smartDeviceId: toIntOrNull(json['smartDeviceId']),
remark: json['remark'] as String?,
);
Map<String, dynamic> toJson() => {
'equipName': equipName,
'quantity': quantity,
if (equipId != null) 'equipId': equipId,
if (smartDeviceId != null) 'smartDeviceId': smartDeviceId,
if (remark != null && remark!.isNotEmpty) 'remark': remark,
};
ProjectPlanDetail copyWith({String? equipName, num? quantity, String? remark}) =>
ProjectPlanDetail(
id: id,
planId: planId,
customerId: customerId,
equipName: equipName ?? this.equipName,
quantity: quantity ?? this.quantity,
equipId: equipId,
smartDeviceId: smartDeviceId,
remark: remark ?? this.remark,
);
}
/// 智电改造方案,与后端 `/api/project-plans` 契约对齐。
class ProjectPlan {
const ProjectPlan({
required this.id,
required this.planNo,
required this.customerId,
required this.status,
this.registrar,
this.registerTime,
this.planDesc,
this.planFileUrl,
this.createdAt,
this.details = const [],
});
final int id;
final String planNo;
final int customerId;
/// 方案登记人(业务员姓名文本)
final String? registrar;
/// 方案登记时间(DATEONLY,`YYYY-MM-DD`)
final String? registerTime;
final String? planDesc;
/// 方案最终文件 URL
final String? planFileUrl;
final String? createdAt;
/// 设备明细,仅详情接口返回
final List<ProjectPlanDetail> details;
final int status;
factory ProjectPlan.fromJson(Map<String, dynamic> json) => ProjectPlan(
id: toIntOr(json['id'], 0),
planNo: json['planNo'] as String? ?? '',
customerId: toIntOr(json['customerId'], 0),
registrar: json['registrar'] as String?,
registerTime: json['registerTime'] as String?,
planDesc: json['planDesc'] as String?,
planFileUrl: json['planFileUrl'] as String?,
createdAt: json['created_at'] as String?,
details: (json['details'] as List? ?? [])
.map((e) => ProjectPlanDetail.fromJson((e as Map).cast<String, dynamic>()))
.toList(),
status: toIntOr(json['status'], 0),
);
/// 设备明细合计数量。
num get detailQuantityTotal =>
details.fold<num>(0, (sum, d) => sum + d.quantity);
String get displayTitle => planNo.isNotEmpty ? planNo : '方案 #$id';
}
class ProjectPlanPage {
const ProjectPlanPage({
required this.list,
required this.total,
required this.page,
required this.pageSize,
});
final List<ProjectPlan> list;
final int total;
final int page;
final int pageSize;
}