Browse Source

feat : 更新企业信息

dev
徐振升 2 weeks ago
parent
commit
517488e1fd
  1. 8
      .vscode/settings.json
  2. 21
      lib/app/core/extensions/datetime_extension.dart
  3. 11
      lib/app/core/services/database_service.dart
  4. 132
      lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart
  5. 24
      lib/app/features/enterprise/data/model/enterprise_list_item_model.dart
  6. 47
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  7. 29
      lib/app/features/enterprise/domain/entities/enterprise.dart
  8. 24
      lib/app/features/enterprise/domain/entities/enterprise_list_item.dart
  9. 16
      lib/app/features/enterprise/domain/repositories/enterprise_repository.dart
  10. 12
      lib/app/features/enterprise/domain/usecases/add_enterprise.dart
  11. 36
      lib/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart
  12. 0
      lib/app/features/enterprise/domain/usecases/editor_enterprise.dart
  13. 26
      lib/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart
  14. 22
      lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart
  15. 24
      lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
  16. 89
      lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart
  17. 80
      lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart
  18. 38
      lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart
  19. 217
      lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart
  20. 25
      lib/app/features/enterprise/presentation/widgets/enterprise_card.dart
  21. 3
      lib/app/features/home/bindings/home_binding.dart
  22. 28
      lib/app/features/navigation/presentation/bindings/navigation_binding.dart
  23. 1
      lib/app/features/navigation/presentation/pages/navigation_page.dart

8
.vscode/settings.json vendored

@ -1,3 +1,9 @@
{
"cSpell.words": ["fenix", "Getx", "tdesign"]
"cSpell.words": [
"fenix",
"Getx",
"tdesign",
"usecase",
"usecases"
]
}

21
lib/app/core/extensions/datetime_extension.dart

@ -0,0 +1,21 @@
// lib/app/core/extensions/datetime_extension.dart
import 'package:intl/intl.dart';
extension DateTimeFormatting on DateTime {
/// 'yyyy-MM-dd HH:mm:ss'
String toDateTimeString() {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(this);
}
/// 'yyyy-MM-dd'
String toDateString() {
return DateFormat('yyyy-MM-dd').format(this);
}
/// 'HH:mm:ss'
String toTimeString() {
return DateFormat('HH:mm:ss').format(this);
}
//
}

11
lib/app/core/services/database_service.dart

