。。。

master
hs 2026-02-28 14:38:07 +08:00
parent 256a8bec1a
commit e9644fea24
51 changed files with 2874 additions and 438 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 84 KiB

BIN
assets/images/ico_quit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assets/study/bgimg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
assets/study/copy-one.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
assets/study/err.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/study/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
assets/study/right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/study/time.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -70,8 +70,6 @@ PODS:
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- nfc_manager (0.0.1):
- Flutter
- objective_c (0.0.1):
- Flutter
- open_file_ios (0.0.1):
@ -120,7 +118,6 @@ DEPENDENCIES:
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- nfc_manager (from `.symlinks/plugins/nfc_manager/ios`)
- objective_c (from `.symlinks/plugins/objective_c/ios`)
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
@ -171,8 +168,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
nfc_manager:
:path: ".symlinks/plugins/nfc_manager/ios"
objective_c:
:path: ".symlinks/plugins/objective_c/ios"
open_file_ios:
@ -216,7 +211,6 @@ SPEC CHECKSUMS:
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
nfc_manager: f6d5609c09b4640b914a3dc67479a2e392965fd0
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499

View File

@ -491,13 +491,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 62;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -507,7 +508,7 @@
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq-des";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -686,13 +687,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 62;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -702,7 +704,7 @@
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq-des";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -718,13 +720,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 62;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "秦港安全";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -734,7 +737,7 @@
PRODUCT_BUNDLE_IDENTIFIER = uni.UNI85F7A17;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq-des";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "qa-zsaq";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@ -33,7 +33,10 @@ enum UploadFileType {
hiddenDangerRectificationPlan('8', 'hidden_danger_rectification_plan'),
/// - : '9', : 'confined_space_confirmation_signature'
confinedSpaceConfirmationSignature('9', 'confined_space_confirmation_signature'),
confinedSpaceConfirmationSignature(
'9',
'confined_space_confirmation_signature',
),
/// - : '10', : 'labor_contract_image'
laborContractImage('10', 'labor_contract_image'),
@ -54,7 +57,10 @@ enum UploadFileType {
socialSecurityCardPhoto('15', 'social_security_card_photo'),
/// - : '16', : 'work_related_injury_insurance_certificate'
workRelatedInjuryInsuranceCertificate('16', 'work_related_injury_insurance_certificate'),
workRelatedInjuryInsuranceCertificate(
'16',
'work_related_injury_insurance_certificate',
),
/// - : '17', : 'special_equipment_inspection_photo'
specialEquipmentInspectionPhoto('17', 'special_equipment_inspection_photo'),
@ -63,25 +69,46 @@ enum UploadFileType {
personnelCertificate('18', 'personnel_certificate'),
/// - : '19', : 'three_level_safety_education_training'
threeLevelSafetyEducationTraining('19', 'three_level_safety_education_training'),
threeLevelSafetyEducationTraining(
'19',
'three_level_safety_education_training',
),
/// - : '20', : 'major_hazard_source_alarm_before_handling_photo'
majorHazardSourceAlarmBeforeHandlingPhoto('20', 'major_hazard_source_alarm_before_handling_photo'),
majorHazardSourceAlarmBeforeHandlingPhoto(
'20',
'major_hazard_source_alarm_before_handling_photo',
),
/// - : '21', : 'major_hazard_source_alarm_after_handling_photo'
majorHazardSourceAlarmAfterHandlingPhoto('21', 'major_hazard_source_alarm_after_handling_photo'),
majorHazardSourceAlarmAfterHandlingPhoto(
'21',
'major_hazard_source_alarm_after_handling_photo',
),
/// - : '22', : 'smart_access_control_external_vehicle_driver_license_photo'
smartAccessControlExternalVehicleDriverLicensePhoto('22', 'smart_access_control_external_vehicle_driver_license_photo'),
smartAccessControlExternalVehicleDriverLicensePhoto(
'22',
'smart_access_control_external_vehicle_driver_license_photo',
),
/// - : '23', : 'smart_access_control_external_vehicle_registration_photo'
smartAccessControlExternalVehicleRegistrationPhoto('23', 'smart_access_control_external_vehicle_registration_photo'),
smartAccessControlExternalVehicleRegistrationPhoto(
'23',
'smart_access_control_external_vehicle_registration_photo',
),
/// - : '50', : 'safety_and_environmental_inspection_final_acceptance_image'
safetyAndEnvironmentalInspectionFinalAcceptanceImage('50', 'safety_and_environmental_inspection_final_acceptance_image'),
safetyAndEnvironmentalInspectionFinalAcceptanceImage(
'50',
'safety_and_environmental_inspection_final_acceptance_image',
),
/// - : '101', : 'hidden_danger_extension_temporary_measures_attachment'
hiddenDangerExtensionTemporaryMeasuresAttachment('101', 'hidden_danger_extension_temporary_measures_attachment'),
hiddenDangerExtensionTemporaryMeasuresAttachment(
'101',
'hidden_danger_extension_temporary_measures_attachment',
),
/// - : '102', : 'hidden_danger_video'
hiddenDangerVideo('102', 'hidden_danger_video'),
@ -96,28 +123,49 @@ enum UploadFileType {
rectificationSuggestionsAndPlan('107', 'rectification_suggestions_and_plan'),
/// - : '108', : 'major_hidden_danger_investigation_report'
majorHiddenDangerInvestigationReport('108', 'major_hidden_danger_investigation_report'),
majorHiddenDangerInvestigationReport(
'108',
'major_hidden_danger_investigation_report',
),
/// - : '109', : 'major_hidden_danger_safety_committee_or_party_committee_resolution_record'
majorHiddenDangerSafetyCommitteeOrPartyCommitteeResolutionRecord('109', 'major_hidden_danger_safety_committee_or_party_committee_resolution_record'),
majorHiddenDangerSafetyCommitteeOrPartyCommitteeResolutionRecord(
'109',
'major_hidden_danger_safety_committee_or_party_committee_resolution_record',
),
/// - - : '110', : 'significant_hidden_danger_rectification_temporary_disposal_measures'
significantHiddenDangerRectificationTemporaryDisposalMeasures('110', 'significant_hidden_danger_rectification_temporary_disposal_measures'),
significantHiddenDangerRectificationTemporaryDisposalMeasures(
'110',
'significant_hidden_danger_rectification_temporary_disposal_measures',
),
/// - - : '111', : 'significant_hidden_danger_rectification_hidden_danger_rectification_process_record'
significantHiddenDangerRectificationHiddenDangerRectificationProcessRecord('111', 'significant_hidden_danger_rectification_hidden_danger_rectification_process_record'),
significantHiddenDangerRectificationHiddenDangerRectificationProcessRecord(
'111',
'significant_hidden_danger_rectification_hidden_danger_rectification_process_record',
),
/// - : '112', : 'supplement_major_hidden_danger_information'
supplementMajorHiddenDangerInformation('112', 'supplement_major_hidden_danger_information'),
supplementMajorHiddenDangerInformation(
'112',
'supplement_major_hidden_danger_information',
),
/// - : '113', : 'safety_committee_office_meeting_record'
safetyCommitteeOfficeMeetingRecord('113', 'safety_committee_office_meeting_record'),
safetyCommitteeOfficeMeetingRecord(
'113',
'safety_committee_office_meeting_record',
),
/// - : '114', : 'alarm_photo'
alarmPhoto('114', 'alarm_photo'),
/// - : '115', : 'fire_equipment_type_qualification_photo'
fireEquipmentTypeQualificationPhoto('115', 'fire_equipment_type_qualification_photo'),
fireEquipmentTypeQualificationPhoto(
'115',
'fire_equipment_type_qualification_photo',
),
/// - : '116', : 'hot_work_personnel_photo'
hotWorkPersonnelPhoto('116', 'hot_work_personnel_photo'),
@ -126,43 +174,70 @@ enum UploadFileType {
safetyPledgeSignature('117', 'safety_pledge_signature'),
/// - : '118', : 'hot_work_unit_responsible_person_confirmation_signature'
hotWorkUnitResponsiblePersonConfirmationSignature('118', 'hot_work_unit_responsible_person_confirmation_signature'),
hotWorkUnitResponsiblePersonConfirmationSignature(
'118',
'hot_work_unit_responsible_person_confirmation_signature',
),
/// - : '119', : 'on_site_jurisdiction_unit_responsible_person_signature'
onSiteJurisdictionUnitResponsiblePersonSignature('119', 'on_site_jurisdiction_unit_responsible_person_signature'),
onSiteJurisdictionUnitResponsiblePersonSignature(
'119',
'on_site_jurisdiction_unit_responsible_person_signature',
),
/// - : '120', : 'hot_work_permit_issuing_unit_signature'
hotWorkPermitIssuingUnitSignature('120', 'hot_work_permit_issuing_unit_signature'),
hotWorkPermitIssuingUnitSignature(
'120',
'hot_work_permit_issuing_unit_signature',
),
/// - : '121', : 'hot_work_permit_signature'
hotWorkPermitSignature('121', 'hot_work_permit_signature'),
/// - : '122', : 'pre_hot_work_jurisdiction_unit_confirmation_signature'
preHotWorkJurisdictionUnitConfirmationSignature('122', 'pre_hot_work_jurisdiction_unit_confirmation_signature'),
preHotWorkJurisdictionUnitConfirmationSignature(
'122',
'pre_hot_work_jurisdiction_unit_confirmation_signature',
),
/// - : '123', : 'on_site_responsible_person_confirmation_signature'
onSiteResponsiblePersonConfirmationSignature('123', 'on_site_responsible_person_confirmation_signature'),
onSiteResponsiblePersonConfirmationSignature(
'123',
'on_site_responsible_person_confirmation_signature',
),
/// - : '124', : 'post_hot_work_site_jurisdiction_unit_confirmation'
postHotWorkSiteJurisdictionUnitConfirmation('124', 'post_hot_work_site_jurisdiction_unit_confirmation'),
postHotWorkSiteJurisdictionUnitConfirmation(
'124',
'post_hot_work_site_jurisdiction_unit_confirmation',
),
/// - : '125', : 'delayed_fire_monitoring_pictures'
delayedFireMonitoringPictures('125', 'delayed_fire_monitoring_pictures'),
/// - : '126', : 'safety_and_environmental_inspection_initiation_signature'
safetyAndEnvironmentalInspectionInitiationSignature('126', 'safety_and_environmental_inspection_initiation_signature'),
safetyAndEnvironmentalInspectionInitiationSignature(
'126',
'safety_and_environmental_inspection_initiation_signature',
),
/// - : '127', : 'inspector_confirmation_signature'
inspectorConfirmationSignature('127', 'inspector_confirmation_signature'),
/// - : '128', : 'inspected_person_confirmation_signature'
inspectedPersonConfirmationSignature('128', 'inspected_person_confirmation_signature'),
inspectedPersonConfirmationSignature(
'128',
'inspected_person_confirmation_signature',
),
/// - : '129', : 'safety_and_environmental_inspection_appeal_signature'
safetyAndEnvironmentalInspectionAppealSignature('129', 'safety_and_environmental_inspection_appeal_signature'),
// safetyAndEnvironmentalInspectionAppealSignature('129', 'safety_and_environmental_inspection_appeal_signature'),
/// - : '130', : 'hk_safety_committee_office_director_safety_director_issuance'
hkSafetyCommitteeOfficeDirectorSafetyDirectorIssuance('130', 'hk_safety_committee_office_director_safety_director_issuance'),
hkSafetyCommitteeOfficeDirectorSafetyDirectorIssuance(
'130',
'hk_safety_committee_office_director_safety_director_issuance',
),
/// - : '131', : 'hot_work_contracting_unit_signature'
hotWorkContractingUnitSignature('131', 'hot_work_contracting_unit_signature'),
@ -189,31 +264,55 @@ enum UploadFileType {
hiddenDisposalPlan('138', 'hidden_disposal_plan'),
/// - - : '139', : 'safety_environmental_inspection_inspection_signature'
safetyEnvironmentalInspectionInspectionSignature('139', 'safety_environmental_inspection_inspection_signature'),
safetyEnvironmentalInspectionInspectionSignature(
'139',
'safety_environmental_inspection_inspection_signature',
),
/// - - : '140', : 'safety_environmental_inspection_inspection_situation'
safetyEnvironmentalInspectionInspectionSituation('140', 'safety_environmental_inspection_inspection_situation'),
safetyEnvironmentalInspectionInspectionSituation(
'140',
'safety_environmental_inspection_inspection_situation',
),
/// - - : '141', : 'safety_environmental_inspection_inspector_signature'
safetyEnvironmentalInspectionInspectorSignature('141', 'safety_environmental_inspection_inspector_signature'),
safetyEnvironmentalInspectionInspectorSignature(
'141',
'safety_environmental_inspection_inspector_signature',
),
/// - - : '142', : 'safety_environmental_inspection_inspected_signature'
safetyEnvironmentalInspectionInspectedSignature('142', 'safety_environmental_inspection_inspected_signature'),
safetyEnvironmentalInspectionInspectedSignature(
'142',
'safety_environmental_inspection_inspected_signature',
),
/// - - : '143', : 'safety_environmental_inspection_inspected_file'
safetyEnvironmentalInspectionInspectedFile('143', 'safety_environmental_inspection_inspected_file'),
safetyEnvironmentalInspectionInspectedFile(
'143',
'safety_environmental_inspection_inspected_file',
),
/// - - : '144', : 'safety_environmental_inspection_defense_signature'
safetyEnvironmentalInspectionDefenseSignature('144', 'safety_environmental_inspection_defense_signature'),
safetyEnvironmentalInspectionDefenseSignature(
'144',
'safety_environmental_inspection_defense_signature',
),
/// - : '145', : 'qualified_list_inspection'
qualifiedListInspection('145', 'qualified_list_inspection'),
/// - - : '146', : 'safety_environmental_inspection_acceptance'
safetyEnvironmentalInspectionAcceptance('146', 'safety_environmental_inspection_acceptance'),
safetyEnvironmentalInspectionAcceptance(
'146',
'safety_environmental_inspection_acceptance',
),
/// - : '147', : 'hidden_qualified_listInspection_signature'
hiddenQualifiedListInspectionSignature('147', 'hidden_qualified_listInspection_signature'),
hiddenQualifiedListInspectionSignature(
'147',
'hidden_qualified_listInspection_signature',
),
/// - - : '148', : 'special_qualification'
specialQualification('148', 'special_qualification'),
@ -234,7 +333,10 @@ enum UploadFileType {
regulationsResourceLibrary('153', 'regulations_resource_library'),
/// - : '154', : 'responsibility_based_resource_library'
responsibilityBasedResourceLibrary('154', 'responsibility_based_resource_library'),
responsibilityBasedResourceLibrary(
'154',
'responsibility_based_resource_library',
),
/// - : '155', : 'institutional_resource_library'
institutionalResourceLibrary('155', 'institutional_resource_library'),
@ -246,7 +348,10 @@ enum UploadFileType {
supervisionPersonnelVehicle('157', 'supervision_personnel_vehicle'),
/// - : '158', : 'supervision_personnel_vehicle_license'
supervisionPersonnelVehicleLicense('158', 'supervision_personnel_vehicle_license'),
supervisionPersonnelVehicleLicense(
'158',
'supervision_personnel_vehicle_license',
),
/// - : '159', : 'special_operation_personnel_photo'
specialOperationPersonnelPhoto('159', 'special_operation_personnel_photo'),
@ -258,7 +363,25 @@ enum UploadFileType {
mainResponsiblePersonPhoto('161', 'main_responsible_person_photo'),
/// - : '162', : 'photos_safety_production_management_personnel'
photosSafetyProductionManagementPersonnel('162', 'photos_safety_production_management_personnel'),
photosSafetyProductionManagementPersonnel(
'162',
'photos_safety_production_management_personnel',
),
// - : '163', : 'education_and_training_application'
educationAndTrainingApplication('163', 'education_and_training_application'),
// - : '164', : 'cover_of_education_and_training_courses'
coverOfEducationAndTrainingCourses(
'164',
'cover_of_education_and_training_courses',
),
/// 线 - : '165', : 'online_learning_sign_signature'
onlineLearningSignSignature('165', 'online_learning_sign_signature'),
/// 线 - : '166', : 'online_learning_exam_signature'
onlineLearningExamSignature('166', 'online_learning_exam_signature'),
/// - : '300', : 'facial_recognition_images'
facialRecognitionImages('300', 'facial_recognition_images'),
@ -266,6 +389,12 @@ enum UploadFileType {
/// ai - : '301', : 'ai_recognition_images'
aiRecognitionImages('301', 'ai_recognition_images'),
/// - : '302', : 'fire_safety_inspection_passed_images'
fireSafetyInspectionPassedImages(
'302',
'fire_safety_inspection_passed_images',
),
/// - : '601', : 'gate_access_vehicle_license_photo'
gateAccessVehicleLicensePhoto('601', 'gate_access_vehicle_license_photo'),
@ -279,7 +408,10 @@ enum UploadFileType {
emissionStandardCertificate('604', 'emission_standard_certificate'),
/// (绿) - : '605', : 'motor_vehicle_registration_certificate_green_book'
motorVehicleRegistrationCertificateGreenBook('605', 'motor_vehicle_registration_certificate_green_book');
motorVehicleRegistrationCertificateGreenBook(
'605',
'motor_vehicle_registration_certificate_green_book',
);
const UploadFileType(this.type, this.path);

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/http/modules/basic_info_api.dart';
import 'package:qhd_prevention/http/ApiService.dart';
//
@ -24,20 +25,17 @@ class DictCategory {
required this.parentId,
required this.dictValue,
required this.dictLabel,
required this.dingValue,
required this.dingName,
});
factory DictCategory.fromJson(Map<String, dynamic> json) {
// id
String parseString(dynamic v) {
if (v == null) return '';
if (v is String) return v;
return v.toString();
}
//
final rawChildren = json['children'];
List<DictCategory> childrenList = [];
if (rawChildren is List) {
@ -51,7 +49,6 @@ class DictCategory {
}
}
//
final extRaw = json['extValues'];
Map<String, dynamic> extMap = {};
if (extRaw is Map) {
@ -60,7 +57,7 @@ class DictCategory {
return DictCategory(
id: parseString(json['id']),
name: parseString(json['dictLabel'] ?? json['name']), //
name: parseString(json['dictLabel'] ?? json['name']),
children: childrenList,
extValues: extMap,
parentId: parseString(json['parentId']),
@ -71,7 +68,6 @@ class DictCategory {
);
}
// Map便使
Map<String, dynamic> toMap() {
return {
'id': id,
@ -84,27 +80,18 @@ class DictCategory {
}
}
///
typedef DictSelectCallback = void Function(String id, String name, Map<String, dynamic>? extraData);
class MultiDictValuesPicker extends StatefulWidget {
///
final String dictType;
/// id, name
final DictSelectCallback onSelected;
///
final bool showSearch;
///
final String title;
///
final String confirmText;
///
final String cancelText;
final bool showIgnoreHidden;
final bool isSafeChekDangerLevel;
final bool allowSelectParent;
const MultiDictValuesPicker({
Key? key,
@ -114,6 +101,9 @@ class MultiDictValuesPicker extends StatefulWidget {
this.title = '请选择',
this.confirmText = '确定',
this.cancelText = '取消',
this.isSafeChekDangerLevel = false,
this.showIgnoreHidden = false,
this.allowSelectParent = true,
}) : super(key: key);
@override
@ -137,6 +127,9 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
final TextEditingController _searchController = TextEditingController();
// id -> node
final Map<String, DictCategory> _idMap = {};
@override
void initState() {
super.initState();
@ -162,9 +155,56 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
});
final result = await BasicInfoApi.getDictValues(widget.dictType);
final raw = result['data'] as List<dynamic>;
List<dynamic> raw = result['data'] as List<dynamic>;
if (!widget.showIgnoreHidden) {
if (widget.dictType == 'hiddenLevel') {
for (int i = 0; i < raw.length; i++) {
if (raw[i]['children'] != null) {
raw[i]['children'].removeWhere((child) => child != null && child['dictLabel'] == '忽略隐患');
}
}
}
}
late List<dynamic> resultRows = [];
if (widget.isSafeChekDangerLevel) {
if (widget.dictType == 'hiddenLevel') {
for (int i = 0; i < raw.length; i++) {
if (raw[i]['children'] != null) {
for (int m = 0; m < raw[i]['children'].length; m++) {
final ch = raw[i]['children'][m];
if (ch != null && (ch['dictLabel'] == '轻微隐患' || ch['dictLabel'] == '一般隐患')) {
resultRows.add(ch);
}
}
}
}
} else {
resultRows = raw;
}
} else {
resultRows = raw;
}
// DictCategory
final loaded = resultRows.map((e) => DictCategory.fromJson(e as Map<String, dynamic>)).toList();
// id map
_idMap.clear();
void buildIdMap(List<DictCategory> nodes) {
for (final n in nodes) {
_idMap[n.id] = n;
if (n.children.isNotEmpty) {
buildIdMap(n.children);
}
}
}
buildIdMap(loaded);
setState(() {
original = raw.map((e) => DictCategory.fromJson(e as Map<String, dynamic>)).toList();
original = loaded;
filtered = original;
loading = false;
});
@ -181,7 +221,6 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
final query = _searchController.text.toLowerCase().trim();
setState(() {
filtered = query.isEmpty ? original : _filterCategories(original, query);
// 便
if (query.isNotEmpty) {
expandedSet.addAll(_getAllExpandableIds(filtered));
}
@ -222,10 +261,33 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
return result;
}
//
bool _isSelectableNode(DictCategory category) {
if (widget.allowSelectParent) {
return true;
}
return category.children.isEmpty;
}
// id
DictCategory? _getTopAncestor(String id) {
if (id.isEmpty) return null;
DictCategory? current = _idMap[id];
if (current == null) return null;
// parentId parentId
while (current != null && current.parentId.isNotEmpty) {
final parent = _idMap[current.parentId];
if (parent == null) break;
current = parent;
}
return current;
}
Widget _buildRow(DictCategory category, int indent) {
final hasChildren = category.children.isNotEmpty;
final isExpanded = expandedSet.contains(category.id);
final isSelected = category.id == selectedId;
final isSelectable = _isSelectableNode(category);
return Column(
children: [
@ -233,16 +295,23 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
onTap: () {
setState(() {
if (hasChildren) {
isExpanded
? expandedSet.remove(category.id)
: expandedSet.add(category.id);
isExpanded ? expandedSet.remove(category.id) : expandedSet.add(category.id);
}
selectedId = category.id;
selectedName = category.name;
selectedExtraData = category.toMap();
if(indent==0){
dingValueWai =category.dictValue;
dingNameWai =category.name;
if (isSelectable) {
selectedId = category.id;
selectedName = category.name;
selectedExtraData = category.toMap();
//
final top = _getTopAncestor(category.id);
if (top != null) {
dingValueWai = top.dictValue;
dingNameWai = top.name;
} else {
// 退
dingValueWai = category.dictValue;
dingNameWai = category.name;
}
}
});
},
@ -255,9 +324,7 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
width: 24,
child: hasChildren
? Icon(
isExpanded
? Icons.arrow_drop_down_rounded
: Icons.arrow_right_rounded,
isExpanded ? Icons.arrow_drop_down_rounded : Icons.arrow_right_rounded,
size: 35,
color: Colors.grey[600],
)
@ -283,19 +350,18 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Icon(
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
child: isSelectable
? Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
color: Colors.blue,
),
)
: null,
),
],
),
),
),
if (hasChildren && isExpanded)
...category.children.map((child) => _buildRow(child, indent + 1)),
if (hasChildren && isExpanded) ...category.children.map((child) => _buildRow(child, indent + 1)),
],
);
}
@ -307,10 +373,7 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
child: Center(
child: Text(
widget.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
);
@ -322,41 +385,38 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
//
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
widget.cancelText,
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
child: Text(widget.cancelText, style: const TextStyle(fontSize: 16, color: Colors.grey)),
),
),
//
if (widget.showSearch) ...[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SearchBarWidget(
controller: _searchController,
isShowSearchButton: false,
onSearch: (keyboard) {},
),
child: SearchBarWidget(controller: _searchController, isShowSearchButton: false, onSearch: (keyboard) {}),
),
),
] else ...[
] else
const Expanded(child: SizedBox()),
],
//
GestureDetector(
onTap: selectedId.isEmpty
? null
: () {
selectedExtraData?['dingValue']=dingValueWai ;
selectedExtraData?['dingName']=dingNameWai ;
// selectedExtraData
final top = _getTopAncestor(selectedId);
if (top != null) {
selectedExtraData ??= {};
selectedExtraData!['dingValue'] = top.dictValue;
selectedExtraData!['dingName'] = top.name;
} else {
selectedExtraData ??= {};
selectedExtraData!['dingValue'] = dingValueWai;
selectedExtraData!['dingName'] = dingNameWai;
}
Navigator.of(context).pop();
widget.onSelected(selectedId, selectedName, selectedExtraData);
},
@ -382,11 +442,7 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('加载中...'),
],
children: [CircularProgressIndicator(), SizedBox(height: 16), Text('加载中...')],
),
);
}
@ -400,16 +456,9 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
const SizedBox(height: 16),
const Text('加载失败', style: TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text(
errorMessage,
style: const TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
Text(errorMessage, style: const TextStyle(fontSize: 12, color: Colors.grey), textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadDictData,
child: const Text('重试'),
),
CustomButton(text: '重试', onPressed: _loadDictData),
],
),
);
@ -419,11 +468,7 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search_off, size: 48, color: Colors.grey),
SizedBox(height: 16),
Text('暂无数据', style: TextStyle(fontSize: 16, color: Colors.grey)),
],
children: [Icon(Icons.search_off, size: 48, color: Colors.grey), SizedBox(height: 16), Text('暂无数据', style: TextStyle(fontSize: 16, color: Colors.grey))],
),
);
}
@ -437,6 +482,23 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
);
}
Widget _buildHintMessage() {
if (!widget.allowSelectParent) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Colors.blue[50],
child: Row(
children: [
Icon(Icons.info_outline, size: 16, color: Colors.blue[700]),
const SizedBox(width: 8),
Expanded(child: Text('只能选择没有子项的节点', style: TextStyle(fontSize: 12, color: Colors.blue[700]))),
],
),
);
}
return const SizedBox.shrink();
}
@override
Widget build(BuildContext context) {
return Container(
@ -444,32 +506,16 @@ class _MultiDictValuesPickerState extends State<MultiDictValuesPicker> {
height: MediaQuery.of(context).size.height * 0.7,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, -2))],
),
child: Column(
children: [
//
// _buildTitleBar(),
//
_buildActionBar(),
const Divider(height: 1),
//
Expanded(
child: _buildContent(),
),
Expanded(child: _buildContent()),
],
),
);
}
}
}

View File

@ -12,6 +12,8 @@ class Category {
final Map<String, dynamic> extValues;
final String departmentId;
final String parentId;
final String corpinfoName;
final String corpinfoId;
final List<Category> childrenList;
Category({
@ -21,6 +23,8 @@ class Category {
required this.extValues,
required this.departmentId,
required this.parentId,
required this.corpinfoName,
required this.corpinfoId,
});
factory Category.fromJson(Map<String, dynamic> json) {
@ -59,18 +63,34 @@ class Category {
extValues: extMap,
departmentId: parseString(json['departmentId']),
parentId: parseString(json['parentId']),
corpinfoName: parseString(json['corpinfoName']),
corpinfoId: parseString(json['corpinfoId']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'childrenList': childrenList.map((e) => e.toJson()).toList(),
'extValues': extValues,
'departmentId': departmentId,
'parentId': parentId,
'corpinfoName': corpinfoName,
'corpinfoId': corpinfoId,
};
}
}
/// id name
typedef DeptSelectCallback = void Function(String id, String name);
typedef DeptSelectCallback = void Function(String id, String name, Map data);
class DepartmentPicker extends StatefulWidget {
/// id name
final DeptSelectCallback onSelected;
///
final Map? data;
const DepartmentPicker({Key? key, required this.onSelected})
const DepartmentPicker({Key? key, required this.onSelected, this.data})
: super(key: key);
@override
@ -80,6 +100,8 @@ class DepartmentPicker extends StatefulWidget {
class _DepartmentPickerState extends State<DepartmentPicker> {
String selectedId = '';
String selectedName = '';
Map selectedData = {};
Set<String> expandedSet = {};
List<Category> original = [];
@ -108,7 +130,7 @@ class _DepartmentPickerState extends State<DepartmentPicker> {
Future<void> _loadData() async {
try {
final result = await BasicInfoApi.getDeptTree({});
final result = await BasicInfoApi.getDeptTree(widget.data!);
final raw = result['data'] as List<dynamic>;
print(raw);
setState(() {
@ -141,6 +163,8 @@ class _DepartmentPickerState extends State<DepartmentPicker> {
extValues: cat.extValues,
departmentId: cat.departmentId,
parentId: cat.parentId,
corpinfoName: cat.corpinfoName,
corpinfoId: cat.corpinfoId,
),
);
}
@ -164,6 +188,7 @@ class _DepartmentPickerState extends State<DepartmentPicker> {
}
selectedId = cat.id;
selectedName = cat.name;
selectedData = cat.toJson();
});
},
child: Container(
@ -240,7 +265,7 @@ class _DepartmentPickerState extends State<DepartmentPicker> {
GestureDetector(
onTap: () {
Navigator.of(context).pop();
widget.onSelected(selectedId, selectedName);
widget.onSelected(selectedId, selectedName, selectedData);
},
child: const Text(
'确定',

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
class SearchBarWidget extends StatefulWidget {
final String hintText;
@ -89,7 +90,11 @@ class _SearchBarWidgetState extends State<SearchBarWidget> {
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 12),
Image.asset('assets/images/search.png', height: 15, width: 15,)
Image.asset(
'assets/images/search.png',
height: 15,
width: 15,
),
],
),
// prefixIcon
@ -114,48 +119,23 @@ class _SearchBarWidgetState extends State<SearchBarWidget> {
),
const SizedBox(width: 10),
if (widget.isShowSearchButton)
SizedBox(
height: innerHeight - 4, // TextField
child: ElevatedButton(
onPressed: () => widget.onSearch(widget.controller.text.trim()),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
elevation: 4,
shadowColor: Colors.black45,
),
child: Text(
widget.buttonText,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
),
if (widget.showResetButton) const SizedBox(width: 10),
if (widget.showResetButton)
SizedBox(
CustomButton(
text: widget.buttonText,
height: innerHeight - 4,
child: ElevatedButton(
onPressed: () {
updateText('');
widget.onReset?.call();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
elevation: 1,
shadowColor: Colors.black26,
),
child: Text(
widget.resetButtonText,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
padding: EdgeInsets.symmetric(horizontal: 15),
onPressed: () => widget.onSearch(widget.controller.text.trim()),
),
if (widget.showResetButton)
CustomButton(
text: widget.resetButtonText,
buttonStyle:ButtonStyleType.secondary,
height: innerHeight - 4,
padding: EdgeInsets.symmetric(horizontal: 15),
onPressed: () {
updateText('');
widget.onReset?.call();
},
),
],
);

View File

@ -152,7 +152,7 @@ extension HttpManagerUpload on HttpManager {
required Map<String, dynamic> fromData,
CancelToken? cancelToken,
}) async {
fromData['corpinfoId'] = '1983773013086048256';
// fromData['corpinfoId'] = '1983773013086048256';
final form = FormData.fromMap(fromData);
final token = SessionService.instance.token ?? '';

View File

@ -13,6 +13,15 @@ class AuthApi {
data: {...data},
);
}
///
static Future<Map<String, dynamic>> userLoginCheckFirm(Map data) {
return HttpManager().request(
ApiService.basePath + (ApiService.isProduct ? '/basicInfo' : '/basicInfo') ,
'/appuser/getUserCorpByPhone',
method: Method.post,
data: {...data},
);
}
///
static Future<Map<String, dynamic>> getUserCaptcha() {
return HttpManager().request(

View File

@ -210,5 +210,14 @@ class CertificateApi {
data: {},
);
}
//
static Future<Map<String, dynamic>> deleteCertificateVerify(String eqUserId) {
return HttpManager().request(
ApiService.basePath,
'/xgfManager/project/projectHasUser?eqUserId=$eqUserId',
method: Method.get,
data: {},
);
}
}

View File

@ -0,0 +1,109 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/HttpManager.dart';
import 'package:qhd_prevention/services/SessionService.dart';
class EduApi {
///
static Future<Map<String, dynamic>> getClassList(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/class/list',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> checkSignIn(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentSign/verify',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> compareFace(Map formData, String path) async {
final data = Map<String, dynamic>.from(formData);
// MultipartFile
data['files'] = await MultipartFile.fromFile(
path,
filename: path.split(Platform.pathSeparator).last,
);
return HttpManager().uploadImages(
baseUrl: '${ApiService.basePath}/edu',
path: '/app/studentSign/compareFace',
fromData: data,
);
}
//
static Future<Map<String, dynamic>> uploadSignature(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentSign/uploadSignUrl',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> getSignInList(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentSign/listAll',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> getExamDetail(String classId) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/classExamPaper/getInfoByClassId/$classId',
method: Method.get,
data: {
},
);
}
//
static Future<Map<String, dynamic>> submitExam(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentExamRecord/submit',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> getExamRecord(Map data) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentExamRecord/list',
method: Method.post,
data: {
...data,
},
);
}
//
static Future<Map<String, dynamic>> getExamRecordDetail(String recordId) async {
return HttpManager().request(
'${ApiService.basePath}/edu',
'/app/studentExamRecord/$recordId',
method: Method.get,
data: {
},
);
}
}

View File

@ -0,0 +1,227 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/modules/edu_api.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
class SigninInformationListPage extends StatefulWidget {
const SigninInformationListPage({Key? key, required this.info})
: super(key: key);
final Map info;
@override
_SigninInformationListPageState createState() => _SigninInformationListPageState();
}
class _SigninInformationListPageState extends State<SigninInformationListPage> {
//
List<dynamic> list = [];
int currentPage = 1;
int rows = 10;
int totalPage = 1;
bool isLoading = false;
final TextEditingController _searchController = TextEditingController();
int sindex = 0; //
String searchKeywords = '';
Map flowData = {};
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_fetchData();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent &&
!isLoading) {
if (currentPage < totalPage) {
currentPage++;
_fetchData();
}
}
}
Future<void> _fetchData() async {
if (isLoading) return;
setState(() => isLoading = true);
try {
final data = {
'pageSize': rows,
'pageIndex': currentPage,
'classId': widget.info['classId'] ?? '',
'studentId': widget.info['studentId'] ?? '',
'phone': SessionService.instance.userName ?? '',
};
final response = await EduApi.getSignInList(data);
setState(() {
final newData = response['data'] as List<dynamic>? ?? [];
if (currentPage == 1) {
list = newData;
} else {
list.addAll(newData);
}
totalPage = response['totalPages'] ?? 1;
isLoading = false;
});
} catch (e) {
print('Error fetching data: $e');
setState(() => isLoading = false);
ToastUtil.showNormal(context, '获取列表失败');
}
}
void _search() {
searchKeywords = _searchController.text.trim();
currentPage = 1;
list.clear();
_fetchData();
}
//
void _reset() {
searchKeywords = '';
currentPage = 1;
list.clear();
_fetchData();
}
Widget _buildListItem(Map<String, dynamic> item) {
int type = item["type"] ?? 1;
return Card(
color: Colors.white,
margin: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
Container(
decoration: BoxDecoration(
),
child: Image.network(
'${ApiService.baseImgPath}${item['faceUrl'] ?? ''}', // URLavatarUrl
width: 100,
// height: 60,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
//
return CircleAvatar(
radius: 5,
backgroundColor: Colors.grey[300],
child: Text(
item['createName']?.substring(0, 1) ?? '',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
);
},
),
),
const SizedBox(width: 10),
Column(
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['createName'] ?? '',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
// const SizedBox(height: 5,),
Text("班级名称: ${widget.info['name'] ?? ''}"),
Text("签到时间: ${item["updateTime"] ?? ''}"),
Text("培训地点: ${item["trainingLocation"] ?? ''}"),
Row(
children: [
Text("状态:"),
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFF94F0BD),
borderRadius: BorderRadius.circular(3),
),
child: Text("${type == 1 ? '认证签到' : '考试签到'}", style: TextStyle(color: Colors.green),)
),
],
),
const SizedBox(height: 8),
],
),
],
)
),
),
);
}
Widget _buildListContent() {
if (isLoading && list.isEmpty) {
return const Center(child: CircularProgressIndicator());
} else if (list.isEmpty) {
return NoDataWidget.show();
} else {
return ListView.builder(
padding: EdgeInsets.zero,
controller: _scrollController,
itemCount: list.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= list.length) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Center(child: CircularProgressIndicator()),
);
}
return _buildListItem(list[index]);
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: MyAppbar(
title: '班级管理',
),
body: SafeArea(
child: Column(
children: [
// List
Expanded(child: _buildListContent()),
],
),
),
);
}
}

