Compare commits
3 Commits
70ac141fb7
...
76d0fdbd16
| Author | SHA1 | Date |
|---|---|---|
|
|
76d0fdbd16 | |
|
|
6ca5a27418 | |
|
|
2b6328e742 |
|
|
@ -1,3 +1,4 @@
|
|||
// <your_file_name>.dart
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
|
@ -66,12 +67,13 @@ bool _isNetworkPath(String? p) {
|
|||
}
|
||||
|
||||
/// 把路径列表转换为存在的本地 File 列表(过滤掉网络路径与空路径与不存在的本地文件)
|
||||
/// 注意:这个函数**可能**会同步访问文件系统(existsSync),它只应在用户触发后调用,不应在 build() 中被频繁调用。
|
||||
List<File> _localFilesFromPaths(List<String>? paths) {
|
||||
if (paths == null) return <File>[];
|
||||
return paths
|
||||
.map((e) => (e ?? '').toString().trim())
|
||||
.where((s) => s.isNotEmpty && !_isNetworkPath(s))
|
||||
.where((s) => File(s).existsSync()) // 只回调真实存在的本地文件
|
||||
.where((s) => File(s).existsSync())
|
||||
.map((s) => File(s))
|
||||
.toList();
|
||||
}
|
||||
|
|
@ -82,7 +84,6 @@ List<String> _normalizePaths(List<String>? src) {
|
|||
return src.map((e) => (e ?? '').toString().trim()).where((s) => s.isNotEmpty).toList();
|
||||
}
|
||||
|
||||
/// ---------- MediaPickerRow ----------
|
||||
/// ---------- MediaPickerRow ----------
|
||||
class MediaPickerRow extends StatefulWidget {
|
||||
final int maxCount;
|
||||
|
|
@ -120,12 +121,29 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
late List<String> _mediaPaths;
|
||||
bool _isProcessing = false;
|
||||
|
||||
/// 缓存每个本地路径是否存在(避免在 build 中反复同步 IO)
|
||||
final Map<String, bool> _localExistsCache = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 初始化内部路径(保留网络路径与本地路径)
|
||||
_mediaPaths = _normalizePaths(widget.initialMediaPaths).take(widget.maxCount).toList();
|
||||
|
||||
// 预先检查一次本地文件是否存在(只在 init 时做一次同步检查)
|
||||
for (final pth in _mediaPaths) {
|
||||
final t = pth.trim();
|
||||
if (!_isNetworkPath(t)) {
|
||||
try {
|
||||
_localExistsCache[t] = File(t).existsSync();
|
||||
} catch (_) {
|
||||
_localExistsCache[t] = false;
|
||||
}
|
||||
} else {
|
||||
_localExistsCache[pth] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在存在本地真实文件时才把 File 列表回调给外部(避免父组件用这个回调覆盖只有网络路径的数据)
|
||||
final initialLocalFiles = _localFilesFromPaths(_mediaPaths);
|
||||
if (initialLocalFiles.isNotEmpty) {
|
||||
|
|
@ -145,6 +163,23 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
|
||||
if (!listEquals(oldList, newList)) {
|
||||
_mediaPaths = newList.take(widget.maxCount).toList();
|
||||
|
||||
// 更新本地存在缓存(同步检查,仅在更新时执行)
|
||||
for (final pth in _mediaPaths) {
|
||||
final t = pth.trim();
|
||||
if (!_localExistsCache.containsKey(t)) {
|
||||
if (!_isNetworkPath(t)) {
|
||||
try {
|
||||
_localExistsCache[t] = File(t).existsSync();
|
||||
} catch (_) {
|
||||
_localExistsCache[t] = false;
|
||||
}
|
||||
} else {
|
||||
_localExistsCache[t] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) setState(() {});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
|
|
@ -190,6 +225,18 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
|
||||
if (_mediaPaths.length < widget.maxCount) {
|
||||
setState(() => _mediaPaths.add(finalPath));
|
||||
|
||||
// 记录缓存(只在添加时检查一次文件是否真实存在)
|
||||
if (!_isNetworkPath(finalPath)) {
|
||||
try {
|
||||
_localExistsCache[finalPath] = File(finalPath).existsSync();
|
||||
} catch (_) {
|
||||
_localExistsCache[finalPath] = false;
|
||||
}
|
||||
} else {
|
||||
_localExistsCache[finalPath] = false;
|
||||
}
|
||||
|
||||
// 回调仅包含本地真实存在的文件(网络路径不会出现在此回调中)
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(finalPath);
|
||||
|
|
@ -248,6 +295,14 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
if (picked != null) {
|
||||
final path = picked.path;
|
||||
setState(() => _mediaPaths.add(path));
|
||||
|
||||
// 记录存在
|
||||
try {
|
||||
_localExistsCache[path] = File(path).existsSync();
|
||||
} catch (_) {
|
||||
_localExistsCache[path] = false;
|
||||
}
|
||||
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(path);
|
||||
}
|
||||
|
|
@ -394,6 +449,11 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
if (picked != null) {
|
||||
final path = picked.path;
|
||||
setState(() => _mediaPaths.add(path));
|
||||
try {
|
||||
_localExistsCache[path] = File(path).existsSync();
|
||||
} catch (_) {
|
||||
_localExistsCache[path] = false;
|
||||
}
|
||||
widget.onChanged(_localFilesFromPaths(_mediaPaths));
|
||||
widget.onMediaAdded?.call(path);
|
||||
}
|
||||
|
|
@ -421,10 +481,12 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
if (!ok) return;
|
||||
|
||||
final removed = _mediaPaths[index];
|
||||
final wasNetwork = _isNetworkPath(removed);
|
||||
|
||||
setState(() => _mediaPaths.removeAt(index));
|
||||
|
||||
// 从缓存中移除
|
||||
_localExistsCache.remove(removed);
|
||||
|
||||
// 始终通知 onMediaRemoved(用于父端业务逻辑)
|
||||
widget.onMediaRemoved?.call(removed);
|
||||
|
||||
|
|
@ -438,6 +500,10 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount;
|
||||
final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0);
|
||||
|
||||
// 预计算缩略图解码宽度(减少内存开销)
|
||||
final tileLogicalW = (MediaQuery.of(context).size.width / 4).round();
|
||||
final cacheWidth = (tileLogicalW * MediaQuery.of(context).devicePixelRatio).round();
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
GridView.builder(
|
||||
|
|
@ -472,24 +538,27 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
|
|||
? Image.network(
|
||||
raw,
|
||||
fit: BoxFit.cover,
|
||||
// request a scaled decode to reduce memory
|
||||
width: tileLogicalW.toDouble(),
|
||||
height: tileLogicalW.toDouble(),
|
||||
// errorBuilder for network errors
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
color: Colors.grey.shade200,
|
||||
child: const Center(child: Icon(Icons.broken_image)),
|
||||
),
|
||||
)
|
||||
: (File(raw).existsSync()
|
||||
? Image.file(
|
||||
: Image.file(
|
||||
File(raw),
|
||||
fit: BoxFit.cover,
|
||||
width: tileLogicalW.toDouble(),
|
||||
height: tileLogicalW.toDouble(),
|
||||
// Use cacheWidth to ask the engine to decode a smaller bitmap (reduces memory).
|
||||
cacheWidth: cacheWidth,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
color: Colors.grey.shade200,
|
||||
child: const Center(child: Icon(Icons.broken_image)),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: Colors.grey.shade200,
|
||||
child: const Center(child: Icon(Icons.broken_image)),
|
||||
)))
|
||||
))
|
||||
: Container(
|
||||
color: Colors.black12,
|
||||
child: const Center(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -563,7 +563,6 @@ class HomePageState extends State<HomePage> {
|
|||
break;
|
||||
}
|
||||
|
||||
_onRefresh();
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,10 +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) {
|
||||
|
|
@ -214,7 +229,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
// 暂停已有播放器(安全)
|
||||
try {
|
||||
if (_videoController != null && _videoController!.value.isPlaying) {
|
||||
|
|
@ -254,7 +268,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
seconds: int.parse('${data['VIDEOTIME'] ?? '0'}'),
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
ToastUtil.showNormal(context, '课件文件资源已失效,请联系管理员');
|
||||
}
|
||||
|
|
@ -269,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -486,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;
|
||||
|
|
@ -506,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;
|
||||
|
|
@ -522,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>?;
|
||||
|
|
@ -531,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;
|
||||
}
|
||||
|
|
@ -560,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 位小数
|
||||
|
|
@ -623,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;
|
||||
|
|
@ -716,6 +684,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
}
|
||||
|
||||
Future<void> _showFaceAuthOnce() async {
|
||||
// 仅在人脸功能开启时调用(
|
||||
if (!_isFace) return;
|
||||
// 先 cancel 定时器,避免跳转过程中再次触发
|
||||
_faceTimer?.cancel();
|
||||
_faceTimer = null;
|
||||
|
|
@ -725,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,
|
||||
);
|
||||
|
||||
|
|
@ -770,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) {
|
||||
|
|
@ -787,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) {
|
||||
|
|
@ -898,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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ class MeasuresListWidget extends StatelessWidget {
|
|||
|
||||
// 期望的固定列宽(可以根据需要微调)
|
||||
double col0Fixed = 40; // 第一列:序号(较窄)
|
||||
double col2Fixed = 60; // 第三列:操作(保留较宽以放按钮)
|
||||
double col3Fixed = 80; // 第四列:确认人(头像列)
|
||||
double col2Fixed = 70; // 第三列:操作(保留较宽以放按钮)
|
||||
double col3Fixed = 60; // 第四列:确认人(头像列)
|
||||
|
||||
// 如果不显示第4列(isAllowEdit == true),则第4列宽为0,不计入固定总和
|
||||
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,
|
||||
),
|
||||
// 问题1~4 + 答案(可编辑或只读)
|
||||
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;
|
||||
|
|
@ -420,24 +439,22 @@ class MeasuresListWidget extends StatelessWidget {
|
|||
'$baseImgPath${rowPaths[i]}',
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
fit: BoxFit.cover,
|
||||
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 {
|
||||
/// 其他安全防护措施数据列表
|
||||
|
|
@ -768,7 +786,7 @@ class SignaturesListWidget extends StatelessWidget {
|
|||
fullUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fill,
|
||||
errorBuilder:
|
||||
(ctx, err, st) => Container(
|
||||
width: 50,
|
||||
|
|
@ -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] : ''),
|
||||
),
|
||||
|
|
@ -1443,7 +1461,7 @@ class SignItemWidget extends StatelessWidget {
|
|||
fullUrl,
|
||||
width: smallThumbSize,
|
||||
height: smallThumbSize,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fill,
|
||||
errorBuilder:
|
||||
(_, __, ___) => Container(
|
||||
width: smallThumbSize,
|
||||
|
|
@ -1897,7 +1915,7 @@ class SignRowImageTitle extends StatelessWidget {
|
|||
fullUrl,
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fill,
|
||||
errorBuilder:
|
||||
(_, __, ___) => Container(
|
||||
width: imageSize,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
|
||||
/// 拍照或选图后的回调(上传)
|
||||
Future<void> _onImageAdded(String localPath) async {
|
||||
if (!mounted) return;
|
||||
LoadingDialogHelper.show();
|
||||
|
||||
try {
|
||||
|
|
@ -101,8 +100,6 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
final res = await ApiService.uploadSaveFile(localPath).timeout(const Duration(seconds: 30));
|
||||
LoadingDialogHelper.hide();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (res['result'] == 'success') {
|
||||
final url = res['FILE_PATH'] as String;
|
||||
setState(() {
|
||||
|
|
@ -444,7 +441,6 @@ class _DangerousOptionsPageState extends State<DangerousOptionsPage> {
|
|||
onChanged: (paths) {},
|
||||
onMediaAdded: _onImageAdded,
|
||||
onMediaRemoved: (path) {
|
||||
// 原逻辑保持:通过包含 localPath 的方式匹配
|
||||
try {
|
||||
final item = imgList.firstWhere((e) => path.contains(e.localPath));
|
||||
_onImageRemoved(item);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ class _HotworkSafeFuncSureState extends State<HotworkSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -132,11 +132,12 @@ class _HotworkAqglDetailState extends State<HotworkAqglDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -134,11 +134,12 @@ class _HotworkDbbzDetailState extends State<HotworkDbbzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -132,11 +132,12 @@ class _HotworkDhspDetailState extends State<HotworkDhspDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -161,82 +161,6 @@ class _HotworkSetSafeDetailState extends State<HotworkSetSafeDetail> {
|
|||
).then((_) {});
|
||||
}
|
||||
|
||||
/// 签字
|
||||
Future<void> _sign() async {
|
||||
await NativeOrientation.setLandscape();
|
||||
final path = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => MineSignPage()),
|
||||
);
|
||||
await NativeOrientation.setPortrait();
|
||||
if (path != null) {
|
||||
final now = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
|
||||
|
||||
setState(() {
|
||||
imagePaths.add(path);
|
||||
signTimes.add(now);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _signListWidget() {
|
||||
return Column(
|
||||
children:
|
||||
imagePaths.map((path) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: // 用一个 ConstrainedBox 限制最大尺寸,并改为 BoxFit.contain
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 200,
|
||||
maxHeight: 150,
|
||||
),
|
||||
child: Image.file(
|
||||
File(path),
|
||||
// 改为完整显示
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
presentOpaque(
|
||||
SingleImageViewer(imageUrl: path),
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: CustomButton(
|
||||
text: 'X',
|
||||
height: 30,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
backgroundColor: Colors.red,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
imagePaths.remove(path);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 作废 -1 通过 1
|
||||
Future<void> _submit(String status) async {
|
||||
List<Map<String, dynamic>> signers = [];
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _HotworkSzdwDetailState extends State<HotworkSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class _HotworkYsgdDetailState extends State<HotworkYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _HotworkZyfzDetailState extends State<HotworkZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ class _CutroadSafeFuncSureState extends State<CutroadSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ class _BreakgroundSafeFuncSureState extends State<BreakgroundSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -131,12 +131,11 @@ class _BreakgroundDzzhDetailState extends State<BreakgroundDzzhDetail> {
|
|||
}
|
||||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -130,12 +130,11 @@ class _BreakgroundShbmDetailState extends State<BreakgroundShbmDetail> {
|
|||
}
|
||||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -130,12 +130,11 @@ class _BreakgroundSpbmDetailState extends State<BreakgroundSpbmDetail> {
|
|||
}
|
||||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _BreakgroundSzdwDetailState extends State<BreakgroundSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class _BreakgroundYsgdDetailState extends State<BreakgroundYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _BreakgroundZyfzDetailState extends State<BreakgroundZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -427,18 +427,6 @@ class _HoistworkDetailFormWidgetState extends State<HoistWorkDetailFormWidget> {
|
|||
onTap: widget.onChooseVideoManager ?? () {},
|
||||
text: pd['VIDEONAME'] ?? '',
|
||||
),
|
||||
if (FormUtils.hasValue(widget.signs, 'SISUO')) ...[
|
||||
const Divider(),
|
||||
ConfirmWithSignWidget(
|
||||
signs: widget.signs,
|
||||
pd: pd,
|
||||
baseImgPath: ApiService.baseImgPath,
|
||||
sectionKey: 'SISUO',
|
||||
nameKey: 'SISUO_USER_NAME',
|
||||
headerTitle: '司索人',
|
||||
roleTitle: '司索人',
|
||||
),
|
||||
]
|
||||
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ class _HoistworkSafeFuncSureState extends State<HoistworkSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _HoistworkDzzhDetailState extends State<HoistworkDzzhDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ class _HoistworkListPageState extends State<HoistworkListPage> {
|
|||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"作业人: ${item['WORK_USER_USER_NAME'] ?? ''}",
|
||||
"吊装作业人: ${item['WORK_USER_USER_NAME'] ?? ''}",
|
||||
softWrap: true,
|
||||
textAlign: TextAlign.right,
|
||||
maxLines: null, // 不限制行数
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ class _HoistworkShbmDetailState extends State<HoistworkShbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ class _HoistworkSpbmDetailState extends State<HoistworkSpbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _HoistworkSzdwDetailState extends State<HoistworkSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class _HoistworkYsgdDetailState extends State<HoistworkYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,18 +132,18 @@ class _HoistworkZyfzDetailState extends State<HoistworkZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
title: '作废原因',
|
||||
hintText: '请输入作废原因',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定'
|
||||
confirmText: '确定',
|
||||
);
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
|
|
@ -181,7 +181,7 @@ class _HoistworkZyfzDetailState extends State<HoistworkZyfzDetail> {
|
|||
if (result['result'] == 'success') {
|
||||
ToastUtil.showSuccess(context, '保存成功');
|
||||
Navigator.of(context).pop(true);
|
||||
}else{
|
||||
} else {
|
||||
ToastUtil.showNormal(context, '操作失败:${result['msg'] ?? '未知错误'}');
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ class _HighworkSafeFuncSureState extends State<HighworkSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ class _HighworkShbmDetailState extends State<HighworkShbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -188,11 +188,11 @@ class _HighworkSpbmDetailState extends State<HighworkSpbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _HighworkSzdwDetailState extends State<HighworkSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class _HighworkYsgdDetailState extends State<HighworkYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,10 +132,6 @@ class _HighworkZyfzDetailState extends State<HighworkZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ class _ElectricitySafeFuncSureState extends State<ElectricitySafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _ElectricityPsdwDetailState extends State<ElectricityPsdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _ElectricityYddwDetailState extends State<ElectricityYddwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入用电单位意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入用电单位意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -185,12 +185,12 @@ class _ElectricityYsgdDetailState extends State<ElectricityYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,18 +132,18 @@ class _ElectricityZyfzDetailState extends State<ElectricityZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
title: '作废原因',
|
||||
hintText: '请输入作废原因',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定'
|
||||
confirmText: '确定',
|
||||
);
|
||||
if (reasonText.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请填写作废原因');
|
||||
|
|
@ -181,7 +181,7 @@ class _ElectricityZyfzDetailState extends State<ElectricityZyfzDetail> {
|
|||
if (result['result'] == 'success') {
|
||||
ToastUtil.showSuccess(context, '保存成功');
|
||||
Navigator.of(context).pop(true);
|
||||
}else{
|
||||
} else {
|
||||
ToastUtil.showNormal(context, '操作失败:${result['msg'] ?? '未知错误'}');
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@ class _BlindboardSafeFuncSureState extends State<BlindboardSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -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,6 +445,22 @@ 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>[
|
||||
|
|
@ -476,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;
|
||||
}
|
||||
|
|
@ -508,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;
|
||||
|
|
@ -546,8 +569,6 @@ class _BlindboardApplyDetailState extends State<BlindboardApplyDetail> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// 初始化拉取数据
|
||||
Future<void> _getData() async {
|
||||
final data = await ApiService.getHomeworkFindById(
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _BlindboardShbmDetailState extends State<BlindboardShbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _BlindboardSpbmDetailState extends State<BlindboardSpbmDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -133,11 +133,11 @@ class _BlindboardSzdwDetailState extends State<BlindboardSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -186,12 +186,12 @@ class _BlindboardYsgdDetailState extends State<BlindboardYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -133,11 +133,11 @@ class _BlindboardZyfzDetailState extends State<BlindboardZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ class _SpaceworkSafeFuncSureState extends State<SpaceworkSafeFuncSure> {
|
|||
],
|
||||
),
|
||||
ItemListWidget.singleLineTitleText(
|
||||
isNumericInput: true,
|
||||
|
||||
label: '其他安全措施:',
|
||||
isEditable: true,
|
||||
hintText: '请输入其他安全措施',
|
||||
|
|
|
|||
|
|
@ -135,11 +135,11 @@ class _SpaceworkDbbzDetailState extends State<SpaceworkDbbzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -133,11 +133,11 @@ class _SpaceworkDhspDetailState extends State<SpaceworkDhspDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -133,11 +133,11 @@ class _SpaceworkSzdwDetailState extends State<SpaceworkSzdwDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入所在单位负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -186,12 +186,12 @@ class _SpaceworkYsgdDetailState extends State<SpaceworkYsgdDetail> {
|
|||
}
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
String reasonText = '';
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (startTime.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请选择验收时间');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,11 +132,11 @@ class _SpaceworkZyfzDetailState extends State<SpaceworkZyfzDetail> {
|
|||
String reasonText = '';
|
||||
String DESCR = _contentController.text.trim();
|
||||
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
if (status == '1') {
|
||||
if (DESCR.isEmpty) {
|
||||
ToastUtil.showNormal(context, '请输入负责人意见');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reasonText = await CustomAlertDialog.showInput(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue