优化ios

This commit is contained in:
2026-08-10 16:26:15 +08:00
parent 9dc445eeb5
commit 87609c7dc4
11 changed files with 746 additions and 612 deletions

View File

@@ -200,31 +200,31 @@ class AuthCubit extends Cubit<AuthState> {
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
tcp.startHeartbeat(interval: const Duration(seconds: 4)); tcp.startHeartbeat(interval: const Duration(seconds: 4));
// 🔥 关键:等待 2.5 秒,看是否收到 have_logged_in // 🔥 已注释:等待 2.5 秒看是否收到 have_logged_in 的验证逻辑
bool isKicked = await _waitForLoginVerification(); // bool isKicked = await _waitForLoginVerification();
//
// if (isKicked) {
// // 🔥 不能进入 APP,必须清除所有登录信息
// debugPrint('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息...');
// _logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息', shouldLog: true);
//
// // 1. 删除已保存的用户信息
// await storage.deleteUser();
// debugPrint('✅ [AUTH] 已删除用户信息');
//
// // 2. 断开 TCP 连接
// tcp.forceDisconnect();
// debugPrint('✅ [AUTH] 已断开 TCP 连接');
//
// // 3. 显示 Toast
// _showLoginFailedToast("账号已在其他设备登录");
// debugPrint('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页');
// _logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页', shouldLog: true);
// return; // 停留在登录页
// }
if (isKicked) { debugPrint('✅ [AUTH] TCP连接成功,验证通过(已跳过等待)');
// 🔥 不能进入 APP,必须清除所有登录信息 _logger.logWithLevel('✅ [AUTH] TCP连接成功,验证通过(已跳过等待)', shouldLog: true);
debugPrint('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息...');
_logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,清除登录信息', shouldLog: true);
// 1. 删除已保存的用户信息
await storage.deleteUser();
debugPrint('✅ [AUTH] 已删除用户信息');
// 2. 断开 TCP 连接
tcp.forceDisconnect();
debugPrint('✅ [AUTH] 已断开 TCP 连接');
// 3. 显示 Toast
_showLoginFailedToast("账号已在其他设备登录");
debugPrint('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页');
_logger.logWithLevel('>>> [AUTH] ⚠️ 登录验证失败,停留在登录页', shouldLog: true);
return; // 停留在登录页
}
debugPrint('✅ [AUTH] TCP连接成功,验证通过');
_logger.logWithLevel('✅ [AUTH] TCP连接成功,验证通过', shouldLog: true);
} catch (e) { } catch (e) {
debugPrint('❌ [AUTH] TCP连接失败:$e'); debugPrint('❌ [AUTH] TCP连接失败:$e');
_logger.logWithLevel('❌ [AUTH] TCP连接失败:$e', level: 'ERROR'); _logger.logWithLevel('❌ [AUTH] TCP连接失败:$e', level: 'ERROR');

View File

@@ -47,7 +47,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
{'name': '后视', 'alignment': Alignment.topRight, 'icon': Icons.arrow_downward}, {'name': '后视', 'alignment': Alignment.topRight, 'icon': Icons.arrow_downward},
{'name': '左视', 'alignment': Alignment.bottomLeft, 'icon': Icons.arrow_back}, {'name': '左视', 'alignment': Alignment.bottomLeft, 'icon': Icons.arrow_back},
{'name': '右视', 'alignment': Alignment.bottomRight, 'icon': Icons.arrow_forward}, {'name': '右视', 'alignment': Alignment.bottomRight, 'icon': Icons.arrow_forward},
{'name': '俯视', 'alignment': Alignment.center, 'icon': Icons.view_agenda}, {'name': '全景', 'alignment': Alignment.center, 'icon': Icons.view_agenda},
]; ];
@override @override

View File