View File

@ -0,0 +1,405 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/constants/app_enums.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/modules/edu_api.dart';
import 'package:qhd_prevention/pages/home/Study/signin_information_list_page.dart';
import 'package:qhd_prevention/pages/home/Study/study_exam_record_list.dart';
import 'package:qhd_prevention/pages/home/Study/study_take_exam_page.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/mine/face_ecognition_page.dart';
import 'package:qhd_prevention/pages/mine/mine_sign_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
/// 9
class StudyClassListPage extends StatefulWidget {
const StudyClassListPage({Key? key}) : super(key: key);
@override
_StudyClassListPageState createState() => _StudyClassListPageState();
}
class _StudyClassListPageState extends State<StudyClassListPage> {
//
List<dynamic> list = [];
int currentPage = 1;
int rows = 10;
int totalPage = 1;
bool isLoading = false;
final TextEditingController _searchController = TextEditingController();
int sindex = 0; //
String searchKeywords = '';
Map flowData = {};
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_fetchData();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent &&
!isLoading) {
if (currentPage < totalPage) {
currentPage++;
_fetchData();
}
}
}
Future<void> _fetchData() async {
if (isLoading) return;
setState(() => isLoading = true);
try {
final data = {
'pageSize': rows,
'pageIndex': currentPage,
'phone': SessionService.instance.userName ?? '',
'likeName': searchKeywords,
};
final response = await EduApi.getClassList(data);
setState(() {
final newData = response['data'] as List<dynamic>? ?? [];
if (currentPage == 1) {
list = newData;
} else {
list.addAll(newData);
}
totalPage = response['totalPages'] ?? 1;
isLoading = false;
});
} catch (e) {
print('Error fetching data: $e');
setState(() => isLoading = false);
ToastUtil.showNormal(context, '获取列表失败');
}
}
// 1- 2- 3- 4-
String getStatusName(int status) {
switch (status) {
case 1:
return '未申请';
case 2:
return '待开班';
case 3:
return '培训中';
case 4:
return '培训结束';
default:
return '';
}
}
void _search() {
searchKeywords = _searchController.text.trim();
currentPage = 1;
list.clear();
_fetchData();
}
//
void _reset() {
searchKeywords = '';
currentPage = 1;
list.clear();
_fetchData();
}
///
Future<void> _handleApply(bool isInPlan, Map planItem) async {
await pushPage(StudyExamRecordList(info: planItem,), context);
_search();
}
///
List<Widget> _buildActionButtons(Map<String, dynamic> item) {
final List<Widget> buttons = [];
buttons.addAll([
CustomButton(
text: '签到信息',
height: 35,
backgroundColor: Colors.blue,
onPressed: () async {
await pushPage(SigninInformationListPage(info: item), context);
},
),
CustomButton(
text: '考试记录',
height: 35,
backgroundColor: Colors.blue,
onPressed: () => _handleApply(true, item),
),
]);
return buttons;
}
Widget _buildListItem(Map<String, dynamic> item) {
final actionButtons = _buildActionButtons(item);
// 10
List<Widget> buttonRowChildren = [];
for (int i = 0; i < actionButtons.length; i++) {
// SizedBox Expanded
buttonRowChildren.add(
Expanded(child: SizedBox(height: 35, child: actionButtons[i])),
);
if (i != actionButtons.length - 1) {
buttonRowChildren.add(const SizedBox(width: 10));
}
}
return Card(
color: Colors.white,
margin: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['name'] ?? '',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 5),
Text("所属单位: ${item['corpName'] ?? ''}"),
Text("培训开始时间: ${item["startTime"] ?? ''}"),
Text("培训结束时间: ${item["endTime"] ?? ''}"),
Text("班级状态: ${getStatusName(item['state'])}"),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: buttonRowChildren,
),
],
),
),
),
);
}
Widget _buildListContent() {
if (isLoading && list.isEmpty) {
return const Center(child: CircularProgressIndicator());
} else if (list.isEmpty) {
return NoDataWidget.show();
} else {
return ListView.builder(
padding: EdgeInsets.zero,
controller: _scrollController,
itemCount: list.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= list.length) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Center(child: CircularProgressIndicator()),
);
}
return _buildListItem(list[index]);
},
);
}
}
void startScan() async {
final result = await pushPage(ScanPage(type: ScanType.SignIn), context);
if (result == null) {
ToastUtil.showNormal(context, '未扫描到二维码');
return;
}
int type = result['type'] ?? 0;
final data = {
...result,
'phone': SessionService.instance.userData?.phone ?? '',
'type': type,
};
LoadingDialogHelper.show();
//
final response = await EduApi.checkSignIn(data);
LoadingDialogHelper.hide();
if (response['success']) {
//
final filePath = await pushPage(
const FaceRecognitionPage(
studentId: '',
data: {},
mode: FaceMode.study,
),
context,
);
final faceData = response['data'];
if (filePath != null) {
//
try {
LoadingDialogHelper.show();
final response = await EduApi.compareFace({
'type': data['type'],
'studentId': faceData['studentId'],
}, filePath);
final faceResultData = response['data'];
if (response['success']) {
final signData = {
'id': faceResultData['id'] ?? '',
'studentId': faceResultData['studentId'] ?? '',
'type': data['type']?? '',
'classId': faceResultData['classId'] ?? '',
'studentSignId':faceResultData['studentSignId']??''
};
_signUpload(signData, type);
} else {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, response['errMessage'] ?? '验证失败');
}
} catch (e) {
ToastUtil.showNormal(context,'验证失败');
LoadingDialogHelper.hide();
print(e);
}
} else {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, '签到失败');
}
} else {
ToastUtil.showNormal(context, response['errMessage'] ?? '签到失败');
}
}
//
Future<void> _signUpload(Map data, int type) async {
LoadingDialogHelper.hide();
UploadFileType fileType =
type == 1
? UploadFileType.onlineLearningSignSignature
: UploadFileType.onlineLearningExamSignature;
final signPath = await pushPage(MineSignPage(), context);
if (signPath != null) {
//
try {
LoadingDialogHelper.show();
//
final response = await FileApi.uploadFile(signPath, fileType, '');
if (response['success']) {
data['signUrl'] = response['data']['filePath'];
final signResult = await EduApi.uploadSignature(data);
LoadingDialogHelper.hide();
if (signResult['success']) {
if (type == 1) {
ToastUtil.showNormal(context, '签到成功');
} else {
//
final examResult = await EduApi.getExamDetail(
data['classId'] ?? '',
);
LoadingDialogHelper.hide();
//
pushPage(
StudyTakeExamPage(examInfo: examResult['data'] ?? {}, signInfo: data),
context,
);
}
} else {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, signResult['errMessage'] ?? '');
}
} else {
LoadingDialogHelper.hide();
ToastUtil.showNormal(context, response['errMessage'] ?? '');
}
} catch (e) {
LoadingDialogHelper.hide();
print(e);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: MyAppbar(
title: '班级管理',
actions: [
GestureDetector(
onTap: startScan,
child: Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: Image.asset(
"assets/icon-apps/home_saoyisao.png",
width: 20,
height: 20,
),
),
),
],
),
body: SafeArea(
child: Column(
children: [
Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Row(
children: [
Expanded(
child: SearchBarWidget(
showResetButton: true,
hintText: "请输入班级名称",
onSearch: (text) {
_search();
},
onReset: () {
_search();
},
controller: _searchController,
),
),
],
),
),
const Divider(height: 1),
// List
Expanded(child: _buildListContent()),
],
),
),
);
}
}

