feat: 完成 Flutter 版智能电网 IoT 管理应用初始化搭建
This commit is contained in:
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,
|
||||
};
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user