refactor: unify registration mode concept to single-code/multi-code shelving
Replace the locked/unlocked terminology with RegistrationMode enum (singleCode/multiCode) to align with boxing module's naming convention. Multi-code mode now allows switching shelf locations with a confirmation dialog when pending data exists. Use shared BoxingModePill widget for consistent UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,12 @@ import 'package:pad_scanner/services/registration_linking.dart';
|
|||||||
import 'package:vibration/vibration.dart';
|
import 'package:vibration/vibration.dart';
|
||||||
import 'package:pad_scanner/pages/boxing_page.dart';
|
import 'package:pad_scanner/pages/boxing_page.dart';
|
||||||
import 'package:pad_scanner/widgets/status_bar.dart';
|
import 'package:pad_scanner/widgets/status_bar.dart';
|
||||||
|
import 'package:pad_scanner/pages/boxing/widgets/boxing_shared_widgets.dart';
|
||||||
|
|
||||||
|
enum RegistrationMode {
|
||||||
|
singleCode, // 单码上架
|
||||||
|
multiCode, // 多码上架
|
||||||
|
}
|
||||||
|
|
||||||
class RegistrationPage extends StatefulWidget {
|
class RegistrationPage extends StatefulWidget {
|
||||||
const RegistrationPage({super.key});
|
const RegistrationPage({super.key});
|
||||||
@@ -29,7 +35,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
final _zongpaiNos = <String>[];
|
final _zongpaiNos = <String>[];
|
||||||
String? _locationCode;
|
String? _locationCode;
|
||||||
CodeType? _locationType;
|
CodeType? _locationType;
|
||||||
bool _isLocked = false;
|
RegistrationMode _mode = RegistrationMode.singleCode;
|
||||||
bool _isSubmitting = false;
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
StatusDotColor _statusDot = StatusDotColor.blue;
|
StatusDotColor _statusDot = StatusDotColor.blue;
|
||||||
@@ -88,7 +94,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
void _onKeyEvent(KeyEvent event) {
|
void _onKeyEvent(KeyEvent event) {
|
||||||
if (event is! KeyDownEvent) return;
|
if (event is! KeyDownEvent) return;
|
||||||
if (_isLockToggleKey(event)) {
|
if (_isLockToggleKey(event)) {
|
||||||
_toggleLock(!_isLocked);
|
_cycleMode();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||||
@@ -109,14 +115,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
_handleZongpaiScan(parsed.value);
|
_handleZongpaiScan(parsed.value);
|
||||||
case CodeType.locationNormal:
|
case CodeType.locationNormal:
|
||||||
case CodeType.locationTransit:
|
case CodeType.locationTransit:
|
||||||
if (!_isLocked) {
|
_handleLocationScan(parsed.value, parsed.type);
|
||||||
_feedbackService.trigger(FeedbackEvent.scanValid);
|
|
||||||
setState(() {
|
|
||||||
_locationCode = parsed.value;
|
|
||||||
_locationType = parsed.type;
|
|
||||||
_clearStatusOverride();
|
|
||||||
});
|
|
||||||
} else {}
|
|
||||||
case CodeType.invalid:
|
case CodeType.invalid:
|
||||||
_feedbackService.trigger(FeedbackEvent.scanInvalid);
|
_feedbackService.trigger(FeedbackEvent.scanInvalid);
|
||||||
_showStatusOverride(
|
_showStatusOverride(
|
||||||
@@ -127,6 +126,71 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleLocationScan(String locationCode, CodeType locationType) {
|
||||||
|
// In singleCode mode or no existing data, switch directly
|
||||||
|
if (_mode == RegistrationMode.singleCode || _zongpaiNos.isEmpty) {
|
||||||
|
_feedbackService.trigger(FeedbackEvent.scanValid);
|
||||||
|
setState(() {
|
||||||
|
_locationCode = locationCode;
|
||||||
|
_locationType = locationType;
|
||||||
|
_clearStatusOverride();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In multiCode mode with existing data, confirm before switching
|
||||||
|
_showLocationSwitchDialog(locationCode, locationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showLocationSwitchDialog(String newLocationCode, CodeType newLocationType) {
|
||||||
|
final count = _zongpaiNos.length;
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
backgroundColor: Colors.orange.shade50,
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning_amber, color: Colors.orange.shade800),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'切换货架号',
|
||||||
|
style: TextStyle(color: Colors.orange.shade900),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Text(
|
||||||
|
'当前已有 $count 条待上架数据,切换货架号不会清除已扫描的总排号。',
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
},
|
||||||
|
child: const Text('取消'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
_feedbackService.trigger(FeedbackEvent.scanValid);
|
||||||
|
setState(() {
|
||||||
|
_locationCode = newLocationCode;
|
||||||
|
_locationType = newLocationType;
|
||||||
|
_clearStatusOverride();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.orange.shade700,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: const Text('确认切换'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _handleZongpaiScan(String zongpaiNo) {
|
void _handleZongpaiScan(String zongpaiNo) {
|
||||||
// If cross-paicha dialog is showing, just vibrate and discard
|
// If cross-paicha dialog is showing, just vibrate and discard
|
||||||
if (_crossPaichaPending) {
|
if (_crossPaichaPending) {
|
||||||
@@ -134,8 +198,8 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In locked mode with existing data, check for cross-paicha scan
|
// In multiCode mode with existing data, check for cross-paicha scan
|
||||||
if (_isLocked &&
|
if (_mode == RegistrationMode.multiCode &&
|
||||||
_zongpaiNos.isNotEmpty &&
|
_zongpaiNos.isNotEmpty &&
|
||||||
_overview?.success == true &&
|
_overview?.success == true &&
|
||||||
!_overview!.items.any((item) => item.zongpaiNo == zongpaiNo)) {
|
!_overview!.items.any((item) => item.zongpaiNo == zongpaiNo)) {
|
||||||
@@ -148,7 +212,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
final shouldRefresh = _overviewZongpaiNo != zongpaiNo;
|
final shouldRefresh = _overviewZongpaiNo != zongpaiNo;
|
||||||
_feedbackService.trigger(FeedbackEvent.scanValid);
|
_feedbackService.trigger(FeedbackEvent.scanValid);
|
||||||
setState(() {
|
setState(() {
|
||||||
if (_isLocked) {
|
if (_mode == RegistrationMode.multiCode) {
|
||||||
if (!_zongpaiNos.contains(zongpaiNo)) {
|
if (!_zongpaiNos.contains(zongpaiNo)) {
|
||||||
_zongpaiNos.add(zongpaiNo);
|
_zongpaiNos.add(zongpaiNo);
|
||||||
}
|
}
|
||||||
@@ -197,9 +261,9 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
_statusText = '正在提交…';
|
_statusText = '正在提交…';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_isLocked && _locationCode != null && _zongpaiNos.isEmpty) {
|
if (_mode == RegistrationMode.multiCode && _locationCode != null && _zongpaiNos.isEmpty) {
|
||||||
_statusDot = StatusDotColor.blue;
|
_statusDot = StatusDotColor.blue;
|
||||||
_statusText = '货位已锁定,请扫描下一张执行卡';
|
_statusText = '多码上架模式,请扫描下一张执行卡';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final hasZongpai = _zongpaiNos.isNotEmpty;
|
final hasZongpai = _zongpaiNos.isNotEmpty;
|
||||||
@@ -343,7 +407,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isLocked && _zongpaiNos.length > 1) {
|
if (_mode == RegistrationMode.multiCode && _zongpaiNos.length > 1) {
|
||||||
if (_isTransitTarget && !await _ensureBatchSamePaicha(baseUrl)) {
|
if (_isTransitTarget && !await _ensureBatchSamePaicha(baseUrl)) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
@@ -420,20 +484,20 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
mode: BoxingMode.singleCode,
|
mode: BoxingMode.singleCode,
|
||||||
codes: [zongpaiNo],
|
codes: [zongpaiNo],
|
||||||
codesToClear: [zongpaiNo],
|
codesToClear: [zongpaiNo],
|
||||||
clearLocation: !_isLocked,
|
clearLocation: _mode == RegistrationMode.singleCode,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_zongpaiNos.remove(zongpaiNo);
|
_zongpaiNos.remove(zongpaiNo);
|
||||||
if (!_isLocked) {
|
if (_mode == RegistrationMode.singleCode) {
|
||||||
_locationCode = null;
|
_locationCode = null;
|
||||||
_locationType = null;
|
_locationType = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final msg = result.isOffShelfSuccess
|
final msg = result.isOffShelfSuccess
|
||||||
? '下架成功'
|
? '下架成功'
|
||||||
: (_isLocked ? '货位已锁定,请扫描下一张执行卡' : '上架成功');
|
: (_mode == RegistrationMode.multiCode ? '多码上架模式,请扫描下一张执行卡' : '上架成功');
|
||||||
_showStatusOverride(
|
_showStatusOverride(
|
||||||
msg,
|
msg,
|
||||||
StatusDotColor.green,
|
StatusDotColor.green,
|
||||||
@@ -886,20 +950,18 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
_loadOverview(newZongpaiNo);
|
_loadOverview(newZongpaiNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleLock(bool value) {
|
void _cycleMode() {
|
||||||
if (value && _locationCode == null) {
|
_feedbackService.trigger(FeedbackEvent.modeSwitch);
|
||||||
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
|
||||||
_showStatusOverride(
|
|
||||||
'请先扫描货位号',
|
|
||||||
StatusDotColor.red,
|
|
||||||
const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLocked = value;
|
switch (_mode) {
|
||||||
if (!value && _zongpaiNos.length > 1) {
|
case RegistrationMode.singleCode:
|
||||||
_zongpaiNos.removeRange(0, _zongpaiNos.length - 1);
|
_mode = RegistrationMode.multiCode;
|
||||||
|
case RegistrationMode.multiCode:
|
||||||
|
_mode = RegistrationMode.singleCode;
|
||||||
|
// 切换到单码时,只保留最后一个总排号
|
||||||
|
if (_zongpaiNos.length > 1) {
|
||||||
|
_zongpaiNos.removeRange(0, _zongpaiNos.length - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -930,7 +992,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
children: [
|
children: [
|
||||||
const Text('上架登记'),
|
const Text('上架登记'),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(child: _buildLockHeader()),
|
Expanded(child: _buildModePill()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
titleSpacing: 12,
|
titleSpacing: 12,
|
||||||
@@ -948,53 +1010,12 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLockHeader() {
|
Widget _buildModePill() {
|
||||||
final backgroundColor =
|
return BoxingModePill(
|
||||||
_isLocked ? Colors.orange.shade700 : Colors.blue.shade700;
|
isSingle: _mode == RegistrationMode.singleCode,
|
||||||
final label = _isLocked ? '货位锁定' : '未锁定';
|
label: _mode == RegistrationMode.singleCode ? '单码上架' : '多码上架',
|
||||||
|
enabled: !_isSubmitting,
|
||||||
return Material(
|
onTap: _cycleMode,
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: backgroundColor,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
onTap: () => _toggleLock(!_isLocked),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 36,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.swap_horiz, color: Colors.white, size: 18),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Flexible(
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
maxLines: 1,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'P2',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white.withValues(alpha: 0.88),
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,7 +1357,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
style: rowStyle,
|
style: rowStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isScanned && _isLocked) ...[
|
if (isScanned && _mode == RegistrationMode.multiCode) ...[
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 28,
|
width: 28,
|
||||||
@@ -1447,7 +1468,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
|||||||
: Text(
|
: Text(
|
||||||
registrationSubmitLabel(
|
registrationSubmitLabel(
|
||||||
isTransitTarget: _isTransitTarget,
|
isTransitTarget: _isTransitTarget,
|
||||||
isLocked: _isLocked,
|
isMultiCode: _mode == RegistrationMode.multiCode,
|
||||||
zongpaiCount: _zongpaiNos.length,
|
zongpaiCount: _zongpaiNos.length,
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ bool isTransitTarget(CodeType? locationType, String? locationCode) {
|
|||||||
|
|
||||||
String registrationSubmitLabel({
|
String registrationSubmitLabel({
|
||||||
required bool isTransitTarget,
|
required bool isTransitTarget,
|
||||||
required bool isLocked,
|
required bool isMultiCode,
|
||||||
required int zongpaiCount,
|
required int zongpaiCount,
|
||||||
}) {
|
}) {
|
||||||
if (!isTransitTarget) {
|
if (!isTransitTarget) {
|
||||||
return isLocked && zongpaiCount > 1 ? '批量上架($zongpaiCount 条)' : '确 认 上 架';
|
return isMultiCode && zongpaiCount > 1 ? '批量上架($zongpaiCount 条)' : '确 认 上 架';
|
||||||
}
|
}
|
||||||
return isLocked && zongpaiCount > 1 ? '批量转运并凑箱($zongpaiCount 条)' : '转运并装箱';
|
return isMultiCode && zongpaiCount > 1 ? '批量转运并凑箱($zongpaiCount 条)' : '转运并装箱';
|
||||||
}
|
}
|
||||||
|
|
||||||
Color registrationSubmitColor({
|
Color registrationSubmitColor({
|
||||||
|
|||||||
Reference in New Issue
Block a user