View File

@ -0,0 +1,294 @@
// lib/pages/study_exam_record_detail.dart
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/http/modules/edu_api.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import '../../../tools/tools.dart';
class StudyExamRecordDetail extends StatefulWidget {
final String examId;
const StudyExamRecordDetail({Key? key, required this.examId,}) : super(key: key);
@override
_StudyExamRecordDetailState createState() => _StudyExamRecordDetailState();
}
class Question {
final String questionDry; //
final String questionType; // '1','2','3','4'
final Map<String, String> options; // A/B/C/D
final String answer; //
String choiceAnswer; //
final int score; //
final String descr; // /
bool answered; // /
Question({
required this.questionDry,
required this.questionType,
required this.options,
required this.answer,
required this.choiceAnswer,
required this.score,
required this.descr,
this.answered = true,
});
factory Question.fromJson(Map<String, dynamic> json) {
// questionType
String type = (json['questionType'] ?? 1).toString();
final opts = <String, String>{};
if (type == '1' || type == '2' || type == '3') {
opts['A'] = json['optionA'] ?? '';
opts['B'] = json['optionB'] ?? '';
if (type != '3') {
opts['C'] = json['optionC'] ?? '';
opts['D'] = json['optionD'] ?? '';
}
}
final qDry = json['questionDry'] ?? '';
final userAnswer = json['choiceAnswer'] ?? '';
final correct = json['answer'] ?? '';
final scoreVal = () {
final val = json['score'];
if (val is int) return val;
if (val is String) {
final parsed = int.tryParse(val);
return parsed ?? 0;
}
return 0;
}();
print('$userAnswer$correct');
final descr = json['descr'] ?? json['explain'] ?? '';
return Question(
questionDry: qDry,
questionType: type,
options: opts,
answer: correct,
choiceAnswer: userAnswer,
score: scoreVal,
descr: descr,
answered: true,
);
}
}
class _StudyExamRecordDetailState extends State<StudyExamRecordDetail> {
bool loading = true;
List<Question> questions = [];
Map<String, dynamic> paperInfo = {};
int current = 0;
final Map<String, String> questionTypeMap = {
'1': '单选题',
'2': '多选题',
'3': '判断题',
'4': '填空题',
};
@override
void initState() {
super.initState();
_fetchData();
}
Future<void> _fetchData() async {
setState(() => loading = true);
final res = await EduApi.getExamRecordDetail(widget.examId);
if (res['success']) {
paperInfo = res['data'];
var list = paperInfo['examRecordItemList'] as List? ?? [];
questions = list.map((e) {
if (e is Map<String, dynamic>) return Question.fromJson(e);
return Question.fromJson(Map<String, dynamic>.from(e));
}).toList();
//
for (var q in questions) {
q.answered = true;
}
} else {
questions = [];
paperInfo = {};
}
setState(() => loading = false);
}
Widget _buildOptions(Question q) {
if (q.questionType == '4') {
// choiceAnswer
return TextField(
controller: TextEditingController(text: q.choiceAnswer),
readOnly: true,
maxLength: 255,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
);
}
List<String> keys = q.questionType == '3' ? ['A', 'B'] : ['A', 'B', 'C', 'D'];
return Column(
children: keys.map((key) {
final isChecked = (q.choiceAnswer ?? '').contains(key);
final isCorrect = (q.answer ?? '').contains(key);
final right = q.answered && isCorrect && isChecked; //
final err = q.answered && !isCorrect && isChecked; //
final warn = q.answered && isCorrect && !isChecked; //
return Container(
margin: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
color: right
? Colors.green
: err
? Colors.red
: warn
? Colors.green
: Colors.black12,
shape: BoxShape.circle,
),
child: Text(
key,
style: TextStyle(
color: (right || err || warn) ? Colors.white : Colors.black87,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
q.options[key] ?? '',
style: TextStyle(
color: right
? Colors.green
: err
? Colors.red
: warn
? Colors.green
: Colors.black87,
fontSize: 16,
),
),
),
],
),
);
}).toList(),
);
}
String _renderAnswerText(Question q) {
// A/B true/false
if (q.questionType == '3') {
// choiceAnswer 'A' 'B'
final ans = (q.choiceAnswer ?? '').toUpperCase();
if (ans == 'A' || ans == 'TRUE' || ans == 'T') return '';
if (ans == 'B' || ans == 'FALSE' || ans == 'F') return '';
return q.choiceAnswer;
}
return q.choiceAnswer;
}
@override
Widget build(BuildContext context) {
final q = questions.isNotEmpty ? questions[current] : null;
return Scaffold(
appBar: MyAppbar(title: '考试记录'),
body: loading
? const Center(child: CircularProgressIndicator())
: questions.isEmpty
? NoDataWidget.show()
: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// &
Stack(
children: [
Container(
width: double.infinity,
height: 100,
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('assets/study/bgimg1.png'),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(8),
),
),
Positioned.fill(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${paperInfo['examName'] ?? ''}',
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
const Padding(padding: EdgeInsets.symmetric(horizontal: 15), child: Divider(color: Colors.white30, height: 20)),
Text(
'当前试题 ${current + 1}/${questions.length}',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
],
)),
),
],
),
const SizedBox(height: 16),
//
if (q != null) ...[
Text(
'${current + 1}. ${q.questionDry} ${questionTypeMap[q.questionType] ?? ''}',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
_buildOptions(q),
const Divider(),
Text('我的答案: ${_renderAnswerText(q)}'),
Text('正确答案: ${q.answer}'),
const SizedBox(height: 8),
Text('权威解读: ${q.descr}'),
],
const Spacer(),
//
Row(
children: [
if (current > 0)
Expanded(
child: CustomButton(
text: '上一题',
backgroundColor: const Color(0xFFD7D7D7),
textColor: Colors.black54,
onPressed: () => setState(() => current--),
),
),
if (current > 0 && current < questions.length - 1) const SizedBox(width: 16),
if (current < questions.length - 1)
Expanded(
child: CustomButton(
text: '下一题',
backgroundColor: Colors.blue,
onPressed: () => setState(() => current++),
),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,219 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/modules/edu_api.dart';
import 'package:qhd_prevention/pages/home/Study/study_exam_record_detail.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
class StudyExamRecordList extends StatefulWidget {
const StudyExamRecordList({Key? key, required this.info}) : super(key: key);
final Map info;
@override
_StudyExamRecordListState createState() => _StudyExamRecordListState();
}
class _StudyExamRecordListState extends State<StudyExamRecordList> {
//
List<dynamic> list = [];
int currentPage = 1;
int rows = 10;
int totalPage = 1;
bool isLoading = false;
final TextEditingController _searchController = TextEditingController();
int sindex = 0; //
String searchKeywords = '';
Map flowData = {};
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_fetchData();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent &&
!isLoading) {
if (currentPage < totalPage) {
currentPage++;
_fetchData();
}
}
}
Future<void> _fetchData() async {
if (isLoading) return;
setState(() => isLoading = true);
try {
final data = {
'pageSize': rows,
'pageIndex': currentPage,
'likeStudentId': widget.info['studentId'] ?? '',
};
final response = await EduApi.getExamRecord(data);
setState(() {
final newData = response['data'] as List<dynamic>? ?? [];
if (currentPage == 1) {
list = newData;
} else {
list.addAll(newData);
}
totalPage = response['totalPages'] ?? 1;
isLoading = false;
});
} catch (e) {
print('Error fetching data: $e');
setState(() => isLoading = false);
ToastUtil.showNormal(context, '获取列表失败');
}
}
void _search() {
searchKeywords = _searchController.text.trim();
currentPage = 1;
list.clear();
_fetchData();
}
//
void _reset() {
searchKeywords = '';
currentPage = 1;
list.clear();
_fetchData();
}
//
Future<void> _getExamDetail(Map<String, dynamic> item) async {
pushPage(StudyExamRecordDetail(examId: item['id'] ?? ''), context);
}
Widget _buildListItem(Map<String, dynamic> item) {
int type = item["type"] ?? 1;
return Card(
color: Colors.white,
margin: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
_getExamDetail(item);
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['examName'] ?? '试卷名称',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Text("考试时间: "), Text("分数: "), Text("考试情况: ")],
),
const SizedBox(width: 30),
Column(
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${item['examTimeBegin'] ?? ''}"),
Text("${item["examScore"] ?? ''}"),
Text("${item["result"] == 1 ? '通过' : '未通过'}"),
],
),
],
),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// const SizedBox(),
// CustomButton(
// text: '查看',
// padding: EdgeInsetsGeometry.symmetric(horizontal: 40),
// height: 35,
// onPressed: () {
// _getExamDetail(item);
// },
// ),
const SizedBox(height: 5),
CustomButton(
text: '查看',
padding: EdgeInsetsGeometry.symmetric(horizontal: 40),
height: 35,
onPressed: () {
_getExamDetail(item);
},
// ]
),
],
),
),
),
);
}
Widget _buildListContent() {
if (isLoading && list.isEmpty) {
return const Center(child: CircularProgressIndicator());
} else if (list.isEmpty) {
return NoDataWidget.show();
} else {
return ListView.builder(
padding: EdgeInsets.zero,
controller: _scrollController,
itemCount: list.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= list.length) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Center(child: CircularProgressIndicator()),
);
}
return _buildListItem(list[index]);
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: MyAppbar(title: '考试记录'),
body: SafeArea(
child: Column(
children: [
// List
Expanded(child: _buildListContent()),
],
),
),
);
}
}

