feat: 完成 Flutter 版智能电网 IoT 管理应用初始化搭建
This commit is contained in:
commit
f61b7bf90b
120 files changed
+15229
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/network/api_client.dart';
|
||||
import '../../../core/network/api_response.dart';
|
||||
import '../../../core/providers.dart';
|
||||
import 'survey_models.dart';
|
||||
|
||||
final surveyApiProvider = Provider<SurveyApi>(
|
||||
(ref) => SurveyApi(ref.watch(apiClientProvider)),
|
||||
);
|
||||
|
||||
/// SOP 回执增量保存结果。
|
||||
class SopSaveResult {
|
||||
const SopSaveResult({required this.updated, required this.skipped, required this.sopResults});
|
||||
|
||||
final int updated;
|
||||
final List<int> skipped;
|
||||
final List<SurveySopResult> sopResults;
|
||||
|
||||
factory SopSaveResult.fromJson(Map<String, dynamic> json) => SopSaveResult(
|
||||
updated: (json['updated'] as num?)?.toInt() ?? 0,
|
||||
skipped: (json['skipped'] as List? ?? []).map((e) => (e as num).toInt()).toList(),
|
||||
sopResults: (json['sopResults'] as List? ?? [])
|
||||
.map((e) => SurveySopResult.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 勘察工单接口,对应 uni-app 版 `api/survey.ts`。
|
||||
class SurveyApi {
|
||||
SurveyApi(this._client);
|
||||
|
||||
final ApiClient _client;
|
||||
|
||||
Future<ApiResponse<SurveyPage>> list({
|
||||
int page = 1,
|
||||
int pageSize = 100,
|
||||
String? keyword,
|
||||
int? status,
|
||||
int? customerId,
|
||||
int? priority,
|
||||
int? workerId,
|
||||
int? salesmanId,
|
||||
String? planStartDate,
|
||||
String? planEndDate,
|
||||
int? includeDraft,
|
||||
}) => _client.get<SurveyPage>(
|
||||
'/survey-orders/',
|
||||
query: {
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
'keyword': keyword,
|
||||
'status': status,
|
||||
'customerId': customerId,
|
||||
'priority': priority,
|
||||
'workerId': workerId,
|
||||
'salesmanId': salesmanId,
|
||||
'planStartDate': planStartDate,
|
||||
'planEndDate': planEndDate,
|
||||
'includeDraft': includeDraft,
|
||||
},
|
||||
parse: (d) {
|
||||
final m = (d as Map).cast<String, dynamic>();
|
||||
return SurveyPage(
|
||||
list: (m['list'] as List? ?? [])
|
||||
.map((e) => SurveyItem.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<SurveyItem>> detail(int id) => _client.get<SurveyItem>(
|
||||
'/survey-orders/$id',
|
||||
parse: (d) => SurveyItem.fromJson((d as Map).cast<String, dynamic>()),
|
||||
);
|
||||
|
||||
Future<ApiResponse<SurveyItem>> update(int id, Map<String, dynamic> body) =>
|
||||
_client.put<SurveyItem>(
|
||||
'/survey-orders/$id',
|
||||
body: body,
|
||||
parse: (d) => SurveyItem.fromJson((d as Map).cast<String, dynamic>()),
|
||||
);
|
||||
|
||||
/// 按 SOP 检查项增量保存现场回执;
|
||||
/// 仅现场勘察中(status=1)且工单对当前用户可见时后端才放行。
|
||||
Future<ApiResponse<SopSaveResult>> saveSopResults(int id, List<SopResultPayload> results) =>
|
||||
_client.put<SopSaveResult>(
|
||||
'/survey-orders/$id/sop-results',
|
||||
body: {'results': results.map((e) => e.toJson()).toList()},
|
||||
parse: (d) => SopSaveResult.fromJson((d as Map).cast<String, dynamic>()),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/utils/id_util.dart';
|
||||
|
||||
/// 勘察工单状态:0 待指派 1 现场勘察中 2 待技术复核 3 已归档 4 预留 5 已取消。
|
||||
abstract final class SurveyStatus {
|
||||
static const pendingAssign = 0;
|
||||
static const surveying = 1;
|
||||
static const pendingReview = 2;
|
||||
static const archived = 3;
|
||||
static const reserved = 4;
|
||||
static const canceled = 5;
|
||||
}
|
||||
|
||||
/// 优先级:1 特急 P1 2 加急 P2 3 常规 P3。
|
||||
abstract final class SurveyPriority {
|
||||
static const urgent = 1;
|
||||
static const expedited = 2;
|
||||
static const regular = 3;
|
||||
}
|
||||
|
||||
/// App 端展示用的状态枚举(与 uni-app 版 `SurveyStatusMap` 一一对应)。
|
||||
enum SurveyStatusKey { pending, inProgress, review, completed, canceled }
|
||||
|
||||
const surveyStatusMap = <SurveyStatusKey, ({String text, ChipTone tone})>{
|
||||
SurveyStatusKey.pending: (text: '待处理', tone: ChipTone.warn),
|
||||
SurveyStatusKey.inProgress: (text: '进行中', tone: ChipTone.primary),
|
||||
SurveyStatusKey.review: (text: '待复核', tone: ChipTone.secondary),
|
||||
SurveyStatusKey.completed: (text: '已完成', tone: ChipTone.tertiary),
|
||||
SurveyStatusKey.canceled: (text: '已取消', tone: ChipTone.error),
|
||||
};
|
||||
|
||||
/// 数字状态 → App 展示键。
|
||||
SurveyStatusKey mapSurveyStatus(int? code) => switch (code) {
|
||||
SurveyStatus.surveying => SurveyStatusKey.inProgress,
|
||||
SurveyStatus.pendingReview => SurveyStatusKey.review,
|
||||
SurveyStatus.archived => SurveyStatusKey.completed,
|
||||
SurveyStatus.canceled => SurveyStatusKey.canceled,
|
||||
_ => SurveyStatusKey.pending,
|
||||
};
|
||||
|
||||
String surveyStatusText(int? code) => surveyStatusMap[mapSurveyStatus(code)]!.text;
|
||||
|
||||
ChipTone surveyStatusTone(int? code) => surveyStatusMap[mapSurveyStatus(code)]!.tone;
|
||||
|
||||
const surveyPriorityMap = <int, ({String text, ChipTone tone})>{
|
||||
1: (text: '特急 P1', tone: ChipTone.error),
|
||||
2: (text: '加急 P2', tone: ChipTone.warn),
|
||||
3: (text: '常规 P3', tone: ChipTone.muted),
|
||||
};
|
||||
|
||||
String surveyPriorityText(int? p) => surveyPriorityMap[p]?.text ?? '—';
|
||||
|
||||
ChipTone surveyPriorityTone(int? p) => surveyPriorityMap[p]?.tone ?? ChipTone.muted;
|
||||
|
||||
/// 列表卡片左侧色条颜色。
|
||||
Color surveyStatusBarColor(int? status) => switch (status) {
|
||||
SurveyStatus.pendingAssign => AppColors.statusWarn,
|
||||
SurveyStatus.surveying => AppColors.primary,
|
||||
SurveyStatus.pendingReview => AppColors.secondary,
|
||||
SurveyStatus.archived => AppColors.tertiaryContainer,
|
||||
SurveyStatus.canceled => AppColors.statusError,
|
||||
_ => AppColors.borderRule,
|
||||
};
|
||||
|
||||
class SurveyCustomer {
|
||||
const SurveyCustomer({required this.id, required this.customerName, this.city, this.address});
|
||||
|
||||
final int id;
|
||||
final String customerName;
|
||||
final String? city;
|
||||
final String? address;
|
||||
|
||||
factory SurveyCustomer.fromJson(Map<String, dynamic> json) => SurveyCustomer(
|
||||
id: toIntOr(json['id'], 0),
|
||||
customerName: json['customerName'] as String? ?? '',
|
||||
city: json['city'] as String?,
|
||||
address: json['address'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class SurveyWorker {
|
||||
const SurveyWorker({required this.id, required this.workerName, this.phone});
|
||||
|
||||
final int id;
|
||||
final String workerName;
|
||||
final String? phone;
|
||||
|
||||
factory SurveyWorker.fromJson(Map<String, dynamic> json) => SurveyWorker(
|
||||
id: toIntOr(json['id'], 0),
|
||||
workerName: json['workerName'] as String? ?? '',
|
||||
phone: json['phone'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
class SurveySopResult {
|
||||
const SurveySopResult({
|
||||
required this.id,
|
||||
required this.surveyOrderId,
|
||||
required this.sopItemId,
|
||||
required this.checked,
|
||||
required this.itemName,
|
||||
this.itemDesc = '',
|
||||
this.receiptType = '',
|
||||
this.receiptRequire = '',
|
||||
this.required = false,
|
||||
this.receiptValue = const [],
|
||||
this.receiptUrls = const [],
|
||||
this.remark = '',
|
||||
});
|
||||
|
||||
final int id;
|
||||
final int surveyOrderId;
|
||||
final int sopItemId;
|
||||
final bool checked;
|
||||
final String itemName;
|
||||
final String itemDesc;
|
||||
final String receiptType;
|
||||
final String receiptRequire;
|
||||
final bool required;
|
||||
|
||||
/// 回执内容:photo/meter 类为照片对象 key 数组,form 类为单元素文本值数组。
|
||||
final List<String> receiptValue;
|
||||
|
||||
/// 详情接口附带:照片类回执的预签名访问 URL(与 receiptValue 顺序一致)。
|
||||
final List<String> receiptUrls;
|
||||
final String remark;
|
||||
|
||||
factory SurveySopResult.fromJson(Map<String, dynamic> json) => SurveySopResult(
|
||||
id: toIntOr(json['id'], 0),
|
||||
surveyOrderId: toIntOr(json['surveyOrderId'], 0),
|
||||
sopItemId: toIntOr(json['sopItemId'], 0),
|
||||
checked: json['checked'] == true,
|
||||
itemName: json['itemName'] as String? ?? '',
|
||||
itemDesc: json['itemDesc'] as String? ?? '',
|
||||
receiptType: json['receiptType'] as String? ?? '',
|
||||
receiptRequire: json['receiptRequire'] as String? ?? '',
|
||||
required: json['required'] == true,
|
||||
receiptValue: (json['receiptValue'] as List? ?? []).map((e) => e.toString()).toList(),
|
||||
receiptUrls: (json['receiptUrls'] as List? ?? []).map((e) => e.toString()).toList(),
|
||||
remark: json['remark'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// 逐项回执的增量提交体:只传需要变更的 SOP 项,未传项保持原值。
|
||||
class SopResultPayload {
|
||||
const SopResultPayload({required this.sopItemId, this.checked, this.receiptValue, this.remark});
|
||||
|
||||
final int sopItemId;
|
||||
final bool? checked;
|
||||
final List<String>? receiptValue;
|
||||
final String? remark;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'sopItemId': sopItemId,
|
||||
if (checked != null) 'checked': checked,
|
||||
if (receiptValue != null) 'receiptValue': receiptValue,
|
||||
if (remark != null) 'remark': remark,
|
||||
};
|
||||
}
|
||||
|
||||
/// 勘察工单,与后端 `/api/survey-orders` 契约对齐。
|
||||
class SurveyItem {
|
||||
const SurveyItem({
|
||||
required this.id,
|
||||
required this.surveyNo,
|
||||
required this.customerId,
|
||||
required this.surveyor,
|
||||
required this.priority,
|
||||
required this.taskTitle,
|
||||
required this.siteLocation,
|
||||
required this.source,
|
||||
required this.loopSpec,
|
||||
required this.progressDone,
|
||||
required this.drawingCount,
|
||||
required this.techContact,
|
||||
required this.slaLevel,
|
||||
required this.urgeCount,
|
||||
required this.isDraft,
|
||||
required this.photos,
|
||||
required this.reviewRemark,
|
||||
required this.cancelReason,
|
||||
required this.remark,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.customerName,
|
||||
required this.workerName,
|
||||
required this.salesmanName,
|
||||
required this.salesmanPhone,
|
||||
this.workerId,
|
||||
this.surveyTime,
|
||||
this.loopCount,
|
||||
this.progressTotal,
|
||||
this.salesmanId,
|
||||
this.planStart,
|
||||
this.planEnd,
|
||||
this.expectMinutes,
|
||||
this.expectArrivalAt,
|
||||
this.lastUrgedAt,
|
||||
this.estimatedFee,
|
||||
this.actualFee,
|
||||
this.reviewedAt,
|
||||
this.customer,
|
||||
this.worker,
|
||||
this.sopResults,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String surveyNo;
|
||||
final int customerId;
|
||||
final String surveyor;
|
||||
final int? workerId;
|
||||
final String? surveyTime;
|
||||
final int priority;
|
||||
final String taskTitle;
|
||||
final String siteLocation;
|
||||
final String source;
|
||||
final String loopSpec;
|
||||
final int? loopCount;
|
||||
final int progressDone;
|
||||
final int? progressTotal;
|
||||
final int drawingCount;
|
||||
final int? salesmanId;
|
||||
final String techContact;
|
||||
final String? planStart;
|
||||
final String? planEnd;
|
||||
final int? expectMinutes;
|
||||
final String? expectArrivalAt;
|
||||
final String slaLevel;
|
||||
final int urgeCount;
|
||||
final String? lastUrgedAt;
|
||||
final int isDraft;
|
||||
final num? estimatedFee;
|
||||
final num? actualFee;
|
||||
final List<String> photos;
|
||||
final String reviewRemark;
|
||||
final String? reviewedAt;
|
||||
final String cancelReason;
|
||||
final String remark;
|
||||
final int status;
|
||||
final String? createdAt;
|
||||
final String? updatedAt;
|
||||
|
||||
// decorate 注入的冗余字段
|
||||
final SurveyCustomer? customer;
|
||||
final String customerName;
|
||||
final SurveyWorker? worker;
|
||||
final String workerName;
|
||||
final String salesmanName;
|
||||
final String salesmanPhone;
|
||||
|
||||
// 仅详情接口返回
|
||||
final List<SurveySopResult>? sopResults;
|
||||
|
||||
factory SurveyItem.fromJson(Map<String, dynamic> json) => SurveyItem(
|
||||
id: toIntOr(json['id'], 0),
|
||||
surveyNo: json['surveyNo'] as String? ?? '',
|
||||
customerId: toIntOr(json['customerId'], 0),
|
||||
surveyor: json['surveyor'] as String? ?? '',
|
||||
workerId: toIntOrNull(json['workerId']),
|
||||
surveyTime: json['surveyTime'] as String?,
|
||||
priority: toIntOr(json['priority'], 3),
|
||||
taskTitle: json['taskTitle'] as String? ?? '',
|
||||
siteLocation: json['siteLocation'] as String? ?? '',
|
||||
source: json['source'] as String? ?? '',
|
||||
loopSpec: json['loopSpec'] as String? ?? '',
|
||||
loopCount: toIntOrNull(json['loopCount']),
|
||||
progressDone: toIntOr(json['progressDone'], 0),
|
||||
progressTotal: toIntOrNull(json['progressTotal']),
|
||||
drawingCount: toIntOr(json['drawingCount'], 0),
|
||||
salesmanId: toIntOrNull(json['salesmanId']),
|
||||
techContact: json['techContact'] as String? ?? '',
|
||||
planStart: json['planStart'] as String?,
|
||||
planEnd: json['planEnd'] as String?,
|
||||
expectMinutes: toIntOrNull(json['expectMinutes']),
|
||||
expectArrivalAt: json['expectArrivalAt'] as String?,
|
||||
slaLevel: json['slaLevel'] as String? ?? '',
|
||||
urgeCount: toIntOr(json['urgeCount'], 0),
|
||||
lastUrgedAt: json['lastUrgedAt'] as String?,
|
||||
isDraft: toIntOr(json['isDraft'], 0),
|
||||
estimatedFee: json['estimatedFee'] as num?,
|
||||
actualFee: json['actualFee'] as num?,
|
||||
photos: (json['photos'] as List? ?? []).map((e) => e.toString()).toList(),
|
||||
reviewRemark: json['reviewRemark'] as String? ?? '',
|
||||
reviewedAt: json['reviewedAt'] as String?,
|
||||
cancelReason: json['cancelReason'] as String? ?? '',
|
||||
remark: json['remark'] as String? ?? '',
|
||||
status: toIntOr(json['status'], 0),
|
||||
createdAt: json['createdAt'] as String?,
|
||||
updatedAt: json['updatedAt'] as String?,
|
||||
customer: json['customer'] == null
|
||||
? null
|
||||
: SurveyCustomer.fromJson((json['customer'] as Map).cast<String, dynamic>()),
|
||||
customerName: json['customerName'] as String? ?? '',
|
||||
worker: json['worker'] == null
|
||||
? null
|
||||
: SurveyWorker.fromJson((json['worker'] as Map).cast<String, dynamic>()),
|
||||
workerName: json['workerName'] as String? ?? '',
|
||||
salesmanName: json['salesmanName'] as String? ?? '',
|
||||
salesmanPhone: json['salesmanPhone'] as String? ?? '',
|
||||
sopResults: json['sopResults'] == null
|
||||
? null
|
||||
: (json['sopResults'] as List)
|
||||
.map((e) => SurveySopResult.fromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
/// 计划时间展示,等价于 `formatSurveyPlanTime`。
|
||||
String get planTimeText => formatSurveyPlanTime(planStart, planEnd);
|
||||
|
||||
String get displayTitle => taskTitle.isNotEmpty ? taskTitle : '勘察 #$surveyNo';
|
||||
|
||||
String get displayCustomer => customerName.isNotEmpty ? customerName : (customer?.customerName ?? '客户 #$customerId');
|
||||
}
|
||||
|
||||
class SurveyPage {
|
||||
const SurveyPage({
|
||||
required this.list,
|
||||
required this.total,
|
||||
required this.page,
|
||||
required this.pageSize,
|
||||
});
|
||||
|
||||
final List<SurveyItem> list;
|
||||
final int total;
|
||||
final int page;
|
||||
final int pageSize;
|
||||
}
|
||||
|
||||
/// 勘察单计划时间展示:`05-12 09:00 - 11:00` / `05-12 09:00 - 05-13 10:00` / `未安排`。
|
||||
String formatSurveyPlanTime(String? planStart, String? planEnd) {
|
||||
if ((planStart == null || planStart.isEmpty) && (planEnd == null || planEnd.isEmpty)) {
|
||||
return '未安排';
|
||||
}
|
||||
String hm(DateTime dt) =>
|
||||
'${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
||||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||
String md(DateTime dt) =>
|
||||
'${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')}';
|
||||
|
||||
final start = parseSurveyDate(planStart);
|
||||
final end = parseSurveyDate(planEnd);
|
||||
if (start != null && end != null) {
|
||||
final sameDay =
|
||||
start.year == end.year && start.month == end.month && start.day == end.day;
|
||||
return sameDay ? '${hm(start)} - ${md(end)}' : '${hm(start)} - ${hm(end)}';
|
||||
}
|
||||
final only = start ?? end;
|
||||
return only == null ? (planStart ?? planEnd ?? '未安排') : hm(only);
|
||||
}
|
||||
|
||||
DateTime? parseSurveyDate(String? raw) {
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
return DateTime.tryParse(raw);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_typography.dart';
|
||||
import '../../../shared/widgets/app_feedback.dart';
|
||||
import '../../../shared/widgets/x_card.dart';
|
||||
import '../../../shared/widgets/x_chip.dart';
|
||||
import '../../../shared/widgets/x_common.dart';
|
||||
import '../../../shared/widgets/x_icon.dart';
|
||||
import '../../../shared/widgets/x_top_bar.dart';
|
||||
import '../data/survey_api.dart';
|
||||
import '../data/survey_models.dart';
|
||||
|
||||
/// 勘测任务列表,对应 `pages/survey/list.vue`。
|
||||
class SurveyListPage extends ConsumerStatefulWidget {
|
||||
const SurveyListPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SurveyListPage> createState() => _SurveyListPageState();
|
||||
}
|
||||
|
||||
class _SurveyListPageState extends ConsumerState<SurveyListPage> {
|
||||
List<SurveyItem> _list = const [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final res = await ref.read(surveyApiProvider).list(pageSize: 100);
|
||||
if (!mounted) return;
|
||||
if (res.isOk && res.data != null) {
|
||||
setState(() => _list = res.data!.list);
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted) showAppToast('加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _goForm(SurveyItem item) async {
|
||||
await context.push('/survey/form?id=${item.id}');
|
||||
if (mounted) _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return XPageShell(
|
||||
topBar: XTopBar.brand(title: '勘测任务'),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(16, 8, 16, 4),
|
||||
child: Text(
|
||||
'管理您的现场勘测与检查任务',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.textMuted),
|
||||
),
|
||||
),
|
||||
Expanded(child: _buildContent()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
if (_list.isEmpty) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
children: const [XPlaceholder(text: '暂无勘测任务')],
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
itemCount: _list.length,
|
||||
itemBuilder: (context, index) => _buildCard(_list[index]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(SurveyItem item) {
|
||||
return XCard(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
onTap: () => _goForm(item),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${item.customerName.isEmpty ? '未知客户' : item.customerName}的勘测',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'任务ID:${item.surveyNo}',
|
||||
style: AppTypography.dataMono.copyWith(
|
||||
color: AppColors.textMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
XChip(
|
||||
surveyStatusText(item.status),
|
||||
tone: surveyStatusTone(item.status),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 14),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildLine(
|
||||
'location',
|
||||
AppColors.primary,
|
||||
item.siteLocation.isEmpty ? '地点未填' : item.siteLocation,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildLine(
|
||||
'schedule',
|
||||
AppColors.statusWarn,
|
||||
'计划时间:${item.planTimeText}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLine(String icon, Color color, String text) {
|
||||
return Row(
|
||||
children: [
|
||||
XIcon(icon, size: 18, color: color),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user