。。。
|
Before Width: | Height: | Size: 305 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 529 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
'确定',
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 ?? '';
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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: {},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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'] ?? ''}', // 假设头像URL字段名为avatarUrl,根据实际情况调整
|
||||
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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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++),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(() {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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, '请求失败,请重试');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ class _FirmListPageState extends State<FirmListPage> {
|
|||
// 尽量让每个字母区域可点击,并根据启用状态改变样式
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
horizontal: 4,
|
||||
horizontal: 2,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class AuthService {
|
|||
};
|
||||
}
|
||||
} else {
|
||||
StorageService.instance.remove('key.saveJoinFirmInfo');
|
||||
return {
|
||||
'isChooseFirm': false,
|
||||
'isInfoComplete': isInfoComplete,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +154,7 @@ flutter:
|
|||
- assets/js/
|
||||
- assets/map/
|
||||
- assets/tabbar/
|
||||
- assets/study/
|
||||
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
|
|
|||