feat: 新增勘察明细管理与修改密码功能
This commit is contained in:
1 parent
56830e94ec
commit
774403785c
12 files changed
+1553
-18
No files matched your search
@@ -33,6 +33,19 @@ class OrderApi {
|
||||
Future<ApiResponse<void>> grab(String source, int id) =>
|
||||
_client.post<void>('/worker-app/orders/$source/$id/grab');
|
||||
|
||||
/// 保存勘察明细(全量替换)。仅本人已接单且状态为「现场勘察中」的勘察单可写,
|
||||
/// 成功返回服务端最新明细(含预签名 `photoUrls`)。
|
||||
Future<ApiResponse<List<SurveyDetail>>> saveSurveyDetails(
|
||||
int id,
|
||||
List<SurveyDetail> details,
|
||||
) => _client.put<List<SurveyDetail>>(
|
||||
'/worker-app/orders/survey/$id/details',
|
||||
body: {'details': details.map((d) => d.toPayload()).toList()},
|
||||
parse: (d) => (d as List? ?? const [])
|
||||
.map((e) => SurveyDetail.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
/// 完成订单。非本人订单后端返回 403。
|
||||
Future<ApiResponse<void>> complete(String source, int id) =>
|
||||
_client.put<void>('/worker-app/orders/$source/$id/complete');
|
||||
|
||||
@@ -22,6 +22,8 @@ class Order {
|
||||
required this.laborFee,
|
||||
required this.materialFee,
|
||||
required this.estimatedDuration,
|
||||
this.details = const [],
|
||||
this.topology,
|
||||
});
|
||||
|
||||
/// `repair-12`
|
||||
@@ -52,6 +54,18 @@ class Order {
|
||||
final double materialFee;
|
||||
final String estimatedDuration;
|
||||
|
||||
/// 勘察明细(仅 `source == "survey"` 的详情接口返回)。
|
||||
final List<SurveyDetail> details;
|
||||
|
||||
/// 客户空间拓扑(仅 `source == "survey"` 的详情接口返回)。
|
||||
final CustomerTopology? topology;
|
||||
|
||||
/// 是否为现场勘察单。
|
||||
bool get isSurvey => source == 'survey';
|
||||
|
||||
/// 是否处于「现场勘察中(1)」,仅该状态可维护勘察明细。
|
||||
bool get surveyEditable => source == 'survey' && status == 1;
|
||||
|
||||
/// 来源中文名,用于收益明细副标题等场景。
|
||||
String get sourceName => switch (source) {
|
||||
'repair' => '家庭电路维修',
|
||||
@@ -109,5 +123,164 @@ class Order {
|
||||
laborFee: toDoubleOr(json['laborFee'], 0),
|
||||
materialFee: toDoubleOr(json['materialFee'], 0),
|
||||
estimatedDuration: json['estimatedDuration'] as String? ?? '',
|
||||
details: (json['details'] as List? ?? const [])
|
||||
.map((e) => SurveyDetail.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
topology: json['topology'] is Map
|
||||
? CustomerTopology.fromJson(
|
||||
(json['topology'] as Map).cast<String, dynamic>(),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 勘察明细:客户空间拓扑中的「建筑 + 楼层 + 房间(选填) + 现场照片 + 备注」。
|
||||
class SurveyDetail {
|
||||
const SurveyDetail({
|
||||
this.id = 0,
|
||||
required this.buildingId,
|
||||
required this.buildingName,
|
||||
required this.floorNo,
|
||||
this.roomId,
|
||||
this.roomName = '',
|
||||
this.photos = const [],
|
||||
this.photoUrls = const [],
|
||||
this.remark = '',
|
||||
});
|
||||
|
||||
/// 后端主键,新增行传 0(服务端忽略)。
|
||||
final int id;
|
||||
final int buildingId;
|
||||
final String buildingName;
|
||||
|
||||
/// 楼层:地上 `"1"`、地下 `"-1"`。
|
||||
final String floorNo;
|
||||
|
||||
final int? roomId;
|
||||
final String roomName;
|
||||
|
||||
/// 对象存储 key,提交时传数组。
|
||||
final List<String> photos;
|
||||
|
||||
/// 预签名访问 URL,仅用于预览。
|
||||
final List<String> photoUrls;
|
||||
final String remark;
|
||||
|
||||
/// 楼层展示文案:正数 `N层`、负数 `负N层`,无值返回空串。
|
||||
String get floorLabel {
|
||||
final n = int.tryParse(floorNo);
|
||||
if (n == null || n == 0) return '';
|
||||
return n > 0 ? '$n层' : '负${-n}层';
|
||||
}
|
||||
|
||||
factory SurveyDetail.fromJson(Map<String, dynamic> json) => SurveyDetail(
|
||||
id: toIntOr(json['id'], 0),
|
||||
buildingId: toIntOr(json['buildingId'], 0),
|
||||
buildingName: json['buildingName'] as String? ?? '',
|
||||
floorNo: json['floorNo']?.toString() ?? '',
|
||||
roomId: toIntOrNull(json['roomId']),
|
||||
roomName: json['roomName'] as String? ?? '',
|
||||
photos:
|
||||
(json['photos'] as List?)?.map((e) => e.toString()).toList() ??
|
||||
const [],
|
||||
photoUrls:
|
||||
(json['photoUrls'] as List?)?.map((e) => e.toString()).toList() ??
|
||||
const [],
|
||||
remark: json['remark'] as String? ?? '',
|
||||
);
|
||||
|
||||
/// 提交给后端的字段(全量替换数组的元素)。
|
||||
Map<String, dynamic> toPayload() => {
|
||||
'buildingId': buildingId,
|
||||
'buildingName': buildingName,
|
||||
'floorNo': floorNo,
|
||||
'roomId': roomId,
|
||||
'roomName': roomName,
|
||||
'photos': photos,
|
||||
'remark': remark,
|
||||
};
|
||||
}
|
||||
|
||||
/// 拓扑建筑:`floorCount` 地上层数、`floorDownCount` 地下层数。
|
||||
class TopologyBuilding {
|
||||
const TopologyBuilding({
|
||||
required this.id,
|
||||
required this.buildingName,
|
||||
required this.floorCount,
|
||||
required this.floorDownCount,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String buildingName;
|
||||
final int floorCount;
|
||||
final int floorDownCount;
|
||||
|
||||
factory TopologyBuilding.fromJson(Map<String, dynamic> json) =>
|
||||
TopologyBuilding(
|
||||
id: toIntOr(json['id'], 0),
|
||||
buildingName: json['buildingName'] as String? ?? '',
|
||||
floorCount: toIntOr(json['floorCount'], 0),
|
||||
floorDownCount: toIntOr(json['floorDownCount'], 0),
|
||||
);
|
||||
|
||||
/// 生成楼层选项:value 地上 `"1"`/地下 `"-1"`,label `"1层"`/`"负1层"`。
|
||||
List<({String value, String label})> get floorOptions => [
|
||||
for (var i = 1; i <= floorCount; i++) (value: '$i', label: '$i层'),
|
||||
for (var i = 1; i <= floorDownCount; i++) (value: '-$i', label: '负$i层'),
|
||||
];
|
||||
}
|
||||
|
||||
/// 拓扑房间:按 `buildingId` + `floorNo` 归属。
|
||||
class TopologyRoom {
|
||||
const TopologyRoom({
|
||||
required this.id,
|
||||
required this.roomName,
|
||||
required this.buildingId,
|
||||
required this.floorNo,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String roomName;
|
||||
final int buildingId;
|
||||
final String floorNo;
|
||||
|
||||
factory TopologyRoom.fromJson(Map<String, dynamic> json) => TopologyRoom(
|
||||
id: toIntOr(json['id'], 0),
|
||||
roomName: json['roomName'] as String? ?? '',
|
||||
buildingId: toIntOr(json['buildingId'], 0),
|
||||
floorNo: json['floorNo']?.toString() ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// 客户空间拓扑:建筑 + 房间。
|
||||
class CustomerTopology {
|
||||
const CustomerTopology({this.buildings = const [], this.rooms = const []});
|
||||
|
||||
final List<TopologyBuilding> buildings;
|
||||
final List<TopologyRoom> rooms;
|
||||
|
||||
factory CustomerTopology.fromJson(Map<String, dynamic> json) =>
|
||||
CustomerTopology(
|
||||
buildings: (json['buildings'] as List? ?? const [])
|
||||
.map(
|
||||
(e) => TopologyBuilding.fromJson((e as Map).cast<String, dynamic>()),
|
||||
)
|
||||
.toList(),
|
||||
rooms: (json['rooms'] as List? ?? const [])
|
||||
.map((e) => TopologyRoom.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
/// 按建筑 + 楼层过滤房间,用于明细编辑时的房间下拉。
|
||||
List<TopologyRoom> roomsOf(int buildingId, String floorNo) => rooms
|
||||
.where((r) => r.buildingId == buildingId && r.floorNo == floorNo)
|
||||
.toList();
|
||||
|
||||
/// 按 id 查建筑。
|
||||
TopologyBuilding? buildingOf(int id) {
|
||||
for (final b in buildings) {
|
||||
if (b.id == id) return b;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user