退出登录+机器状态+切换设备+历史作业记录+设备信息

This commit is contained in:
2026-02-25 13:41:23 +08:00
parent 3c48a96d26
commit d81d0c04ea
32 changed files with 1940 additions and 142 deletions

View File

@@ -7,6 +7,16 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 关键! -->
<uses-permission android:name="android.permission.CAMERA" />
<application
android:label="飒沓机器人"
android:name="${applicationName}"
@@ -38,11 +48,20 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- 高德 Key -->
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="bbb1f0f20eed6bf679eddf2625630aba" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and

View File

@@ -45,5 +45,11 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问您的位置以显示地图</string>
<key>NSCameraUsageDescription</key>
<string>需要访问相机以扫描二维码</string>
</dict>
</plist>

View File

@@ -21,7 +21,10 @@ import '../../features/auth/presentation/bloc/auth_cubit.dart';
import '../../features/auth/presentation/bloc/login_bloc.dart';
import '../../features/auth/presentation/bloc/login_cubit.dart';
import '../../features/devices/data/datasources/device_http_datasource.dart';
import '../../features/devices/data/repositories/device_hostriry_work_impl.dart';
import '../../features/devices/data/repositories/device_repository_impl.dart';
import '../../features/devices/domain/repositories/device_hostrity_work_repository.dart';
import '../../features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart';
import '../app/app_user_cubit.dart';
import '../network/dio_client.dart';
@@ -74,6 +77,16 @@ Future<void> init() async {
localTokenStorage: sl(), // 自动寻找已注册的 TokenStorage
),
);
//工作历史
sl.registerLazySingleton<DeviceHostrityWorkRepositoryRepository>(
() => DeviceHostrityWorkRepositoryImpl(
client: sl<Dio>(), // 使用 Dio 实例
userStorage: sl<UserStorage>(),
),
);
sl.registerLazySingleton<DeviceRepository>(() => DeviceRepositoryImpl(sl()));
sl.registerLazySingleton<RemoteControlRepository>(
() => RemoteControlRepositoryImpl(sl(), sl()),
@@ -83,6 +96,9 @@ Future<void> init() async {
sl.registerLazySingleton(() => LoginUseCase(sl()));
sl.registerLazySingleton(() => GetUserDeviceUseCase(sl()));
sl.registerLazySingleton(() => DiffSteerUseCase());
sl.registerLazySingleton(() => DeviceWorkHostrirty(sl()));
/// 5. 状态管理 (Cubit/Bloc)
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
@@ -106,4 +122,14 @@ Future<void> init() async {
/// 8. 页面级 Bloc/Cubit (Factory)
sl.registerFactory(() => LoginBloc(sl()));
sl.registerFactory(() => LoginCubit(sl(), sl()));
}

View File

@@ -13,12 +13,12 @@ class SentryLoggerImpl implements ILoggerService {
@override
Future<void> init() async {
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.environment = EnvConfig.environment;
// 生产环境只采样 20% 的性能数据,测试环境全开
options.tracesSampleRate = EnvConfig.isProduction ? 0.2 : 1.0;
});
// await SentryFlutter.init((options) {
// options.dsn = EnvConfig.sentryDsn;
// options.environment = EnvConfig.environment;
// // 生产环境只采样 20% 的性能数据,测试环境全开
// options.tracesSampleRate = EnvConfig.isProduction ? 0.2 : 1.0;
// });
await _collectDeviceInfo();
await _prepareLocalFile();

View File

@@ -2,13 +2,13 @@ import 'dart:convert';
import 'protocol_decoder.dart';
import 'tcp/tcp_client.dart';
// 注意:这里需要导入你具体的 UserModel
// import '../../features/auth/data/models/user_model.dart';
class NetMessageDispatcher {
final TcpClient tcpClient;
NetMessageDispatcher(this.tcpClient);
/// 过滤特定指令的流
Stream<RawPacket> onCommand(int commandCode) {
return tcpClient.packetStream.where((p) => p.command == commandCode);
@@ -23,4 +23,7 @@ class NetMessageDispatcher {
Stream<Map<String, dynamic>> onJsonMessage(int cmd) {
return onCommand(cmd).map((p) => jsonDecode(utf8.decode(p.payload)));
}
}

View File

@@ -13,6 +13,11 @@ class TcpClient {
final _controller = StreamController<RawPacket>.broadcast();
Stream<RawPacket> get packetStream => _controller.stream;
// 新增:心跳定时器
Timer? _heartbeatTimer;
// 通过参数配置,不硬编码
Future<void> connect({required String host, required int port}) async {
try {
@@ -22,10 +27,14 @@ class TcpClient {
timeout: const Duration(seconds: 5),
);
_socket!.listen(
(data) {
_socket!.listen((data) {
var packets = _decoder.decode(data);
for (var packet in packets) {
// 🔥 关键修改:若收到服务端心跳(cmd == 0xFF),立即回复一个心跳包
if (packet.command == 0xFF) {
debugPrint('收到服务端心跳,自动回复...');
sendHeartbeat(); // 回复 AB AA FF AA AB
}
_controller.add(packet);
}
},
@@ -57,10 +66,47 @@ class TcpClient {
_socket!.add(builder.takeBytes());
}
void sendHeartbeat() {
if (_socket == null) return;
final builder = BytesBuilder()
..addByte(0xAB)
..addByte(0xAA)
..addByte(0xFF)
..addByte(0xAA)
..addByte(0xAB);
_socket!.add(builder.takeBytes());
}
// 新增:启动心跳(每 4 秒发送一次 0xFF 指令)
void startHeartbeat({Duration interval = const Duration(seconds: 4)}) {
if (_heartbeatTimer != null) return; // 防止重复启动
_heartbeatTimer = Timer.periodic(interval, (_) {
// 发送心跳帧:AB AA FF 00 00 AA AB(与 sendRaw 一致)
//sendRaw(0xFF, []);
sendHeartbeat();
});
debugPrint("TCP Connected");
}
// 新增:停止心跳
void stopHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = null;
}
void disconnect() {
stopHeartbeat(); // 先停心跳
_socket?.destroy();
_socket = null;
_controller.close();
debugPrint("TCP Disconnected");
}
}

View File

@@ -1,17 +1,20 @@
import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
import 'package:maibu_satabot_v2/features/ai/presentation/routes/ai_routes.dart';
import 'package:maibu_satabot_v2/features/auth/presentation/pages/login_page.dart';
import 'package:maibu_satabot_v2/features/auth/presentation/routes/auth_routes.dart';
import 'package:maibu_satabot_v2/features/home/presentation/routes/home_routes.dart';
import 'package:maibu_satabot_v2/features/my/presentation/routes/my_routes.dart';
import '../../features/auth/presentation/bloc/auth_cubit.dart';
import '../../features/auth/presentation/bloc/auth_state.dart';
import '../../features/machine_details/presentation/pages/machine_details_page.dart';
import '../../features/main_container/presentation/main_wrapper.dart';
import 'go_router_refresh_stream.dart';
GoRouter createRouter(AuthCubit authCubit) {
return GoRouter(
initialLocation: RoutePaths.login,
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
@@ -27,6 +30,18 @@ GoRouter createRouter(AuthCubit authCubit) {
return null;
},
routes: [
// 注册 machineDetails 路由
GoRoute(
path: RoutePaths.machineDetails,
builder: (context, state) => const MachineDetailsPage(), // 替换为实际页面
),
// 注册登录注册 路由
GoRoute(
path: RoutePaths.machineDetails,
builder: (context, state) => const LoginPage(), // 替换为实际页面
),
// 1. 外部路由(无底部导航栏)
...AuthRoutes.routes,
...HomeRoutes.routes,

View File

@@ -13,4 +13,9 @@ class RoutePaths {
static const remoteControl = '/home/remote_control';
static const routePlan = '/home/route_plan';
static const runningStatus = '/home/running_status';
static const machineDetails = '/machine_details/machine_details';
}

View File

@@ -45,13 +45,15 @@ class AuthCubit extends Cubit<AuthState> {
/// 当 HTTP 登录/注册成功后调用
Future<void> loginSuccess(UserEntity user) async {
await storage.saveUser(user);
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)); // 👈 启动心跳
appCubit.setAuth(user);
emit(AuthAuthenticated(user));
}
/// 退出登录 (主动或被动)
Future<void> logout() async {
print("退出登录");
await storage.deleteUser();
tcp.disconnect();
appCubit.clearAuth();
@@ -65,6 +67,7 @@ class AuthCubit extends Cubit<AuthState> {
// 假设 0x12 是踢下线或多设备登录提醒
_kickOutSub = dispatcher.onJsonMessage(0x12).listen((json) {
// 如果后端发来指令确认需要退出
// print('>>> [AUTH] 收到 0x12: $json'); //
logout();
});
}

View File

@@ -0,0 +1,72 @@
// device_hostrity_work_repository_impl.dart
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'package:fpdart/fpdart.dart';
import 'package:maibu_satabot_v2/core/storage/user_storage.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_hostrity_work_repository.dart';
class DeviceHostrityWorkRepositoryImpl implements DeviceHostrityWorkRepositoryRepository {
final Dio client;
final UserStorage userStorage;
DeviceHostrityWorkRepositoryImpl({required this.client, required this.userStorage});
@override
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> getDeviceHRunStatistics({
required String deviceId,
}) async {
try {
debugPrint('开始获取用户信息...');
final user = await userStorage.getUser();
if (user == null || user.token == null) {
debugPrint('用户未登录或缺少 token');
return Left(DeviceFailure.unauthorized(message: 'User not logged in or token is missing'));
}
final token = user.token!;
debugPrint('用户信息获取成功,Token: $token');
final response = await client.get(
'https://serviceri.satabot.com/iot/device/getDeviceRunStatistics?deviceId=$deviceId',
options: Options(
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
),
);
if (response.statusCode == 200) {
final jsonBody = response.data;
if (jsonBody['code'] == 200) {
final List<dynamic> data = jsonBody['data'];
final List<DeviceRunStatisticsEntity> statistics = data
.map((item) => DeviceRunStatisticsEntity(
startTime: item['startTime'] as String, // 开始时间
endTime: item['endTime'] as String, // 结束时间
workArea: (item['workArea'] as num).toDouble(), // 工作面积
distance: item['distance'] != null ? (item['distance'] as num).toDouble() : null, // 工作距离
time: item['time'] as int, // 花费时间
)).toList();
return Right(statistics);
} else {
debugPrint('服务器返回错误码: ${jsonBody['code']}');
return Left(DeviceFailure.serverError(message: '服务器返回错误码: ${jsonBody['code']}'));
}
} else {
debugPrint('网络请求失败,状态码: ${response.statusCode}');
return Left(DeviceFailure.networkError(message: '网络请求失败,状态码: ${response.statusCode}'));
}
} on DioException catch (e) {
debugPrint('Dio 请求失败: ${e.message}');
return Left(DeviceFailure.networkError(message: e.message ?? '未知错误'));
} catch (e) {
debugPrint('请求过程中发生异常: $e');
return Left(DeviceFailure.unknownError(message: e.toString()));
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:equatable/equatable.dart';
class DeviceRunStatisticsEntity extends Equatable {
final String startTime; // 开始时间
final String endTime; // 结束时间
final double workArea; // 工作面积
final double? distance; // 工作距离(可为空)
final int time; // 花费时间
const DeviceRunStatisticsEntity({
required this.startTime,
required this.endTime,
required this.workArea,
this.distance,
required this.time,
});
factory DeviceRunStatisticsEntity.fromJson(Map<String, dynamic> json) {
return DeviceRunStatisticsEntity(
startTime: json['startTime'] as String,
endTime: json['endTime'] as String,
workArea: (json['workArea'] as num).toDouble(),
distance: json['distance'] != null ? (json['distance'] as num).toDouble() : null,
time: json['time'] as int,
);
}
@override
List<Object?> get props => [startTime, endTime, workArea, distance, time];
}

View File

@@ -3,6 +3,26 @@ import '../../../../core/error/failure.dart';
class DeviceFailure extends Failure implements Exception {
final int? code;
DeviceFailure(super.message, {this.code});
// 新增静态方法:服务器错误
static DeviceFailure serverError({required String message}) {
return DeviceFailure('服务器错误: $message');
}
// 新增静态方法:网络错误
static DeviceFailure networkError({required String message}) {
return DeviceFailure('网络错误: $message');
}
static DeviceFailure unknownError({required String message}) {
return DeviceFailure('未知错误: $message');
}
static DeviceFailure unauthorized({required String message}) {
return DeviceFailure('未授权: $message');
}
}
class DeviceNotConnectedFailure extends DeviceFailure {
@@ -16,3 +36,5 @@ class DeviceOfflineFailure extends DeviceFailure {
class DeviceNotBoundFailure extends DeviceFailure {
DeviceNotBoundFailure(super.message);
}

View File

@@ -0,0 +1,14 @@
import 'package:fpdart/fpdart.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
import '../entities/device_run_hostrity_entity.dart';
abstract class DeviceHostrityWorkRepositoryRepository {
//获取设备的历史作业记录
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> getDeviceHRunStatistics({
required String deviceId,
});
}

View File

@@ -0,0 +1,27 @@
import 'package:fpdart/fpdart.dart';
import 'package:maibu_satabot_v2/core/domain/usecases/base_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_repository.dart';
import '../entities/device_run_hostrity_entity.dart';
import '../repositories/device_hostrity_work_repository.dart';
class DeviceWorkHostrirty implements BaseUseCase<List<DeviceRunStatisticsEntity>, BindDeviceParams> {
final DeviceHostrityWorkRepositoryRepository deviceRepository;
DeviceWorkHostrirty(this.deviceRepository);
@override
Future<Either<DeviceFailure, List<DeviceRunStatisticsEntity>>> call(BindDeviceParams params) async {
return await deviceRepository.getDeviceHRunStatistics(
deviceId: params.deviceId,
);
}
}
class BindDeviceParams {
final String deviceId;
final String deviceAlias;
BindDeviceParams(this.deviceId, this.deviceAlias);
}

View File

@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/app/app_user_cubit.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../widgets/map/test_amap_page.dart';
import '../widgets/map/testmap_pages.dart';
class RoutePlanPage extends StatelessWidget {
const RoutePlanPage({super.key});
@@ -19,10 +21,7 @@ class RoutePlanPage extends StatelessWidget {
return Scaffold(
backgroundColor: const Color(0xFFF7F7F7),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [],
),
body: MapPageEnterprise(),
);
}
}

View File

@@ -1,27 +1,280 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/network/net_message_dispatcher.dart';
import '../../../../core/network/protocol_decoder.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
class RunningStatusPage extends StatelessWidget {
class RunningStatusPage extends StatefulWidget {
const RunningStatusPage({super.key});
@override
Widget build(BuildContext context) {
// 同时监听用户和设备状态
final userState = context.read<AppUserCubit>().state;
State<RunningStatusPage> createState() => _RunningStatusPageState();
}
class _RunningStatusPageState extends State<RunningStatusPage> {
final _dispatcher = sl<NetMessageDispatcher>();
late StreamSubscription<RawPacket> _sub;
// 实时设备状态数据
String _yaw = '--';
String _satelliteCnt = '--';
String _qual = '--';
String _leftSpeed = '--';
String _rightSpeed = '--';
String _voltage = '--';
// 实时设备状态数据
String _pitch = '--';
String _roll = '--';
String _latitude = '--';
String _longitude = '--';
String _timeStamp = '--';
String _knifeCuttingSpeed = '--';
String _controlMode = '--';
String _workingArea = '--';
String _battery = '--';
String _obstacleFlag = '--';
// 视图切换状态
bool _isCardView = true;
@override
void initState() {
super.initState();
_sub = _dispatcher.onCommand(0x02).listen(_onDeviceData);
}
void _onDeviceData(RawPacket packet) {
try {
final csv = utf8.decode(packet.payload);
final fields = csv.split(',');
// 至少需 16 个字段(索引 10=航向角, 13=卫星数, 14=定位质量)
if (fields.length >= 16) {
setState(() {
_voltage = fields[0]; // 电压
_leftSpeed = fields[1]; // 左轮目标速度
_rightSpeed = fields[2]; // 右轮目标速度
_pitch = fields[11]; // 俯仰角
_roll = fields[12]; // 翻滚角
_latitude = fields[16]; // 纬度
_longitude = fields[17]; // 经度
_timeStamp = '${fields[16]} ${fields[17]}'; // 时间戳(合并字段)
_knifeCuttingSpeed = fields[19]; // 割刀速度
_controlMode = fields[20]; // 控制模式
_workingArea = fields[21]; // 作业面积
_battery = fields[22]; // 电量
_obstacleFlag = fields[23]; // 障碍物标志
_yaw = fields[10]; // 航向角
_satelliteCnt = fields[13]; // 卫星数
_qual = fields[14]; // 定位质量
});
}
} catch (_) {
// 解析失败时忽略,避免崩溃
}
}
@override
void dispose() {
_sub.cancel();
super.dispose();
}
void _refreshDeviceData() {
final deviceState = context.read<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
if (currentDevice == null)
return const Scaffold(body: Center(child: Text("加载中...")));
if (currentDevice != null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("数据刷新成功!"), duration: Duration(seconds: 1)),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("暂无设备,无法刷新"), duration: Duration(seconds: 1)),
);
}
}
@override
Widget build(BuildContext context) {
final deviceState = context.read<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
if (currentDevice == null) {
return const Scaffold(
body: Center(child: Text("加载中...")),
);
}
return Scaffold(
backgroundColor: const Color(0xFFF7F7F7),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [],
slivers: [
// 1. 顶部导航栏
SliverAppBar(
backgroundColor: const Color(0xFF1677FF),
pinned: true,
centerTitle: true,
elevation: 0,
title: const Text(
"设备数据监控",
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
// 2. 状态信息行(动态更新)
SliverToBoxAdapter(
child: Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("航向角状态: $_yaw°", style: const TextStyle(fontSize: 14)),
Text("定位质量: $_qual", style: const TextStyle(fontSize: 14)),
Text("卫星数: $_satelliteCnt", style: const TextStyle(fontSize: 14)),
],
),
),
),
// 3. 选项卡 + 刷新按钮
SliverToBoxAdapter(
child: Container(
color: Colors.white,
margin: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 切换选项卡
Row(
children: [
GestureDetector(
onTap: () => setState(() => _isCardView = true),
child: _buildTab("卡片", isActive: _isCardView),
),
const SizedBox(width: 24),
GestureDetector(
onTap: () => setState(() => _isCardView = false),
child: _buildTab("图表", isActive: !_isCardView),
),
],
),
// 刷新按钮
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1677FF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
onPressed: _refreshDeviceData,
child: const Text("刷新数据", style: TextStyle(fontSize: 14)),
),
],
),
),
),
// 4. 内容区域
SliverFillRemaining(
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: _isCardView ? _buildCardContentView() : _buildChartContentView(),
),
),
],
),
);
}
Widget _buildTab(String title, {required bool isActive}) {
return Text(
title,
style: TextStyle(
color: isActive ? const Color(0xFF1677FF) : const Color(0xFF666666),
fontSize: 16,
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
decoration: isActive ? TextDecoration.underline : null,
decorationColor: const Color(0xFF1677FF),
decorationThickness: 2,
),
);
}
Widget _buildCardContentView() {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("设备实时状态", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_CardItem(title: "电压", value: "$_voltage V"),
_CardItem(title: "左轮速度", value: "$_leftSpeed rpm"),
_CardItem(title: "右轮速度", value: "$_rightSpeed rpm"),
_CardItem(title: "航向角", value: "$_yaw°"),
_CardItem(title: "俯仰角", value: "$_pitch°"),
_CardItem(title: "翻滚角", value: "$_roll°"),
_CardItem(title: "卫星数", value: _satelliteCnt),
_CardItem(title: "定位质量", value: _qual),
_CardItem(title: "纬度", value: _latitude),
_CardItem(title: "经度", value: _longitude),
_CardItem(title: "时间戳", value: _timeStamp),
_CardItem(title: "割刀速度", value: "$_knifeCuttingSpeed rpm"),
_CardItem(title: "控制模式", value: _controlMode),
_CardItem(title: "作业面积", value: "$_workingArea m²"),
_CardItem(title: "电量", value: "$_battery%"),
_CardItem(title: "障碍物标志", value: _obstacleFlag),
],
),
);
}
Widget _buildChartContentView() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("图表视图", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
Text("可接入 fl_chart / charts_flutter 实现折线图/柱状图", style: TextStyle(color: Color(0xFF999999))),
],
),
);
}
}
class _CardItem extends StatelessWidget {
final String title;
final String value;
const _CardItem({required this.title, required this.value});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: const TextStyle(fontSize: 16, color: Color(0xFF333333))),
Text(value, style: const TextStyle(fontSize: 16, color: Color(0xFF666666))),
],
),
);
}