@ -6,6 +6,7 @@ import 'package:path/path.dart';
const String _createProblemsTable = '''
CREATE TABLE problems(
id TEXT PRIMARY KEY,
enterpriseId TEXT NOT NULL,
description TEXT NOT NULL,
location TEXT NOT NULL,
imageUrls TEXT NOT NULL,
@ -39,7 +40,7 @@ const String _createEnterprisesTable = '''
///
///
class DatabaseService extends GetxService {
static const String _dbName = 'problems.db';
static const String _dbName = 'database.db';
static const int _dbVersion = 1;
Database? _database;
@ -88,6 +89,14 @@ class DatabaseService extends GetxService {
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
Get.log('正在将数据库从版本 $oldVersion 升级到 $newVersion...');
//
if (oldVersion < 2) {
// 1 2
// problems enterpriseId
await db.execute('''
ALTER TABLE problems ADD COLUMN enterpriseId TEXT
''');
Get.log('`problems` 表已成功添加 `enterpriseId` 字段');
}
}
@override

132
lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart

@ -1,20 +1,136 @@
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_list_item_model.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:sqflite/sqflite.dart';
abstract class EnterpriseLocalDataSource {
///
Future<void> addEnterprise(EnterpriseModel enterprise);
Future<void> updateEnterprise(EnterpriseModel enterprise);
Future<List<EnterpriseListItemModel>> getEnterpriseListItems({
String? name,
String? type,
DateTime? startDate,
DateTime? endDate,
});
///
}
class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
final DatabaseService _databaseService;
const EnterpriseLocalDataSourceImpl({
required DatabaseService databaseService,
}) : _databaseService = databaseService;
@override
Future<void> addEnterprise(EnterpriseModel enterprise) async {
// // Enterprise
// // 使 SQLiteSharedPreferences
// final db = await databaseService.database;
// await db.insert(
// 'enterprises', //
// enterprise.toMap(),
// conflictAlgorithm: ConflictAlgorithm.replace,
// );
final db = await _databaseService.database;
await db.insert(
"enterprises",
enterprise.toMap(),
// conflictAlgorithm (id)
// ConflictAlgorithm.replace ID
//
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
@override
Future<void> updateEnterprise(EnterpriseModel enterprise) async {
final db = await _databaseService.database;
await db.update(
'enterprises',
enterprise.toMap(),
where: 'id = ?', // ID
whereArgs: [enterprise.id],
);
}
@override
Future<List<EnterpriseListItemModel>> getEnterpriseListItems({
String? name,
String? type,
DateTime? startDate,
DateTime? endDate,
}) async {
final db = await _databaseService.database;
// pendingCreate 0
final int pendingCreateIndex = SyncStatus.pendingCreate.index;
// WHERE
final List<String> whereClauses = [];
final List<dynamic> whereArgs = [];
// 1. ()
if (name != null && name.isNotEmpty) {
whereClauses.add('e.name LIKE ?');
whereArgs.add('%$name%');
}
// 2. ()
if (type != null && type.isNotEmpty) {
whereClauses.add('e.type = ?');
whereArgs.add(type);
}
// 3.
if (startDate != null) {
whereClauses.add('e.creationTime >= ?');
whereArgs.add(startDate.millisecondsSinceEpoch);
}
// 4. []
if (endDate != null) {
// endDate
final endOfDay = DateTime(
endDate.year,
endDate.month,
endDate.day,
23,
59,
59,
);
whereClauses.add('e.creationTime <= ?');
whereArgs.add(endOfDay.millisecondsSinceEpoch);
}
// --- ---
// WHERE
final String whereString = whereClauses.isNotEmpty
? 'WHERE ${whereClauses.join(' AND ')}'
: '';
// SQL
final String sql =
'''
SELECT
e.*,
COUNT(p.id) AS totalProblems,
COUNT(CASE WHEN p.syncStatus != $pendingCreateIndex THEN 1 ELSE NULL END) AS uploadedProblems,
COUNT(CASE WHEN p.syncStatus == $pendingCreateIndex THEN 1 ELSE NULL END) AS pendingProblems
FROM
enterprises e
LEFT JOIN
problems p ON e.id = p.enterpriseId
$whereString
GROUP BY
e.id
ORDER BY
e.creationTime DESC
''';
//
// Get.log('查询 SQL: $sql');
// Get.log('参数: $whereArgs');
final List<Map<String, dynamic>> maps = await db.rawQuery(sql, whereArgs);
return List.generate(maps.length, (i) {
return EnterpriseListItemModel.fromMap(maps[i]);
});
}
}

24
lib/app/features/enterprise/data/model/enterprise_list_item_model.dart

@ -0,0 +1,24 @@
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
class EnterpriseListItemModel extends EnterpriseListItem {
const EnterpriseListItemModel({
required super.enterprise,
required super.totalProblems,
required super.uploadedProblems,
required super.pendingProblems,
});
/// Map
/// map JOIN COUNT
factory EnterpriseListItemModel.fromMap(Map<String, dynamic> map) {
return EnterpriseListItemModel(
// EnterpriseModel fromMap
enterprise: EnterpriseModel.fromMap(map),
// map
totalProblems: map['totalProblems'] ?? 0,
uploadedProblems: map['uploadedProblems'] ?? 0,
pendingProblems: map['pendingProblems'] ?? 0,
);
}
}

47
lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart

@ -1,10 +1,8 @@
// ...
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:uuid/uuid.dart';
@ -20,23 +18,8 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
@override
Future<void> addEnterprise(Enterprise enterprise) async {
final now = DateTime.now();
// 1.
final fullEnterprise = Enterprise(
id: uuid.v4(), // ID
name: enterprise.name,
type: enterprise.type,
// ... ...
syncStatus: SyncStatus.pendingCreate, //
lastModifiedTime: now,
creationTime: now,
);
final enterpriseModel = EnterpriseModel.fromEntity(enterprise);
// 2.
final enterpriseModel = EnterpriseModel.fromEntity(fullEnterprise);
// 3.
await localDataSource.addEnterprise(enterpriseModel);
}
@ -63,4 +46,30 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
// TODO: implement syncUpdate
throw UnimplementedError();
}
@override
Future<List<EnterpriseListItem>> getEnterpriseListItems({
String? name,
String? type,
DateTime? startDate,
DateTime? endDate,
}) async {
// DataSource
final models = await localDataSource.getEnterpriseListItems(
name: name,
type: type,
startDate: startDate,
endDate: endDate,
);
// EnterpriseListItemModel EnterpriseListItem
return models;
}
@override
Future<void> updateEnterprise(Enterprise enterprise) async {
//
final enterpriseModel = EnterpriseModel.fromEntity(enterprise);
//
await localDataSource.updateEnterprise(enterpriseModel);
}
}

