。。。。
parent
6ca5a27418
commit
76d0fdbd16
|
|
@ -352,6 +352,13 @@ setState(() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _mainWidget() {
|
Widget _mainWidget() {
|
||||||
|
bool isShowCheck = false;
|
||||||
|
if (FormUtils.hasValue(inspectedForm, 'hiddenList')) {
|
||||||
|
List list = inspectedForm['hiddenList'];
|
||||||
|
if (list.isEmpty) {
|
||||||
|
isShowCheck = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return ListView(
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -380,8 +387,9 @@ setState(() {
|
||||||
: false,
|
: false,
|
||||||
yesLabel: '同意',
|
yesLabel: '同意',
|
||||||
noLabel: '申辩',
|
noLabel: '申辩',
|
||||||
isEdit: true,
|
isEdit: isShowCheck ? true : false,
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
|
text: inspectedForm['INSPECTION_STATUS'] == '3' ? '同意' : '申辩',
|
||||||
horizontalPadding: 3,
|
horizontalPadding: 3,
|
||||||
verticalPadding: 0,
|
verticalPadding: 0,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
||||||
late List<dynamic> _list = [];
|
late List<dynamic> _list = [];
|
||||||
late String _classId = '';
|
late String _classId = '';
|
||||||
late String _post_id = '';
|
late String _post_id = '';
|
||||||
|
late Map _classInfo = {};
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -32,6 +33,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
||||||
final result = await ApiService.getClassList(_classId, _post_id);
|
final result = await ApiService.getClassList(_classId, _post_id);
|
||||||
if (result['result'] == 'success') {
|
if (result['result'] == 'success') {
|
||||||
final List<dynamic> newList = result['varList'] ?? [];
|
final List<dynamic> newList = result['varList'] ?? [];
|
||||||
|
_classInfo = result['classInfo'] ?? {};
|
||||||
setState(() {
|
setState(() {
|
||||||
_list.addAll(newList);
|
_list.addAll(newList);
|
||||||
});
|
});
|
||||||
|
|
@ -46,7 +48,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
|
||||||
final result = await ApiService.getVideoPermissions();
|
final result = await ApiService.getVideoPermissions();
|
||||||
if (result['result'] == 'success') {
|
if (result['result'] == 'success') {
|
||||||
SessionService.instance.setStudyToken(result['token'] ?? '');
|
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
|
@override
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,17 @@ enum TakeExamType { video_study, strengththen, list }
|
||||||
|
|
||||||
class StudyDetailPage extends StatefulWidget {
|
class StudyDetailPage extends StatefulWidget {
|
||||||
final Map studyDetailDetail;
|
final Map studyDetailDetail;
|
||||||
|
final Map classInfo;
|
||||||
final String studentId;
|
final String studentId;
|
||||||
final Map studyData;
|
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
|
@override
|
||||||
State<StudyDetailPage> createState() => _StudyDetailPageState();
|
State<StudyDetailPage> createState() => _StudyDetailPageState();
|
||||||
|
|
@ -54,6 +61,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
int _currentNodeIndex = 0;
|
int _currentNodeIndex = 0;
|
||||||
bool _hasNodes = false;
|
bool _hasNodes = false;
|
||||||
Duration _lastReported = Duration.zero;
|
Duration _lastReported = Duration.zero;
|
||||||
|
bool _isFace = false;
|
||||||
|
|
||||||
// 上报控制相关字段
|
// 上报控制相关字段
|
||||||
bool _endReported = false; // 确保最终结束上报只执行一次
|
bool _endReported = false; // 确保最终结束上报只执行一次
|
||||||
|
|
@ -69,6 +77,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
WakelockPlus.enable();
|
WakelockPlus.enable();
|
||||||
_classId = widget.studyDetailDetail['CLASS_ID'] ?? '';
|
_classId = widget.studyDetailDetail['CLASS_ID'] ?? '';
|
||||||
_classCurriculumId = widget.studyDetailDetail['CLASSCURRICULUM_ID'] ?? '';
|
_classCurriculumId = widget.studyDetailDetail['CLASSCURRICULUM_ID'] ?? '';
|
||||||
|
_isFace = widget.classInfo['ISFACE'] == '1' ? true : false;
|
||||||
_tabController = TabController(length: 2, vsync: this);
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_showFaceIntro();
|
_showFaceIntro();
|
||||||
|
|
@ -99,16 +108,19 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showFaceIntro() async {
|
Future<void> _showFaceIntro() async {
|
||||||
final ok = await CustomAlertDialog.showConfirm(
|
if (_isFace) {
|
||||||
context,
|
final ok = await CustomAlertDialog.showConfirm(
|
||||||
title: '温馨提示',
|
context,
|
||||||
content: '重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。',
|
title: '温馨提示',
|
||||||
cancelText: '取消',
|
content:
|
||||||
confirmText: '同意并继续',
|
'重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。',
|
||||||
);
|
cancelText: '取消',
|
||||||
if (!ok) {
|
confirmText: '同意并继续',
|
||||||
Navigator.of(context).pop();
|
);
|
||||||
return;
|
if (!ok) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,8 +191,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
return '$s%';
|
return '$s%';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> _onVideoTap(
|
Future<void> _onVideoTap(
|
||||||
Map<String, dynamic> data,
|
Map<String, dynamic> data,
|
||||||
bool hasNodes,
|
bool hasNodes,
|
||||||
|
|
@ -191,9 +201,15 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
debugPrint('_onVideoTap ignored because a video is loading');
|
debugPrint('_onVideoTap ignored because a video is loading');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 后端清除人脸计时
|
if (_isFace) {
|
||||||
await ApiService.fnClearUserFaceTime();
|
try {
|
||||||
_faceTimer?.cancel();
|
await ApiService.fnClearUserFaceTime();
|
||||||
|
} catch (e, st) {
|
||||||
|
debugPrint('fnClearUserFaceTime error: $e\n$st');
|
||||||
|
}
|
||||||
|
_faceTimer?.cancel();
|
||||||
|
_faceTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果当前有视频播放且有未上报的进度,先做一次快照上报(避免丢失)
|
// 如果当前有视频播放且有未上报的进度,先做一次快照上报(避免丢失)
|
||||||
if (_currentVideoData != null && _lastReported > Duration.zero) {
|
if (_currentVideoData != null && _lastReported > Duration.zero) {
|
||||||
|
|
@ -252,7 +268,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'),
|
seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ToastUtil.showNormal(context, '课件文件资源已失效,请联系管理员');
|
ToastUtil.showNormal(context, '课件文件资源已失效,请联系管理员');
|
||||||
}
|
}
|
||||||
|
|
@ -267,59 +282,69 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
// 视频
|
// 视频
|
||||||
await _getVideoPlayInfo(data['VIDEOCOURSEWARE_ID']);
|
await _getVideoPlayInfo(data['VIDEOCOURSEWARE_ID']);
|
||||||
LoadingDialogHelper.hide();
|
LoadingDialogHelper.hide();
|
||||||
_startFaceTimer();
|
if (_isFace) {
|
||||||
|
_startFaceTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _navigateFaceIfNeeded(FutureOr<void> Function() onPass) async {
|
Future<void> _navigateFaceIfNeeded(FutureOr<void> Function() onPass) async {
|
||||||
if (_info?['ISFACE'] == '1') {
|
if (!_isFace) {
|
||||||
LoadingDialogHelper.show();
|
await onPass();
|
||||||
final resData = await ApiService.fnGetUserFace();
|
return;
|
||||||
LoadingDialogHelper.hide();
|
}
|
||||||
final pd_data = resData['pd'];
|
LoadingDialogHelper.show();
|
||||||
if (FormUtils.hasValue(pd_data, 'USERAVATARURL')) {
|
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 _exitTopRouteAndWait();
|
||||||
await _lockPortrait();
|
await _lockPortrait();
|
||||||
final passed = await pushPage<bool>(
|
await pushPage(
|
||||||
FaceRecognitionPage(studentId: widget.studentId,
|
const FaceRecognitionPage(
|
||||||
VIDEOCOURSEWARE_ID:"",CURRICULUM_ID:"",
|
studentId: '',
|
||||||
CHAPTER_ID: "",CLASS_ID: "",
|
VIDEOCOURSEWARE_ID: '',
|
||||||
mode: FaceMode.auto),
|
CURRICULUM_ID: '',
|
||||||
|
CHAPTER_ID: '',
|
||||||
|
CLASS_ID: '',
|
||||||
|
mode: FaceMode.manual,
|
||||||
|
),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
await _restoreDefaultOrientations();
|
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();
|
_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 {
|
}) async {
|
||||||
// snapshot 必须包含 VIDEOCOURSEWARE_ID
|
// snapshot 必须包含 VIDEOCOURSEWARE_ID
|
||||||
if (snapshot['VIDEOCOURSEWARE_ID'] == null ||
|
if (snapshot['VIDEOCOURSEWARE_ID'] == null ||
|
||||||
snapshot['VIDEOCOURSEWARE_ID'] == '') return;
|
snapshot['VIDEOCOURSEWARE_ID'] == '')
|
||||||
|
return;
|
||||||
|
|
||||||
// 如果已经上报结束并且当前不是结束上报,则跳过非结束上报(原逻辑)
|
// 如果已经上报结束并且当前不是结束上报,则跳过非结束上报(原逻辑)
|
||||||
if (_endReported && !end) return;
|
if (_endReported && !end) return;
|
||||||
|
|
@ -504,7 +538,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
|
|
||||||
// 如果这是“非视频课件”(通过 snapshot['IS_VIDEO']==1 标记),
|
// 如果这是“非视频课件”(通过 snapshot['IS_VIDEO']==1 标记),
|
||||||
// 则按照“学完即 100%”的规则单次上报并把 UI 设置为 100%。
|
// 则按照“学完即 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;
|
const int maxRetries = 2;
|
||||||
int attempt = 0;
|
int attempt = 0;
|
||||||
|
|
@ -520,7 +556,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (snapshot['IS_NODE'] == true) {
|
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;
|
final ni = snapshot['NODE_INDEX'] as int? ?? _currentNodeIndex;
|
||||||
if (fi >= 0 && fi < _videoList.length) {
|
if (fi >= 0 && fi < _videoList.length) {
|
||||||
final nodes = _videoList[fi]['nodes'] as List<dynamic>?;
|
final nodes = _videoList[fi]['nodes'] as List<dynamic>?;
|
||||||
|
|
@ -529,7 +566,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final fi = snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
|
final fi =
|
||||||
|
snapshot['FIRST_INDEX'] as int? ?? _currentFirstIndex;
|
||||||
if (fi >= 0 && fi < _videoList.length) {
|
if (fi >= 0 && fi < _videoList.length) {
|
||||||
_videoList[fi]['percent'] = str;
|
_videoList[fi]['percent'] = str;
|
||||||
}
|
}
|
||||||
|
|
@ -558,15 +596,20 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
|
|
||||||
// 以下为视频上报原有逻辑:解析后端返回数据并更新进度
|
// 以下为视频上报原有逻辑:解析后端返回数据并更新进度
|
||||||
final resTraw = pd['RESOURCETIME'];
|
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 videoTimeRaw = snapshot['VIDEOTIME'];
|
||||||
final videoTime = (videoTimeRaw is String)
|
final videoTime =
|
||||||
? double.tryParse(videoTimeRaw) ?? 1.0
|
(videoTimeRaw is String)
|
||||||
: (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0);
|
? double.tryParse(videoTimeRaw) ?? 1.0
|
||||||
|
: (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1.0);
|
||||||
|
|
||||||
final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 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);
|
double pctClamped = pctDouble.clamp(0.00, 100.00);
|
||||||
// 四舍五入到 2 位小数
|
// 四舍五入到 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() {
|
void _startFaceTimer() {
|
||||||
|
// 仅在人脸功能开启时启动计时器
|
||||||
|
if (!_isFace) return;
|
||||||
|
|
||||||
_faceTimer = Timer.periodic(Duration(seconds: _faceTime), (_) async {
|
_faceTimer = Timer.periodic(Duration(seconds: _faceTime), (_) async {
|
||||||
final res = await ApiService.fnGetUserFaceTime();
|
final res = await ApiService.fnGetUserFaceTime();
|
||||||
final isPlaying = _videoController?.value.isPlaying == true;
|
final isPlaying = _videoController?.value.isPlaying == true;
|
||||||
|
|
@ -714,6 +684,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showFaceAuthOnce() async {
|
Future<void> _showFaceAuthOnce() async {
|
||||||
|
// 仅在人脸功能开启时调用(
|
||||||
|
if (!_isFace) return;
|
||||||
// 先 cancel 定时器,避免跳转过程中再次触发
|
// 先 cancel 定时器,避免跳转过程中再次触发
|
||||||
_faceTimer?.cancel();
|
_faceTimer?.cancel();
|
||||||
_faceTimer = null;
|
_faceTimer = null;
|
||||||
|
|
@ -723,10 +695,14 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
await _lockPortrait();
|
await _lockPortrait();
|
||||||
|
|
||||||
final passed = await pushPage<bool>(
|
final passed = await pushPage<bool>(
|
||||||
FaceRecognitionPage(studentId: widget.studentId,
|
FaceRecognitionPage(
|
||||||
VIDEOCOURSEWARE_ID: "",CURRICULUM_ID: "",
|
studentId: widget.studentId,
|
||||||
CHAPTER_ID: "",CLASS_ID: "",
|
VIDEOCOURSEWARE_ID: "",
|
||||||
mode: FaceMode.auto),
|
CURRICULUM_ID: "",
|
||||||
|
CHAPTER_ID: "",
|
||||||
|
CLASS_ID: "",
|
||||||
|
mode: FaceMode.auto,
|
||||||
|
),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -768,7 +744,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
debugPrint('Error resuming playback after face auth: $e\n$st');
|
debugPrint('Error resuming playback after face auth: $e\n$st');
|
||||||
}
|
}
|
||||||
// 恢复人脸验证计时器
|
// 恢复人脸验证计时器
|
||||||
_startFaceTimer();
|
if (_isFace) {
|
||||||
|
_startFaceTimer();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ToastUtil.showError(context, '人脸验证未通过,无法继续');
|
ToastUtil.showError(context, '人脸验证未通过,无法继续');
|
||||||
if (_videoController != null) {
|
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) {
|
Widget _buildVideoOrCover(double containerW, double containerH) {
|
||||||
final c = _videoController;
|
final c = _videoController;
|
||||||
if (c != null && c.value.isInitialized) {
|
if (c != null && c.value.isInitialized) {
|
||||||
|
|
@ -896,9 +950,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
(item['NAME'] ?? '').toString(),
|
(item['NAME'] ?? '').toString(),
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 15),
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -116,9 +116,6 @@ class _TakeExamPageState extends State<TakeExamPage> {
|
||||||
content: '您无考试次数!',
|
content: '您无考试次数!',
|
||||||
);
|
);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (widget.jumpType == 2) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -235,10 +232,17 @@ class _TakeExamPageState extends State<TakeExamPage> {
|
||||||
content:
|
content:
|
||||||
passed
|
passed
|
||||||
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
|
? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!'
|
||||||
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!',
|
: '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再 厉!',
|
||||||
cancelText: '',
|
cancelText: '',
|
||||||
confirmText: '确定',
|
confirmText: '确定',
|
||||||
);
|
);
|
||||||
|
if (widget.jumpType == 2) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
if (widget.jumpType == 3) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,8 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
|
|
||||||
// 期望的固定列宽(可以根据需要微调)
|
// 期望的固定列宽(可以根据需要微调)
|
||||||
double col0Fixed = 40; // 第一列:序号(较窄)
|
double col0Fixed = 40; // 第一列:序号(较窄)
|
||||||
double col2Fixed = 80; // 第三列:操作(保留较宽以放按钮)
|
double col2Fixed = 70; // 第三列:操作(保留较宽以放按钮)
|
||||||
double col3Fixed = 80; // 第四列:确认人(头像列)
|
double col3Fixed = 60; // 第四列:确认人(头像列)
|
||||||
|
|
||||||
// 如果不显示第4列(isAllowEdit == true),则第4列宽为0,不计入固定总和
|
// 如果不显示第4列(isAllowEdit == true),则第4列宽为0,不计入固定总和
|
||||||
final showCol3 = !isAllowEdit;
|
final showCol3 = !isAllowEdit;
|
||||||
|
|
@ -112,7 +112,8 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Table(
|
return Table(
|
||||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
// 改为 top 对齐,保证多行内容时单元格从顶部开始布局
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.top,
|
||||||
columnWidths: columnWidths,
|
columnWidths: columnWidths,
|
||||||
border: TableBorder(
|
border: TableBorder(
|
||||||
horizontalInside: BorderSide(color: Colors.grey.shade300),
|
horizontalInside: BorderSide(color: Colors.grey.shade300),
|
||||||
|
|
@ -123,8 +124,8 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
TableRow(
|
TableRow(
|
||||||
decoration: BoxDecoration(color: Colors.grey.shade100),
|
decoration: BoxDecoration(color: Colors.grey.shade100),
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(2),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'序号',
|
'序号',
|
||||||
|
|
@ -132,8 +133,8 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'安全措施',
|
'安全措施',
|
||||||
|
|
@ -142,13 +143,14 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
isAllowEdit ? '操作' : '是否\n涉及',
|
isAllowEdit ? '操作' : '是否\n涉及',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -168,41 +170,48 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
for (var item in measuresList)
|
for (var item in measuresList)
|
||||||
TableRow(
|
TableRow(
|
||||||
children: [
|
children: [
|
||||||
|
// 序号
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('${measuresList.indexOf(item) + 1}'),
|
child:
|
||||||
|
Text('${measuresList.indexOf(item) + 1}'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 第二列:措施 + 签名 + 问题答案
|
// 第二列:措施 + 签名 + 问题答案
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 主要安全措施
|
// 主要安全措施(允许多行)
|
||||||
Text(
|
Text(
|
||||||
item['PROTECTIVE_MEASURES'] as String? ?? '',
|
item['PROTECTIVE_MEASURES'] as String? ?? '',
|
||||||
|
softWrap: true,
|
||||||
),
|
),
|
||||||
// 问题1~4 + 答案(可编辑或只读)
|
// 问题1~4 + 答案(可编辑或只读)
|
||||||
for (var i = 1; i <= 4; i++)
|
for (var i = 1; i <= 4; i++)
|
||||||
if ((item['QUESTION$i'] as String?)
|
if ((item['QUESTION$i'] as String?)
|
||||||
?.isNotEmpty ??
|
?.isNotEmpty ??
|
||||||
false)
|
false)
|
||||||
_buildQnA(item, i),
|
_buildQnA(item, i),
|
||||||
// 操作图片
|
// 操作图片
|
||||||
if (item.containsKey('IMG_PATH') &&
|
if (item.containsKey('IMG_PATH') &&
|
||||||
(item['IMG_PATH'] as String).isNotEmpty &&
|
(item['IMG_PATH'] as String).isNotEmpty &&
|
||||||
isShowSign)
|
isShowSign)
|
||||||
Row(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 6),
|
||||||
..._buildImageRows(
|
child: Row(
|
||||||
context,
|
children: [
|
||||||
(item['IMG_PATH'] as String).split(','),
|
..._buildImageRows(
|
||||||
'',
|
context,
|
||||||
8,
|
(item['IMG_PATH'] as String).split(','),
|
||||||
),
|
'',
|
||||||
],
|
8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -210,92 +219,93 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
|
|
||||||
// 第三列:状态文字/按钮 + 操作图片
|
// 第三列:状态文字/按钮 + 操作图片
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// 签字或状态
|
// 签字或状态
|
||||||
isAllowEdit
|
isAllowEdit
|
||||||
? TextButton(
|
? TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onSign?.call(item);
|
onSign?.call(item);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
(item['SIGN_ITEM'] ?? '')
|
(item['SIGN_ITEM'] ?? '')
|
||||||
.toString()
|
.toString()
|
||||||
.isNotEmpty
|
.isNotEmpty
|
||||||
? '已签字'
|
? '已签字'
|
||||||
: '签字',
|
: '签字',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: (item['SIGN_ITEM'] ?? '')
|
||||||
(item['SIGN_ITEM'] ?? '')
|
.toString()
|
||||||
.toString()
|
.isNotEmpty
|
||||||
.isNotEmpty
|
? Colors.grey.shade600
|
||||||
? Colors.grey.shade600
|
: Colors.blue,
|
||||||
: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
(item['STATUS'] as String?) == '-1'
|
|
||||||
? '不涉及'
|
|
||||||
: '涉及',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
(item['STATUS'] as String?) == '-1'
|
|
||||||
? Colors.black
|
|
||||||
: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
(item['STATUS'] as String?) == '-1'
|
||||||
|
? '不涉及'
|
||||||
|
: '涉及',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 第四列:确认人(只在非编辑模式显示)
|
// 第四列:确认人(只在非编辑模式显示)
|
||||||
if (!isAllowEdit)
|
if (!isAllowEdit)
|
||||||
Column(
|
Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
padding: const EdgeInsets.all(6),
|
||||||
children: [
|
child: Column(
|
||||||
if (item.containsKey('SIGN_PATH') &&
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
(item['SIGN_PATH'] as String).isNotEmpty)
|
children: [
|
||||||
...((item['SIGN_PATH'] as String)
|
if (item.containsKey('SIGN_PATH') &&
|
||||||
.split(',')
|
(item['SIGN_PATH'] as String).isNotEmpty)
|
||||||
.map((s) => s.trim())
|
...((item['SIGN_PATH'] as String)
|
||||||
.where((s) => s.isNotEmpty)).map((path) {
|
.split(',')
|
||||||
final imageUrl = '$baseImgPath$path';
|
.map((s) => s.trim())
|
||||||
return Padding(
|
.where((s) => s.isNotEmpty)).map((path) {
|
||||||
padding: const EdgeInsets.only(top: 5),
|
final imageUrl = '$baseImgPath$path';
|
||||||
child: Center(
|
return Padding(
|
||||||
child: GestureDetector(
|
padding: const EdgeInsets.only(top: 5),
|
||||||
onTap: () {
|
child: Center(
|
||||||
presentOpaque(
|
child: GestureDetector(
|
||||||
SingleImageViewer(
|
onTap: () {
|
||||||
imageUrl: imageUrl,
|
presentOpaque(
|
||||||
),
|
SingleImageViewer(
|
||||||
context,
|
imageUrl: imageUrl,
|
||||||
);
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start, // 顶部对齐,允许左侧文本换行并撑高
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Container(
|
||||||
'$question:',
|
padding: const EdgeInsets.only(right: 8), // 给文本和输入框一些间距
|
||||||
style: const TextStyle(fontWeight: FontWeight.w800),
|
child: Text(
|
||||||
|
'$question:',
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis, // 超出显示省略号(你可以改为 visible 或 null)
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w800),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: TextFormField(
|
child: SizedBox(
|
||||||
initialValue: answer,
|
child: TextFormField(
|
||||||
textAlign: TextAlign.center,
|
initialValue: answer,
|
||||||
// 输入文字居中对齐
|
textAlign: TextAlign.center,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
horizontal: 4,
|
horizontal: 4,
|
||||||
),
|
),
|
||||||
// 自定义边框颜色
|
enabledBorder: OutlineInputBorder(
|
||||||
enabledBorder: OutlineInputBorder(
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
),
|
||||||
),
|
focusedBorder: OutlineInputBorder(
|
||||||
focusedBorder: OutlineInputBorder(
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8, bottom: 6),
|
||||||
child: Column(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Expanded(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
flex: 3,
|
||||||
child: Row(
|
child: Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
'$question:',
|
||||||
children: [
|
style: const TextStyle(fontWeight: FontWeight.w800),
|
||||||
Text(
|
softWrap: true,
|
||||||
'$question:',
|
),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w800),
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
Text(answer.isNotEmpty ? answer : '0'),
|
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(
|
List<Widget> _buildImageRows(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<String> paths,
|
List<String> paths,
|
||||||
String time,
|
String time,
|
||||||
double right,
|
double right,
|
||||||
) {
|
) {
|
||||||
if (paths.isEmpty) return [];
|
if (paths.isEmpty) return [];
|
||||||
|
|
||||||
const int imagesPerRow = 4;
|
const int imagesPerRow = 4;
|
||||||
|
|
@ -423,21 +442,19 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
errorBuilder:
|
errorBuilder:
|
||||||
(_, __, ___) => Container(
|
(_, __, ___) => Container(
|
||||||
width: imageSize,
|
width: imageSize,
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
color: Colors.grey.shade200,
|
color: Colors.grey.shade200,
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.broken_image,
|
Icons.broken_image,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (i != rowPaths.length - 1) SizedBox(width: spacing),
|
if (i != rowPaths.length - 1) SizedBox(width: spacing),
|
||||||
],
|
],
|
||||||
// 如果希望每行右侧也填充占位使宽度平均,可加 Expanded 占位
|
|
||||||
// Expanded(child: SizedBox()),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -445,6 +462,7 @@ class MeasuresListWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 其他安全防护措施表格组件
|
/// 其他安全防护措施表格组件
|
||||||
class OtherMeasuresWidget extends StatelessWidget {
|
class OtherMeasuresWidget extends StatelessWidget {
|
||||||
/// 其他安全防护措施数据列表
|
/// 其他安全防护措施数据列表
|
||||||
|
|
@ -854,7 +872,7 @@ class SignaturesListWidget extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(5),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -862,7 +880,7 @@ class SignaturesListWidget extends StatelessWidget {
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 2),
|
||||||
TextField(
|
TextField(
|
||||||
controller: TextEditingController(text: descr),
|
controller: TextEditingController(text: descr),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
|
@ -873,7 +891,7 @@ class SignaturesListWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(5),
|
||||||
child: Text('$personDes: $name'),
|
child: Text('$personDes: $name'),
|
||||||
),
|
),
|
||||||
for (var i = 0; i < signPaths.length; i++)
|
for (var i = 0; i < signPaths.length; i++)
|
||||||
|
|
@ -897,7 +915,7 @@ class SignaturesListWidget extends StatelessWidget {
|
||||||
(_, __, ___) => const Icon(Icons.broken_image),
|
(_, __, ___) => const Icon(Icons.broken_image),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(i < signTimes.length ? signTimes[i] : ''),
|
child: Text(i < signTimes.length ? signTimes[i] : ''),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -167,13 +167,13 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
|
||||||
hintText: '请选择动火人及证书编号',
|
hintText: '请选择动火人及证书编号',
|
||||||
onTap: widget.onChooseHotworkUser,
|
onTap: widget.onChooseHotworkUser,
|
||||||
),
|
),
|
||||||
if (FormUtils.hasValue(pd, 'WORK_USER_DEPARTMENT_NAME') &&
|
if (FormUtils.hasValue(pd, 'UNIT_NAME') &&
|
||||||
!widget.isEditable) ...[
|
!widget.isEditable) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
ItemListWidget.singleLineTitleText(
|
ItemListWidget.singleLineTitleText(
|
||||||
label: '作业单位:',
|
label: '作业单位:',
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
text: pd['WORK_USER_DEPARTMENT_NAME'] ?? '',
|
text: pd['UNIT_NAME'] ?? '',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') &&
|
if (FormUtils.hasValue(pd, 'CONFIRM_USER_NAME') &&
|
||||||
|
|
@ -186,6 +186,14 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
|
||||||
text: pd['CONFIRM_USER_NAME'] ?? '',
|
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(),
|
const Divider(),
|
||||||
ItemListWidget.twoRowButtonTitleText(
|
ItemListWidget.twoRowButtonTitleText(
|
||||||
label: '关联其他特殊作业及安全作业票编号',
|
label: '关联其他特殊作业及安全作业票编号',
|
||||||
|
|
@ -388,14 +396,7 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
|
||||||
text: pd['VIDEONAME'] ?? '',
|
text: pd['VIDEONAME'] ?? '',
|
||||||
),
|
),
|
||||||
|
|
||||||
if (FormUtils.hasValue(pd, 'ANALYZE_TIME') && !widget.isEditable) ...[
|
|
||||||
const Divider(),
|
|
||||||
ItemListWidget.OneRowStartButtonTitle(
|
|
||||||
label: '气体分析信息:',
|
|
||||||
text: pd['ANALYZE_USER_NAME'] ?? '',
|
|
||||||
onTap: widget.onAnalyzeTap,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -364,35 +364,33 @@ class _HotWorkListPageState extends State<HotWorkListPage> {
|
||||||
overflow: TextOverflow.visible,
|
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(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"安全交底人: ${item['CONFESS_USER_NAME'] ?? ''}",
|
"安全交底人: ${item['CONFESS_USER_NAME'] ?? ''}",
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"接受交底人: ${item['ACCEPT_CONFESS_USER_NAME'] ?? ''}",
|
"接受交底人: ${item['ACCEPT_CONFESS_USER_NAME'] ?? ''}",
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: null,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
|
@ -404,19 +402,10 @@ class _HotWorkListPageState extends State<HotWorkListPage> {
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}",
|
|
||||||
softWrap: true,
|
|
||||||
maxLines: null,
|
|
||||||
overflow: TextOverflow.visible,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"所在单位负责人: ${item['LEADER_USER_NAME'] ?? ''}",
|
"所在单位负责人: ${item['LEADER_USER_NAME'] ?? ''}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.left,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
|
|
|
||||||
|
|
@ -65,13 +65,17 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
late List<dynamic> boardList = [
|
late List<dynamic> boardList = [
|
||||||
{'BOARD_MATERIAL': '', 'BOARD_SPECIFICATION': '', 'BOARD_NO': ''},
|
{'BOARD_MATERIAL': '', 'BOARD_SPECIFICATION': '', 'BOARD_NO': ''},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// ------------------- 新增 -------------------
|
/// ------------------- 新增 -------------------
|
||||||
/// 视频监控摄像
|
/// 视频监控摄像
|
||||||
late List<dynamic> videoMonitoringList = [];
|
late List<dynamic> videoMonitoringList = [];
|
||||||
|
|
||||||
/// 承包商列表
|
/// 承包商列表
|
||||||
late List<dynamic> unitAllList = [];
|
late List<dynamic> unitAllList = [];
|
||||||
|
|
||||||
/// 作业区域列表
|
/// 作业区域列表
|
||||||
late List<dynamic> workAreaList = [];
|
late List<dynamic> workAreaList = [];
|
||||||
|
|
||||||
/// --------------------------------------
|
/// --------------------------------------
|
||||||
late Map<String, dynamic> signs = {};
|
late Map<String, dynamic> signs = {};
|
||||||
late List<Map<String, dynamic>> measuresList = [];
|
late List<Map<String, dynamic>> measuresList = [];
|
||||||
|
|
@ -86,8 +90,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
final TextEditingController _relatedController = TextEditingController();
|
final TextEditingController _relatedController = TextEditingController();
|
||||||
final TextEditingController _riskController = TextEditingController();
|
final TextEditingController _riskController = TextEditingController();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 存储各单位的人员列表
|
// 存储各单位的人员列表
|
||||||
final Map<EditUserType, List<Map<String, dynamic>>> _personCache = {};
|
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_ID'] = SessionService.instance.loginUserId;
|
||||||
pd['APPLY_USER_NAME'] = SessionService.instance.username;
|
pd['APPLY_USER_NAME'] = SessionService.instance.username;
|
||||||
pd['IS_CONTRACTOR_WORK'] = '0';
|
pd['IS_CONTRACTOR_WORK'] = '0';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getVideoList();
|
_getVideoList();
|
||||||
|
|
@ -132,15 +133,16 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
pd['RISK_IDENTIFICATION'] = _riskController.text.trim();
|
pd['RISK_IDENTIFICATION'] = _riskController.text.trim();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ---------------------------- 新增 --------------------------------
|
/// ---------------------------- 新增 --------------------------------
|
||||||
/// 视频监控摄像头
|
/// 视频监控摄像头
|
||||||
Future<void> _chooseVideoManager() async {
|
Future<void> _chooseVideoManager() async {
|
||||||
final choice = await BottomPicker.show<String>(
|
final choice = await BottomPicker.show<String>(
|
||||||
context,
|
context,
|
||||||
items:
|
items:
|
||||||
videoMonitoringList
|
videoMonitoringList
|
||||||
.map((item) => item['VIDEONAME'] as String)
|
.map((item) => item['VIDEONAME'] as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
itemBuilder: (item) => Text(item, textAlign: TextAlign.center),
|
||||||
initialIndex: 0,
|
initialIndex: 0,
|
||||||
);
|
);
|
||||||
|
|
@ -149,7 +151,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
pd['VIDEONAME'] = choice;
|
pd['VIDEONAME'] = choice;
|
||||||
|
|
||||||
Map<String, dynamic> result = videoMonitoringList.firstWhere(
|
Map<String, dynamic> result = videoMonitoringList.firstWhere(
|
||||||
(item) => item['VIDEONAME'] == choice,
|
(item) => item['VIDEONAME'] == choice,
|
||||||
orElse: () => {}, // 避免找不到时报错
|
orElse: () => {}, // 避免找不到时报错
|
||||||
);
|
);
|
||||||
if (FormUtils.hasValue(result, 'VIDEOMANAGER_ID')) {
|
if (FormUtils.hasValue(result, 'VIDEOMANAGER_ID')) {
|
||||||
|
|
@ -159,6 +161,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 选择承包商
|
/// 选择承包商
|
||||||
Future<void> _chooseUnitManager() async {
|
Future<void> _chooseUnitManager() async {
|
||||||
final choice = await BottomPicker.show<String>(
|
final choice = await BottomPicker.show<String>(
|
||||||
|
|
@ -172,7 +175,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
pd['UNITS_NAME'] = choice;
|
pd['UNITS_NAME'] = choice;
|
||||||
|
|
||||||
Map<String, dynamic> result = unitAllList.firstWhere(
|
Map<String, dynamic> result = unitAllList.firstWhere(
|
||||||
(item) => item['UNITS_NAME'] == choice,
|
(item) => item['UNITS_NAME'] == choice,
|
||||||
orElse: () => {}, // 避免找不到时报错
|
orElse: () => {}, // 避免找不到时报错
|
||||||
);
|
);
|
||||||
if (FormUtils.hasValue(result, 'UNITS_ID')) {
|
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')) {
|
if (!FormUtils.hasValue(pd, 'ELECTRONIC_FENCE_AREA_ID')) {
|
||||||
ToastUtil.showNormal(context, '请选择作业区域');
|
ToastUtil.showNormal(context, '请选择作业区域');
|
||||||
return;
|
return;
|
||||||
|
|
@ -194,10 +196,11 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
setState(() {
|
setState(() {
|
||||||
pd['LONGITUDE'] = mapData['longitue'] ?? '';
|
pd['LONGITUDE'] = mapData['longitue'] ?? '';
|
||||||
pd['LATITUDE'] = mapData['latitude'] ?? '';
|
pd['LATITUDE'] = mapData['latitude'] ?? '';
|
||||||
pd['LATITUDE_LONGITUDE'] = '${mapData['longitue'] ?? ''},${mapData['latitude'] ?? ''}';
|
pd['LATITUDE_LONGITUDE'] =
|
||||||
|
'${mapData['longitue'] ?? ''},${mapData['latitude'] ?? ''}';
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 作业区域
|
/// 作业区域
|
||||||
Future<void> _getWorkArea() async {
|
Future<void> _getWorkArea() async {
|
||||||
//FocusHelper.clearFocus(context);
|
//FocusHelper.clearFocus(context);
|
||||||
|
|
@ -208,18 +211,19 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder:
|
builder:
|
||||||
(_) => WorkAreaPicker(
|
(_) => WorkAreaPicker(
|
||||||
onSelected: (String id, String POSITIONS, String name) {
|
onSelected: (String id, String POSITIONS, String name) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pd['ELECTRONIC_FENCE_AREA_ID'] = id;
|
pd['ELECTRONIC_FENCE_AREA_ID'] = id;
|
||||||
pd['POSITIONS'] = POSITIONS;
|
pd['POSITIONS'] = POSITIONS;
|
||||||
pd['PLS_NAME'] = name;
|
pd['PLS_NAME'] = name;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).then((_) {
|
).then((_) {
|
||||||
//FocusHelper.clearFocus(context);
|
//FocusHelper.clearFocus(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取摄像头列表
|
/// 获取摄像头列表
|
||||||
Future<void> _getVideoList() async {
|
Future<void> _getVideoList() async {
|
||||||
final result = await ApiService.getVideomanagerList();
|
final result = await ApiService.getVideomanagerList();
|
||||||
|
|
@ -227,6 +231,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
videoMonitoringList = result['varList'] ?? [];
|
videoMonitoringList = result['varList'] ?? [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获承包商列表
|
/// 获承包商列表
|
||||||
Future<void> _getUnitListAll() async {
|
Future<void> _getUnitListAll() async {
|
||||||
final result = await ApiService.getUnitListAll();
|
final result = await ApiService.getUnitListAll();
|
||||||
|
|
@ -316,7 +321,7 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
isEditable: isEditable,
|
isEditable: isEditable,
|
||||||
isClean: isClean,
|
isClean: isClean,
|
||||||
onTapClean: () {
|
onTapClean: () {
|
||||||
ToastUtil.showNormal(context, '已清除');
|
ToastUtil.showNormal(context, '已清除');
|
||||||
setState(() {
|
setState(() {
|
||||||
set_pd_USER_ID(type, '');
|
set_pd_USER_ID(type, '');
|
||||||
set_pd_USER_Name(type, '');
|
set_pd_USER_Name(type, '');
|
||||||
|
|
@ -440,9 +445,23 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
];
|
];
|
||||||
final level = pd['WORK_TYPE'] ?? '';
|
final level = pd['WORK_TYPE'] ?? '';
|
||||||
print('---level-$level');
|
print('---level-$level');
|
||||||
|
int index = 0;
|
||||||
for (Map item in boardList) {
|
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>[
|
final unitRules = <EditUserType>[
|
||||||
EditUserType.GUARDIAN,
|
EditUserType.GUARDIAN,
|
||||||
|
|
@ -478,7 +497,8 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
return;
|
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, '请选择承包商');
|
ToastUtil.showNormal(context, '请选择承包商');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -510,7 +530,8 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
pd['ACTION_USER'] = SessionService.instance.username;
|
pd['ACTION_USER'] = SessionService.instance.username;
|
||||||
pd['APPLY_STATUS'] = status;
|
pd['APPLY_STATUS'] = status;
|
||||||
pd['boardList'] = jsonEncode(boardList).toString();
|
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') {
|
if (msg == 'add') {
|
||||||
pd['STEP_ID'] = status;
|
pd['STEP_ID'] = status;
|
||||||
|
|
@ -548,8 +569,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 初始化拉取数据
|
/// 初始化拉取数据
|
||||||
Future<void> _getData() async {
|
Future<void> _getData() async {
|
||||||
final data = await ApiService.getHomeworkFindById(
|
final data = await ApiService.getHomeworkFindById(
|
||||||
|
|
|
||||||
|
|
@ -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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.7.0
|
sdk: ^3.7.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue