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,77 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/api_response.dart';
import '../../../core/providers.dart';
import 'customer_models.dart';
final customerApiProvider = Provider<CustomerApi>(
(ref) => CustomerApi(ref.watch(apiClientProvider)),
);
/// 客户档案接口,对应 uni-app 版 `api/customer.ts`。
class CustomerApi {
CustomerApi(this._client);
final ApiClient _client;
Future<ApiResponse<CustomerPage>> list({
int page = 1,
int pageSize = 100,
String? keyword,
int? status,
int? salesmanId,
}) => _client.get<CustomerPage>(
'/customers',
query: {
'page': page,
'pageSize': pageSize,
'keyword': keyword,
'status': status,
'salesmanId': salesmanId,
},
parse: (d) {
final m = (d as Map).cast<String, dynamic>();
return CustomerPage(
list: (m['list'] as List? ?? [])
.map((e) => Customer.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<Customer>> detail(int id) => _client.get<Customer>(
'/customers/$id',
parse: (d) => Customer.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<Customer>> create(Map<String, dynamic> body) =>
_client.post<Customer>(
'/customers',
body: body,
parse: (d) => Customer.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<Customer>> update(int id, Map<String, dynamic> body) =>
_client.put<Customer>(
'/customers/$id',
body: body,
parse: (d) => Customer.fromJson((d as Map).cast<String, dynamic>()),
);
Future<ApiResponse<void>> delete(int id) => _client.delete<void>('/customers/$id');
Future<ApiResponse<void>> updateStatus(int id, int status) =>
_client.put<void>('/customers/$id/status', body: {'status': status});
Future<ApiResponse<List<CustomerOption>>> options() =>
_client.get<List<CustomerOption>>(
'/customers/options/all',
parse: (d) => (d as List)
.map((e) => CustomerOption.fromJson((e as Map).cast<String, dynamic>()))
.toList(),
);
}
@@ -0,0 +1,143 @@
import 'package:flutter/painting.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/utils/id_util.dart';
/// 客户档案,与后端 `/api/customers` 契约对齐(`types/customer.ts`)。
class Customer {
const Customer({
required this.id,
required this.customerNo,
required this.customerName,
required this.province,
required this.city,
required this.district,
required this.address,
required this.contactPerson,
required this.contactPhone,
required this.postalCode,
required this.fax,
required this.customerLevel,
required this.status,
this.salesmanId,
this.createdAt,
this.updatedAt,
});
final int id;
final String customerNo;
final String customerName;
final String province;
final String city;
final String district;
final String address;
final String contactPerson;
final String contactPhone;
final String postalCode;
final String fax;
final String customerLevel;
final int status;
final int? salesmanId;
final String? createdAt;
final String? updatedAt;
factory Customer.fromJson(Map<String, dynamic> json) => Customer(
id: toIntOr(json['id'], 0),
customerNo: json['customerNo'] as String? ?? '',
customerName: json['customerName'] as String? ?? '',
province: json['province'] as String? ?? '',
city: json['city'] as String? ?? '',
district: json['district'] as String? ?? '',
address: json['address'] as String? ?? '',
contactPerson: json['contactPerson'] as String? ?? '',
contactPhone: json['contactPhone'] as String? ?? '',
postalCode: json['postalCode'] as String? ?? '',
fax: json['fax'] as String? ?? '',
customerLevel: json['customerLevel'] as String? ?? '',
status: toIntOr(json['status'], 1),
salesmanId: toIntOrNull(json['salesmanId']),
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
);
Map<String, dynamic> toJson() => {
'id': id,
'customerNo': customerNo,
'customerName': customerName,
'province': province,
'city': city,
'district': district,
'address': address,
'contactPerson': contactPerson,
'contactPhone': contactPhone,
'postalCode': postalCode,
'fax': fax,
'customerLevel': customerLevel,
'status': status,
if (salesmanId != null) 'salesmanId': salesmanId,
};
/// 列表卡片右侧的「地区」展示:省市拼接,缺失时提示未填写。
String get locationText {
final text = '$province$city';
return text.isEmpty ? '未填写地区' : text;
}
}
/// 客户下拉选项(拜访/客户关系选择用)。
class CustomerOption {
const CustomerOption({
required this.id,
required this.customerNo,
required this.customerName,
required this.status,
});
final int id;
final String customerNo;
final String customerName;
final int status;
factory CustomerOption.fromJson(Map<String, dynamic> json) => CustomerOption(
id: toIntOr(json['id'], 0),
customerNo: json['customerNo'] as String? ?? '',
customerName: json['customerName'] as String? ?? '',
status: toIntOr(json['status'], 1),
);
}
class CustomerPage {
const CustomerPage({
required this.list,
required this.total,
required this.page,
required this.pageSize,
});
final List<Customer> list;
final int total;
final int page;
final int pageSize;
}
/// 客户状态:1 意向 2 签约 3 运维中 4 终止
const customerStatusMap = <int, ({String text, ChipTone tone})>{
1: (text: '意向', tone: ChipTone.warn),
2: (text: '签约', tone: ChipTone.primary),
3: (text: '运维中', tone: ChipTone.tertiary),
4: (text: '终止', tone: ChipTone.error),
};
String customerStatusText(int status) => customerStatusMap[status]?.text ?? '未知';
ChipTone customerStatusTone(int status) =>
customerStatusMap[status]?.tone ?? ChipTone.muted;
/// 列表卡片左侧色条颜色。
Color customerStatusBarColor(int status) => switch (status) {
1 => AppColors.statusWarn,
2 => AppColors.primary,
3 => AppColors.tertiaryContainer,
4 => AppColors.statusError,
_ => AppColors.borderRule,
};
+99
View File
@@ -0,0 +1,99 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/api_response.dart';
import '../../../core/providers.dart';
import '../../../core/utils/id_util.dart';
final userApiProvider = Provider<UserApi>(
(ref) => UserApi(ref.watch(apiClientProvider)),
);
class SysRole {
const SysRole({required this.id, required this.roleCode, required this.roleName});
final int id;
final String roleCode;
final String roleName;
factory SysRole.fromJson(Map<String, dynamic> json) => SysRole(
id: toIntOr(json['id'], 0),
roleCode: json['roleCode'] as String? ?? '',
roleName: json['roleName'] as String? ?? '',
);
}
class SysUser {
const SysUser({
required this.id,
required this.username,
required this.realName,
this.phone,
this.email,
this.status,
});
final int id;
final String username;
final String realName;
final String? phone;
final String? email;
final int? status;
/// 下拉项展示文本:姓名 · 手机号
String get optionLabel {
final name = realName.isNotEmpty ? realName : username;
return phone == null || phone!.isEmpty ? name : '$name · $phone';
}
factory SysUser.fromJson(Map<String, dynamic> json) => SysUser(
id: toIntOr(json['id'], 0),
username: json['username'] as String? ?? '',
realName: json['realName'] as String? ?? '',
phone: json['phone'] as String?,
email: json['email'] as String?,
status: toIntOrNull(json['status']),
);
}
/// 用户/角色接口,对应 uni-app 版 `api/user.ts`。
class UserApi {
UserApi(this._client);
final ApiClient _client;
Future<ApiResponse<List<SysRole>>> roles() => _client.get<List<SysRole>>(
'/users/roles',
parse: (d) => (d as List)
.map((e) => SysRole.fromJson((e as Map).cast<String, dynamic>()))
.toList(),
);
Future<ApiResponse<List<SysUser>>> list({
int page = 1,
int pageSize = 100,
int? status,
int? roleId,
}) => _client.get<List<SysUser>>(
'/users',
query: {'page': page, 'pageSize': pageSize, 'status': status, 'roleId': roleId},
parse: (d) {
final m = (d as Map).cast<String, dynamic>();
return (m['list'] as List? ?? [])
.map((e) => SysUser.fromJson((e as Map).cast<String, dynamic>()))
.toList();
},
);
/// 业务员下拉选项:先按 roleCode 解析 SALESMAN 角色 id,再取其启用状态的用户。
Future<List<SysUser>> salesmanOptions() async {
final roleRes = await roles();
final salesmanRole = (roleRes.data ?? [])
.where((r) => r.roleCode == 'SALESMAN')
.firstOrNull;
if (salesmanRole == null) return const [];
final userRes = await list(page: 1, pageSize: 100, status: 1, roleId: salesmanRole.id);
return userRes.data ?? const [];
}
}