。。。。

main
hs 2025-09-17 18:05:47 +08:00
parent 6ca5a27418
commit 76d0fdbd16
10 changed files with 473 additions and 398 deletions

View File

@ -352,6 +352,13 @@ setState(() {
}
Widget _mainWidget() {
bool isShowCheck = false;
if (FormUtils.hasValue(inspectedForm, 'hiddenList')) {
List list = inspectedForm['hiddenList'];
if (list.isEmpty) {
isShowCheck = true;
}
}
return ListView(
children: [
Column(
@ -380,8 +387,9 @@ setState(() {
: false,
yesLabel: '同意',
noLabel: '申辩',
isEdit: true,
isEdit: isShowCheck ? true : false,
isRequired: false,
text: inspectedForm['INSPECTION_STATUS'] == '3' ? '同意' : '申辩',
horizontalPadding: 3,
verticalPadding: 0,
onChanged: (val) {

View File

@ -16,6 +16,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
late List<dynamic> _list = [];
late String _classId = '';
late String _post_id = '';
late Map _classInfo = {};
@override
void initState() {
super.initState();
@ -32,6 +33,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
final result = await ApiService.getClassList(_classId, _post_id);
if (result['result'] == 'success') {
final List<dynamic> newList = result['varList'] ?? [];
_classInfo = result['classInfo'] ?? {};
setState(() {
_list.addAll(newList);
});
@ -46,7 +48,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
final result = await ApiService.getVideoPermissions();
if (result['result'] == 'success') {
SessionService.instance.setStudyToken(result['token'] ?? '');
pushPage(StudyDetailPage(item, widget.studyData['STUDENT_ID'],widget.studyData), context);
pushPage(StudyDetailPage(_classInfo, item, widget.studyData['STUDENT_ID'],widget.studyData), context);
}
}
@override

View File

@ -24,10 +24,17 @@ enum TakeExamType { video_study, strengththen, list }
class StudyDetailPage extends StatefulWidget {
final Map studyDetailDetail;
final Map classInfo;
final String studentId;
final Map studyData;
const StudyDetailPage(this.studyDetailDetail, this.studentId, this.studyData,{super.key});
const StudyDetailPage(
this.classInfo,
this.studyDetailDetail,
this.studentId,
this.studyData, {
super.key,
});
@override
State<StudyDetailPage> createState() => _StudyDetailPageState();
@ -54,6 +61,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
int _currentNodeIndex = 0;
bool _hasNodes = false;
Duration _lastReported = Duration.zero;
bool _isFace = false;
//
bool _endReported = false; //
@ -69,6 +77,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
WakelockPlus.enable();
_classId = widget.studyDetailDetail['CLASS_ID'] ?? '';
_classCurriculumId = widget.studyDetailDetail['CLASSCURRICULUM_ID'] ?? '';
_isFace = widget.classInfo['ISFACE'] == '1' ? true : false;
_tabController = TabController(length: 2, vsync: this);
WidgetsBinding.instance.addPostFrameCallback((_) {
_showFaceIntro();
@ -99,16 +108,19 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}
Future<void> _showFaceIntro() async {
final ok = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。',
cancelText: '取消',
confirmText: '同意并继续',
);
if (!ok) {
Navigator.of(context).pop();
return;
if (_isFace) {
final ok = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content:
'重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。',
cancelText: '取消',
confirmText: '同意并继续',
);
if (!ok) {
Navigator.of(context).pop();
return;
}
}
}
@ -179,8 +191,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
return '$s%';
}
Future<void> _onVideoTap(
Map<String, dynamic> data,
bool hasNodes,
@ -191,9 +201,15 @@ class _StudyDetailPageState extends State<StudyDetailPage>
debugPrint('_onVideoTap ignored because a video is loading');
return;
}
//
await ApiService.fnClearUserFaceTime();
_faceTimer?.cancel();
if (_isFace) {
try {
await ApiService.fnClearUserFaceTime();
} catch (e, st) {
debugPrint('fnClearUserFaceTime error: $e\n$st');
}
_faceTimer?.cancel();
_faceTimer = null;
}
//
if (_currentVideoData != null && _lastReported > Duration.zero) {
@ -252,7 +268,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'),
);
}
} else {
ToastUtil.showNormal(context, '课件文件资源已失效,请联系管理员');
}
@ -267,59 +282,69 @@ class _StudyDetailPageState extends State<StudyDetailPage>
//
await _getVideoPlayInfo(data['VIDEOCOURSEWARE_ID']);
LoadingDialogHelper.hide();
_startFaceTimer();
if (_isFace) {
_startFaceTimer();
}
}
});
}
Future<void> _navigateFaceIfNeeded(FutureOr<void> Function() onPass) async {
if (_info?['ISFACE'] == '1') {
LoadingDialogHelper.show();
final resData = await ApiService.fnGetUserFace();
LoadingDialogHelper.hide();
final pd_data = resData['pd'];
if (FormUtils.hasValue(pd_data, 'USERAVATARURL')) {
// 退
if (!_isFace) {
await onPass();
return;
}
LoadingDialogHelper.show();
final resData = await ApiService.fnGetUserFace();
LoadingDialogHelper.hide();
final pd_data = resData['pd'];
if (FormUtils.hasValue(pd_data, 'USERAVATARURL')) {
// 退
await _exitTopRouteAndWait();
await _lockPortrait();
final passed = await pushPage<bool>(
FaceRecognitionPage(
studentId: widget.studentId,
VIDEOCOURSEWARE_ID: "",
CURRICULUM_ID: "",
CHAPTER_ID: "",
CLASS_ID: "",
mode: FaceMode.auto,
),
context,
);
await _restoreDefaultOrientations();
if (passed == true) {
LoadingDialogHelper.show();
await ApiService.fnSetUserFaceTime(_faceTime);
await onPass();
} else {
ToastUtil.showError(context, '人脸验证未通过,无法继续');
}
} else {
final ok = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '您当前还未进行人脸认证,请先进行认证',
cancelText: '取消',
);
if (ok) {
await _exitTopRouteAndWait();
await _lockPortrait();
final passed = await pushPage<bool>(
FaceRecognitionPage(studentId: widget.studentId,
VIDEOCOURSEWARE_ID:"",CURRICULUM_ID:"",
CHAPTER_ID: "",CLASS_ID: "",
mode: FaceMode.auto),
await pushPage(
const FaceRecognitionPage(
studentId: '',
VIDEOCOURSEWARE_ID: '',
CURRICULUM_ID: '',
CHAPTER_ID: '',
CLASS_ID: '',
mode: FaceMode.manual,
),
context,
);
await _restoreDefaultOrientations();
if (passed == true) {
LoadingDialogHelper.show();
await ApiService.fnSetUserFaceTime(_faceTime);
await onPass();
} else {
ToastUtil.showError(context, '人脸验证未通过,无法继续');
}
} else {
final ok = await CustomAlertDialog.showConfirm(
context,
title: '温馨提示',
content: '您当前还未进行人脸认证,请先进行认证',
cancelText: '取消',
);
if (ok) {
await _exitTopRouteAndWait();
await _lockPortrait();
await pushPage(
const FaceRecognitionPage(studentId: '',
VIDEOCOURSEWARE_ID: '',CURRICULUM_ID: '',
CHAPTER_ID: '',CLASS_ID: '',
mode: FaceMode.manual),
context,
);
await _restoreDefaultOrientations();
}
}
} else {
await onPass();
}
}
@ -471,8 +496,16 @@ class _StudyDetailPageState extends State<StudyDetailPage>
_exitTopRouteIfPresent();
});
ApiService.fnClearUserFaceTime();
_faceTimer?.cancel();
//
if (_isFace) {
try {
ApiService.fnClearUserFaceTime();
} catch (e, st) {
debugPrint('fnClearUserFaceTime error on end: $e\n$st');
}
_faceTimer?.cancel();
_faceTimer = null;
}
}
}
}
@ -484,7 +517,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}) async {
// snapshot VIDEOCOURSEWARE_ID
if (snapshot['VIDEOCOURSEWARE_ID'] == null ||
snapshot['VIDEOCOURSEWARE_ID'] == '') return;
snapshot['VIDEOCOURSEWARE_ID'] == '')
return;
//
if (_endReported && !end) return;
@ -504,7 +538,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
// snapshot['IS_VIDEO']==1
// 100% UI 100%
final isNonVideo = (snapshot['IS_VIDEO'] != null && snapshot['IS_VIDEO'].toString() == '1');
final isNonVideo =
(snapshot['IS_VIDEO'] != null &&
snapshot['IS_VIDEO'].toString() == '1');
const int maxRetries = 2;
int attempt = 0;
@ -520,7 +556,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
if (mounted) {
setState(() {
if (snapshot['IS_NODE'] == true) {
final fi = snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
final fi =
snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
final ni = snapshot['NODE_INDEX'] as int? ?? _currentNodeIndex;
if (fi >= 0 && fi < _videoList.length) {
final nodes = _videoList[fi]['nodes'] as List<dynamic>?;
@ -529,7 +566,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}
}
} else {
final fi = snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
final fi =
snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
if (fi >= 0 && fi < _videoList.length) {
_videoList[fi]['percent'] = str;
}
@ -558,15 +596,20 @@ class _StudyDetailPageState extends State<StudyDetailPage>
//
final resTraw = pd['RESOURCETIME'];
final resT = (resTraw is num) ? resTraw.toDouble() : double.tryParse('$resTraw') ?? seconds.toDouble();
final resT =
(resTraw is num)
? resTraw.toDouble()
: double.tryParse('$resTraw') ?? seconds.toDouble();
final videoTimeRaw = snapshot['VIDEOTIME'];
final videoTime = (videoTimeRaw is String)
? double.tryParse(videoTimeRaw) ?? 1.0
: (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0);
final videoTime =
(videoTimeRaw is String)
? double.tryParse(videoTimeRaw) ?? 1.0
: (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0);
final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0;
final pctDouble = comp ? 100.0 : ((resT / (videoTime > 0 ? videoTime : 1.0)) * 100.0);
final pctDouble =
comp ? 100.0 : ((resT / (videoTime > 0 ? videoTime : 1.0)) * 100.0);
//
double pctClamped = pctDouble.clamp(0.00, 100.00);
// 2
@ -621,83 +664,10 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}
}
///
Future<void> _startExam(Map resData) async {
Map pd = resData['pd'] ?? {};
Map paper = resData['paper'] ?? {};
setState(() {
_loading = true;
});
final arguments = {
'STAGEEXAMPAPERINPUT_ID': paper['STAGEEXAMPAPERINPUT_ID'] ?? '',
'STAGEEXAMPAPER_ID': paper['STAGEEXAMPAPER_ID'] ?? '',
'CLASS_ID': _classId,
'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId,
'NUMBEROFEXAMS': pd['NUMBEROFEXAMS'] ?? '',
};
print('--_startExam data---$arguments');
final data = await ApiService.getStartExam(arguments);
setState(() {
_loading = false;
});
if (data['result'] == 'success') {
pushPage(
TakeExamPage(
examInfo: {
'CLASS_ID': _classId,
'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId,
'STRENGTHEN_PAPER_QUESTION_ID':
paper['STAGEEXAMPAPERINPUT_ID'] ?? '',
...data,
},
examType: TakeExamType.video_study, jumpType: 2,
),
context,
);
} else {
ToastUtil.showError(context, '请求错误');
}
}
void _exitTopRouteIfPresent() {
final route = ModalRoute.of(context);
if (route == null) return;
// route
if (!route.isCurrent) {
// pop
try {
Navigator.of(context).maybePop();
} catch (_) {
//
}
}
}
/// 退退
Future<void> _exitTopRouteAndWait({int waitMs = 300}) async {
_exitTopRouteIfPresent();
await Future.delayed(Duration(milliseconds: waitMs));
}
///
Future<void> _lockPortrait() async {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
}
///
Future<void> _restoreDefaultOrientations() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
void _startFaceTimer() {
//
if (!_isFace) return;
_faceTimer = Timer.periodic(Duration(seconds: _faceTime), (_) async {
final res = await ApiService.fnGetUserFaceTime();
final isPlaying = _videoController?.value.isPlaying == true;
@ -714,6 +684,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}
Future<void> _showFaceAuthOnce() async {
//
if (!_isFace) return;
// cancel
_faceTimer?.cancel();
_faceTimer = null;
@ -723,10 +695,14 @@ class _StudyDetailPageState extends State<StudyDetailPage>
await _lockPortrait();
final passed = await pushPage<bool>(
FaceRecognitionPage(studentId: widget.studentId,
VIDEOCOURSEWARE_ID: "",CURRICULUM_ID: "",
CHAPTER_ID: "",CLASS_ID: "",
mode: FaceMode.auto),
FaceRecognitionPage(
studentId: widget.studentId,
VIDEOCOURSEWARE_ID: "",
CURRICULUM_ID: "",
CHAPTER_ID: "",
CLASS_ID: "",
mode: FaceMode.auto,
),
context,
);
@ -768,7 +744,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
debugPrint('Error resuming playback after face auth: $e\n$st');
}
//
_startFaceTimer();
if (_isFace) {
_startFaceTimer();
}
} else {
ToastUtil.showError(context, '人脸验证未通过,无法继续');
if (_videoController != null) {
@ -785,6 +763,82 @@ class _StudyDetailPageState extends State<StudyDetailPage>
}
}
///
Future<void> _startExam(Map resData) async {
Map pd = resData['pd'] ?? {};
Map paper = resData['paper'] ?? {};
setState(() {
_loading = true;
});
final arguments = {
'STAGEEXAMPAPERINPUT_ID': paper['STAGEEXAMPAPERINPUT_ID'] ?? '',
'STAGEEXAMPAPER_ID': paper['STAGEEXAMPAPER_ID'] ?? '',
'CLASS_ID': _classId,
'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId,
'NUMBEROFEXAMS': pd['NUMBEROFEXAMS'] ?? '',
};
print('--_startExam data---$arguments');
final data = await ApiService.getStartExam(arguments);
setState(() {
_loading = false;
});
if (data['result'] == 'success') {
pushPage(
TakeExamPage(
examInfo: {
'CLASS_ID': _classId,
'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId,
'STRENGTHEN_PAPER_QUESTION_ID':
paper['STAGEEXAMPAPERINPUT_ID'] ?? '',
...data,
},
examType: TakeExamType.video_study,
jumpType: 3,
),
context,
);
} else {
ToastUtil.showError(context, '请求错误');
}
}
void _exitTopRouteIfPresent() {
final route = ModalRoute.of(context);
if (route == null) return;
// route
if (!route.isCurrent) {
// pop
try {
Navigator.of(context).maybePop();
} catch (_) {
//
}
}
}
/// 退退
Future<void> _exitTopRouteAndWait({int waitMs = 300}) async {
_exitTopRouteIfPresent();
await Future.delayed(Duration(milliseconds: waitMs));
}
///
Future<void> _lockPortrait() async {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
}
///
Future<void> _restoreDefaultOrientations() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
Widget _buildVideoOrCover(double containerW, double containerH) {
final c = _videoController;
if (c != null && c.value.isInitialized) {
@ -896,9 +950,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
const SizedBox(width: 5),
Text(
(item['NAME'] ?? '').toString(),
style: const TextStyle(
fontSize: 15,
),
style: const TextStyle(fontSize: 15),
),
],
),

View File

@ -116,9 +116,6 @@ class _TakeExamPageState extends State<TakeExamPage> {
content: '您无考试次数!',
);
if (ok) {
if (widget.jumpType == 2) {
Navigator.pop(context);
}
Navigator.pop(context);
};
}
@ -235,10 +232,17 @@ class _TakeExamPageState extends State<TakeExamPage> {
content:
passed
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再 厉!',
cancelText: '',
confirmText: '确定',
);
if (widget.jumpType == 2) {
Navigator.pop(context);
}
if (widget.jumpType == 3) {
Navigator.pop(context);
Navigator.pop(context);
}
Navigator.of(context).pop();
}
}

View File

@ -68,8 +68,8 @@ class MeasuresListWidget extends StatelessWidget {
//
double col0Fixed = 40; //
double col2Fixed = 80; //
double col3Fixed = 80; //
double col2Fixed = 70; //
double col3Fixed = 60; //
// 4isAllowEdit == true40
final showCol3 = !isAllowEdit;
@ -112,7 +112,8 @@ class MeasuresListWidget extends StatelessWidget {
}
return Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
// top
defaultVerticalAlignment: TableCellVerticalAlignment.top,
columnWidths: columnWidths,
border: TableBorder(
horizontalInside: BorderSide(color: Colors.grey.shade300),
@ -123,8 +124,8 @@ class MeasuresListWidget extends StatelessWidget {
TableRow(
decoration: BoxDecoration(color: Colors.grey.shade100),
children: [
const Padding(
padding: EdgeInsets.all(2),
Padding(
padding: const EdgeInsets.all(6),
child: Center(
child: Text(
'序号',
@ -132,8 +133,8 @@ class MeasuresListWidget extends StatelessWidget {
),
),
),
const Padding(
padding: EdgeInsets.all(8),
Padding(
padding: const EdgeInsets.all(8),
child: Center(
child: Text(
'安全措施',
@ -142,13 +143,14 @@ class MeasuresListWidget extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.all(2),
padding: const EdgeInsets.all(6),
child: Center(
child: Text(
isAllowEdit ? '操作' : '是否\n涉及',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
@ -168,41 +170,48 @@ class MeasuresListWidget extends StatelessWidget {
for (var item in measuresList)
TableRow(
children: [
//
Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(6),
child: Center(
child: Text('${measuresList.indexOf(item) + 1}'),
child:
Text('${measuresList.indexOf(item) + 1}'),
),
),
// + +
Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
//
Text(
item['PROTECTIVE_MEASURES'] as String? ?? '',
softWrap: true,
),
// 14 +
for (var i = 1; i <= 4; i++)
if ((item['QUESTION$i'] as String?)
?.isNotEmpty ??
?.isNotEmpty ??
false)
_buildQnA(item, i),
//
if (item.containsKey('IMG_PATH') &&
(item['IMG_PATH'] as String).isNotEmpty &&
isShowSign)
Row(
children: [
..._buildImageRows(
context,
(item['IMG_PATH'] as String).split(','),
'',
8,
),
],
Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
children: [
..._buildImageRows(
context,
(item['IMG_PATH'] as String).split(','),
'',
8,
),
],
),
),
],
),
@ -210,92 +219,93 @@ class MeasuresListWidget extends StatelessWidget {
// / +
Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
isAllowEdit
? TextButton(
onPressed: () {
onSign?.call(item);
},
child: Text(
(item['SIGN_ITEM'] ?? '')
.toString()
.isNotEmpty
? '已签字'
: '签字',
style: TextStyle(
color:
(item['SIGN_ITEM'] ?? '')
.toString()
.isNotEmpty
? Colors.grey.shade600
: Colors.blue,
),
),
)
: Text(
(item['STATUS'] as String?) == '-1'
? '不涉及'
: '涉及',
style: TextStyle(
color:
(item['STATUS'] as String?) == '-1'
? Colors.black
: Colors.black,
),
onPressed: () {
onSign?.call(item);
},
child: Text(
(item['SIGN_ITEM'] ?? '')
.toString()
.isNotEmpty
? '已签字'
: '签字',
style: TextStyle(
color: (item['SIGN_ITEM'] ?? '')
.toString()
.isNotEmpty
? Colors.grey.shade600
: Colors.blue,
),
),
)
: Text(
(item['STATUS'] as String?) == '-1'
? '不涉及'
: '涉及',
style: const TextStyle(
color: Colors.black,
),
),
],
),
),
//
if (!isAllowEdit)
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (item.containsKey('SIGN_PATH') &&
(item['SIGN_PATH'] as String).isNotEmpty)
...((item['SIGN_PATH'] as String)
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)).map((path) {
final imageUrl = '$baseImgPath$path';
return Padding(
padding: const EdgeInsets.only(top: 5),
child: Center(
child: GestureDetector(
onTap: () {
presentOpaque(
SingleImageViewer(
imageUrl: imageUrl,
),
context,
);
},
child: Image.network(
imageUrl,
width: 40,
height: 40,
fit: BoxFit.fill,
errorBuilder:
(_, __, ___) => Container(
width: 40,
height: 40,
color: Colors.grey.shade200,
child: const Icon(
Icons.broken_image,
size: 18,
color: Colors.grey,
),
Padding(
padding: const EdgeInsets.all(6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (item.containsKey('SIGN_PATH') &&
(item['SIGN_PATH'] as String).isNotEmpty)
...((item['SIGN_PATH'] as String)
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)).map((path) {
final imageUrl = '$baseImgPath$path';
return Padding(
padding: const EdgeInsets.only(top: 5),
child: Center(
child: GestureDetector(
onTap: () {
presentOpaque(
SingleImageViewer(
imageUrl: imageUrl,
),
context,
);
},
child: Image.network(
imageUrl,
width: 40,
height: 40,
fit: BoxFit.fill,
errorBuilder:
(_, __, ___) =>
Container(
width: 40,
height: 40,
color: Colors.grey.shade200,
child: const Icon(
Icons.broken_image,
size: 18,
color: Colors.grey,
),
),
),
),
),
),
);
}).toList(),
],
);
}).toList(),
],
),
),
],
),
@ -319,39 +329,46 @@ class MeasuresListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, //
children: [
Expanded(
flex: 2,
child: Text(
'$question:',
style: const TextStyle(fontWeight: FontWeight.w800),
child: Container(
padding: const EdgeInsets.only(right: 8), //
child: Text(
'$question:',
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis, // visible null
style: const TextStyle(fontWeight: FontWeight.w800),
),
),
),
Expanded(
flex: 1,
child: TextFormField(
initialValue: answer,
textAlign: TextAlign.center,
//
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 4,
),
//
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
child: SizedBox(
child: TextFormField(
initialValue: answer,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 4,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300),
),
),
onChanged: (val) {
item[key] = val;
},
),
onChanged: (val) {
item[key] = val;
},
),
),
],
@ -359,25 +376,27 @@ class MeasuresListWidget extends StatelessWidget {
);
}
//
//
return Padding(
padding: const EdgeInsets.only(top: 8),
child: Column(
padding: const EdgeInsets.only(top: 8, bottom: 6),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'$question:',
style: const TextStyle(fontWeight: FontWeight.w800),
),
Text(answer.isNotEmpty ? answer : '0'),
],
Expanded(
flex: 3,
child: Text(
'$question:',
style: const TextStyle(fontWeight: FontWeight.w800),
softWrap: true,
),
),
const SizedBox(width: 12),
Expanded(
flex: 1,
child: Text(
answer.isNotEmpty ? answer : '0',
textAlign: TextAlign.right,
),
),
const Divider(),
],
),
);
@ -385,11 +404,11 @@ class MeasuresListWidget extends StatelessWidget {
/// +
List<Widget> _buildImageRows(
BuildContext context,
List<String> paths,
String time,
double right,
) {
BuildContext context,
List<String> paths,
String time,
double right,
) {
if (paths.isEmpty) return [];
const int imagesPerRow = 4;
@ -423,21 +442,19 @@ class MeasuresListWidget extends StatelessWidget {
fit: BoxFit.fill,
errorBuilder:
(_, __, ___) => Container(
width: imageSize,
height: imageSize,
color: Colors.grey.shade200,
child: const Icon(
Icons.broken_image,
size: 18,
color: Colors.grey,
),
),
width: imageSize,
height: imageSize,
color: Colors.grey.shade200,
child: const Icon(
Icons.broken_image,
size: 18,
color: Colors.grey,
),
),
),
),
if (i != rowPaths.length - 1) SizedBox(width: spacing),
],
// 使 Expanded
// Expanded(child: SizedBox()),
],
),
);
@ -445,6 +462,7 @@ class MeasuresListWidget extends StatelessWidget {
}
}
///
class OtherMeasuresWidget extends StatelessWidget {
///
@ -854,7 +872,7 @@ class SignaturesListWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -862,7 +880,7 @@ class SignaturesListWidget extends StatelessWidget {
label,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
const SizedBox(height: 2),
TextField(
controller: TextEditingController(text: descr),
maxLines: null,
@ -873,7 +891,7 @@ class SignaturesListWidget extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(5),
child: Text('$personDes: $name'),
),
for (var i = 0; i < signPaths.length; i++)
@ -897,7 +915,7 @@ class SignaturesListWidget extends StatelessWidget {
(_, __, ___) => const Icon(Icons.broken_image),
),
),
const SizedBox(width: 16),
const SizedBox(width: 10),
Expanded(
child: Text(i < signTimes.length ? signTimes[i] : ''),
),

View File

@ -167,13 +167,13 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
hintText: '请选择动火人及证书编号',
onTap: widget.onChooseHotworkUser,
),
if (FormUtils.hasValue(pd, 'WORK_USER_DEPARTMENT_NAME') &&
if (FormUtils.hasValue(pd, 'UNIT_NAME') &&
!widget.isEditable) ...[
const Divider(),
ItemListWidget.singleLineTitleText(
label: '作业单位:',
isEditable: false,
text: pd['WORK_USER_DEPARTMENT_NAME'] ?? '',
text: pd['UNIT_NAME'] ?? '',
),
],
if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') &&
@ -186,6 +186,14 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
text: pd['CONFIRM_USER_NAME'] ?? '',
),
],
if (FormUtils.hasValue(pd, 'ANALYZE_TIME') && !widget.isEditable) ...[
const Divider(),
ItemListWidget.OneRowStartButtonTitle(
label: '气体分析信息:',
text: pd['ANALYZE_USER_NAME'] ?? '',
onTap: widget.onAnalyzeTap,
),
],
const Divider(),
ItemListWidget.twoRowButtonTitleText(
label: '关联其他特殊作业及安全作业票编号',
@ -388,14 +396,7 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
text: pd['VIDEONAME'] ?? '',
),
if (FormUtils.hasValue(pd, 'ANALYZE_TIME') && !widget.isEditable) ...[
const Divider(),
ItemListWidget.OneRowStartButtonTitle(
label: '气体分析信息:',
text: pd['ANALYZE_USER_NAME'] ?? '',
onTap: widget.onAnalyzeTap,
),
],
],
),
);