View File

@@ -1,10 +1,12 @@
import 'package:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import '../../../../core/app/app_user_cubit.dart';
import '../../../../core/router/route_paths.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import '../../../devices/presentation/bloc/devices_state.dart';
// 导入你的主题文件以获取 offWhite
@@ -391,8 +393,10 @@ class ImmersionHeader extends StatelessWidget {
const SizedBox(height: 8),
CCPrimaryButton(
onPressed: () {
context.push(RoutePaths.machineDetails);
print("通过按钮进入详情");
},
text: "详情",
width: 80,
height: 30,

View File

@@ -0,0 +1,51 @@
import 'dart:math';
import 'package:latlong2/latlong.dart';
class CoordTransform {
static const double pi = 3.1415926535897932384626;
static const double a = 6378245.0;
static const double ee = 0.00669342162296594323;
static bool outOfChina(double lat, double lon) {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
/// WGS84 → GCJ02(GPS → 高德)
static LatLng wgs84ToGcj02(double lat, double lon) {
if (outOfChina(lat, lon)) {
return LatLng(lat, lon);
}
double dLat = _transformLat(lon - 105.0, lat - 35.0);
double dLon = _transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * pi;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) /
((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) /
(a / sqrtMagic * cos(radLat) * pi);
return LatLng(lat + dLat, lon + dLon);
}
static double _transformLat(double x, double y) {
return -100.0 +
2.0 * x +
3.0 * y +
0.2 * y * y +
0.1 * x * y +
0.2 * sqrt(x.abs());
}
static double _transformLon(double x, double y) {
return 300.0 +
x +
2.0 * y +
0.1 * x * x +
0.1 * x * y +
0.1 * sqrt(x.abs());
}
}

View File

@@ -0,0 +1,178 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'CoordTransform/coord_transform.dart';
class AmapFlutterMapPage extends StatefulWidget {
const AmapFlutterMapPage({super.key});
@override
State<AmapFlutterMapPage> createState() => _AmapFlutterMapPageState();
}
class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
final MapController _mapController = MapController();
static const String amapKey = "bbb1f0f20eed6bf679eddf2625630aba";
LatLng _currentCenter = const LatLng(39.908823, 116.397470);
bool _isLoading = true;
// 新增:标记页面是否已销毁,防止异步操作访问已释放的控制器
bool _isDisposed = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkAndGetLocation();
});
}
// 新增:页面销毁时标记状态
@override
void dispose() {
_isDisposed = true; // 先标记销毁状态
// _mapController.dispose(); // 再销毁控制器
super.dispose();
}
Future<void> _checkAndGetLocation() async {
var status = await Permission.location.status;
if (status.isDenied) {
status = await Permission.location.request();
if (status.isDenied) {
if (!_isDisposed) { // 防护:页面没销毁才弹提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('定位权限被拒绝,无法获取当前位置')),
);
setState(() { _isLoading = false; });
}
return;
}
}
if (status.isPermanentlyDenied) {
if (!_isDisposed) { // 防护:页面没销毁才弹提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('定位权限被永久拒绝,请去设置开启'),
action: SnackBarAction(label: '去设置', onPressed: openAppSettings),
),
);
setState(() { _isLoading = false; });
}
return;
}
await _getCurrentLocation();
}
Future<void> _getCurrentLocation() async {
if (_isDisposed) return;
setState(() { _isLoading = true; });
print('📍 开始定位...');
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
if (!_isDisposed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('定位服务未启用,请开启定位服务')),
);
setState(() { _isLoading = false; });
}
return;
}
try {
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.bestForNavigation,
forceAndroidLocationManager: true,
timeLimit: const Duration(seconds: 8),
);
if (_isDisposed) return;
// ⭐⭐⭐ 关键修复点:WGS84 → GCJ02 ⭐⭐⭐
final gcjPoint = CoordTransform.wgs84ToGcj02(
position.latitude,
position.longitude,
);
setState(() {
_currentCenter = gcjPoint;
_isLoading = false;
});
if (!_isDisposed) {
_mapController.move(gcjPoint, 15.0);
}
print('✅ 定位成功(GCJ02): ${gcjPoint.latitude}, ${gcjPoint.longitude}');
} catch (e) {
if (!_isDisposed) {
setState(() { _isLoading = false; });
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('定位失败: $e'),
action: SnackBarAction(
label: '重试',
onPressed: _checkAndGetLocation,
),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('路径规划'),
actions: [
IconButton(
icon: const Icon(Icons.my_location),
onPressed: _checkAndGetLocation,
tooltip: '定位到我的位置',
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: FlutterMap(
mapController: _mapController,
options: MapOptions(
center: _currentCenter,
zoom: 15.0,
minZoom: 3.0,
maxZoom: 18.0,
interactiveFlags: InteractiveFlag.all,
),
children: [
TileLayer(
urlTemplate:
"https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}&key=$amapKey",
subdomains: const ['1', '2', '3', '4'],
tileProvider: NetworkTileProvider(),
),
MarkerLayer(
markers: [
Marker(
point: _currentCenter,
width: 40,
height: 40,
child: const Icon(Icons.location_on, color: Colors.red, size: 40),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,228 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
import '../path_list_pages.dart';
class MapPageEnterprise extends StatefulWidget {
const MapPageEnterprise({Key? key}) : super(key: key);
@override
State<MapPageEnterprise> createState() => _MapPageEnterpriseState();
}
class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final MapController _mapController = MapController();
LatLng? _currentLatLng;
StreamSubscription<Position>? _positionSub;
bool _hasMovedOnce = false;
@override
void initState() {
super.initState();
_initLocationEnterprise();
}
/// ===============================
/// 企业级定位初始化(已修复坐标系)
/// ===============================
Future<void> _initLocationEnterprise() async {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return;
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission != LocationPermission.whileInUse &&
permission != LocationPermission.always) {
return;
}
// 1️⃣ 首次强制获取定位
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.bestForNavigation,
);
final gcj = wgs84ToGcj02(position.latitude, position.longitude);
_updateLocation(gcj, moveMap: true);
// 2️⃣ 实时监听(不再强制移动地图)
_positionSub = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 2,
),
).listen((pos) {
final gcj = wgs84ToGcj02(pos.latitude, pos.longitude);
_updateLocation(gcj, moveMap: false);
});
}
/// ===============================
/// 更新位置
/// ===============================
void _updateLocation(LatLng latLng, {bool moveMap = false}) {
setState(() {
_currentLatLng = latLng;
});
if (moveMap && !_hasMovedOnce) {
_hasMovedOnce = true;
_mapController.move(latLng, 17);
}
}
/// 手动回到当前位置
void _moveToCurrentLocation() {
if (_currentLatLng == null) return;
_mapController.move(_currentLatLng!, 17);
}
@override
void dispose() {
_positionSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final menuHeight = 16 * 6; // 假设 VerticalFloatMenu 有 6 个选项,每个高度为 56
final maxTop = screenHeight - menuHeight;
return Scaffold(
body:
SafeArea(
child: Stack(
children: [
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter:
_currentLatLng ?? const LatLng(39.9042, 116.4074),
initialZoom: 15,
maxZoom: 18,
),
children: [
/// 高德瓦片(GCJ-02)
TileLayer(
urlTemplate:
'https://webrd02.is.autonavi.com/appmaptile'
'?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
'&key=bbb1f0f20eed6bf679eddf2625630aba',
),
/// 当前定位 Marker
if (_currentLatLng != null)
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: const Icon(
Icons.my_location,
color: Colors.blue,
size: 30,
),
),
],
),
],
),
Positioned(
height: 336,
right: 16,
top: maxTop > 10 ? 11 : maxTop, // 确保不超出屏幕范围
child: FloatingActionButton(
onPressed: _moveToCurrentLocation,
child: new VerticalFloatMenu(),
),
),
],
),
));
}
}
/// =======================================================
/// 坐标转换:WGS84 -> GCJ02(国内高德/腾讯通用)
/// =======================================================
const double _pi = 3.14159265358979324;
const double _a = 6378245.0;
const double _ee = 0.00669342162296594323;
LatLng wgs84ToGcj02(double lat, double lon) {
if (_outOfChina(lat, lon)) {
return LatLng(lat, lon);
}
double dLat = _transformLat(lon - 105.0, lat - 35.0);
double dLon = _transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * _pi;
double magic = sin(radLat);
magic = 1 - _ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) /
((_a * (1 - _ee)) / (magic * sqrtMagic) * _pi);
dLon = (dLon * 180.0) / (_a / sqrtMagic * cos(radLat) * _pi);
double mgLat = lat + dLat;
double mgLon = lon + dLon;
return LatLng(mgLat, mgLon);
}
bool _outOfChina(double lat, double lon) {
return lon < 72.004 ||
lon > 137.8347 ||
lat < 0.8293 ||
lat > 55.8271;
}
double _transformLat(double x, double y) {
double ret = -100.0 +
2.0 * x +
3.0 * y +
0.2 * y * y +
0.1 * x * y +
0.2 * sqrt(x.abs());
ret += (20.0 * sin(6.0 * x * _pi) +
20.0 * sin(2.0 * x * _pi)) *
2.0 /
3.0;
ret += (20.0 * sin(y * _pi) +
40.0 * sin(y / 3.0 * _pi)) *
2.0 /
3.0;
ret += (160.0 * sin(y / 12.0 * _pi) +
320 * sin(y * _pi / 30.0)) *
2.0 /
3.0;
return ret;
}
double _transformLon(double x, double y) {
double ret = 300.0 +
x +
2.0 * y +
0.1 * x * x +
0.1 * x * y +
0.1 * sqrt(x.abs());
ret += (20.0 * sin(6.0 * x * _pi) +
20.0 * sin(2.0 * x * _pi)) *
2.0 /
3.0;
ret += (20.0 * sin(x * _pi) +
40.0 * sin(x / 3.0 * _pi)) *
2.0 /
3.0;
ret += (150.0 * sin(x / 12.0 * _pi) +
300.0 * sin(x / 30.0 * _pi)) *
2.0 /
3.0;
return ret;
}

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
/// 垂直侧边功能菜单组件 - 纯白不透明长方形、独立可复用
class VerticalFloatMenu extends StatefulWidget {
// 外部可传点击回调,获取选中的索引和名称(可选)
final Function(int index, String name)? onItemTap;
const VerticalFloatMenu({super.key, this.onItemTap});
@override
State<VerticalFloatMenu> createState() => _VerticalFloatMenuState();
}
class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
// 默认选中「编辑」项(索引4),与原设计一致
int _selectedIndex = 4;
// 菜单配置:图标+名称,和原截图完全对应
final List<Map<String, dynamic>> _menuItems = const [
{"icon": Icons.crop_free, "name": "圈地"},
{"icon": Icons.format_list_bulleted, "name": "列表"},
{"icon": Icons.train, "name": "场站"},
{"icon": Icons.navigation, "name": "定位"},
{"icon": Icons.edit_note, "name": "编辑"},
{"icon": Icons.videocam, "name": "监控"},
];
@override
Widget build(BuildContext context) {
// 纯白不透明 + 固定长方形宽度,无阴影/圆角(纯基础样式)
return Container(
width: 56, // 适配悬浮按钮的窄长方形宽度,可自行微调
color: Colors.white, // 纯白不透明背景,核心要求
child: Column(
mainAxisSize: MainAxisSize.min, // 高度自适应子项,不占多余空间
children: _menuItems
.asMap()
.entries
.map((entry) => _buildMenuItem(
entry.value["icon"],
entry.key,
entry.value["name"],
))
.toList(),
),
);
}
/// 构建单个菜单项(含选中高亮、点击事件)
Widget _buildMenuItem(IconData icon, int index, String name) {
final isSelected = _selectedIndex == index;
return InkWell(
// 点击切换选中状态+触发外部回调
onTap: () {
setState(() => _selectedIndex = index);
if (widget.onItemTap != null) widget.onItemTap!(index, name);
},
child: Container(
width: double.infinity, // 撑满父容器宽度,保证点击区域
height: 56, // 单个项固定高度,视觉均匀
alignment: Alignment.center,
// 选中项高亮背景(浅蓝,可自行改色)
color: isSelected ? Colors.lightBlue[50] : Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
color: isSelected ? Colors.lightBlue : Colors.black,
size: 22, // 图标大小微调,适配窄宽度
),
const SizedBox(height: 2),
Text(
name,
style: TextStyle(
fontSize: 10, // 文字大小适配窄宽度
color: isSelected ? Colors.lightBlue : Colors.black,
height: 1, // 取消文字默认行高,避免高度过高
),
),
],
),
),
);
}
}