29
lib/app/features/enterprise/domain/entities/enterprise.dart

@ -33,6 +33,35 @@ class Enterprise extends Equatable implements SyncableEntity {
required this.syncStatus,
});
Enterprise copyWith({
String? id,
SyncStatus? syncStatus,
DateTime? lastModifiedTime,
String? name,
String? type,
String? address,
String? scale,
String? contactPerson,
String? contactPhone,
String? majorHazardsDescription,
DateTime? creationTime,
}) {
return Enterprise(
id: id ?? this.id,
syncStatus: syncStatus ?? this.syncStatus,
lastModifiedTime: lastModifiedTime ?? this.lastModifiedTime,
name: name ?? this.name,
type: type ?? this.type,
address: address ?? this.address,
scale: scale ?? this.scale,
contactPerson: contactPerson ?? this.contactPerson,
contactPhone: contactPhone ?? this.contactPhone,
majorHazardsDescription:
majorHazardsDescription ?? this.majorHazardsDescription,
creationTime: creationTime ?? this.creationTime,
);
}
@override
List<Object?> get props => [id, syncStatus, name, type];
}

24
lib/app/features/enterprise/domain/entities/enterprise_list_item.dart

@ -0,0 +1,24 @@
import 'package:equatable/equatable.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
class EnterpriseListItem extends Equatable {
final Enterprise enterprise;
final int totalProblems;
final int uploadedProblems;
final int pendingProblems;
const EnterpriseListItem({
required this.enterprise,
required this.totalProblems,
required this.uploadedProblems,
required this.pendingProblems,
});
@override
List<Object?> get props => [
enterprise,
totalProblems,
uploadedProblems,
pendingProblems,
];
}

16
lib/app/features/enterprise/domain/repositories/enterprise_repository.dart

@ -1,12 +1,20 @@
import 'package:problem_check_system/app/core/repositories/syncable_repository.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import '../entities/enterprise.dart';
abstract class EnterpriseRepository implements SyncableRepository<Enterprise> {
///
/// ID生成
///
Future<void> addEnterprise(Enterprise enterprise);
// --- ---
// Future<List<EnterpriseListItem>> getEnterpriseListItems();
///
Future<void> updateEnterprise(Enterprise enterprise);
///
Future<List<EnterpriseListItem>> getEnterpriseListItems({
String? name,
String? type,
DateTime? startDate,
DateTime? endDate,
});
}

12
lib/app/features/enterprise/domain/usecases/add_enterprise.dart

@ -1,12 +0,0 @@
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
class AddEnterprise {
final EnterpriseRepository repository;
AddEnterprise({required this.repository});
Future<void> call(Enterprise enterprise) async {
await repository.addEnterprise(enterprise);
}
}

36
lib/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart

@ -0,0 +1,36 @@
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:uuid/uuid.dart';
class AddEnterpriseUsecase {
final EnterpriseRepository repository;
final Uuid uuid;
AddEnterpriseUsecase({required this.repository, required this.uuid});
Future<void> call({
required String name,
required String type,
String address = "",
String scale = "",
String contactPerson = "",
String contactPhone = "",
String majorHazardsDescription = "",
}) async {
final enterprise = Enterprise(
id: uuid.v4(),
name: name,
type: type,
address: address,
scale: scale,
contactPerson: contactPerson,
contactPhone: contactPhone,
majorHazardsDescription: majorHazardsDescription,
lastModifiedTime: DateTime.now(),
creationTime: DateTime.now(),
syncStatus: SyncStatus.pendingCreate,
);
await repository.addEnterprise(enterprise);
}
}

0
lib/app/features/enterprise/domain/usecases/editor_enterprise.dart

26
lib/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart

@ -0,0 +1,26 @@
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
class EditEnterpriseUsecase {
final EnterpriseRepository repository;
EditEnterpriseUsecase({required this.repository});
/// [] Usecase
/// Enterprise
Future<void> call(Enterprise updatedEnterprise) async {
// 1.
final enterpriseToSave = updatedEnterprise.copyWith(
lastModifiedTime: DateTime.now(),
// 2.
//
syncStatus: updatedEnterprise.syncStatus == SyncStatus.pendingCreate
? SyncStatus.pendingCreate
: SyncStatus.pendingUpdate,
);
// 3.
await repository.updateEnterprise(enterpriseToSave);
}
}