View File

@ -1,18 +0,0 @@
import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
class HotSafeWorkChoosePage extends StatefulWidget {
const HotSafeWorkChoosePage({super.key});
@override
State<HotSafeWorkChoosePage> createState() => _HotSafeWorkChoosePageState();
}
class _HotSafeWorkChoosePageState extends State<HotSafeWorkChoosePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: 'dong'),
);
}
}

View File

@ -364,35 +364,33 @@ class _HotWorkListPageState extends State<HotWorkListPage> {
overflow: TextOverflow.visible,
),
),
const SizedBox(width: 8),
Expanded(
child: item['APPROVE_USER_NAME'] != null
? Text(
"审批部门负责人: ${item['APPROVE_USER_NAME'] ?? ''}",
textAlign: TextAlign.right,
softWrap: true,
maxLines: null,
overflow: TextOverflow.visible,
)
: const SizedBox.shrink(),
),
],
),
Row(
children: [
Expanded(
child: Text(
"安全交底人: ${item['CONFESS_USER_NAME'] ?? ''}",
softWrap: true,
maxLines: null,
textAlign: TextAlign.right,
overflow: TextOverflow.visible,
),
),
const SizedBox(width: 8),
],
),
Row(
children: [
Expanded(
child: Text(
"接受交底人: ${item['ACCEPT_CONFESS_USER_NAME'] ?? ''}",
softWrap: true,
maxLines: null,
overflow: TextOverflow.visible,
),
),
Expanded(
child: Text(
"作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}",
textAlign: TextAlign.right,
softWrap: true,
maxLines: null,
@ -404,19 +402,10 @@ class _HotWorkListPageState extends State<HotWorkListPage> {
Row(
children: [
Expanded(
child: Text(
"作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}",
softWrap: true,
maxLines: null,
overflow: TextOverflow.visible,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
"所在单位负责人: ${item['LEADER_USER_NAME'] ?? ''}",
textAlign: TextAlign.right,
textAlign: TextAlign.left,
softWrap: true,
maxLines: null,
overflow: TextOverflow.visible,

View File

@ -65,13 +65,17 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
late List<dynamic> boardList = [
{'BOARD_MATERIAL': '', 'BOARD_SPECIFICATION': '', 'BOARD_NO': ''},
];
/// ------------------- -------------------
///
late List<dynamic> videoMonitoringList = [];
///
late List<dynamic> unitAllList = [];
///
late List<dynamic> workAreaList = [];
/// --------------------------------------
late Map<String, dynamic> signs = {};
late List<Map<String, dynamic>> measuresList = [];
@ -86,8 +90,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
final TextEditingController _relatedController = TextEditingController();
final TextEditingController _riskController = TextEditingController();
//
final Map<EditUserType, List<Map<String, dynamic>>> _personCache = {};
@ -105,7 +107,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
pd['APPLY_USER_ID'] = SessionService.instance.loginUserId;
pd['APPLY_USER_NAME'] = SessionService.instance.username;
pd['IS_CONTRACTOR_WORK'] = '0';
}
_getVideoList();
@ -132,15 +133,16 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
pd['RISK_IDENTIFICATION'] = _riskController.text.trim();
});
}
/// ---------------------------- --------------------------------
///
Future<void> _chooseVideoManager() async {
final choice = await BottomPicker.show<String>(
context,
items:
videoMonitoringList
.map((item) => item['VIDEONAME'] as String)
.toList(),
videoMonitoringList
.map((item) => item['VIDEONAME'] as String)
.toList(),
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
initialIndex: 0,
);
@ -149,7 +151,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
pd['VIDEONAME'] = choice;
Map<String, dynamic> result = videoMonitoringList.firstWhere(
(item) => item['VIDEONAME'] == choice,
(item) => item['VIDEONAME'] == choice,
orElse: () => {}, //
);
if (FormUtils.hasValue(result, 'VIDEOMANAGER_ID')) {
@ -159,6 +161,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
});
}
}
///
Future<void> _chooseUnitManager() async {
final choice = await BottomPicker.show<String>(
@ -172,7 +175,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
pd['UNITS_NAME'] = choice;
Map<String, dynamic> result = unitAllList.firstWhere(
(item) => item['UNITS_NAME'] == choice,
(item) => item['UNITS_NAME'] == choice,
orElse: () => {}, //
);
if (FormUtils.hasValue(result, 'UNITS_ID')) {
@ -183,9 +186,8 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
}
}
///
Future<void> _showLocationHandle() async{
Future<void> _showLocationHandle() async {
if (!FormUtils.hasValue(pd, 'ELECTRONIC_FENCE_AREA_ID')) {
ToastUtil.showNormal(context, '请选择作业区域');
return;
@ -194,10 +196,11 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
setState(() {
pd['LONGITUDE'] = mapData['longitue'] ?? '';
pd['LATITUDE'] = mapData['latitude'] ?? '';
pd['LATITUDE_LONGITUDE'] = '${mapData['longitue'] ?? ''},${mapData['latitude'] ?? ''}';
pd['LATITUDE_LONGITUDE'] =
'${mapData['longitue'] ?? ''},${mapData['latitude'] ?? ''}';
});
}
///
Future<void> _getWorkArea() async {
//FocusHelper.clearFocus(context);
@ -208,18 +211,19 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
backgroundColor: Colors.transparent,
builder:
(_) => WorkAreaPicker(
onSelected: (String id, String POSITIONS, String name) {
setState(() {
pd['ELECTRONIC_FENCE_AREA_ID'] = id;
pd['POSITIONS'] = POSITIONS;
pd['PLS_NAME'] = name;
});
},
),
onSelected: (String id, String POSITIONS, String name) {
setState(() {
pd['ELECTRONIC_FENCE_AREA_ID'] = id;
pd['POSITIONS'] = POSITIONS;
pd['PLS_NAME'] = name;
});
},
),
).then((_) {
//FocusHelper.clearFocus(context);
});
}
///
Future<void> _getVideoList() async {
final result = await ApiService.getVideomanagerList();
@ -227,6 +231,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
videoMonitoringList = result['varList'] ?? [];
});
}
///
Future<void> _getUnitListAll() async {
final result = await ApiService.getUnitListAll();
@ -316,7 +321,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
isEditable: isEditable,
isClean: isClean,
onTapClean: () {
ToastUtil.showNormal(context, '已清除');
ToastUtil.showNormal(context, '已清除');
setState(() {
set_pd_USER_ID(type, '');
set_pd_USER_Name(type, '');
@ -440,9 +445,23 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
];
final level = pd['WORK_TYPE'] ?? '';
print('---level-$level');
int index = 0;
for (Map item in boardList) {
if (!FormUtils.hasValue(item, 'BOARD_MATERIAL')) {
ToastUtil.showNormal(context, '请填写盲板抽堵参数第${index+1}项材质');
return;
}
if (!FormUtils.hasValue(item, 'BOARD_SPECIFICATION')) {
ToastUtil.showNormal(context, '请填写盲板抽堵参数第${index+1}项规格');
return;
}
if (!FormUtils.hasValue(item, 'BOARD_NO')) {
ToastUtil.showNormal(context, '请填写盲板抽堵参数第${index+1}项编号');
return;
}
index += 1;
}
///
final unitRules = <EditUserType>[
EditUserType.GUARDIAN,
@ -478,7 +497,8 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
return;
}
if (pd['IS_CONTRACTOR_WORK'] == '1' && !FormUtils.hasValue(pd, 'UNITS_ID')) {
if (pd['IS_CONTRACTOR_WORK'] == '1' &&
!FormUtils.hasValue(pd, 'UNITS_ID')) {
ToastUtil.showNormal(context, '请选择承包商');
return;
}
@ -510,7 +530,8 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
pd['ACTION_USER'] = SessionService.instance.username;
pd['APPLY_STATUS'] = status;
pd['boardList'] = jsonEncode(boardList).toString();
pd['SPECIAL_WORK'] = FormUtils.hasValue(pd, 'SPECIAL_WORK') ? pd['SPECIAL_WORK'] : '';
pd['SPECIAL_WORK'] =
FormUtils.hasValue(pd, 'SPECIAL_WORK') ? pd['SPECIAL_WORK'] : '';
//
if (msg == 'add') {
pd['STEP_ID'] = status;
@ -548,8 +569,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
}
}
///
Future<void> _getData() async {
final data = await ApiService.getHomeworkFindById(

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.1.2+9
version: 2.1.2+10
environment:
sdk: ^3.7.0