Browse Source

feat : 企业信息上传页

dev
徐振升 2 weeks ago
parent
commit
31aea56732
  1. 6
      lib/app/core/pages/widgets/upload_app_bar.dart
  2. 15
      lib/app/features/enterprise/data/datasources/enterprise_local_data_source.dart
  3. 10
      lib/app/features/enterprise/data/repositories_impl/enterprise_repository_impl.dart
  4. 4
      lib/app/features/enterprise/domain/repositories/enterprise_repository.dart
  5. 2
      lib/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart
  6. 10
      lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart
  7. 29
      lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart
  8. 51
      lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
  9. 91
      lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart
  10. 2
      lib/app/features/problem/presentation/views/problem_upload_page.dart
  11. 6
      lib/main.dart

6
lib/app/core/pages/widgets/upload_app_bar.dart

@ -6,13 +6,13 @@ import 'package:get/get.dart';
class UploadAppBar extends StatelessWidget implements PreferredSizeWidget { class UploadAppBar extends StatelessWidget implements PreferredSizeWidget {
const UploadAppBar({ const UploadAppBar({
super.key, super.key,
required this.selectedAll, required this.allSelected,
required this.selectedCount, required this.selectedCount,
required this.buttonVisible, required this.buttonVisible,
this.onButtonPressed, this.onButtonPressed,
}); });
final int selectedCount; final int selectedCount;
final bool selectedAll; final bool allSelected;
final bool buttonVisible; final bool buttonVisible;
final VoidCallback? onButtonPressed; final VoidCallback? onButtonPressed;
@ -50,7 +50,7 @@ class UploadAppBar extends StatelessWidget implements PreferredSizeWidget {
TextButton( TextButton(
onPressed: buttonVisible ? onButtonPressed : null, onPressed: buttonVisible ? onButtonPressed : null,
child: Text( child: Text(
selectedAll ? "全选" : "取消全选", allSelected ? "取消全选" : "全选",
style: TextStyle( style: TextStyle(
color: buttonVisible ? Colors.white : Colors.grey[300], color: buttonVisible ? Colors.white : Colors.grey[300],
fontSize: 15.sp, fontSize: 15.sp,

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

@ -13,6 +13,7 @@ abstract class EnterpriseLocalDataSource {
String? type, String? type,
DateTime? startDate, DateTime? startDate,
DateTime? endDate, DateTime? endDate,
bool? isUploaded,
}); });
Future<List<EnterpriseModel>> getUnsyncedEnterprises(); Future<List<EnterpriseModel>> getUnsyncedEnterprises();
@ -59,11 +60,12 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
String? type, String? type,
DateTime? startDate, DateTime? startDate,
DateTime? endDate, DateTime? endDate,
bool? isUploaded,
}) async { }) async {
final db = await _databaseService.database; final db = await _databaseService.database;
// pendingCreate 0 // pendingCreate 0
final int pendingCreateIndex = SyncStatus.pendingCreate.index; final int syncedIndex = SyncStatus.synced.index;
// WHERE // WHERE
final List<String> whereClauses = []; final List<String> whereClauses = [];
@ -101,7 +103,12 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
whereClauses.add('e.creationTime <= ?'); whereClauses.add('e.creationTime <= ?');
whereArgs.add(endOfDay.millisecondsSinceEpoch); whereArgs.add(endOfDay.millisecondsSinceEpoch);
} }
// --- --- // 5.
if (isUploaded != null) {
final operator = isUploaded ? '=' : '!=';
whereClauses.add('e.syncStatus $operator ?');
whereArgs.add(syncedIndex);
}
// WHERE // WHERE
final String whereString = whereClauses.isNotEmpty final String whereString = whereClauses.isNotEmpty
@ -114,8 +121,8 @@ class EnterpriseLocalDataSourceImpl implements EnterpriseLocalDataSource {
SELECT SELECT
e.*, e.*,
COUNT(p.id) AS totalProblems, COUNT(p.id) AS totalProblems,
COUNT(CASE WHEN p.syncStatus != $pendingCreateIndex THEN 1 ELSE NULL END) AS uploadedProblems, COUNT(CASE WHEN p.syncStatus == $syncedIndex THEN 1 ELSE NULL END) AS uploadedProblems,
COUNT(CASE WHEN p.syncStatus == $pendingCreateIndex THEN 1 ELSE NULL END) AS pendingProblems COUNT(CASE WHEN p.syncStatus != $syncedIndex THEN 1 ELSE NULL END) AS pendingProblems
FROM FROM
$_tableName e $_tableName e
LEFT JOIN LEFT JOIN

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

@ -1,3 +1,4 @@
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_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/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/data/model/enterprise_model.dart';
@ -53,6 +54,7 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
String? type, String? type,
DateTime? startDate, DateTime? startDate,
DateTime? endDate, DateTime? endDate,
bool? isUploaded,
}) async { }) async {
// DataSource // DataSource
final models = await localDataSource.getEnterpriseListItems( final models = await localDataSource.getEnterpriseListItems(
@ -60,6 +62,7 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
type: type, type: type,
startDate: startDate, startDate: startDate,
endDate: endDate, endDate: endDate,
isUploaded: isUploaded,
); );
// EnterpriseListItemModel EnterpriseListItem // EnterpriseListItemModel EnterpriseListItem
return models; return models;
@ -72,11 +75,4 @@ class EnterpriseRepositoryImpl implements EnterpriseRepository {
// //
await localDataSource.updateEnterprise(enterpriseModel); await localDataSource.updateEnterprise(enterpriseModel);
} }
@override
Future<List<Enterprise>> getUnsyncedEnterprises() async {
final List<EnterpriseModel> enterpriseModels = await localDataSource
.getUnsyncedEnterprises();
return enterpriseModels.map((model) => model.toEntity()).toList();
}
} }

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

