一次大的提交

This commit is contained in:
2026-08-07 08:49:29 +08:00
parent bf2fc49c19
commit db70665ced
181 changed files with 23060 additions and 2804 deletions

View File

@@ -0,0 +1,581 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
import '../../../../../core/app/app_user_cubit.dart';
import '../../../site/presentation/cubit/site_cubit.dart';
import '../../domain/entities/device_run_param_entity.dart';
import '../../domain/usecases/device_run_param_usecases.dart';
import '../../../../../core/services/device_permission_service.dart';
class RobotParamSettingsPage extends StatefulWidget {
final Map<String, dynamic> robot;
const RobotParamSettingsPage({super.key, required this.robot});
@override
State<RobotParamSettingsPage> createState() => _RobotParamSettingsPageState();
}
class _RobotParamSettingsPageState extends State<RobotParamSettingsPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
bool _isLoading = true;
bool _isSaving = false;
String? _errorMessage;
DeviceRunParamEntity? _param;
// 表单控制器(仅可编辑的5个字段)
late TextEditingController _runSpeedController;
late TextEditingController _leftForwardGainController;
late TextEditingController _leftBackwardGainController;
late TextEditingController _rightForwardGainController;
late TextEditingController _rightBackwardGainController;
String get _deviceId => widget.robot['name'] as String? ?? '';
// ============ 字段定义 ============
static const _editableKeys = {
'runSpeed',
'leftForwardGain',
'leftBackwardGain',
'rightForwardGain',
'rightBackwardGain',
};
static const _skipKeys = {
'createBy', 'createTime', 'updateBy', 'updateTime', 'delFlag', 'remark',
};
static const _fieldLabels = {
'id': 'ID',
'deviceId': '设备ID',
'siteId': '场站ID',
'orgId': '组织ID',
'runSpeed': '运行速度',
'header1': 'Header1',
'header2': 'Header2',
'cmd': 'CMD',
'chipUidSign': '芯片UID标记',
'chipUid': '芯片UID',
'remoteConfig': '远程配置',
'knifeMotorMode': '刀盘电机模式',
'walkMotorMode': '行走电机模式',
'leftMotorReverse': '左电机反转',
'rightMotorReverse': '右电机反转',
'swapChannel': '交换通道',
'use4G': '使用4G',
'forwardSpeedLimit': '前进限速',
'turnSpeedLimit': '转弯限速',
'knifePolarity': '刀盘极性',
'fanPolarity': '风扇极性',
'throttlePolarity': '油门极性',
'liftProtectTime': '升降保护时间',
'dualRtk': '双RTK',
'knifeChannel': '刀盘通道',
'fanChannel': '风扇通道',
'throttleChannel': '油门通道',
'remoteType': '遥控类型',
'relayBoard': '继电器板',
'liftChannel': '升降通道',
'chassisChannel': '底盘通道',
'armChannel': '机械臂通道',
'fuelPumpChannel': '燃油泵通道',
'wifiName': 'WiFi名称',
'wifiPassword': 'WiFi密码',
'batteryType': '电池类型',
'driveType': '驱动类型',
'gearRatio': '齿轮比',
'robotLength': '机器人长度',
'robotWidth': '机器人宽度',
'robotHeight': '机器人高度',
'knifeWidth': '刀盘宽度',
'tyreSize': '轮胎尺寸',
'leftForwardGain': '左轮前进',
'leftBackwardGain': '左轮后退',
'rightForwardGain': '右轮前进',
'rightBackwardGain': '右轮后退',
'firmwareVersion': '固件版本',
'crc16': 'CRC16',
'tail1': '尾部1',
'tail2': '尾部2',
};
// ============ 生命周期 ============
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_initControllers();
_loadData();
}
void _initControllers() {
_runSpeedController = TextEditingController();
_leftForwardGainController = TextEditingController();
_leftBackwardGainController = TextEditingController();
_rightForwardGainController = TextEditingController();
_rightBackwardGainController = TextEditingController();
}
void _fillControllers(DeviceRunParamEntity param) {
_runSpeedController.text = param.runSpeed.toStringAsFixed(0);
_leftForwardGainController.text = param.leftForwardGain.toString();
_leftBackwardGainController.text = param.leftBackwardGain.toString();
_rightForwardGainController.text = param.rightForwardGain.toString();
_rightBackwardGainController.text = param.rightBackwardGain.toString();
}
TextEditingController? _controllerForKey(String key) {
switch (key) {
case 'runSpeed': return _runSpeedController;
case 'leftForwardGain': return _leftForwardGainController;
case 'leftBackwardGain': return _leftBackwardGainController;
case 'rightForwardGain': return _rightForwardGainController;
case 'rightBackwardGain': return _rightBackwardGainController;
default: return null;
}
}
void _showReadonlyTip() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('暂不支持修改'),
duration: Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.only(bottom: 80, left: 80, right: 80),
),
);
}
@override
void dispose() {
_tabController.dispose();
_runSpeedController.dispose();
_leftForwardGainController.dispose();
_leftBackwardGainController.dispose();
_rightForwardGainController.dispose();
_rightBackwardGainController.dispose();
super.dispose();
}
// ============ 数据加载 ============
Future<void> _loadData() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
final useCase = GetIt.I<GetDeviceRunParamUseCase>();
final result = await useCase.execute(_deviceId);
if (!mounted) return;
result.fold(
(failure) {
setState(() {
_isLoading = false;
_errorMessage = failure.message;
});
},
(param) {
setState(() {
_isLoading = false;
_param = param;
});
_fillControllers(param);
},
);
}
// ============ 保存 ============
Future<void> _handleSave() async {
if (_isSaving || _param == null) return;
setState(() => _isSaving = true);
try {
// 🔐 前置权限校验:只有 code=200 && data=true 才允许保存
final permissionService = GetIt.I<DevicePermissionService>();
final hasPermission = await permissionService.checkPermission(_deviceId);
if (!mounted) return;
if (!hasPermission) {
setState(() => _isSaving = false);
_showSnackBar('权限校验未通过,无法保存', isError: true);
return;
}
// 权限通过提示
await showDialog(
context: context,
barrierDismissible: false,
builder: (dialogCtx) => AlertDialog(
title: const Row(
children: [
Icon(Icons.check_circle, color: Colors.green, size: 28),
SizedBox(width: 8),
Text('权限通过'),
],
),
content: const Text('您有权限操作此设备'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogCtx),
child: const Text('确定'),
),
],
),
);
if (!mounted) return;
final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id ?? 0;
final orgId = GetIt.I<AppUserCubit>().state.user?.orgId ?? 0;
final useCase = GetIt.I<SaveDeviceRunParamUseCase>();
final params = <String, dynamic>{
'id': _param!.id,
'deviceId': _deviceId,
'siteId': siteId,
'orgId': orgId,
'runSpeed': int.tryParse(_runSpeedController.text) ?? _param!.runSpeed,
'leftForwardGain':
double.tryParse(_leftForwardGainController.text) ??
_param!.leftForwardGain,
'leftBackwardGain':
double.tryParse(_leftBackwardGainController.text) ??
_param!.leftBackwardGain,
'rightForwardGain':
double.tryParse(_rightForwardGainController.text) ??
_param!.rightForwardGain,
'rightBackwardGain':
double.tryParse(_rightBackwardGainController.text) ??
_param!.rightBackwardGain,
};
final result = await useCase.execute(params);
if (!mounted) return;
result.fold(
(failure) {
setState(() => _isSaving = false);
_showSnackBar('保存失败: ${failure.message}', isError: true);
},
(success) {
setState(() => _isSaving = false);
_showSnackBar('保存成功');
Navigator.pop(context);
},
);
} catch (e) {
if (mounted) {
setState(() => _isSaving = false);
_showSnackBar('保存异常: $e', isError: true);
}
}
}
void _showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError ? Colors.red : Colors.green,
duration: const Duration(seconds: 2),
),
);
}
// ============ UI ============
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
child: Scaffold(
backgroundColor: const Color(0xFFF5F6F8),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0.5,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2129)),
onPressed: () => Navigator.pop(context),
),
title: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'参数设置',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
const SizedBox(height: 2),
Text(
_deviceId,
style: const TextStyle(
fontSize: 11,
color: Color(0xFF86909C),
),
maxLines: 2,
softWrap: true,
textAlign: TextAlign.center,
),
],
),
centerTitle: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(44),
child: Container(
color: Colors.white,
child: TabBar(
controller: _tabController,
indicatorColor: const Color(0xFF165DFF),
indicatorWeight: 2,
labelColor: const Color(0xFF165DFF),
unselectedLabelColor: const Color(0xFF86909C),
labelStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
tabs: const [
Tab(text: '参数设置'),
Tab(text: '增益参数设置'),
],
),
),
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildParamTab(),
_buildGainParamTab(),
],
),
bottomNavigationBar: _buildBottomBar(),
),
);
}
// ============ 参数设置 Tab ============
Widget _buildParamTab() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_errorMessage != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 48, color: Color(0xFF86909C)),
const SizedBox(height: 16),
Text(
_errorMessage!,
style: const TextStyle(fontSize: 14, color: Color(0xFF86909C)),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadData,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
foregroundColor: Colors.white,
),
child: const Text('重试'),
),
],
),
);
}
final raw = _param!.rawData;
final keys = raw.keys
.where((k) => !_skipKeys.contains(k))
.toList();
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: List.generate(keys.length, (i) {
final key = keys[i];
final label = _fieldLabels[key] ?? key;
final isEditable = _editableKeys.contains(key);
final isLast = i == keys.length - 1;
final value = raw[key];
return Column(
children: [
_buildFieldRow(key, label, value, isEditable),
if (!isLast) _buildDivider(),
],
);
}),
),
),
);
}
// ============ 增益参数 Tab ============
Widget _buildGainParamTab() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.construction, size: 48, color: Color(0xFFC9CDD4)),
SizedBox(height: 16),
Text(
'暂未开放',
style: TextStyle(fontSize: 14, color: Color(0xFF86909C)),
),
],
),
);
}
// ============ 单行字段 ============
Widget _buildFieldRow(
String key,
String label,
dynamic value,
bool editable,
) {
final controller = editable ? _controllerForKey(key) : null;
final displayValue = value?.toString() ?? '-';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
SizedBox(
width: 90,
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF4E5969),
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 8),
Expanded(
child: editable
? TextField(
controller: controller,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[\d.]')),
],
style: const TextStyle(fontSize: 14, color: Color(0xFF1D2129)),
decoration: _inputDecoration(),
)
: GestureDetector(
onTap: _showReadonlyTip,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE5E6EB)),
),
child: Text(
displayValue,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF86909C),
),
),
),
),
),
],
),
);
}
InputDecoration _inputDecoration() {
return const InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
filled: true,
fillColor: Color(0xFFF7F8FA),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
borderSide: BorderSide(color: Color(0xFFE5E6EB)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
borderSide: BorderSide(color: Color(0xFFE5E6EB)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
borderSide: BorderSide(color: Color(0xFF165DFF)),
),
isDense: true,
);
}
Widget _buildDivider() {
return const Divider(height: 1, color: Color(0xFFF2F3F5));
}
// ============ 底部保存按钮 ============
Widget _buildBottomBar() {
final bottom = MediaQuery.of(context).padding.bottom;
return Container(
padding: EdgeInsets.fromLTRB(16, 12, 16, 12 + bottom),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: ElevatedButton(
onPressed: _isLoading || _isSaving ? null : _handleSave,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF),
foregroundColor: Colors.white,
elevation: 0,
minimumSize: const Size(double.infinity, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isSaving
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Text(
'保存',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
);
}
}