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:pad_scanner/pages/boxing_page.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 {
|
||||
const RegistrationPage({super.key});
|
||||
@@ -29,7 +35,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
final _zongpaiNos = <String>[];
|
||||
String? _locationCode;
|
||||
CodeType? _locationType;
|
||||
bool _isLocked = false;
|
||||
RegistrationMode _mode = RegistrationMode.singleCode;
|
||||
bool _isSubmitting = false;
|
||||
|
||||
StatusDotColor _statusDot = StatusDotColor.blue;
|
||||
@@ -88,7 +94,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
void _onKeyEvent(KeyEvent event) {
|
||||
if (event is! KeyDownEvent) return;
|
||||
if (_isLockToggleKey(event)) {
|
||||
_toggleLock(!_isLocked);
|
||||
_cycleMode();
|
||||
return;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||
@@ -109,14 +115,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
_handleZongpaiScan(parsed.value);
|
||||
case CodeType.locationNormal:
|
||||
case CodeType.locationTransit:
|
||||
if (!_isLocked) {
|
||||
_feedbackService.trigger(FeedbackEvent.scanValid);
|
||||
setState(() {
|
||||
_locationCode = parsed.value;
|
||||
_locationType = parsed.type;
|
||||
_clearStatusOverride();
|
||||
});
|
||||
} else {}
|
||||
_handleLocationScan(parsed.value, parsed.type);
|
||||
case CodeType.invalid:
|
||||
_feedbackService.trigger(FeedbackEvent.scanInvalid);
|
||||
_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) {
|
||||
// If cross-paicha dialog is showing, just vibrate and discard
|
||||
if (_crossPaichaPending) {
|
||||
@@ -134,8 +198,8 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
// In locked mode with existing data, check for cross-paicha scan
|
||||
if (_isLocked &&
|
||||
// In multiCode mode with existing data, check for cross-paicha scan
|
||||
if (_mode == RegistrationMode.multiCode &&
|
||||
_zongpaiNos.isNotEmpty &&
|
||||
_overview?.success == true &&
|
||||
!_overview!.items.any((item) => item.zongpaiNo == zongpaiNo)) {
|
||||
@@ -148,7 +212,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
final shouldRefresh = _overviewZongpaiNo != zongpaiNo;
|
||||
_feedbackService.trigger(FeedbackEvent.scanValid);
|
||||
setState(() {
|
||||
if (_isLocked) {
|
||||
if (_mode == RegistrationMode.multiCode) {
|
||||
if (!_zongpaiNos.contains(zongpaiNo)) {
|
||||
_zongpaiNos.add(zongpaiNo);
|
||||
}
|
||||
@@ -197,9 +261,9 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
_statusText = '正在提交…';
|
||||
return;
|
||||
}
|
||||
if (_isLocked && _locationCode != null && _zongpaiNos.isEmpty) {
|
||||
if (_mode == RegistrationMode.multiCode && _locationCode != null && _zongpaiNos.isEmpty) {
|
||||
_statusDot = StatusDotColor.blue;
|
||||
_statusText = '货位已锁定,请扫描下一张执行卡';
|
||||
_statusText = '多码上架模式,请扫描下一张执行卡';
|
||||
return;
|
||||
}
|
||||
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 (!mounted) return;
|
||||
setState(() => _isSubmitting = false);
|
||||
@@ -420,20 +484,20 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
mode: BoxingMode.singleCode,
|
||||
codes: [zongpaiNo],
|
||||
codesToClear: [zongpaiNo],
|
||||
clearLocation: !_isLocked,
|
||||
clearLocation: _mode == RegistrationMode.singleCode,
|
||||
);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_zongpaiNos.remove(zongpaiNo);
|
||||
if (!_isLocked) {
|
||||
if (_mode == RegistrationMode.singleCode) {
|
||||
_locationCode = null;
|
||||
_locationType = null;
|
||||
}
|
||||
});
|
||||
final msg = result.isOffShelfSuccess
|
||||
? '下架成功'
|
||||
: (_isLocked ? '货位已锁定,请扫描下一张执行卡' : '上架成功');
|
||||
: (_mode == RegistrationMode.multiCode ? '多码上架模式,请扫描下一张执行卡' : '上架成功');
|
||||
_showStatusOverride(
|
||||
msg,
|
||||
StatusDotColor.green,
|
||||
@@ -886,20 +950,18 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
_loadOverview(newZongpaiNo);
|
||||
}
|
||||
|
||||
void _toggleLock(bool value) {
|
||||
if (value && _locationCode == null) {
|
||||
_feedbackService.trigger(FeedbackEvent.submitFailure);
|
||||
_showStatusOverride(
|
||||
'请先扫描货位号',
|
||||
StatusDotColor.red,
|
||||
const Duration(seconds: 2),
|
||||
);
|
||||
return;
|
||||
}
|
||||
void _cycleMode() {
|
||||
_feedbackService.trigger(FeedbackEvent.modeSwitch);
|
||||
setState(() {
|
||||
_isLocked = value;
|
||||
if (!value && _zongpaiNos.length > 1) {
|
||||
_zongpaiNos.removeRange(0, _zongpaiNos.length - 1);
|
||||
switch (_mode) {
|
||||
case RegistrationMode.singleCode:
|
||||
_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: [
|
||||
const Text('上架登记'),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _buildLockHeader()),
|
||||
Expanded(child: _buildModePill()),
|
||||
],
|
||||
),
|
||||
titleSpacing: 12,
|
||||
@@ -948,53 +1010,12 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLockHeader() {
|
||||
final backgroundColor =
|
||||
_isLocked ? Colors.orange.shade700 : Colors.blue.shade700;
|
||||
final label = _isLocked ? '货位锁定' : '未锁定';
|
||||
|
||||
return Material(
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Widget _buildModePill() {
|
||||
return BoxingModePill(
|
||||
isSingle: _mode == RegistrationMode.singleCode,
|
||||
label: _mode == RegistrationMode.singleCode ? '单码上架' : '多码上架',
|
||||
enabled: !_isSubmitting,
|
||||
onTap: _cycleMode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1336,7 +1357,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
style: rowStyle,
|
||||
),
|
||||
),
|
||||
if (isScanned && _isLocked) ...[
|
||||
if (isScanned && _mode == RegistrationMode.multiCode) ...[
|
||||
const SizedBox(width: 6),
|
||||
SizedBox(
|
||||
width: 28,
|
||||
@@ -1447,7 +1468,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
|
||||
: Text(
|
||||
registrationSubmitLabel(
|
||||
isTransitTarget: _isTransitTarget,
|
||||
isLocked: _isLocked,
|
||||
isMultiCode: _mode == RegistrationMode.multiCode,
|
||||
zongpaiCount: _zongpaiNos.length,
|
||||
),
|
||||
style: const TextStyle(
|
||||
|
||||
Reference in New Issue
Block a user