@ -16,8 +16,6 @@ abstract class EnterpriseRepository implements SyncableRepository<Enterprise> {
String? type, String? type,
DateTime? startDate, DateTime? startDate,
DateTime? endDate, DateTime? endDate,
bool? isUploaded,
}); });
///
Future<List<Enterprise>> getUnsyncedEnterprises();
} }

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

@ -11,12 +11,14 @@ class GetEnterpriseListUsecase {
String? type, String? type,
DateTime? startDate, DateTime? startDate,
DateTime? endDate, DateTime? endDate,
bool? isUploaded,
}) async { }) async {
return await repository.getEnterpriseListItems( return await repository.getEnterpriseListItems(
name: name, name: name,
type: type, type: type,
startDate: startDate, startDate: startDate,
endDate: endDate, endDate: endDate,
isUploaded: isUploaded,
); );
} }
} }

10
lib/app/features/enterprise/domain/usecases/get_unsynced_enterprises_usecase.dart

@ -1,10 +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 GetUnsyncedEnterprisesUsecase {
final EnterpriseRepository enterpriseRepository;
const GetUnsyncedEnterprisesUsecase({required this.enterpriseRepository});
Future<List<Enterprise>> call() async {
return await enterpriseRepository.getUnsyncedEnterprises();
}
}

29
lib/app/features/enterprise/presentation/bindings/enterprise_upload_binding.dart

@ -1,9 +1,36 @@
import 'package:get/get.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/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/enterprise/presentation/controllers/enterprise_upload_controller.dart'; import 'package:problem_check_system/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart';
class EnterpriseUploadBinding extends Bindings { class EnterpriseUploadBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.lazyPut<EnterpriseUploadController>(() => EnterpriseUploadController()); 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.lazyPut(
() => GetEnterpriseListUsecase(
repository: Get.find<EnterpriseRepository>(),
),
);
Get.lazyPut<EnterpriseUploadController>(
() => EnterpriseUploadController(
getEnterpriseListUsecase: Get.find<GetEnterpriseListUsecase>(),
),
);
} }
} }

51
lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart

@ -1,31 +1,21 @@
// lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart // lib/app/features/enterprise/presentation/controllers/enterprise_upload_controller.dart
import 'package:get/get.dart'; import 'package:get/get.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/entities/enterprise_list_item.dart';
// Usecase import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_enterprise_list_usecase.dart';
// import 'package:problem_check_system/app/features/enterprise/domain/usecases/get_pending_uploads_usecase.dart';
/// -----------------------------------------------------------------------------
/// []
/// -----------------------------------------------------------------------------
///
/// `BaseEnterpriseListController`
///
/// **:**
/// - ****
/// - (`selectedEnterprises`)
/// -
/// -
///
class EnterpriseUploadController extends GetxController { class EnterpriseUploadController extends GetxController {
// final GetPendingUploadsUsecase getPendingUploadsUsecase; final GetEnterpriseListUsecase getEnterpriseListUsecase;
// EnterpriseUploadController({required this.getPendingUploadsUsecase}); EnterpriseUploadController({required this.getEnterpriseListUsecase});
// --- --- // --- ---
final enterpriseList = <EnterpriseListItem>[].obs;
final isLoading = false.obs; final isLoading = false.obs;
final selectedEnterprises = <Enterprise>{}.obs; // 使 Set final enterprises = <EnterpriseListItem>[].obs;
final selectedEnterprises = <EnterpriseListItem>{}.obs;
bool get allSelected =>
enterprises.isNotEmpty &&
enterprises.length == selectedEnterprises.length;
@override @override
void onInit() { void onInit() {
@ -33,13 +23,7 @@ class EnterpriseUploadController extends GetxController {
fetchPendingUploads(); fetchPendingUploads();
} }
void onItemTap(EnterpriseListItem item) { void onSelectionChanged(EnterpriseListItem enterprise) {
//
onSelectionChanged(item.enterprise);
}
void onSelectionChanged(Enterprise enterprise) {
//
if (selectedEnterprises.contains(enterprise)) { if (selectedEnterprises.contains(enterprise)) {
selectedEnterprises.remove(enterprise); selectedEnterprises.remove(enterprise);
} else { } else {
@ -47,14 +31,21 @@ class EnterpriseUploadController extends GetxController {
} }
} }
void toggleSelectAll() {
if (allSelected) {
selectedEnterprises.clear();
} else {
//
selectedEnterprises.addAll(enterprises);
}
}
/// ///
Future<void> fetchPendingUploads() async { Future<void> fetchPendingUploads() async {
isLoading.value = true; isLoading.value = true;
try { try {
// --- 使 --- final data = await getEnterpriseListUsecase();
await Future.delayed(const Duration(seconds: 1)); enterprises.assignAll(data);
// final mockData = createMockEnterpriseListItems();
// enterpriseList.assignAll(mockData);
} catch (e) { } catch (e) {
Get.snackbar('错误', '加载待上传列表失败: $e'); Get.snackbar('错误', '加载待上传列表失败: $e');
} finally { } finally {

91
lib/app/features/enterprise/presentation/pages/enterprise_upload_page.dart

@ -12,30 +12,39 @@ class EnterpriseUploadPage extends GetView<EnterpriseUploadController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: UploadAppBar( appBar: PreferredSize(
selectedCount: 10, preferredSize: const Size.fromHeight(kToolbarHeight),
selectedAll: true, child: Obx(
buttonVisible: false, () => UploadAppBar(
onButtonPressed: () {}, selectedCount: controller.selectedEnterprises.length,
allSelected: controller.allSelected,
buttonVisible: controller.enterprises.isNotEmpty,
onButtonPressed: () => controller.toggleSelectAll(),
),
),
), ),
body: _buildEnterpriseList(), body: _buildEnterpriseList(),
bottomSheet: Container( bottomNavigationBar: Container(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey.shade300)), border: Border(top: BorderSide(color: Colors.grey.shade300)),
), ),
child: ElevatedButton( child: Obx(
onPressed: null, () => ElevatedButton(
style: ElevatedButton.styleFrom( onPressed: controller.selectedEnterprises.isNotEmpty
backgroundColor: Colors.blue, ? controller.confirmUpload
foregroundColor: Colors.white, : null,
shape: RoundedRectangleBorder( style: ElevatedButton.styleFrom(
borderRadius: BorderRadius.circular(8.r), backgroundColor: Colors.blue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
minimumSize: Size(double.infinity, 48.h),
), ),
minimumSize: Size(double.infinity, 48.h), child: Text('点击上传 (${controller.selectedEnterprises.length})'),
), ),
child: Text('点击上传 (1)'),
), ),
), ),
); );
@ -45,11 +54,11 @@ class EnterpriseUploadPage extends GetView<EnterpriseUploadController> {
// 使 Obx controller Rx // 使 Obx controller Rx
return Obx(() { return Obx(() {
// //
if (controller.isLoading.value && controller.enterpriseList.isEmpty) { if (controller.isLoading.value && controller.enterprises.isEmpty) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
// //
if (controller.enterpriseList.isEmpty) { if (controller.enterprises.isEmpty) {
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -74,34 +83,36 @@ class EnterpriseUploadPage extends GetView<EnterpriseUploadController> {
onRefresh: () async => controller.fetchPendingUploads(), onRefresh: () async => controller.fetchPendingUploads(),
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
itemCount: controller.enterpriseList.length, itemCount: controller.enterprises.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = controller.enterpriseList[index]; final item = controller.enterprises[index];
final enterprise = item.enterprise;
// : base controller // : base controller
final isSelected = controller.selectedEnterprises.contains(
enterprise,
);
return Padding( return Obx(() {
padding: EdgeInsets.only(bottom: 12.h), final isSelected = controller.selectedEnterprises.contains(item);
child: UnifiedEnterpriseCard(
enterpriseListItem: item, return Padding(
isSelected: isSelected, padding: EdgeInsets.only(bottom: 12.h),
// itemMode mode child: UnifiedEnterpriseCard(
// --- [] controller --- enterpriseListItem: item,
onTap: () => controller.onItemTap(item), isSelected: isSelected,
actions: Padding( // itemMode mode
// Checkbox // --- [] controller ---
padding: EdgeInsets.only(right: 8.w), onTap: () => controller.onSelectionChanged(item),
child: Checkbox( actions: Padding(
value: isSelected, // Checkbox
onChanged: (value) {}, padding: EdgeInsets.only(right: 8.w),
// controller.toggleSelection(enterprise.id), child: Checkbox(
value: isSelected,
activeColor: Theme.of(context).primaryColor,
onChanged: (value) {
controller.onSelectionChanged(item);
},
),
), ),
), ),
), );
); });
}, },
), ),
); );

2
lib/app/features/problem/presentation/views/problem_upload_page.dart

@ -14,7 +14,7 @@ class ProblemUploadPage extends GetView<ProblemController> {
return Scaffold( return Scaffold(
appBar: UploadAppBar( appBar: UploadAppBar(
selectedCount: controller.selectedCount, selectedCount: controller.selectedCount,
selectedAll: controller.allSelected.value, allSelected: controller.allSelected.value,
buttonVisible: controller.unUploadedProblems.isNotEmpty, buttonVisible: controller.unUploadedProblems.isNotEmpty,
onButtonPressed: controller.selectAll, onButtonPressed: controller.selectAll,
), ),

6
lib/main.dart

@ -55,10 +55,8 @@ class MainApp extends StatelessWidget {
useMaterial3: true, useMaterial3: true,
// 使 colorSchemeSeed M3 // 使 colorSchemeSeed M3
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue),
seedColor: const Color(0xFF3B82F6), primaryColor: Colors.lightBlue,
),
// () NavigationBar // () NavigationBar
navigationBarTheme: NavigationBarThemeData( navigationBarTheme: NavigationBarThemeData(
// 1. // 1.

Loading…
Cancel
Save