@@ -142,12 +142,15 @@ class _BindDevicePageState extends State<BindDevicePage> {
children: [ children: [
Expanded( Expanded(
child: SizedBox( child: SizedBox(
height: 44, height: 48,
child: OutlinedButton( child: OutlinedButton(
onPressed: state.isSubmitting onPressed: state.isSubmitting
? null ? null
: () => Navigator.of(context).pop(), : () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
side: const BorderSide( side: const BorderSide(
color: Color(0xFFE5E6EB), color: Color(0xFFE5E6EB),
), ),
@@ -158,7 +161,8 @@ class _BindDevicePageState extends State<BindDevicePage> {
child: const Text( child: const Text(
'取消', '取消',
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 14,
height: 1.0,
color: Color(0xFF4E5969), color: Color(0xFF4E5969),
), ),
), ),
@@ -168,7 +172,7 @@ class _BindDevicePageState extends State<BindDevicePage> {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: SizedBox( child: SizedBox(
height: 44, height: 48,
child: ElevatedButton( child: ElevatedButton(
onPressed: state.isSubmitting onPressed: state.isSubmitting
? null ? null
@@ -176,6 +180,9 @@ class _BindDevicePageState extends State<BindDevicePage> {
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF165DFF), backgroundColor: const Color(0xFF165DFF),
elevation: 0, elevation: 0,
padding: const EdgeInsets.symmetric(
vertical: 10,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
@@ -192,7 +199,8 @@ class _BindDevicePageState extends State<BindDevicePage> {
: const Text( : const Text(
'确定', '确定',
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 14,
height: 1.0,
color: Colors.white, color: Colors.white,
), ),
), ),

View File

@@ -36,8 +36,15 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
late DroneOsdDataSource _osdDataSource; late DroneOsdDataSource _osdDataSource;
StreamSubscription<DroneOsdEntity>? _stationOsdSubscription; StreamSubscription<DroneOsdEntity>? _stationOsdSubscription;
/// 静态缓存:跨页面实例保留上次的 host 数据
static final Map<String, dynamic> _cachedHostData = {};
final ValueNotifier<Map<String, dynamic>> _stationHostData = final ValueNotifier<Map<String, dynamic>> _stationHostData =
ValueNotifier<Map<String, dynamic>>({}); ValueNotifier<Map<String, dynamic>>(
// 初始化时从缓存加载,进入页面立即显示
Map<String, dynamic>.from(_cachedHostData),
);
@override @override
void initState() { void initState() {
@@ -73,47 +80,52 @@ class _DroneStationDetailPageState extends State<DroneStationDetailPage> {
final hostData = data['data'] is Map ? (data['data'] as Map)['host'] : null; final hostData = data['data'] is Map ? (data['data'] as Map)['host'] : null;
if (hostData == null || hostData is! Map) return; if (hostData == null || hostData is! Map) return;
final Map<String, dynamic> parsed = {}; // 🔥 基于已有数据合并,只更新非 null 字段,避免 Type 2 消息覆盖已有值
final Map<String, dynamic> merged = Map<String, dynamic>.from(_stationHostData.value);
parsed['environment_temperature'] = void _put(String key, dynamic value) {
(hostData['environment_temperature'] as num?)?.toDouble(); if (value != null) merged[key] = value;
parsed['humidity'] = (hostData['humidity'] as num?)?.toDouble(); }
parsed['wind_speed'] = (hostData['wind_speed'] as num?)?.toDouble();
parsed['rainfall'] = hostData['rainfall']?.toString(); _put('environment_temperature', (hostData['environment_temperature'] as num?)?.toDouble());
parsed['cover_state'] = hostData['cover_state']?.toString(); _put('humidity', (hostData['humidity'] as num?)?.toDouble());
parsed['drone_in_dock'] = hostData['drone_in_dock']?.toString(); _put('wind_speed', (hostData['wind_speed'] as num?)?.toDouble());
parsed['temperature'] = (hostData['temperature'] as num?)?.toDouble(); _put('rainfall', hostData['rainfall']?.toString());
parsed['putter_state'] = hostData['putter_state']?.toString(); _put('cover_state', hostData['cover_state']?.toString());
parsed['supplement_light_state'] = hostData['supplement_light_state'] _put('drone_in_dock', hostData['drone_in_dock']?.toString());
?.toString(); _put('temperature', (hostData['temperature'] as num?)?.toDouble());
parsed['alarm_state'] = hostData['alarm_state']?.toString(); _put('putter_state', hostData['putter_state']?.toString());
parsed['emergency_stop_state'] = hostData['emergency_stop_state'] _put('supplement_light_state', hostData['supplement_light_state']?.toString());
?.toString(); _put('alarm_state', hostData['alarm_state']?.toString());
parsed['silent_mode'] = hostData['silent_mode']?.toString(); _put('emergency_stop_state', hostData['emergency_stop_state']?.toString());
parsed['mode_code'] = hostData['mode_code']?.toString(); _put('silent_mode', hostData['silent_mode']?.toString());
parsed['heading'] = (hostData['heading'] as num?)?.toDouble(); _put('mode_code', hostData['mode_code']?.toString());
parsed['height'] = (hostData['height'] as num?)?.toDouble(); _put('heading', (hostData['heading'] as num?)?.toDouble());
parsed['latitude'] = (hostData['latitude'] as num?)?.toDouble(); _put('height', (hostData['height'] as num?)?.toDouble());
parsed['longitude'] = (hostData['longitude'] as num?)?.toDouble(); _put('latitude', (hostData['latitude'] as num?)?.toDouble());
parsed['home_position_is_valid'] = hostData['home_position_is_valid'] _put('longitude', (hostData['longitude'] as num?)?.toDouble());
?.toString(); _put('home_position_is_valid', hostData['home_position_is_valid']?.toString());
parsed['battery_store_mode'] = hostData['battery_store_mode']?.toString(); _put('battery_store_mode', hostData['battery_store_mode']?.toString());
parsed['first_power_on'] = hostData['first_power_on']?.toString(); _put('first_power_on', hostData['first_power_on']?.toString());
parsed['drone_charge_state'] = hostData['drone_charge_state']; _put('drone_charge_state', hostData['drone_charge_state']);
parsed['air_conditioner'] = hostData['air_conditioner']; _put('air_conditioner', hostData['air_conditioner']);
parsed['network_state'] = hostData['network_state']; _put('network_state', hostData['network_state']);
parsed['position_state'] = hostData['position_state']; _put('position_state', hostData['position_state']);
parsed['storage'] = hostData['storage']; _put('storage', hostData['storage']);
parsed['sub_device'] = hostData['sub_device']; _put('sub_device', hostData['sub_device']);
parsed['alternate_land_point'] = hostData['alternate_land_point']; _put('alternate_land_point', hostData['alternate_land_point']);
if (hostData['air_conditioner'] is Map) { if (hostData['air_conditioner'] is Map) {
final ac = hostData['air_conditioner'] as Map; final ac = hostData['air_conditioner'] as Map;
parsed['air_conditioner_state'] = ac['air_conditioner_state']?.toString(); _put('air_conditioner_state', ac['air_conditioner_state']?.toString());
parsed['air_conditioner_switch_time'] = ac['switch_time']?.toString(); _put('air_conditioner_switch_time', ac['switch_time']?.toString());
} }
_stationHostData.value = Map<String, dynamic>.from(parsed); _stationHostData.value = merged;
// 同步写入静态缓存
_cachedHostData
..clear()
..addAll(merged);
} }
/// 🔥 页面重新激活时调用(从其他页面返回时) /// 🔥 页面重新激活时调用(从其他页面返回时)

View File

@@ -25,10 +25,73 @@ class DroneOsdCard extends StatefulWidget {
State<DroneOsdCard> createState() => _DroneOsdCardState(); State<DroneOsdCard> createState() => _DroneOsdCardState();
} }
/// 单个字段的静态元数据(图标、标签、key、默认颜色)
class _FieldDef {
final IconData icon;
final String label;
final String key;
final Color defaultColor;
const _FieldDef({
required this.icon,
required this.label,
required this.key,
required this.defaultColor,
});
}
class _DroneOsdCardState extends State<DroneOsdCard> { class _DroneOsdCardState extends State<DroneOsdCard> {
StreamSubscription<DroneOsdEntity>? _subscription; StreamSubscription<DroneOsdEntity>? _subscription;
final List<Map<String, dynamic>> _osdFields = [];
final Map<String, String> _cachedValues = {}; /// 字段静态定义(顺序即展示顺序)
static const _fieldDefs = <_FieldDef>[
_FieldDef(
icon: Icons.battery_full_rounded,
label: '电量',
key: 'battery',
defaultColor: Color(0xFF00B42A),
),
_FieldDef(
icon: Icons.height_rounded,
label: '高度',
key: 'height',
defaultColor: Color(0xFF165DFF),
),
_FieldDef(
icon: Icons.trending_flat_rounded,
label: '水平速度',
key: 'horizontalSpeed',
defaultColor: Color(0xFF165DFF),
),
_FieldDef(
icon: Icons.trending_up_rounded,
label: '垂直速度',
key: 'verticalSpeed',
defaultColor: Color(0xFF00B42A),
),
_FieldDef(
icon: Icons.navigation_rounded,
label: '航向角',
key: 'heading',
defaultColor: Color(0xFF165DFF),
),
_FieldDef(
icon: Icons.satellite_rounded,
label: 'GPS',
key: 'gps',
defaultColor: Color(0xFF00B42A),
),
_FieldDef(
icon: Icons.satellite_alt_rounded,
label: 'RTK',
key: 'rtk',
defaultColor: Color(0xFF00B42A),
),
];
/// 每个字段的值和颜色用独立 ValueNotifier
/// 数据更新时只重建对应字段的 Text,不再 setState 整个卡片
final Map<String, ValueNotifier<String>> _valueNotifiers = {};
final Map<String, ValueNotifier<Color>> _colorNotifiers = {};
/// 是否正在等待任务下发后的无人机推送数据 /// 是否正在等待任务下发后的无人机推送数据
bool _isWaitingForPush = false; bool _isWaitingForPush = false;
@@ -43,7 +106,7 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initFields(); _initNotifiers();
_isWaitingForPush = droneTaskStateManager.isWaitingForOsdPush.value; _isWaitingForPush = droneTaskStateManager.isWaitingForOsdPush.value;
droneTaskStateManager.isWaitingForOsdPush.addListener(_onWaitingChanged); droneTaskStateManager.isWaitingForOsdPush.addListener(_onWaitingChanged);
if (_isWaitingForPush) { if (_isWaitingForPush) {
@@ -52,6 +115,13 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
_subscribeStream(); _subscribeStream();
} }
void _initNotifiers() {
for (final def in _fieldDefs) {
_valueNotifiers[def.key] = ValueNotifier('--');
_colorNotifiers[def.key] = ValueNotifier(def.defaultColor);
}
}
void _onWaitingChanged() { void _onWaitingChanged() {
if (!mounted) return; if (!mounted) return;
final waiting = droneTaskStateManager.isWaitingForOsdPush.value; final waiting = droneTaskStateManager.isWaitingForOsdPush.value;
@@ -105,63 +175,15 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
droneTaskStateManager.isWaitingForOsdPush.removeListener(_onWaitingChanged); droneTaskStateManager.isWaitingForOsdPush.removeListener(_onWaitingChanged);
_localWaitTimeoutTimer?.cancel(); _localWaitTimeoutTimer?.cancel();
_subscription?.cancel(); _subscription?.cancel();
for (final n in _valueNotifiers.values) {
n.dispose();
}
for (final n in _colorNotifiers.values) {
n.dispose();
}
super.dispose(); super.dispose();
} }
void _initFields() {
_osdFields.addAll([
{
'icon': Icons.battery_full_rounded,
'label': '电量',
'key': 'battery',
'value': '未知',
'color': const Color(0xFF00B42A),
},
{
'icon': Icons.height_rounded,
'label': '高度',
'key': 'height',
'value': '未知',
'color': const Color(0xFF165DFF),
},
{
'icon': Icons.trending_flat_rounded,
'label': '水平速度',
'key': 'horizontalSpeed',
'value': '未知',
'color': const Color(0xFF165DFF),
},
{
'icon': Icons.trending_up_rounded,
'label': '垂直速度',
'key': 'verticalSpeed',
'value': '未知',
'color': const Color(0xFF00B42A),
},
{
'icon': Icons.navigation_rounded,
'label': '航向角',
'key': 'heading',
'value': '未知',
'color': const Color(0xFF165DFF),
},
{
'icon': Icons.satellite_rounded,
'label': 'GPS',
'key': 'gps',
'value': '未知',
'color': const Color(0xFF00B42A),
},
{
'icon': Icons.satellite_alt_rounded,
'label': 'RTK',
'key': 'rtk',
'value': '未知',
'color': const Color(0xFF00B42A),
},
]);
}
void _subscribeStream() { void _subscribeStream() {
if (widget.deviceSn.isEmpty) return; if (widget.deviceSn.isEmpty) return;
@@ -171,6 +193,20 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
}); });
} }
/// 更新单个字段的值和颜色(仅当值变化时才触发监听者重建)
void _updateField(String key, String value, [Color? color]) {
final vNotifier = _valueNotifiers[key];
if (vNotifier != null && vNotifier.value != value) {
vNotifier.value = value;
}
if (color != null) {
final cNotifier = _colorNotifiers[key];
if (cNotifier != null && cNotifier.value != color) {
cNotifier.value = color;
}
}
}
void _parseOsdFields(DroneOsdEntity osd) { void _parseOsdFields(DroneOsdEntity osd) {
final data = osd.rawData; final data = osd.rawData;
final dataMap = data['data'] is Map ? data['data'] as Map : null; final dataMap = data['data'] is Map ? data['data'] as Map : null;
@@ -186,22 +222,24 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
if (droneData == null) return; if (droneData == null) return;
// 收到有效的无人机推送数据,标记已收到数据 // 收到有效的无人机推送数据,标记已收到数据
// 即使接口返回 isDroneOnline=false,MQTT 推送了数据说明无人机实际已在线 // 首次收到数据需要 setState 切换卡片显示,之后数据更新通过 ValueNotifier 局部刷新
final wasFirstData = !_hasReceivedData;
_hasReceivedData = true; _hasReceivedData = true;
// 收到有效的无人机推送数据,停止本地“推送信息检测中”转圈 // 收到有效的无人机推送数据,停止本地"推送信息检测中"转圈
// 注意:只清除本地状态,不清除全局状态 // 注意:只清除本地状态,不清除全局状态
// 因为本卡片在用户离开详情页期间仍可能收到旧 OSD 数据, // 因为本卡片在用户离开详情页期间仍可能收到旧 OSD 数据,
// 提前清除全局状态会导致用户返回后看不到转圈效果 // 提前清除全局状态会导致用户返回后看不到转圈效果
if (_isWaitingForPush) { final needStopWaiting = _isWaitingForPush;
if (needStopWaiting) {
_localWaitTimeoutTimer?.cancel(); _localWaitTimeoutTimer?.cancel();
setState(() { _isWaitingForPush = false;
_isWaitingForPush = false;
});
} }
final parsedValues = <String, String>{}; // 仅在首次切换卡片显示 / 停止等待转圈时调用 setState(很低频)
final parsedColors = <String, Color>{}; if (wasFirstData || needStopWaiting) {
if (mounted) setState(() {});
}
double? height = double? height =
(droneData['height'] as num?)?.toDouble() ?? (droneData['height'] as num?)?.toDouble() ??
@@ -235,77 +273,41 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
final int? rtkSatellites = droneData['rtk_satellites'] as int?; final int? rtkSatellites = droneData['rtk_satellites'] as int?;
if (batteryPercent != null) { if (batteryPercent != null) {
parsedValues['battery'] = '$batteryPercent%'; _updateField('battery', '$batteryPercent%');
parsedColors['battery'] = batteryPercent > 50
? const Color(0xFF00B42A)
: batteryPercent > 20
? const Color(0xFFFF7D00)
: const Color(0xFFF53F3F);
} }
if (height != null) { if (height != null) {
parsedValues['height'] = '${height.toStringAsFixed(1)}m'; _updateField('height', '${height.toStringAsFixed(1)}m');
parsedColors['height'] = const Color(0xFF165DFF);
} }
if (horizontalSpeed != null) { if (horizontalSpeed != null) {
parsedValues['horizontalSpeed'] = _updateField(
'${horizontalSpeed.toStringAsFixed(1)}m/s'; 'horizontalSpeed',
'${horizontalSpeed.toStringAsFixed(1)}m/s',
);
} }
if (verticalSpeed != null) { if (verticalSpeed != null) {
parsedValues['verticalSpeed'] = '${verticalSpeed.toStringAsFixed(1)}m/s'; _updateField('verticalSpeed', '${verticalSpeed.toStringAsFixed(1)}m/s');
parsedColors['verticalSpeed'] = verticalSpeed > 5
? const Color(0xFFF53F3F)
: const Color(0xFF00B42A);
} }
if (heading != null) { if (heading != null) {
parsedValues['heading'] = '${heading.toStringAsFixed(0)}°'; _updateField('heading', '${heading.toStringAsFixed(0)}°');
} }
if (gpsSatellites != null) { if (gpsSatellites != null) {
parsedValues['gps'] = '$gpsSatellites颗'; _updateField('gps', '$gpsSatellites颗');
parsedColors['gps'] = gpsSatellites >= 6
? const Color(0xFF00B42A)
: const Color(0xFFFF7D00);
} }
// RTK:优先展示定位状态(is_fixed),其次回退到卫星数 // RTK:优先展示定位状态(is_fixed),其次回退到卫星数
if (rtkFixed != null) { if (rtkFixed != null) {
String rtkStatus;
Color rtkColor;
switch (rtkFixed) { switch (rtkFixed) {
case 2: case 2:
rtkStatus = '固定解'; _updateField('rtk', '固定解');
rtkColor = const Color(0xFF00B42A);
break; break;
case 1: case 1:
rtkStatus = '浮点解'; _updateField('rtk', '浮点解');
rtkColor = const Color(0xFFFF7D00);
break; break;
default: default:
rtkStatus = '未定位'; _updateField('rtk', '未定位');
rtkColor = const Color(0xFF86909C);
} }
parsedValues['rtk'] = rtkStatus;
parsedColors['rtk'] = rtkColor;
} else if (rtkSatellites != null) { } else if (rtkSatellites != null) {
parsedValues['rtk'] = '$rtkSatellites颗'; _updateField('rtk', '$rtkSatellites颗');
parsedColors['rtk'] = rtkSatellites >= 4
? const Color(0xFF00B42A)
: const Color(0xFFFF7D00);
} }
if (!mounted) return;
setState(() {
for (var field in _osdFields) {
final key = field['key'] as String;
if (parsedValues.containsKey(key)) {
_cachedValues[key] = parsedValues[key]!;
field['value'] = parsedValues[key];
} else if (_cachedValues.containsKey(key)) {
field['value'] = _cachedValues[key];
}
if (parsedColors.containsKey(key)) {
field['color'] = parsedColors[key];
}
}
});
} }
@override @override
@@ -516,8 +518,8 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
const columns = 3; const columns = 3;
final itemWidth = final itemWidth =
(constraints.maxWidth - spacing * (columns - 1)) / columns; (constraints.maxWidth - spacing * (columns - 1)) / columns;
final items = _osdFields.map((field) { final items = _fieldDefs.map((def) {
return _buildOsdGridItem(field, itemWidth); return _buildOsdGridItem(def, itemWidth);
}).toList(); }).toList();
return Wrap( return Wrap(
spacing: spacing, spacing: spacing,
@@ -531,53 +533,63 @@ class _DroneOsdCardState extends State<DroneOsdCard> {
); );
} }
Widget _buildOsdGridItem(Map<String, dynamic> field, double width) { /// 每个字段用 ValueListenableBuilder 包裹,数据变化时只重建这一格
final color = field['color'] as Color; Widget _buildOsdGridItem(_FieldDef def, double width) {
return SizedBox( return SizedBox(
width: width, width: width,
child: Container( child: ValueListenableBuilder<Color>(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), valueListenable: _colorNotifiers[def.key]!,
decoration: BoxDecoration( builder: (context, color, _) {
color: color.withOpacity(0.08), return Container(
borderRadius: BorderRadius.circular(6), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
border: Border.all(color: color.withOpacity(0.2), width: 1), decoration: BoxDecoration(
), color: color.withOpacity(0.08),
child: Column( borderRadius: BorderRadius.circular(6),
mainAxisSize: MainAxisSize.min, border: Border.all(color: color.withOpacity(0.2), width: 1),
children: [ ),
Row( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(field['icon'] as IconData, color: color, size: 14), Row(
const SizedBox(width: 2), mainAxisAlignment: MainAxisAlignment.center,
Expanded( children: [
child: Text( Icon(def.icon, color: color, size: 14),
field['label'] as String, const SizedBox(width: 2),
style: const TextStyle( Expanded(
fontSize: 9, child: Text(
color: Color(0xFF86909C), def.label,
style: const TextStyle(
fontSize: 9,
color: Color(0xFF86909C),
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
textAlign: TextAlign.center, ],
maxLines: 1, ),
overflow: TextOverflow.ellipsis, const SizedBox(height: 3),
), ValueListenableBuilder<String>(
valueListenable: _valueNotifiers[def.key]!,
builder: (context, value, _) {
return Text(
value,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: color,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
), ),
], ],
), ),
const SizedBox(height: 3), );
Text( },
field['value'] as String,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: color,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
), ),
); );
} }

View File

@@ -41,7 +41,7 @@ class _RobotHeaderCardState extends State<RobotHeaderCard> {
'alignment': Alignment.bottomRight, 'alignment': Alignment.bottomRight,
'icon': Icons.arrow_forward, 'icon': Icons.arrow_forward,
}, },
{'name': '俯视', 'alignment': Alignment.center, 'icon': Icons.view_agenda}, {'name': '全景', 'alignment': Alignment.center, 'icon': Icons.view_agenda},
]; ];
@override @override

View File

@@ -23,18 +23,23 @@ class WorkOrderRemoteDataSourceImpl implements WorkOrderRemoteDataSource {
if (orgId != null) { if (orgId != null) {
queryParameters['orgId'] = orgId; queryParameters['orgId'] = orgId;
} }
print('[WorkOrder] 请求URL: ${HttpApiConsts.workOrderList}');
print('[WorkOrder] 请求参数: $queryParameters');
final response = await _dio.get( final response = await _dio.get(
HttpApiConsts.workOrderList, HttpApiConsts.workOrderList,
queryParameters: queryParameters, queryParameters: queryParameters,
); );
final responseData = response.data; final responseData = response.data;
print('[WorkOrder] 响应状态码: ${response.statusCode}');
print('[WorkOrder] 响应数据: $responseData');
final int code = responseData['code'] ?? -1; final int code = responseData['code'] ?? -1;
if (code != 0 && code != 200) { if (code != 0 && code != 200) {
throw Exception('获取工单列表失败: code=$code'); throw Exception('获取工单列表失败: code=$code');
} }
final List<dynamic> rows = responseData['rows'] ?? []; final List<dynamic> rows = responseData['rows'] ?? [];
print('[WorkOrder] 返回数据条数: ${rows.length}');
return rows return rows
.map((item) => WorkOrderModel.fromJson(item as Map<String, dynamic>)) .map((item) => WorkOrderModel.fromJson(item as Map<String, dynamic>))
.toList(); .toList();

View File

@@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart'; import 'package:maibu_satabot_v2/features/v2/site/presentation/cubit/site_cubit.dart';
import '../../../../../core/app/app_user_cubit.dart';
import '../../../../../core/consts/workorder_consts.dart'; import '../../../../../core/consts/workorder_consts.dart';
import '../../../../../core/error/failure.dart'; import '../../../../../core/error/failure.dart';
import '../../../../../core/error/workorder_failure.dart'; import '../../../../../core/error/workorder_failure.dart';
@@ -115,7 +114,6 @@ class WorkOrderCubit extends Cubit<WorkOrderState> {
try { try {
final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id; final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id;
final orgId = GetIt.I<AppUserCubit>().state.user?.orgId;
List<WorkOrderEntity> allWorkOrders = []; List<WorkOrderEntity> allWorkOrders = [];
String? errorMessage; String? errorMessage;
@@ -124,7 +122,6 @@ class WorkOrderCubit extends Cubit<WorkOrderState> {
page: 1, page: 1,
pageSize: _pageSize, pageSize: _pageSize,
siteId: siteId, siteId: siteId,
orgId: orgId,
); );
listResult.fold((Failure failure) { listResult.fold((Failure failure) {
@@ -169,12 +166,10 @@ class WorkOrderCubit extends Cubit<WorkOrderState> {
try { try {
final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id; final siteId = GetIt.I<SiteCubit>().state.selectedSite?.id;
final orgId = GetIt.I<AppUserCubit>().state.user?.orgId;
final result = await getWorkOrderListUseCase.execute( final result = await getWorkOrderListUseCase.execute(
page: currentState.currentPage + 1, page: currentState.currentPage + 1,
pageSize: _pageSize, pageSize: _pageSize,
siteId: siteId, siteId: siteId,
orgId: orgId,
); );
result.fold( result.fold(

View File

@@ -984,10 +984,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3" sha256: ce3f059ebd6dbfab7292bba0e893e354b46730636820d3c9ef69005ce2d55bce
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.5.7" version: "7.4.0"
mqtt_client: mqtt_client:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -139,7 +139,7 @@ dependencies:
markdown_widget: ^2.0.0 # 产品参数用的 markdown 渲染 markdown_widget: ^2.0.0 # 产品参数用的 markdown 渲染
flutter_markdown: ^0.7.1 flutter_markdown: ^0.7.1
mobile_scanner: ^3.4.1 mobile_scanner: ^7.4.0
vibration: ^3.1.8 vibration: ^3.1.8
flutter_patcher: ^0.1.2 # Add flutter_patcher here flutter_patcher: ^0.1.2 # Add flutter_patcher here
open_file: ^3.3.2 # 打开文件(安装 APK) open_file: ^3.3.2 # 打开文件(安装 APK)