View File

@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/IconBadgeButton.dart';
import 'package:qhd_prevention/pages/home/Study/study_class_list_page.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/smart_image.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:qhd_prevention/common/route_aware_state.dart';
class StudyTabListPage extends StatefulWidget {
const StudyTabListPage({super.key});
@override
State<StudyTabListPage> createState() => _StudyTabListPageState();
}
class _StudyTabListPageState extends RouteAwareState<StudyTabListPage> {
late List<Map<String, dynamic>> buttonInfos = [
{
"icon": "assets/images/icon_study01.png",
"title": "培训管理",
"unreadCount": 0,
}
];
@override
void initState() {
super.initState();
}
Future<void> onVisible() async {
_getData();
}
Future<void> _getData() async {
// final data = await HiddenDangerApi.specialCheckWork();
// setState(() {
// final eight_work_count = data['count'] ?? {};
// buttonInfos = [
// {
// "icon": "assets/icon-apps/icon-807.png",
// "title": "动火作业",
// "unreadCount": eight_work_count['HOTWORK_COUNT'],
// }
// ];
// });
}
void _handleIconTap(int index) async {
switch (index) {
case 0:
await pushPage(StudyClassListPage(), context);
break;
}
_getData();
}
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: MyAppbar(title: '特殊作业'),
// body: SafeArea(
// child: WorkTabIconGrid(
// buttonInfos: buttonInfos,
// onItemPressed: _handleItemPressed,
// ),
// ),
// );
// }
@override
Widget build(BuildContext context) {
double bannerHeight = 730/1125 * MediaQuery.of(context).size.width;
const double iconSectionHeight = 150.0;
const double iconOverlapBanner = 30.0; // banner
return PopScope(
canPop: true,
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: MyAppbar(
title: '培训管理',
backgroundColor: Colors.transparent,
),
body: ListView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.zero,
children: [
SizedBox(
height: bannerHeight + iconSectionHeight,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
top: 0,
left: 0,
right: 0,
height: bannerHeight,
child: _buildBannerSection(bannerHeight),
),
Positioned(
left: 10,
right: 10,
top: bannerHeight - iconOverlapBanner,
height: iconSectionHeight,
child: _buildIconSection(context),
),
],
),
),
],
),
),
);
}
// Banner
Widget _buildBannerSection(double bannerHeight) {
return Stack(
children: [
//
Image.asset(
"assets/images/study_banner.png",
width: MediaQuery.of(context).size.width,
height: bannerHeight,
fit: BoxFit.fitWidth,
),
],
);
}
Widget _buildIconSection(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
child: Column(
children: [
_buildIconRow(startIndex: 0),
],
),
);
}
Widget _buildIconRow({required int startIndex}) {
final List<Widget> cells = List.generate(4, (i) {
final int idx = startIndex + i;
if (idx < buttonInfos.length) {
return Expanded(
child: Center(child: _buildIconButton(buttonInfos[idx], idx, context)),
);
} else {
return const Expanded(
child: SizedBox.shrink(),
);
}
});
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: cells,
);
}
Widget _buildIconButton(Map<String, dynamic> info, int index, BuildContext context) {
return IconBadgeButton(
iconPath: info['icon'] ?? '',
title: info['title'] ?? '',
unreadCount: (info['unreadCount'] ?? 0) is int ? info['unreadCount'] as int : int.tryParse('${info['unreadCount']}') ?? 0,
onTap: () => _handleIconTap(index),
);
}
}

View File

@ -0,0 +1,458 @@
// lib/pages/study_take_exam_page.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/modules/edu_api.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'dart:convert';
import 'package:qhd_prevention/tools/tools.dart';
class Question {
final String questionDry;
final String questionType;
final String questionId;
final String answer; //
final double score;
final Map<String, String> options;
// UI
String choiceAnswer;
Question({
required this.questionDry,
required this.questionType,
required this.questionId,
required this.answer,
required this.score,
required this.options,
this.choiceAnswer = '',
});
factory Question.fromJson(Map<String, dynamic> json) {
final type = '${json['questionType'] ?? ''}';
final opts = <String, String>{};
if (type == '1' || type == '2' || type == '3') {
opts['A'] = json['optionA'] as String? ?? '';
opts['B'] = json['optionB'] as String? ?? '';
if (type != '3') {
opts['C'] = json['optionC'] as String? ?? '';
opts['D'] = json['optionD'] as String? ?? '';
}
}
//
final answer = json['answer'] as String? ?? '';
final qDry = json['questionDry'] as String? ?? '';
final qId = json['questionId'] as String? ?? '';
double score = json['score'];
return Question(
questionDry: qDry,
questionType: type,
questionId: qId,
answer: answer,
score: score,
options: opts,
);
}
///
Map<String, dynamic> toSubmitJson() {
return {
'questionId': questionId,
'answer': answer,
'choiceAnswer': choiceAnswer,
'score': score,
};
}
}
class StudyTakeExamPage extends StatefulWidget {
const StudyTakeExamPage({
required this.examInfo,
required this.signInfo,
super.key,
});
final Map<String, dynamic> examInfo;
final Map signInfo;
@override
State<StudyTakeExamPage> createState() => _StudyTakeExamPageState();
}
class _StudyTakeExamPageState extends State<StudyTakeExamPage> {
late final List<Question> questions;
late final Map<String, dynamic> info;
int current = 0;
late int remainingSeconds;
Timer? _timer;
final String _startExamTime = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
final questionTypeMap = <String, String>{
'1': '单选题',
'2': '多选题',
'3': '判断题',
'4': '填空题',
};
@override
void initState() {
super.initState();
info = widget.examInfo as Map<String, dynamic>? ?? {};
final rawList = widget.examInfo['questionList'] as List<dynamic>? ?? [];
questions = rawList.map((e) => Question.fromJson(e as Map<String, dynamic>)).toList();
final minutes = info['examTime'] as int? ?? 0;
remainingSeconds = minutes * 60;
_startTimer();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _showTip(String content) {
return CustomAlertDialog.showAlert(
context,
title: '温馨提示',
content: content,
confirmText: '确认',
);
}
bool _validateCurrentAnswer() {
final q = questions[current];
if (q.choiceAnswer.isEmpty) {
_showTip('请对本题进行作答。');
return false;
}
if (q.questionType == '2' && q.choiceAnswer.split(',').length < 2) {
_showTip('多选题最少需要选择两个答案。');
return false;
}
return true;
}
void _startTimer() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (remainingSeconds <= 0) {
timer.cancel();
_onTimeUp();
} else {
setState(() => remainingSeconds--);
}
});
}
/// 便
void _resetForRetry() {
//
for (var q in questions) {
q.choiceAnswer = '';
}
//
setState(() {
current = 0;
final minutes = info['examTime'] as int? ?? 0;
remainingSeconds = minutes * 60;
});
//
_startTimer();
}
void _chooseTopic(String type, String key) {
final q = questions[current];
if (type == 'radio' || type == 'judge') {
q.choiceAnswer = (q.choiceAnswer == key) ? '' : key;
} else {
final chars = q.choiceAnswer.isEmpty ? <String>[] : q.choiceAnswer.split(',');
if (chars.contains(key)) {
chars.remove(key);
} else {
chars.add(key);
}
chars.sort();
q.choiceAnswer = chars.join(',');
}
setState(() {});
}
void _nextQuestion() {
if (!_validateCurrentAnswer()) return;
if (current < questions.length - 1) setState(() => current++);
}
void _previousQuestion() {
if (current > 0) setState(() => current--);
}
void _confirmSubmit() async {
if (!_validateCurrentAnswer()) return;
final ok = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '请确认是否交卷!',
cancelText: '取消',
);
if (ok) {
_submit();
}
}
Future<void> _submit() async {
LoadingDialogHelper.show(message: '正在提交');
//
for (var q in questions) {
if (q.questionType == '2') {
//
q.choiceAnswer = q.choiceAnswer.replaceAll(',', '');
}
}
// JSON JSON
final questionList = questions.map((q) => q.toSubmitJson()).toList();
final data = {
'studentId': widget.signInfo['studentId'] ?? '',
'classId': widget.examInfo['classId'] ?? '',
'corpinfoId': SessionService.instance.tenantId ?? '',
'classExamPaperId': widget.examInfo['classExamPaperId'] ?? '',
'examPaperId': widget.examInfo['examPaperId'] ?? '',
'studentSignId': widget.signInfo['studentSignId'],
'examTimeBegin': _startExamTime,
//
'examTimeEnd': DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()),
'questionList': questionList, // List<Map<String,dynamic>>
};
final res = await EduApi.submitExam(data);
LoadingDialogHelper.hide();
if (res['success']) {
final data = res['data'] as Map<String, dynamic>? ?? {};
final score = data['examScore'] ?? 0;
final passed = data['result'] == 1;
// /
final result = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: passed
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
cancelText: passed ? '' : '继续考试',
confirmText: '确定',
);
//
if (passed || result) {
Navigator.of(context).pop();
return;
}
// ->
_resetForRetry();
} else {
//
final msg = res['message'] ?? '提交失败,请重试';
ToastUtil.showError(context, msg);
}
}
void _onTimeUp() {
ToastUtil.showError(context, '考试时间已结束');
_submit();
}
Widget _buildOptions(Question q) {
if (q.questionType == '4') {
return TextField(
controller: TextEditingController(text: q.choiceAnswer),
onChanged: (val) => q.choiceAnswer = val,
maxLength: 255,
decoration: const InputDecoration(
hintText: '请输入内容',
border: OutlineInputBorder(),
),
);
}
final keys = q.questionType == '3' ? ['A', 'B'] : ['A', 'B', 'C', 'D'];
return Column(
children: keys.map((key) {
final active = q.choiceAnswer.split(',').contains(key);
return GestureDetector(
onTap: () => _chooseTopic(
q.questionType == '3' ? 'judge' : (q.questionType == '2' ? 'multiple' : 'radio'),
key,
),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
color: active ? Colors.blue : Colors.grey.shade200,
shape: BoxShape.circle,
),
child: Text(
key,
style: TextStyle(
color: active ? Colors.white : Colors.black87,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
q.options[key] ?? '',
style: const TextStyle(fontSize: 15),
),
),
],
),
),
);
}).toList(),
);
}
String get _formattedTime {
final m = remainingSeconds ~/ 60;
final s = remainingSeconds % 60;
return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
final q = questions.isNotEmpty ? questions[current] : null;
return PopScope(
canPop: true, //
child: Scaffold(
backgroundColor: Colors.white,
appBar: const MyAppbar(title: '课程考试', isBack: false),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Container(
width: double.infinity,
height: 120,
decoration: BoxDecoration(
image: const DecorationImage(
image: AssetImage('assets/study/bgimg1.png'),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(8),
),
),
Positioned.fill(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'试卷名称:${widget.examInfo['examName'] ?? ''}',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'当前试题 ${current + 1}/${questions.length}',
style: const TextStyle(
color: Colors.white,
fontSize: 15,
),
),
const SizedBox(height: 8),
Text(
'考试剩余时间:$_formattedTime',
style: const TextStyle(
color: Colors.white,
fontSize: 15,
),
),
],
),
),
),
],
),
const SizedBox(height: 16),
if (q != null)
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${current + 1}. ${q.questionDry} ${questionTypeMap[q.questionType] ?? ''}',
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 15,
),
),
const SizedBox(height: 16),
_buildOptions(q),
const SizedBox(height: 24),
],
),
),
)
else
const Expanded(child: SizedBox()),
const SizedBox(height: 8),
Row(
children: [
if (current > 0)
Expanded(
child: CustomButton(
text: '上一题',
backgroundColor: const Color(0xFFD7D7D7),
textColor: Colors.black54,
onPressed: _previousQuestion,
),
),
if (current > 0 && current < questions.length - 1) const SizedBox(width: 16),
if (current < questions.length - 1)
Expanded(
child: CustomButton(
text: '下一题',
backgroundColor: Colors.blue,
onPressed: _nextQuestion,
),
),
if (current == questions.length - 1)
Expanded(
child: CustomButton(
text: '交卷',
backgroundColor: Colors.blue,
onPressed: _confirmSubmit,
),
),
],
),
],
),
),
),
);
}
}