22
lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart

@ -0,0 +1,22 @@
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
class GetEnterpriseListUsecase {
final EnterpriseRepository repository;
const GetEnterpriseListUsecase({required this.repository});
Future<List<EnterpriseListItem>> call({
String? name,
String? type,
DateTime? startDate,
DateTime? endDate,
}) async {
return await repository.getEnterpriseListItems(
name: name,
type: type,
startDate: startDate,
endDate: endDate,
);
}
}

24
lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart

@ -1,13 +1,16 @@
// lib/app/features/enterprise/presentation/bindings/enterprise_form_binding.dart
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart'; //
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart';
import 'package:uuid/uuid.dart'; //
class EnterpriseFormBinding extends Bindings {
@override
@ -19,7 +22,9 @@ class EnterpriseFormBinding extends Bindings {
final Enterprise? enterpriseToEdit = Get.arguments as Enterprise?;
Get.lazyPut<EnterpriseLocalDataSource>(
() => EnterpriseLocalDataSourceImpl(),
() => EnterpriseLocalDataSourceImpl(
databaseService: Get.find<DatabaseService>(),
),
);
Get.lazyPut<EnterpriseRemoteDataSource>(
() => EnterpriseRemoteDataSourceImpl(),
@ -30,15 +35,22 @@ class EnterpriseFormBinding extends Bindings {
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
)),
);
Get.lazyPut<AddEnterprise>(
() => AddEnterprise(repository: Get.find<EnterpriseRepository>()),
Get.lazyPut<AddEnterpriseUsecase>(
() => AddEnterpriseUsecase(
repository: Get.find<EnterpriseRepository>(),
uuid: Get.find<Uuid>(),
),
);
Get.lazyPut<EditEnterpriseUsecase>(
() => EditEnterpriseUsecase(repository: Get.find<EnterpriseRepository>()),
);
// 2. null Controller
Get.lazyPut<EnterpriseFormController>(
() => EnterpriseFormController(
initialData: enterpriseToEdit,
usecase: Get.find(),
addEnterpriseUsecase: Get.find<AddEnterpriseUsecase>(),
editEnterpriseUsecase: Get.find<EditEnterpriseUsecase>(),
),
);
}

89
lib/app/features/enterprise/presentation/controllers/enterprise_form_controller.dart

@ -1,19 +1,25 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise.dart';
import 'package:uuid/uuid.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/add_enterprise_usecase.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/editor_enterprise_usecase.dart';
class EnterpriseFormController extends GetxController {
//
final Enterprise? initialData;
final AddEnterprise usecase;
final AddEnterpriseUsecase addEnterpriseUsecase;
final EditEnterpriseUsecase editEnterpriseUsecase;
final formKey = GlobalKey<FormState>();
EnterpriseFormController({this.initialData, required this.usecase});
EnterpriseFormController({
this.initialData,
required this.addEnterpriseUsecase,
required this.editEnterpriseUsecase,
});
// --- ---
var isSubmitting = false.obs;
var isEditMode = false.obs;
var pageTitle = '新增企业'.obs;
final Rx<CompanyType?> selectedType = Rx<CompanyType?>(null);
@ -39,7 +45,16 @@ class EnterpriseFormController extends GetxController {
pageTitle.value = '修改信息';
//
enterpriseNameController.text = initialData!.name;
// ...
enterpriseAddressController.text = initialData!.address ?? '';
contactPersonController.text = initialData!.contactPerson ?? '';
contactPhoneController.text = initialData!.contactPhone ?? '';
hazardSourceController.text = initialData!.majorHazardsDescription ?? '';
selectedType.value = CompanyType.values.firstWhereOrNull(
(e) => e.displayText == initialData!.type,
);
selectedScope.value = CompanyScope.values.firstWhereOrNull(
(e) => e.displayText == initialData!.scale,
);
} else {
isEditMode.value = false;
pageTitle.value = '新增企业';
@ -49,36 +64,74 @@ class EnterpriseFormController extends GetxController {
//
void submitForm() {
if (formKey.currentState?.validate() ?? false) {
if (isEditMode.value) {
_updateEnterprise();
} else {
_createEnterprise();
}
}
}
void _createEnterprise() async {
final enterprise = Enterprise(
id: Uuid().v4(),
try {
isSubmitting.value = true; //
await addEnterpriseUsecase(
name: enterpriseNameController.text,
type: selectedType.value!.displayText,
address: enterpriseAddressController.text,
scale: selectedScope.value!.displayText,
scale: selectedScope.value == null
? ""
: selectedScope.value!.displayText,
contactPerson: contactPersonController.text,
contactPhone: contactPhoneController.text,
majorHazardsDescription: hazardSourceController.text,
lastModifiedTime: DateTime.now(),
creationTime: DateTime.now(),
syncStatus: SyncStatus.pendingCreate,
);
await usecase.call(enterprise);
Get.back(result: true); //
// []
Get.snackbar('成功', '企业信息已保存');
} catch (e) {
// []
Get.snackbar('错误', '保存失败,请稍后重试');
} finally {
isSubmitting.value = false; //
}
}
void _updateEnterprise() {
print('执行修改逻辑, ID: ${initialData!.id}');
// API
// Get.find<ApiService>().updateEnterprise(initialData!.id, ...);
Get.back(result: true); //
void _updateEnterprise() async {
// initialData null
if (initialData == null) return;
try {
// 1. UI ()
isSubmitting.value = true;
// 2. UI
// 使 Usecase copyWith 便
// id, creationTime
final updatedEnterprise = initialData!.copyWith(
name: enterpriseNameController.text,
type: selectedType.value!.displayText,
address: enterpriseAddressController.text,
scale: selectedScope.value?.displayText,
contactPerson: contactPersonController.text,
contactPhone: contactPhoneController.text,
majorHazardsDescription: hazardSourceController.text,
);
// 3. EditEnterpriseUsecase
await editEnterpriseUsecase(updatedEnterprise);
// 4.
Get.back(result: true);
Get.snackbar('成功', '企业信息已更新');
} catch (e) {
// 5.
Get.snackbar('错误', '更新失败,请稍后重试');
} finally {
// 6. UI
isSubmitting.value = false;
}
}
@override

80
lib/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart

@ -1,14 +1,27 @@
// lib/app/modules/enterprise_list/enterprise_list_controller.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/data/model/enterprise_model.dart';
import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/core/routes/app_routes.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart';
class EnterpriseListController extends GetxController {
// 使 .obs 使UI会自动更新
final enterpriseList = <Enterprise>[].obs;
// --- ---
var isLoading = false.obs;
var enterpriseList = <EnterpriseListItem>[].obs;
// --- ---
final nameController = TextEditingController();
final Rx<CompanyType?> selectedType = Rx<CompanyType?>(null);
final Rx<DateTime?> startDate = Rx<DateTime?>(null);
final Rx<DateTime?> endDate = Rx<DateTime?>(null);
final GetEnterpriseListUsecase getEnterpriseListUsecase;
EnterpriseListController({required this.getEnterpriseListUsecase});
@override
void onInit() {
@ -16,35 +29,46 @@ class EnterpriseListController extends GetxController {
fetchEnterprises(); //
}
///
Future<void> fetchEnterprises() async {
try {
isLoading.value = true;
final result = await getEnterpriseListUsecase.call(
name: nameController.text,
type: selectedType.value?.displayText,
startDate: startDate.value,
endDate: endDate.value,
);
enterpriseList.assignAll(result);
} catch (e) {
Get.snackbar('错误', '加载企业列表失败: $e');
} finally {
isLoading.value = false;
}
}
/// UI
void search() {
fetchEnterprises();
}
void refreshList() {
fetchEnterprises(); //
fetchEnterprises();
}
// API获取数据的过程
void fetchEnterprises() {
//
Future.delayed(const Duration(milliseconds: 500), () {
// //
// var mockData = [
// Enterprise(
// id: "1",
// name: '企业A',
// type: '类型1',
// address: '地址A',
// contactPerson: '联系人A',
// contactPhone: '123456',
// lastModifiedTime: DateTime.now(),
// creationTime: DateTime.now(),
// syncStatus: SyncStatus.synced,
// ),
// ];
// enterpriseList.assignAll(mockData); //
});
///
void clearFilters() {
nameController.clear();
selectedType.value = null;
startDate.value = null;
endDate.value = null;
fetchEnterprises();
}
//
void onSearch() {
//
@override
void onClose() {
nameController.dispose();
super.onClose();
}
///

38
lib/app/features/enterprise/presentation/pages/enterprise_form_page.dart

@ -15,11 +15,16 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
backgroundColor: const Color(0xFFF5F5F5),
appBar: _buildAppBar(),
body: SafeArea(
child: Form(
key: controller.formKey,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
child: Column(
children: [
buildTitleSection(title: '必填信息'),
@ -30,6 +35,12 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
label: '企业名称',
hint: '达拉特旗',
textController: controller.enterpriseNameController,
validator: (value) {
if (value == null || value.isEmpty) {
return '企业名称不能为空';
}
return null;
},
isRequired: true,
),
_buildCompanyTypeDropdown(),
@ -73,6 +84,7 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
],
),
),
),
);
}
@ -149,6 +161,7 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
required TextEditingController textController,
bool isRequired = false,
bool hasDivider = true,
String? Function(String?)? validator,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -159,6 +172,7 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
),
TextFormField(
controller: textController,
validator: validator,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
@ -187,6 +201,12 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
Obx(
() => DropdownButtonFormField<CompanyType>(
initialValue: controller.selectedType.value,
validator: (value) {
if (value == null) {
return '请选择企业类型';
}
return null;
},
hint: Text(
'请选择企业类型',
style: TextStyle(color: Colors.grey[400], fontSize: 14.sp),
@ -302,8 +322,11 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
),
SizedBox(width: 16.w),
Expanded(
child: ElevatedButton(
onPressed: controller.submitForm,
child: Obx(() {
return ElevatedButton(
onPressed: controller.isSubmitting.value
? null
: controller.submitForm,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2B8CFF),
padding: EdgeInsets.symmetric(vertical: 12.h),
@ -312,11 +335,16 @@ class EnterpriseFormPage extends GetView<EnterpriseFormController> {
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text(
child: controller.isSubmitting.value
? const SizedBox(
child: CircularProgressIndicator(color: Colors.white),
)
: Text(
'确定',
style: TextStyle(fontSize: 16.sp, color: Colors.white),
),
),
);
}),
),
],
),

217
lib/app/features/enterprise/presentation/pages/enterprise_list_page.dart

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart';
import 'package:problem_check_system/app/core/models/company_enum.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/widgets/enterprise_card.dart';
import '../controllers/enterprise_list_controller.dart';
@ -39,94 +41,189 @@ class EnterpriseListPage extends GetView<EnterpriseListController> {
controller.navigateToAddForm();
},
),
IconButton(
icon: Icon(Icons.upload, color: Colors.pink[300]), // 使 .sp
onPressed: () {
controller.navigateToUploadPage();
},
),
// IconButton(
// icon: Icon(Icons.upload, color: Colors.pink[300]), // 使 .sp
// onPressed: () {
// controller.navigateToUploadPage();
// },
// ),
],
),
body: Stack(
children: [
Column(
children: [
_buildFilterBar(),
// 使 Obx Widget
Expanded(
child: Obx(() {
if (controller.enterpriseList.isEmpty) {
return const Center(child: CircularProgressIndicator());
_buildFilterSection(),
const Divider(height: 1, thickness: .1),
Expanded(child: _buildEnterpriseList()),
],
),
],
),
);
}
return ListView.builder(
padding: EdgeInsets.symmetric(
horizontal: 16.w, // 使 .w
vertical: 8.h, // 使 .h
/// []
Widget _buildFilterSection() {
return ExpansionTile(
title: const Text('筛选查询'),
leading: const Icon(Icons.filter_alt_outlined),
initiallyExpanded: false, //
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 1.h),
child: Column(
children: [
// 1.
TextFormField(
controller: controller.nameController,
decoration: InputDecoration(
labelText: '企业名称',
hintText: '请输入关键词',
prefixIcon: const Icon(Icons.business_center),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
isDense: true,
),
itemCount: controller.enterpriseList.length,
itemBuilder: (context, index) {
final enterprise = controller.enterpriseList[index];
return Padding(
padding: EdgeInsets.only(bottom: 12.h), // 使 .h
// child: _EnterpriseCard(enterprise: enterprise),
child: EnterpriseCard(
enterprise: enterprise,
onEdit: () {
controller.navigateToEditForm(enterprise);
},
onViewProblems: () {
controller.navigateToEnterpriseInfoPage();
},
),
SizedBox(height: 12.h),
// 2.
Obx(
() => DropdownButtonFormField<CompanyType>(
initialValue: controller.selectedType.value,
decoration: InputDecoration(
labelText: '企业类型',
prefixIcon: const Icon(Icons.category),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
isDense: true,
),
hint: const Text('请选择企业类型'),
isExpanded: true,
items: CompanyType.values.map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.displayText),
);
}).toList(),
onChanged: (value) {
controller.selectedType.value = value;
},
);
}),
),
],
),
SizedBox(height: 12.h),
// 3.
Row(
children: [
Expanded(
child: _buildDatePickerField('开始日期', controller.startDate),
),
SizedBox(width: 12.w),
Expanded(
child: _buildDatePickerField('结束日期', controller.endDate),
),
],
),
);
}
SizedBox(height: 16.h),
Widget _buildFilterBar() {
return Container(
padding: EdgeInsets.fromLTRB(16.w, 12.h, 16.w, 12.h), // 使 .w .h
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 4.
Row(
children: [
Expanded(
child: Row(
children: [
const Text('企业名称', style: TextStyle(color: Colors.black54)),
SizedBox(width: 4.w), // 使 .w
Icon(
Icons.search,
size: 20.sp,
color: Colors.black54,
), // 使 .sp
child: OutlinedButton.icon(
onPressed: controller.clearFilters,
icon: const Icon(Icons.refresh),
label: const Text('重置'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 12.h),
),
),
),
SizedBox(width: 16.w),
Expanded(
child: ElevatedButton.icon(
onPressed: controller.search,
icon: const Icon(Icons.search),
label: const Text('查询'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 12.h),
),
),
),
],
),
],
),
),
_buildDropdown('选择日期'),
_buildDropdown('近一周'),
_buildDropdown('类型'),
],
);
}
/// []
Widget _buildDatePickerField(String label, Rx<DateTime?> date) {
return InkWell(
onTap: () async {
final pickedDate = await showDatePicker(
context: Get.context!,
initialDate: date.value ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2101),
);
if (pickedDate != null) {
date.value = pickedDate;
}
},
child: InputDecorator(
decoration: InputDecoration(
labelText: label,
prefixIcon: const Icon(Icons.calendar_today),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
isDense: true,
),
child: Obx(
() => Text(
date.value == null ? '请选择日期' : date.value!.toDateTimeString(),
style: TextStyle(
color: date.value == null ? Colors.grey[600] : Colors.black87,
),
),
),
),
);
}
Widget _buildDropdown(String text) {
Widget _buildEnterpriseList() {
return Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
padding: EdgeInsets.symmetric(
horizontal: 16.w, // 使 .w
vertical: 8.h, // 使 .h
),
itemCount: controller.enterpriseList.length,
itemBuilder: (context, index) {
final item = controller.enterpriseList[index];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w), // 使 .w
child: Row(
children: [
Text(text, style: const TextStyle(color: Colors.black54)),
const Icon(Icons.arrow_drop_down, color: Colors.black54),
],
padding: EdgeInsets.only(bottom: 12.h), // 使 .h
// child: _EnterpriseCard(enterprise: enterprise),
child: EnterpriseCard(
enterpriseListItem: item,
onEdit: () {
controller.navigateToEditForm(item.enterprise);
},
onViewProblems: () {
controller.navigateToEnterpriseInfoPage();
},
),
);
},
);
});
}
}

25
lib/app/features/enterprise/presentation/widgets/enterprise_card.dart

@ -1,16 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise.dart';
import 'package:intl/intl.dart';
import 'package:problem_check_system/app/core/extensions/datetime_extension.dart';
import 'package:problem_check_system/app/core/models/sync_status.dart';
import 'package:problem_check_system/app/features/enterprise/domain/entities/enterprise_list_item.dart';
//
class EnterpriseCard extends StatelessWidget {
final Enterprise enterprise;
final EnterpriseListItem enterpriseListItem;
final VoidCallback onEdit;
final VoidCallback onViewProblems;
const EnterpriseCard({
super.key,
required this.enterprise,
required this.enterpriseListItem,
required this.onEdit,
required this.onViewProblems,
});
@ -133,7 +136,7 @@ class EnterpriseCard extends StatelessWidget {
),
SizedBox(height: 4.h), // .h
Text(
enterprise.name,
enterpriseListItem.enterprise.name,
style: TextStyle(
fontSize: 12.5.sp,
fontWeight: FontWeight.w500,
@ -153,7 +156,7 @@ class EnterpriseCard extends StatelessWidget {
),
SizedBox(height: 4.h),
Text(
enterprise.type,
enterpriseListItem.enterprise.type,
style: TextStyle(
fontSize: 12.5.sp,
fontWeight: FontWeight.w500,
@ -171,7 +174,9 @@ class EnterpriseCard extends StatelessWidget {
border: Border.all(color: Colors.red.shade400, width: 1.w),
),
child: Text(
'信息未上传',
enterpriseListItem.enterprise.syncStatus == SyncStatus.synced
? '信息已上传'
: '信息未上传',
style: TextStyle(fontSize: 7.sp, color: Colors.red.shade400),
),
),
@ -194,7 +199,7 @@ class EnterpriseCard extends StatelessWidget {
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
Text(
"111", // enterprise.totalIssues.toString(),
enterpriseListItem.totalProblems.toString(),
style: TextStyle(
fontSize: 12.5.sp,
color: Colors.black87,
@ -208,7 +213,7 @@ class EnterpriseCard extends StatelessWidget {
children: [
Icon(Icons.access_time, color: Colors.grey, size: 16.sp),
Text(
'创建时间: ${enterprise.creationTime}',
'创建时间: ${enterpriseListItem.enterprise.creationTime.toDateTimeString()}',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
@ -222,13 +227,13 @@ class EnterpriseCard extends StatelessWidget {
return Row(
children: [
_buildTag(
text: '已上传 ${enterprise.id}',
text: '已上传 ${enterpriseListItem.uploadedProblems}',
textColor: Colors.blue.shade700,
backgroundColor: Colors.blue.shade50,
),
SizedBox(width: 8.w),
_buildTag(
text: '未上传 ${enterprise.id}',
text: '未上传 ${enterpriseListItem.pendingProblems}',
textColor: Colors.red.shade600,
backgroundColor: Colors.red.shade50,
),

3
lib/app/features/home/bindings/home_binding.dart

@ -2,7 +2,6 @@ import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart';
import 'package:problem_check_system/app/features/home/controllers/home_controller.dart';
import 'package:problem_check_system/app/features/my/controllers/my_controller.dart';
import 'package:problem_check_system/app/features/problem/presentation/controllers/problem_controller.dart';
@ -13,7 +12,7 @@ class HomeBinding implements Bindings {
///
Get.lazyPut<HomeController>(() => HomeController());
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find()));
Get.lazyPut<EnterpriseListController>(() => EnterpriseListController());
// Get.lazyPut<EnterpriseListController>(() => EnterpriseListController());
///
Get.lazyPut<ProblemController>(

28
lib/app/features/navigation/presentation/bindings/navigation_binding.dart

@ -1,7 +1,13 @@
import 'package:get/get.dart';
import 'package:problem_check_system/app/core/models/problem_sync_status.dart';
import 'package:problem_check_system/app/core/repositories/auth_repository.dart';
import 'package:problem_check_system/app/core/services/database_service.dart';
import 'package:problem_check_system/app/core/services/network_status_service.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_local_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/datasources/enterprise_remote_data_source.dart';
import 'package:problem_check_system/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart';
import 'package:problem_check_system/app/features/enterprise/domain/repositories/enterprise_repository.dart';
import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart';
import 'package:problem_check_system/app/features/problem/data/repositories/problem_repository.dart';
import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_list_controller.dart';
import 'package:problem_check_system/app/features/navigation/presentation/controllers/navigation_controller.dart';
@ -17,8 +23,28 @@ class NavigationBinding extends Bindings {
networkStatusService: Get.find<NetworkStatusService>(),
),
);
Get.put<EnterpriseLocalDataSource>(
EnterpriseLocalDataSourceImpl(
databaseService: Get.find<DatabaseService>(),
),
);
Get.put<EnterpriseRemoteDataSource>(EnterpriseRemoteDataSourceImpl());
Get.put<EnterpriseRepository>(
EnterpriseRepositoryImpl(
localDataSource: Get.find<EnterpriseLocalDataSource>(),
remoteDataSource: Get.find<EnterpriseRemoteDataSource>(),
),
);
Get.put<GetEnterpriseListUsecase>(
GetEnterpriseListUsecase(repository: Get.find<EnterpriseRepository>()),
);
Get.lazyPut<EnterpriseListController>(
() => EnterpriseListController(
getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(),
),
);
Get.put(ProblemStateManager(uuid: Get.find(), authRepository: Get.find()));
Get.lazyPut<EnterpriseListController>(() => EnterpriseListController());
///
Get.lazyPut<ProblemController>(

1
lib/app/features/navigation/presentation/pages/navigation_page.dart

@ -47,6 +47,7 @@ class NavigationPage extends GetView<NavigationController> {
},
child: FloatingActionButton(
heroTag: "btn_upload",
shape: const CircleBorder(),
onPressed: isOnline
? () => controller.handleFabUploadTap()
: null,

Loading…
Cancel
Save