View File

@@ -1,17 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fpdart/fpdart.dart' hide State;
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
class WorkParamsCard extends StatelessWidget {
import '../../../../core/di/injection.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
class WorkParamsCard extends StatefulWidget {
const WorkParamsCard({super.key});
@override
State<WorkParamsCard> createState() => _WorkParamsCardState();
}
class _WorkParamsCardState extends State<WorkParamsCard> {
List<DeviceRunStatisticsEntity>? _statistics;
String? _errorMessage;
bool _isLoading = false; // 默认不加载
@override
Widget build(BuildContext context) {
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
// 极淡的阴影
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.02),
@@ -23,13 +42,11 @@ class WorkParamsCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// 左侧蓝色装饰条
Container(
width: 4,
height: 16,
@@ -49,27 +66,28 @@ class WorkParamsCard extends StatelessWidget {
),
],
),
// 刷新状态
Row(
children: [
Icon(Icons.refresh, size: 14, color: Colors.grey[400]),
const SizedBox(width: 4),
Text(
'刚刚更新',
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
),
],
GestureDetector(
onTap: () {
_handleInfoTap(currentDevice); // 包装在同步函数中
}, // 点击事件处理
child: Row(
children: [
Icon(Icons.info, size: 14, color: Colors.grey[400]),
const SizedBox(width: 4),
Text(
'查看详情',
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
),
],
),
),
],
),
const SizedBox(height: 24),
// 数据展示行
IntrinsicHeight(
// 关键:使分割线高度自动充满
child: Row(
children: [
_buildParamItem('作业面积', '0', '亩'),
_buildParamItem('作业面积', '0', '亩'), // 默认值
_buildDivider(),
_buildParamItem('作业里程', '0', 'km'),
_buildDivider(),
@@ -82,6 +100,185 @@ class WorkParamsCard extends StatelessWidget {
);
}
// 点击事件处理
Future<void> _handleInfoTap(DeviceEntity? currentDevice) async {
print("点击触摸了历史作业");
setState(() {
_isLoading = true; // 开始加载
_errorMessage = null; // 清除错误信息
});
print("开始历史作业");
try {
final useCase = DeviceWorkHostrirty(sl());
print("发送获取历史作业记录开始");
//displayName
final deviceId = currentDevice?.deviceName.toString() ?? '';
final params = BindDeviceParams(deviceId, 'your_device_alias');
final result = await useCase(params);
print("发送获取历史作业记录结束");
result.fold(
(failure) {
setState(() {
_errorMessage = failure.message;
_isLoading = false;
});
},
(data) {
setState(() {
_statistics = data;
_isLoading = false;
});
_showDetailsDialog(context, _statistics!); // 展示弹窗
},
);
} catch (e) {
print("捕获异常: $e");
setState(() {
_errorMessage = '请求失败: $e';
_isLoading = false;
});
}
}
// 弹窗展示详情
// 弹窗展示详情(从底部弹出)
// 弹窗展示详情(从底部弹出,覆盖底部导航栏)
void _showDetailsDialog(BuildContext context, List<DeviceRunStatisticsEntity> data) {
showModalBottomSheet(
context: context,
isScrollControlled: true, // 支持全屏高度
useRootNavigator: true, // 覆盖底部导航栏
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), // 顶部圆角
),
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.75, // 占据屏幕 75% 高度
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部拖拽指示器
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 16),
// 标题
const Text(
'历史记录详情',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 16),
// 内容区域
Expanded(
child: SingleChildScrollView(
child: Column(
children: data.map((item) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.05), // 浅蓝背景
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.blue.withOpacity(0.2),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow(Icons.calendar_today, '开始时间', item.startTime),
_buildDetailRow(Icons.calendar_today_outlined, '结束时间', item.endTime),
_buildDetailRow(Icons.square_foot, '工作面积', '${item.workArea} 亩'),
_buildDetailRow(
Icons.directions_car,
'工作距离',
'${item.distance?.toStringAsFixed(2) ?? '未计算'} km',
),
_buildDetailRow(Icons.timer, '花费时间', '${item.time} 小时'),
],
),
);
}).toList(),
),
),
),
const SizedBox(height: 16),
// 关闭按钮
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'关闭',
style: TextStyle(color: Colors.white),
),
),
),
],
),
);
},
);
}
// 构建详情行
Widget _buildDetailRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(icon, size: 16, color: Colors.blue),
const SizedBox(width: 8),
Text(
'$label: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 14,
color: Colors.black54,
),
),
),
],
),
);
}
// 构建单个参数项
Widget _buildParamItem(String label, String value, String unit) {
return Expanded(
@@ -116,7 +313,7 @@ class WorkParamsCard extends StatelessWidget {
fontSize: 28,
fontWeight: FontWeight.w700,
color: Colors.black,
fontFamily: 'Inter', // 建议使用数字显示更漂亮的字体
fontFamily: 'Inter',
),
),
TextSpan(

View File

@@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
class MachineDetailsPage extends StatelessWidget {
const MachineDetailsPage({super.key});
@override
Widget build(BuildContext context) {
// 移除 MaterialApp,直接返回 Scaffold
return const DeviceDetailPage();
}
}
class DeviceDetailPage extends StatelessWidget {
const DeviceDetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
centerTitle: true,
title: const Text(
'设备详情',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () => Navigator.pop(context), // 正确返回上一页
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildDeviceStatusCard(),
const SizedBox(height: 16),
_buildBasicInfoCard(),
const SizedBox(height: 16),
_buildResourceCenterCard(),
const SizedBox(height: 24),
_buildUnbindButton(context),
],
),
),
);
}
// 设备状态卡片
Widget _buildDeviceStatusCard() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Image.asset(
'assets/images/car.png',
width: 380,
fit: BoxFit.contain,
),
const SizedBox(height: 12),
const Text(
'离线',
style: TextStyle(
fontSize: 14,
color: Color(0xFF999999),
),
),
],
),
);
}
// 基本信息卡片
Widget _buildBasicInfoCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 16),
child: Text(
'基本信息',
style: TextStyle(
fontSize: 14,
color: Color(0xFF999999),
),
),
),
_buildInfoRow(
icon: Icons.info_outline,
label: '设备名称',
trailing: Row(
children: const [
Text(''),
SizedBox(width: 8),
Icon(Icons.edit, size: 16, color: Color(0xFF007AFF)),
],
),
),
const Divider(height: 32, color: Color(0xFFF0F0F0)),
_buildInfoRow(
icon: Icons.shield_outlined,
label: '设备ID',
trailing: Row(
children: const [
Text('undefined'),
SizedBox(width: 8),
Icon(Icons.copy, size: 16, color: Color(0xFF999999)),
],
),
),
const Divider(height: 32, color: Color(0xFFF0F0F0)),
_buildInfoRow(
icon: Icons.date_range_outlined,
label: '激活时间',
trailing: const Text(''),
),
],
),
);
}
// 资源中心卡片
Widget _buildResourceCenterCard() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(bottom: 16),
child: Text(
'资源中心',
style: TextStyle(
fontSize: 14,
color: Color(0xFF999999),
),
),
),
_buildResourceItem('使用说明', Icons.description_outlined),
const Divider(height: 32, color: Color(0xFFF0F0F0)),
_buildResourceItem('产品手册', Icons.book_outlined),
],
),
);
}
// 信息行组件
Widget _buildInfoRow({
required IconData icon,
required String label,
required Widget trailing,
}) {
return Row(
children: [
Icon(icon, size: 18, color: const Color(0xFF999999)),
const SizedBox(width: 12),
Text(
label,
style: const TextStyle(fontSize: 16),
),
const Spacer(),
trailing,
],
);
}
// 资源项组件
Widget _buildResourceItem(String title, IconData icon) {
return Row(
children: [
Icon(icon, size: 18, color: const Color(0xFF007AFF)),
const SizedBox(width: 12),
Text(
title,
style: const TextStyle(fontSize: 16),
),
const Spacer(),
const Icon(Icons.arrow_forward_ios, size: 16, color: Color(0xFF999999)),
],
);
}
// 解绑设备按钮
Widget _buildUnbindButton(BuildContext context) {
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: const Color(0xFFFF3B30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
// 实现解绑逻辑
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认解绑'),
content: const Text('确定要解绑该设备吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
// 执行解绑操作
},
child: const Text('确认'),
),
],
),
);
},
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.unarchive_outlined, size: 18),
SizedBox(width: 12),
Text(
'解绑设备',
style: TextStyle(fontSize: 12),
),
],
),
),
);
}
}

View File

@@ -1,17 +1,31 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/router/route_paths.dart';
import '../../../../core/storage/user_storage.dart';
import '../../../auth/presentation/bloc/auth_cubit.dart';
import '../../../auth/presentation/bloc/auth_state.dart';
import '../../../auth/presentation/pages/login_page.dart';
import '../../../web_view/web_view_page.dart';
import '../web_view/user_info_pages.dart';
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
// 假设从 AuthCubit 获取用户信息
final authState = context.watch<AuthCubit>().state;
String userName = '未知用户';
String userId = '未知ID';
if (authState is AuthAuthenticated) {
final user = authState.user;
userName = user.username ?? '未知用户';
userId = user.userId ?? '未知ID';
}
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
@@ -20,7 +34,7 @@ class MyPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部用户信息
_buildUserInfo(),
UserInfoCard(userName: userName, userId: userId),
SizedBox(height: 16),
@@ -43,42 +57,6 @@ class MyPage extends StatelessWidget {
);
}
Widget _buildUserInfo() {
return Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'),
fit: BoxFit.cover,
),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('宋', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text('用户id:songchunyi', style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {},
),
Spacer(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {},
),
],
);
}
Widget _buildBanner(BuildContext context) { // 添加 context 参数
return GestureDetector(
@@ -132,8 +110,7 @@ class MyPage extends StatelessWidget {
}
Widget _buildFunctionList(BuildContext context) { // 添加 context 参数
//https://www.satabot.com/
Widget _buildFunctionList(BuildContext context) {
final items = [
{'icon': Icons.play_arrow, 'title': '操作视频', 'url': 'https://www.satabot.com/Support/index.html'},
{'icon': Icons.car_rental, 'title': '租赁服务', 'url': 'https://www.satabot.com/Leasing.html'},
@@ -141,7 +118,6 @@ class MyPage extends StatelessWidget {
{'icon': Icons.policy, 'title': '售后政策', 'url': 'https://www.satabot.com/Aftersales-Service.html'},
];
return Container(
decoration: BoxDecoration(
color: Colors.white,
@@ -150,36 +126,56 @@ class MyPage extends StatelessWidget {
BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 4),
],
),
child: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: Icon(item['icon'] as IconData),
title: Text(item['title'] as String),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewPage(url: item['url'] as String),
),
child: Column(
children: [
// 功能列表
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(), // 禁止滚动,使其适应父容器高度
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
leading: Icon(item['icon'] as IconData),
title: Text(item['title'] as String),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewPage(url: item['url'] as String),
),
);
},
);
},
);
},
),
// 分割线
Divider(height: 1, color: Colors.grey.withOpacity(0.3)),
// 退出登录按钮
ListTile(
title: Text('退出登录', style: TextStyle(color: Colors.black)),
trailing: Icon(Icons.exit_to_app, color: Colors.black),
onTap: () async {
await GetIt.I<UserStorage>().deleteUser();
GetIt.I<AuthCubit>().logout();
context.go(RoutePaths.login);
},
),
],
),
);
}
Widget _buildLogoutButton(BuildContext context) {
return ElevatedButton(
onPressed: () async {
await GetIt.I<UserStorage>().deleteUser();
context.go('/auth/login');
},
GetIt.I<AuthCubit>().logout();
context.go(RoutePaths.login);
}
,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.red,
@@ -189,9 +185,6 @@ class MyPage extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.exit_to_app, size: 16, color: Colors.red),
SizedBox(width: 8),
Text('退出登录'),
],
),
);

View File

@@ -2,11 +2,14 @@ import 'package:go_router/go_router.dart';
import 'package:maibu_satabot_v2/features/my/presentation/pages/my_page.dart';
import '../../../../core/router/route_paths.dart';
import '../../../auth/presentation/pages/login_page.dart';
class MyRoutes {
/// 1. 全屏功能页面(不带导航栏)
/// 在 createRouter 的根 routes 中使用 ...HomeRoutes.routes 引入
static List<RouteBase> get routes => [
// GoRoute(
// path: RoutePaths.remoteControl,
// builder: (context, state) => const RemoteControlPage(), // 需导入对应 Page

View File

@@ -0,0 +1,47 @@
// user_info_card.dart
import 'package:flutter/material.dart';
class UserInfoCard extends StatelessWidget {
final String userName;
final String userId;
const UserInfoCard({super.key, required this.userName, required this.userId});
@override
Widget build(BuildContext context) {
return Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'),
fit: BoxFit.cover,
),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(userName, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text('用户id:$userId', style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {},
),
Spacer(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {},
),
],
);
}
}

View File

@@ -44,16 +44,26 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 在 runApp 之前调用 appStarted,确保 GoRouter 初始化时能获取到正确的初始状态
sl<AuthCubit>().appStarted();
return MultiBlocProvider(
providers: [
//BlocProvider<LoginBloc>(create: (_) => sl<LoginBloc>()),
// 核心修正:在这里提供 AuthCubit
BlocProvider<AuthCubit>(create: (_) => sl<AuthCubit>()),
BlocProvider<AppUserCubit>(create: (_) => sl<AppUserCubit>()),
BlocProvider<LoginCubit>(create: (_) => sl<LoginCubit>()),
BlocProvider<DevicesCubit>(
create: (_) =>
sl<DevicesCubit>()
..fetchAllDevices(sl<AppUserCubit>().state.user!.username),
create: (_) {
// 增加一个健壮性检查
final user = sl<AppUserCubit>().state.user;
final devicesCubit = sl<DevicesCubit>();
if (user != null) {
devicesCubit.fetchAllDevices(user.username);
}
return devicesCubit;
},
),
],
child: MaterialApp.router(

View File

@@ -8,9 +8,9 @@ import Foundation
import device_info_plus
import file_selector_macos
import flutter_webrtc
import geolocator_apple
import isar_community_flutter_libs
import package_info_plus
import path_provider_foundation
import sentry_flutter
import shared_preferences_foundation
import webview_flutter_wkwebview
@@ -19,9 +19,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))

View File

@@ -117,10 +117,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.12.1"
version: "8.12.3"
cc_ui_kit:
dependency: "direct main"
description:
@@ -160,6 +160,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
code_builder:
dependency: transitive
description:
@@ -220,10 +228,10 @@ packages:
dependency: transitive
description:
name: dart_webrtc
sha256: "51bcda4ba5d7dd9e65a309244ce3ac0b58025e6e1f6d7442cee4cd02134ef65f"
sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.0"
version: "1.7.0"
dartx:
dependency: transitive
description:
@@ -252,10 +260,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.9.0"
version: "5.9.1"
dio_web_adapter:
dependency: transitive
description:
@@ -365,6 +373,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.2.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -395,10 +411,10 @@ packages:
dependency: "direct main"
description:
name: flutter_webrtc
sha256: "71a38363a5b50603e405c275f30de2eb90f980b0cc94b0e1e9d8b9d6a6b03bf0"
sha256: "0f86b518e9349e71a136a96e0ea11294cad8a8531b2bc9ae99e69df332ac898a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
version: "1.3.0"
fpdart:
dependency: "direct main"
description:
@@ -415,6 +431,54 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2
url: "https://pub.flutter-io.cn"
source: hosted
version: "13.0.4"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.6.2"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.13"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.3"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.5"
get_it:
dependency: "direct main"
description:
@@ -435,10 +499,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
url: "https://pub.flutter-io.cn"
source: hosted
version: "17.0.1"
version: "17.1.0"
google_fonts:
dependency: "direct main"
description:
@@ -463,6 +527,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
name: hooks
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
http:
dependency: transitive
description:
@@ -611,18 +683,26 @@ packages:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.2"
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.9.0"
version: "4.10.0"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.1"
leak_tracker:
dependency: transitive
description:
@@ -651,10 +731,18 @@ packages:
dependency: transitive
description:
name: lints
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.0"
version: "6.1.0"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
logger:
dependency: transitive
description:
@@ -695,6 +783,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.17.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
mime:
dependency: transitive
description:
@@ -703,6 +799,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
@@ -711,6 +815,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.2.5"
package_config:
dependency: transitive
description:
@@ -771,10 +883,10 @@ packages:
dependency: transitive
description:
name: path_provider_foundation
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.1"
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
@@ -799,6 +911,54 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.flutter-io.cn"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
@@ -823,6 +983,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.8"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
pool:
dependency: transitive
description:
@@ -839,6 +1007,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.3"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
provider:
dependency: transitive
description:
@@ -867,18 +1043,18 @@ packages:
dependency: transitive
description:
name: sentry
sha256: fa14b2177d2cd4489ec78817ec4e52affe1be9f595718de970a5e4ed2bccc530
sha256: aad20a9073e674c29a0db32bea5f70b4181c059b5a2da4430d67888d349b0eef
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.10.0"
version: "9.11.0"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: "00d168260c9be3777e4f1c9ca7a2e4f2c48566926ec22a64bbacae5a94ff72a3"
sha256: cb1c8fb5da69c3e2c207422017b92eb625f5056e80783d052569af9a932e1f20
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.10.0"
version: "9.11.0"
shared_preferences:
dependency: "direct main"
description:
@@ -891,10 +1067,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.18"
version: "2.4.20"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1052,6 +1228,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.1"
uuid:
dependency: transitive
description:
@@ -1080,10 +1264,10 @@ packages:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.19"
version: "1.1.20"
vector_math:
dependency: transitive
description:
@@ -1136,10 +1320,10 @@ packages:
dependency: transitive
description:
name: webrtc_interface
sha256: "2e604a31703ad26781782fb14fa8a4ee621154ee2c513d2b9938e486fa695233"
sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
version: "1.4.0"
webview_flutter:
dependency: "direct main"
description:
@@ -1188,6 +1372,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
xdg_directories:
dependency: transitive
description:
@@ -1221,5 +1413,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.0 <4.0.0"
flutter: ">=3.38.0"
dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -102,6 +102,18 @@ dependencies:
intl: ^0.20.2
webview_flutter: ^4.4.2 # 将此行移到这里
geolocator: ^13.0.1
flutter_map: ^6.1.0 # 建议使用最新稳定版
latlong2: ^0.9.0 # 处理经纬度的依赖
#coord_convert: ^1.0.0
#高德地图
#amap_flutter_map: ^3.0.0 # 最新稳定版,适配 Flutter 3.x
#amap_flutter_base: ^3.0.0 # 必须与 map 版本一致
# 高德地图需要的权限
permission_handler: ^12.0.1
#qr_code_scanner: ^1.0.1 # 用于扫描二维码
dev_dependencies:

View File

@@ -8,7 +8,9 @@
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <geolocator_windows/geolocator_windows.h>
#include <isar_community_flutter_libs/isar_flutter_libs_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -16,8 +18,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterWebRTCPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
IsarFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
}

View File

@@ -5,7 +5,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
flutter_webrtc
geolocator_windows
isar_community_flutter_libs
permission_handler_windows
sentry_flutter
)