View File

@ -5,12 +5,17 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:qhd_prevention/common/route_aware_state.dart';
import 'package:qhd_prevention/common/route_service.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/Study/study_tab_list_page.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/home/unit/unit_tab_page.dart';
import 'package:qhd_prevention/pages/main_tab.dart';
import 'package:qhd_prevention/pages/mine/onboarding_full_page.dart';
import 'package:qhd_prevention/pages/user/choose_userFirm_page.dart';
import 'package:qhd_prevention/pages/user/firm_list_page.dart';
import 'package:qhd_prevention/services/auth_service.dart';
import 'package:qhd_prevention/tools/h_colors.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -31,11 +36,16 @@ class HomePageState extends RouteAwareState<HomePage>
int _currentPage = 0;
bool _isMobileSelected = true; //
void startScan() {
Navigator.push(
void startScan() async {
final result = await pushPage(
ScanPage(type: ScanType.Onboarding),
context,
MaterialPageRoute(builder: (_) => ScanPage(type: ScanType.Onboarding)),
);
if (result == null) {
return;
}
pushPage(OnboardingFullPage(scanData: result), context);
}
// key
@ -152,10 +162,13 @@ class HomePageState extends RouteAwareState<HomePage>
bool _initialLoadingHidden = true;
bool _firstLoad = false;
///
bool _isShowCheckLogin = false;
@override
void initState() {
super.initState();
_isShowCheckLogin = widget.isChooseFirm;
// _getNeedSafetyCommitment();
_buttonVisibility = List.filled(buttonInfos.length, true);
@ -175,6 +188,8 @@ class HomePageState extends RouteAwareState<HomePage>
curve: Curves.easeInOut,
);
} catch (_) {}
_getNeedSafetyCommitment();
setState(() {});
});
@ -187,6 +202,47 @@ class HomePageState extends RouteAwareState<HomePage>
});
}
///
Future<void> _getNeedSafetyCommitment() async {
if (_isShowCheckLogin) {
return;
}
final prefs = await SharedPreferences.getInstance();
final phone = prefs.getString('savePhone') ?? '';
final pwd = prefs.getString('savePass') ?? '';
final result = await AuthApi.userLoginCheckFirm({'phone': phone});
if (result['success']) {
final resData = result['data'];
List firmList = resData['userCorpInfoCOList'] ?? [];
if (firmList.isNotEmpty) {
_isShowCheckLogin = true;
CustomAlertDialog.showAlert(
context,
title: '温馨提示',
content: '您的入职申请已通过',
confirmText: '立即入职',
onConfirm: () async {
//
Map data = {'unitId': firmList.first['corpinfoId'] ?? ''};
final res = await AuthService.gbsLogin(phone, pwd, data);
LoadingDialogHelper.hide();
if (res['success'] == true) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const MainPage(isChooseFirm: true),
),
);
}
},
);
} else {
_isShowCheckLogin = false;
}
}
}
void _onScroll() {
final offset =
_scrollController.hasClients ? _scrollController.offset : 0.0;
@ -292,6 +348,7 @@ class HomePageState extends RouteAwareState<HomePage>
bannerHeight - iconOverlapBanner + iconSectionHeight + 60;
final double statusBar = MediaQuery.of(context).padding.top;
final double indicatorCoverHeight = statusBar + 80.0; // 80
return PopScope(
canPop: false,
@ -299,7 +356,10 @@ class HomePageState extends RouteAwareState<HomePage>
extendBodyBehindAppBar: true,
body: Stack(
children: [
RefreshIndicator(
backgroundColor: Colors.white, // <-
color: Colors.blue, //
onRefresh: _onRefresh,
child: ListView(
controller: _scrollController,
@ -360,12 +420,10 @@ class HomePageState extends RouteAwareState<HomePage>
),
const SizedBox(height: 20),
]
],
],
),
),
// AppBar/ ListView
Positioned(
top: 0,
@ -396,7 +454,7 @@ class HomePageState extends RouteAwareState<HomePage>
opacity: _showFloatingAppBar ? 1.0 : 0.0,
child: SizedBox(
height: _floatingBarHeight,
child: const SizedBox()
child: const SizedBox(),
),
),
),
@ -405,15 +463,16 @@ class HomePageState extends RouteAwareState<HomePage>
Positioned(
top: statusBar + 14,
width: screenWidth(context),
child: Center(child:
Text(
'秦港-相关方安全管理',
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w600,
color: Colors.white,
child: Center(
child: Text(
'秦港-相关方安全管理',
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),)
),
),
// AppBar
_buildFixedTopIcons(context),
@ -424,7 +483,7 @@ class HomePageState extends RouteAwareState<HomePage>
}
Future<void> _joinFirm() async {
pushPage(FirmListPage(isBack: true,), context);
pushPage(FirmListPage(isBack: true), context);
}
//
@ -435,32 +494,32 @@ class HomePageState extends RouteAwareState<HomePage>
return Positioned(
top: topOffset,
right: 12,
right: 8,
child: Row(
children: [
GestureDetector(
onTap: startScan,
child: Container(
width: 38,
height: 38,
width: 30,
height: 30,
alignment: Alignment.center,
child: Image.asset(
"assets/icon-apps/home_saoyisao.png",
width: 22,
height: 22,
width: 20,
height: 20,
),
),
),
GestureDetector(
onTap: _joinFirm,
child: Container(
width: 38,
height: 38,
width: 30,
height: 30,
alignment: Alignment.center,
child: Image.asset(
"assets/icon-apps/home_add.png",
width: 22,
height: 22,
width: 20,
height: 20,
),
),
),
@ -568,60 +627,71 @@ class HomePageState extends RouteAwareState<HomePage>
BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)),
],
),
child: widget.isChooseFirm
? Column(
children: [
// 4
Row(
children: List.generate(4, (i) {
final idx = i;
if (idx < buttonInfos.length) {
return Expanded(
child: Center(child: _buildIconButton(buttonInfos[idx], context)),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
child:
widget.isChooseFirm
? Column(
children: [
// 4
Row(
children: List.generate(4, (i) {
final idx = i;
if (idx < buttonInfos.length) {
return Expanded(
child: Center(
child: _buildIconButton(buttonInfos[idx], context),
),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
if (hasSecondRow) const SizedBox(height: 20),
if (hasSecondRow) const SizedBox(height: 20),
// 4
if (hasSecondRow)
Row(
children: List.generate(4, (i) {
final idx = 4 + i; // 4
if (idx < buttonInfos.length) {
return Expanded(
child: Center(child: _buildIconButton(buttonInfos[idx], context)),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
],
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/ico1.png', width: 100, height: 100),
const SizedBox(height: 10),
CustomButton(
text: '点击入职企业',
onPressed: () {
pushPage(FirmListPage(isBack: true,), context);
},
)
],
),
),
// 4
if (hasSecondRow)
Row(
children: List.generate(4, (i) {
final idx = 4 + i; // 4
if (idx < buttonInfos.length) {
return Expanded(
child: Center(
child: _buildIconButton(
buttonInfos[idx],
context,
),
),
);
} else {
return const Expanded(child: SizedBox());
}
}),
),
],
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/ico1.png',
width: 100,
height: 100,
),
const SizedBox(height: 10),
CustomButton(
text: '点击入职企业',
onPressed: () {
pushPage(FirmListPage(isBack: true), context);
},
),
],
),
),
);
}
//
Widget _buildIconButton(Map<String, dynamic> info, BuildContext context) {
return GestureDetector(
@ -674,6 +744,7 @@ class HomePageState extends RouteAwareState<HomePage>
case "口门门禁":
break;
case "入港培训":
pushPage(StudyTabListPage(), context);
break;
default:
break;

View File

@ -15,6 +15,8 @@ import 'package:qhd_prevention/services/SessionService.dart';
enum ScanType {
///
Onboarding,
//
SignIn,
}
class ScanPage extends StatefulWidget {
@ -60,10 +62,13 @@ class _ScanPageState extends State<ScanPage> {
}
void _showResult(String result) {
if (widget.type == ScanType.Onboarding) {
final json = jsonDecode(result);
Navigator.pop(context, json);
}
final json = jsonDecode(result);
Navigator.pop(context, json);
// if (widget.type == ScanType.Onboarding) {
// //{\"corpinfoName\":\"相关方测试单位\",\"corpinfoId\":\"74f140f388ec40f091e8e7afc6f1175e\",\"id\":\"2008789163605929984\"}
// final json = jsonDecode(result);
// Navigator.pop(context, json);
// }
}

View File

@ -79,7 +79,7 @@ class _UnitQuitApplyPageState extends State<UnitQuitApplyPage> {
const Divider(),
ItemListWidget.singleLineTitleText(
label: '申请人',
text: SessionService.instance.name,
text: SessionService.instance.userData?.name,
isEditable: false,
),
const Divider(),

View File

@ -57,7 +57,7 @@ class _UnitTabPageState extends RouteAwareState<UnitTabPage> {
const double iconSectionHeight = 150.0;
const double iconOverlapBanner = 30.0; // banner
return PopScope(
canPop: false,
canPop: true,
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: MyAppbar(

View File

@ -41,8 +41,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
late List<Widget> _pages;
late List<bool> _tabVisibility; // Tab
final List<String> _titles = ['首页', '通知', '我的'];
@override
void initState() {
super.initState();
@ -55,7 +53,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
MinePage(key: _mineKey, isChooseFirm: widget.isChooseFirm),
];
//
// HeartbeatService().start();
HeartbeatService().start();
}
@override

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/constants/app_enums.dart';
import 'package:qhd_prevention/customWidget/MultiDictValuesPicker.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/item_list_widget.dart';
@ -9,13 +10,14 @@ import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/modules/basic_info_api.dart';
import 'package:qhd_prevention/pages/home/certificate/certificate_list_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'dart:io';
import 'certificate_list_page.dart';
enum CertifitcateTypeMode {
//
specialEquipment('tzsbczry', 2),
@ -95,11 +97,14 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
if (pd['type'] == CertifitcateTypeMode.principal.value) {
_chooseMode = CertifitcateTypeMode.principal;
}
if (pd['type'] == CertifitcateTypeMode.safetyManager.value) {
_chooseMode = CertifitcateTypeMode.safetyManager;
}
if (pd['type'] == CertifitcateTypeMode.specialEquipment.value) {
_chooseMode = CertifitcateTypeMode.specialEquipment;
for (Map item in _equipmentList) {
if (item['dictValue'] == pd['assignmentOperatingItemsCode']) {
_chooseEquipmentTypeList = item['children'];
_chooseEquipmentTypeList = item['children'] ?? [];
break;
}
}
@ -108,7 +113,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
_chooseMode = CertifitcateTypeMode.specialWorker;
for (Map item in _specialWorkList) {
if (item['dictValue'] == pd['industryCategoryCode']) {
_chooseWorkTypeList = item['children'];
_chooseWorkTypeList = item['children'] ?? [];
break;
}
}
@ -295,7 +300,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
pd['userCertificateId'] = result['data']['foreignKey'] ?? '';
}
return true;
}else{
} else {
ToastUtil.showNormal(context, result['errMessage'] ?? '保存失败');
}
} catch (e) {
@ -323,10 +328,10 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
ToastUtil.showNormal(context, '请填写证书编号');
return false;
}
if (_chooseMode == CertifitcateTypeMode.principal ||
_chooseMode == CertifitcateTypeMode.safetyManager) {
if (!(_chooseMode == CertifitcateTypeMode.specialEquipment ||
_chooseMode == CertifitcateTypeMode.specialWorker)) {
if (!FormUtils.hasValue(pd, 'postName')) {
ToastUtil.showNormal(context, '填写岗位名称');
ToastUtil.showNormal(context, '选择岗位');
return false;
}
}
@ -366,10 +371,14 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
ToastUtil.showNormal(context, '请选择有效期结束时间');
return false;
}
if (!FormUtils.hasValue(pd, 'reviewDate')) {
ToastUtil.showNormal(context, '请选择复审时间');
return false;
if (_chooseMode == CertifitcateTypeMode.specialWorker ||
_chooseMode == CertifitcateTypeMode.specialEquipment) {
if (!FormUtils.hasValue(pd, 'reviewDate')) {
ToastUtil.showNormal(context, '请选择复审时间');
return false;
}
}
return true;
}
@ -388,7 +397,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
}
return Scaffold(
appBar: MyAppbar(
title: widget.model == CertifitcateEditMode.edit ? '证书信息添加' : '查看信息',
title: widget.model == CertifitcateEditMode.add ? '证书信息添加' : '查看信息',
isBack: true,
),
body: SafeArea(
@ -477,17 +486,35 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
},
),
const Divider(),
if (!(_chooseMode == CertifitcateTypeMode.specialEquipment ||
_chooseMode == CertifitcateTypeMode.specialWorker)) ...[
ItemListWidget.singleLineTitleText(
if (_chooseMode == CertifitcateTypeMode.principal ||
_chooseMode == CertifitcateTypeMode.safetyManager) ...[
ItemListWidget.selectableLineTitleTextRightButton(
label: '岗位名称:',
isRequired: _isEdit,
text: pd['postName'] ?? '',
hintText: '请输入岗位名称',
isEditable: _isEdit,
onChanged: (value) {
pd['postName'] = value;
text: pd['postName'] ?? '请选择',
isRequired: _isEdit,
onTap: () async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => MultiDictValuesPicker(
title: '岗位名称',
dictType:
_chooseMode == CertifitcateTypeMode.principal
? 'zyfzrgwmc0000'
: 'aqscglrygwmc0000',
allowSelectParent: false,
onSelected: (id, name, extraData) {
setState(() {
pd['postId'] = extraData?['dictValue'];
pd['postName'] = name;
});
},
),
);
},
),
@ -510,7 +537,35 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
if (_chooseMode == CertifitcateTypeMode.specialWorker) ...[
ItemListWidget.selectableLineTitleTextRightButton(
label: '行业类型:',
label: '操作项目:',
isEditable: _isEdit,
text: pd['industryOperatingItemsName'] ?? '请选择',
isRequired: _isEdit,
onTap: () async {
if (_chooseWorkTypeList.isEmpty) {
ToastUtil.showNormal(context, '暂无操作项目');
return;
}
final found = await BottomPicker.show(
context,
items: _chooseWorkTypeList,
itemBuilder:
(i) => Text(
i['dictLabel']!,
textAlign: TextAlign.center,
),
initialIndex: 0,
);
//FocusHelper.clearFocus(context);
if (found != null) {
pd['industryOperatingItemsName'] = found['dictLabel'];
pd['industryOperatingItemsCode'] = found['dictValue'];
}
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '行业类别:',
isEditable: _isEdit,
text: pd['industryCategoryName'] ?? '请选择',
isRequired: _isEdit,
@ -530,7 +585,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
setState(() {
pd['industryCategoryName'] = found['dictLabel'];
pd['industryCategoryCode'] = found['dictValue'];
_chooseWorkTypeList = found['children'];
_chooseWorkTypeList = found['children'] ?? [];
pd['industryOperatingItemsName'] = '';
pd['industryOperatingItemsCode'] = '';
});
@ -538,30 +593,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '操作项目:',
isEditable: _isEdit,
text: pd['industryOperatingItemsName'] ?? '请选择',
isRequired: _isEdit,
onTap: () async {
final found = await BottomPicker.show(
context,
items: _chooseWorkTypeList,
itemBuilder:
(i) => Text(
i['dictLabel']!,
textAlign: TextAlign.center,
),
initialIndex: 0,
);
//FocusHelper.clearFocus(context);
if (found != null) {
pd['industryOperatingItemsName'] = found['dictLabel'];
pd['industryOperatingItemsCode'] = found['dictValue'];
}
},
),
const Divider(),
],
if (_chooseMode == CertifitcateTypeMode.specialEquipment) ...[
ItemListWidget.selectableLineTitleTextRightButton(
@ -585,7 +617,7 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
setState(() {
pd['assignmentOperatingItemsName'] = found['dictLabel'];
pd['assignmentOperatingItemsCode'] = found['dictValue'];
_chooseEquipmentTypeList = found['children'];
_chooseEquipmentTypeList = found['children'] ?? [];
pd['assignmentCategoryName'] = '';
pd['assignmentCategoryCode'] = '';
});
@ -594,11 +626,15 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '行业类型',
label: '作业类别',
isEditable: _isEdit,
text: pd['assignmentCategoryName'] ?? '请选择',
isRequired: _isEdit,
onTap: () async {
if (_chooseEquipmentTypeList.isEmpty) {
ToastUtil.showNormal(context, '暂无行业类型');
return;
}
final found = await BottomPicker.show(
context,
items: _chooseEquipmentTypeList,
@ -652,6 +688,14 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
pd['certificateDateStart'] = DateFormat(
'yyyy-MM-dd',
).format(picked);
//
if (FormUtils.hasValue(pd, 'certificateDateEnd') &&
isAfterStr(
pd['certificateDateStart'],
pd['certificateDateEnd'],
)) {
pd['certificateDateEnd'] = '';
}
});
//FocusHelper.clearFocus(context);
}
@ -663,10 +707,12 @@ class _CertificateDetailPageState extends State<CertificateDetailPage> {
isEditable: _isEdit,
text: pd['certificateDateEnd'] ?? '请选择',
isRequired: _isEdit,
onTap: () async {
DateTime? picked = await BottomDateTimePicker.showDate(
mode: BottomPickerMode.date,
context,
minTimeStr: pd['certificateDateStart'],
);
if (picked != null) {
setState(() {

View File

@ -6,7 +6,6 @@ import 'package:qhd_prevention/customWidget/item_list_widget.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/http/modules/safety_check_api.dart';
import 'package:qhd_prevention/pages/home/certificate/certificate_detail_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
@ -14,6 +13,8 @@ import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/search_bar_widget.dart';
import 'certificate_detail_page.dart';
enum CertifitcateEditMode { add, detail, edit }
class CertificateListPage extends StatefulWidget {
@ -72,19 +73,23 @@ class _CertificateListPageState extends State<CertificateListPage> {
final response = await CertificateApi.getCertificateList(data);
setState(() {
final newData = response['data'] as List<dynamic>? ?? [];
if (currentPage == 1) {
list = newData;
} else {
list.addAll(newData);
if (response['success']) {
final newData = response['data'] as List<dynamic>? ?? [];
if (currentPage == 1) {
list = newData;
} else {
list.addAll(newData);
}
totalPage = response['totalPage'] ?? 1;
}else{
ToastUtil.showNormal(context, response['errMessage']);
}
totalPage = response['totalPage'] ?? 1;
isLoading = false;
});
} catch (e) {
print('Error fetching data: $e');
setState(() => isLoading = false);
ToastUtil.showNormal(context, '获取列表失败');
}
}
@ -110,26 +115,38 @@ class _CertificateListPageState extends State<CertificateListPage> {
///
Future<void> _deleteCertificate(Map<String, dynamic> item) async {
await CustomAlertDialog.showAlert(
context,
title: '温馨提示',
content: '确定要删除此证书吗?',
onConfirm: () async {
try {
final response = await CertificateApi.deleteCertificate(item['id']);
if (response['success'] == true) {
ToastUtil.showNormal(context, '删除成功');
currentPage = 1;
_fetchData();
} else {
ToastUtil.showNormal(context, response['errMessage'] ?? '删除失败');
LoadingDialogHelper.show();
final result = await CertificateApi.deleteCertificateVerify(item['userId'] ?? '');
LoadingDialogHelper.dismiss();
if (result['success']) {
final dataList = result['data'] ?? [];
final projectList = dataList.map((item) {
return item['projectName'];
}).toList();
await CustomAlertDialog.showConfirm(
context,
title: '提示',
content: '${SessionService.instance.userData?.name}】有正在进行的项目,包含【${projectList.join('')}】确定删除吗?\n删除可能会对正在进行的项目造成异常状态,请谨慎操作。',
confirmText: '删除',
onConfirm: () async {
try {
final response = await CertificateApi.deleteCertificate(item['id']);
if (response['success'] == true) {
ToastUtil.showNormal(context, '删除成功');
currentPage = 1;
_fetchData();
} else {
ToastUtil.showNormal(context, response['errMessage'] ?? '删除失败');
}
} catch (e) {
print('Error fetching data: $e');
ToastUtil.showNormal(context, '删除失败');
}
} catch (e) {
print('Error fetching data: $e');
ToastUtil.showNormal(context, '删除失败');
}
},
);
);
}
}
Widget _buildListItem(Map<String, dynamic> item) {

View File

@ -47,7 +47,7 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage>
String _errMsg = '';
bool get _isManualMode =>
(widget.mode == FaceMode.setUpdata || widget.mode == FaceMode.initSave);
(widget.mode == FaceMode.setUpdata || widget.mode == FaceMode.initSave || widget.mode == FaceMode.study);
bool _isInitializing = false;
bool _isTaking = false;
@ -322,11 +322,12 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage>
} catch (_) {}
final XFile pic = await _cameraController!.takePicture();
if (widget.mode == FaceMode.initSave) {
if (widget.mode == FaceMode.initSave || widget.mode == FaceMode.study) {
_hideLoading();
_onSuccess(pic.path);
return;
}
final raw = await FileApi.uploadFile(
pic.path,
UploadFileType.facialRecognitionImages,
@ -372,7 +373,7 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage>
}
Future.delayed(const Duration(milliseconds: 800), () {
if (mounted) Navigator.of(context).pop(true);
if (mounted) Navigator.of(context).pop(filePath);
});
}

View File

@ -5,7 +5,6 @@ import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/certificate/certificate_list_page.dart';
import 'package:qhd_prevention/pages/home/scan_page.dart';
import 'package:qhd_prevention/pages/home/userinfo_page.dart';
import 'package:qhd_prevention/pages/mine/face_ecognition_page.dart';
@ -19,6 +18,8 @@ import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'certificate/certificate_list_page.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key, required this.isChooseFirm});
final bool isChooseFirm;
@ -50,11 +51,24 @@ class MinePageState extends State<MinePage> {
void initState() {
// TODO: implement initState
super.initState();
name = SessionService.instance.name ?? "登录/注册";
phone = SessionService.instance.phone ?? "";
}
@override
_getUserInfo();
}
//
Future<void> _getUserInfo() async {
final res = await BasicInfoApi.getUserMessage(
'${SessionService.instance.accountId}',
);
if (res['success'] == true) {
final data = res['data'] as Map<String, dynamic>;
SessionService.instance.updateFromApiResponse(data);
await SessionService.instance.saveToPrefs();
setState(() {
name = SessionService.instance.userData?.name ?? "登录/注册";
phone = SessionService.instance.userData?.phone ?? "";
});
}
}
@override
Widget build(BuildContext context) {
final double headerHeight = 300.0;
@ -132,7 +146,8 @@ class MinePageState extends State<MinePage> {
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Image.asset(
child:
Image.asset(
"assets/images/my_bg.png",
width: MediaQuery.of(context).size.width, //
fit: BoxFit.cover,
@ -228,6 +243,8 @@ class MinePageState extends State<MinePage> {
}
Widget _buildSloganSection() {
final headerUrl = SessionService.instance.userData?.userAvatarUrl ?? '';
return Container(
margin: EdgeInsets.fromLTRB(0, 100, 0, 0),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
@ -237,10 +254,16 @@ class MinePageState extends State<MinePage> {
children: [
Row(
children: [
CircleAvatar(
backgroundImage: AssetImage("assets/images/my_bg.png"),
radius: 30,
),
headerUrl.isEmpty
? const CircleAvatar(
backgroundImage: AssetImage("assets/images/my_bg.png"),
radius: 30,
)
: CircleAvatar(
backgroundImage: NetworkImage(ApiService.baseImgPath + headerUrl),
radius: 30,
),
const SizedBox(width: 16),
Text(
name,
@ -279,11 +302,12 @@ class MinePageState extends State<MinePage> {
title: "我的信息",
icon: "assets/images/ico9.png",
value: notificationsEnabled,
onChanged: (value) {
pushPage(
onChanged: (value) async {
await pushPage(
FullUserinfoPage(isEidt: false, isChooseFirm: true),
context,
);
},
),
@ -339,12 +363,15 @@ class MinePageState extends State<MinePage> {
},
),
_buildSettingItem(
title: "问题反馈",
icon: "assets/images/ico13.png",
value: passwordChanged,
onChanged: (value) => setState(() => passwordChanged = value!),
),
// _buildSettingItem(
// title: "问题反馈",
// icon: "assets/images/ico13.png",
// value: passwordChanged,
// onChanged: (value) {
// ToastUtil.showNormal(context, '需求待定');
// // pushPage(FeedbackPage(), context);
// },
// ),
// const Divider(height: 1, indent: 60),
_buildSettingItem(
@ -365,7 +392,7 @@ class MinePageState extends State<MinePage> {
if (widget.isChooseFirm)
_buildSettingItem(
title: "切换账户",
icon: "assets/images/ico15.png",
icon: "assets/images/ico_switch.png",
value: logoutSelected,
onChanged: (value) {
pushPage(MineChangeFirmPage(), context);
@ -373,7 +400,7 @@ class MinePageState extends State<MinePage> {
),
_buildSettingItem(
title: "账户注销",
icon: "assets/images/ico15.png",
icon: "assets/images/ico_quit.png",
value: logoutSelected,
onChanged: (value) {
_logout();

View File

@ -6,6 +6,7 @@ import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/pages/user/login_page.dart';
import 'package:qhd_prevention/services/SessionService.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../http/ApiService.dart';
@ -131,7 +132,10 @@ class _MineSetPwdPageState extends State<MineSetPwdPage> {
final oldPwd = _oldPwdController.text.trim();
final newPwd = _newPwdController.text.trim();
final confirmPwd = _confirmPwdController.text.trim();
// if (oldPwd == newPwd) {
// ToastUtil.showNormal(context, '新密码不能喝旧密码相同');
// return;
// }
if (newPwd != confirmPwd) {
ToastUtil.showNormal(context, '新密码和确认密码两次输入的密码不一致');
return;
@ -151,11 +155,11 @@ class _MineSetPwdPageState extends State<MineSetPwdPage> {
ToastUtil.showNormal(context, '新密码必须包含大小写字母、数字和特殊符号。');
return;
}
await _changePass(oldPwd, newPwd, confirmPwd);
}
Future<void> _changePass(String oldPwd, String newPwd, String confirmPwd) async {
LoadingDialogHelper.show();
try {
passData['id'] = SessionService.instance.accountId ?? "";
passData['password'] = oldPwd;
@ -163,7 +167,7 @@ class _MineSetPwdPageState extends State<MineSetPwdPage> {
passData['confirmPassword'] = confirmPwd;
final raw = await AuthApi.changePassWord(passData);
LoadingDialogHelper.hide() ;
if (raw['success'] == true) {
ToastUtil.showNormal(context, '新密码修改成功!');
Navigator.pop(context, true);
@ -181,6 +185,7 @@ class _MineSetPwdPageState extends State<MineSetPwdPage> {
ToastUtil.showNormal(context, raw['errMessage'] ?? '请求失败,请重试');
}
} catch (e) {
LoadingDialogHelper.hide() ;
print('修改密码出错:$e');
ToastUtil.showNormal(context, '请求失败,请重试');
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/item_list_widget.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/modules/basic_info_api.dart';
@ -31,26 +32,31 @@ class _OnboardingFullPageState extends State<OnboardingFullPage> {
@override
void initState() {
super.initState();
_getDept();
}
//
Future<void> _getDept() async {
try {
final data = {
'eqCorpinfoId': widget.scanData['id'],
// 'eqParentId': widget.scanData['corpinfoId'],
};
final result = await BasicInfoApi.getDeptTree(data);
if (result['success'] == true) {
final list = result['data'] ?? [];
if (list.length > 0) {
void chooseUnitHandle() {
final data = {
'eqCorpinfoId': widget.scanData['id'],
// 'eqParentId': widget.scanData['corpinfoId'],
};
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => DepartmentPicker(
data: data,
onSelected: (id, name, data) async {
setState(() {
_deptList = list[0]['childrenList'] ?? [];
pd['departmentId'] = data['id'] ?? '';
pd['departmentName'] = data['name'] ?? '';
pd['corpinfoId'] = data['corpinfoId'] ?? '';
pd['corpinfoName'] = data['corpinfoName'] ?? '';
});
}
}
} catch (e) {}
},
),
).then((_) {});
}
//
@ -109,7 +115,7 @@ class _OnboardingFullPageState extends State<OnboardingFullPage> {
ItemListWidget.singleLineTitleText(
label: '企业名称',
isEditable: false,
text: widget.scanData['corpName'] ?? '',
text: widget.scanData['corpName'] ?? widget.scanData['corpinfoName'] ?? '',
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
@ -118,28 +124,8 @@ class _OnboardingFullPageState extends State<OnboardingFullPage> {
isEditable: true,
text: pd['departmentName'] ?? '请选择',
isRequired: true,
onTap: () async {
if (_deptList.isEmpty) {
ToastUtil.showNormal(context, '暂无部门信息');
return;
}
final found = await BottomPicker.show(
context,
items: _deptList,
itemBuilder:
(i) => Text(i['name']!, textAlign: TextAlign.center),
initialIndex: 0,
);
//FocusHelper.clearFocus(context);
if (found != null) {
setState(() {
pd['departmentId'] = found['id'];
pd['departmentName'] = found['name'];
pd['corpinfoId'] = found['corpinfoId'];
pd['corpinfoName'] = found['corpinfoName'];
});
}
onTap: () {
chooseUnitHandle();
},
),
const Divider(),

View File

@ -458,7 +458,7 @@ class _FirmListPageState extends State<FirmListPage> {
//
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 4,
horizontal: 2,
),
alignment: Alignment.center,
child:

View File

@ -181,6 +181,7 @@ class _FullUserinfoPageState extends State<FullUserinfoPage> {
await BasicInfoApi.updateUserInfo(pd, userIdCard).then((res) {
LoadingDialogHelper.hide();
if (res['success']) {
SessionService.instance.name = pd['name'];
ToastUtil.showNormal(context, '保存成功');
if (widget.isChooseFirm || _isChange) {

View File

@ -39,7 +39,7 @@ class _LoginPageState extends State<LoginPage> {
String _errorMessage = '';
bool _isLoading = false;
bool _obscurePassword = true;
bool _agreed = false;
bool _agreed = true;
String _captchaImageBase64 = '';
String _captchaIdentifier = '';
bool _rememberPassword = true;
@ -505,19 +505,20 @@ class _LoginPageState extends State<LoginPage> {
//
Widget _buildCaptchaImage() {
if (_captchaImageBase64.isEmpty) {
return Container(
width: 100,
height: 40,
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: Text(
'加载中...',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
),
return GestureDetector(
onTap: _getCaptcha,
child: Container(
width: 100,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: Text('加载中...',
style: TextStyle(color: Colors.grey, fontSize: 12)),
),
)
);
}

View File

@ -253,6 +253,7 @@ class SessionService {
//
String? token;
String? name;
String? accessTicket;
String? refreshTicket;
String? expireIn;
@ -271,7 +272,6 @@ class SessionService {
// getter -
String? get userId => userData?.userId;
String? get accountId => userData?.id;
String? get name => userData?.name;
String? get userName => userData?.username;
String? get phone => userData?.phone;
String? get email => userData?.email;
@ -293,6 +293,7 @@ class SessionService {
Future<void> clear({bool clearPrefs = true}) async {
userData = null;
token = null;
name = null;
accessTicket = null;
refreshTicket = null;
expireIn = null;
@ -316,6 +317,7 @@ class SessionService {
//
s.token = json['token'] as String?;
s.name = json['name'] as String?;
s.accessTicket = json['accessTicket'] as String?;
s.refreshTicket = json['refreshTicket'] as String?;
s.expireIn = json['expireIn'] as String?;
@ -330,6 +332,7 @@ class SessionService {
return {
'userData': userData?.toJson(),
'token': token,
'name': name,
'accessTicket': accessTicket,
'refreshTicket': refreshTicket,
'expireIn': expireIn,
@ -356,6 +359,7 @@ class SessionService {
final i = SessionService.instance;
i.userData = newSession.userData;
i.token = newSession.token;
i.name = newSession.name;
i.accessTicket = newSession.accessTicket;
i.refreshTicket = newSession.refreshTicket;
i.expireIn = newSession.expireIn;
@ -391,6 +395,8 @@ class SessionService {
// ---------- convenience setters ----------
void setToken(String t) => token = t;
void setName(String t) => name = t;
void setLoginPhone(String phone) => loginPhone = phone;
void setLoginPass(String pass) => loginPass = pass;

View File

@ -83,6 +83,7 @@ class AuthService {
};
}
} else {
StorageService.instance.remove('key.saveJoinFirmInfo');
return {
'isChooseFirm': false,
'isInfoComplete': isInfoComplete,

179
lib/tools/smart_image.dart Normal file
View File

@ -0,0 +1,179 @@
// lib/widgets/smart_image.dart
import 'package:flutter/material.dart';
/// SmartAssetImage
/// - devicePixelRatio * context.width
/// - 使 ResizeImage
/// - precacheImage/Widget
/// - gaplessPlayback
class SmartAssetImage extends StatefulWidget {
final String assetPath;
final double? height;
final double? width;
final BoxFit fit;
final Color placeholderColor;
final Duration precacheTimeout;
const SmartAssetImage({
Key? key,
required this.assetPath,
this.height,
this.width,
this.fit = BoxFit.cover,
this.placeholderColor = const Color(0xFFE8F4FD),
this.precacheTimeout = const Duration(milliseconds: 800),
}) : super(key: key);
@override
State<SmartAssetImage> createState() => _SmartAssetImageState();
}
class _SmartAssetImageState extends State<SmartAssetImage> {
ImageProvider? _provider;
bool _done = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_prepareImage();
}
Future<void> _prepareImage() async {
//
if (_done) return;
try {
final mq = MediaQuery.of(context);
final logicalWidth = widget.width ?? mq.size.width;
final devicePixelRatio = mq.devicePixelRatio;
final int targetWidth = (logicalWidth * devicePixelRatio).toInt();
// 使 ResizeImage
final provider = ResizeImage(
AssetImage(widget.assetPath),
width: targetWidth,
);
// attempt to precache but don't block too long
final precacheFuture = precacheImage(provider, context);
// optional: bound the wait time to prevent waiting forever
await precacheFuture
.timeout(widget.precacheTimeout, onTimeout: () => null);
if (mounted) {
setState(() {
_provider = provider;
_done = true;
});
}
} catch (e) {
// 退 AssetImage
if (mounted) {
setState(() {
_provider = AssetImage(widget.assetPath);
_done = true;
});
}
}
}
@override
Widget build(BuildContext context) {
final placeholder = Container(
width: widget.width ?? double.infinity,
height: widget.height,
color: widget.placeholderColor,
);
if (_provider == null) {
return placeholder;
}
return Image(
image: _provider!,
width: widget.width ?? double.infinity,
height: widget.height,
fit: widget.fit,
gaplessPlayback: true,
);
}
}
/// SmartNetworkImage ()
/// - placeholder / local placeholder
/// - precacheImage NetworkImage
class SmartNetworkImage extends StatefulWidget {
final String url;
final double? height;
final double? width;
final BoxFit fit;
final Widget? placeholder;
final Duration precacheTimeout;
const SmartNetworkImage({
Key? key,
required this.url,
this.height,
this.width,
this.fit = BoxFit.cover,
this.placeholder,
this.precacheTimeout = const Duration(milliseconds: 800),
}) : super(key: key);
@override
State<SmartNetworkImage> createState() => _SmartNetworkImageState();
}
class _SmartNetworkImageState extends State<SmartNetworkImage> {
ImageProvider? _provider;
bool _done = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_prepare();
}
Future<void> _prepare() async {
if (_done) return;
try {
final provider = NetworkImage(widget.url);
await precacheImage(provider, context).timeout(widget.precacheTimeout, onTimeout: () => null);
if (mounted) {
setState(() {
_provider = provider;
_done = true;
});
}
} catch (e) {
if (mounted) {
setState(() {
_provider = NetworkImage(widget.url); // fallback
_done = true;
});
}
}
}
@override
Widget build(BuildContext context) {
final placeholder = widget.placeholder ??
Container(
width: widget.width ?? double.infinity,
height: widget.height,
color: const Color(0xFFE8F4FD),
);
if (_provider == null) {
return placeholder;
}
return Image(
image: _provider!,
width: widget.width ?? double.infinity,
height: widget.height,
fit: widget.fit,
gaplessPlayback: true,
);
}
}

View File

@ -154,6 +154,7 @@ flutter:
- assets/js/
- assets/map/
- assets/tabbar/
- assets/study/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg