diff --git a/assets/images/scene1.png b/assets/images/scene1.png new file mode 100644 index 00000000..65281097 Binary files /dev/null and b/assets/images/scene1.png differ diff --git a/assets/images/scene2.png b/assets/images/scene2.png new file mode 100644 index 00000000..01edc880 Binary files /dev/null and b/assets/images/scene2.png differ diff --git a/assets/images/scene3.png b/assets/images/scene3.png new file mode 100644 index 00000000..f96da7d9 Binary files /dev/null and b/assets/images/scene3.png differ diff --git a/assets/images/scene4.png b/assets/images/scene4.png new file mode 100644 index 00000000..0e216e88 Binary files /dev/null and b/assets/images/scene4.png differ diff --git a/assets/images/scene5.png b/assets/images/scene5.png new file mode 100644 index 00000000..8aa03064 Binary files /dev/null and b/assets/images/scene5.png differ diff --git a/assets/images/scene6.png b/assets/images/scene6.png new file mode 100644 index 00000000..c2951933 Binary files /dev/null and b/assets/images/scene6.png differ diff --git a/lib/core/router/route_paths.dart b/lib/core/router/route_paths.dart index 4afeb465..eaa6d448 100644 --- a/lib/core/router/route_paths.dart +++ b/lib/core/router/route_paths.dart @@ -14,6 +14,7 @@ class RoutePaths { static const routePlan = '/home/route_plan'; static const runningStatus = '/home/running_status'; static const djMap = '/home/dj_map'; + static const productDesc = '/home/productDesc'; static const machineDetails = '/machine_details/machine_details'; } diff --git a/lib/features/home/presentation/pages/productDesc.dart b/lib/features/home/presentation/pages/productDesc.dart new file mode 100644 index 00000000..630f3b78 --- /dev/null +++ b/lib/features/home/presentation/pages/productDesc.dart @@ -0,0 +1,304 @@ +import 'package:flutter/material.dart'; +import 'package:markdown_widget/markdown_widget.dart'; + +class ProductPage extends StatefulWidget { + const ProductPage({super.key}); + + @override + State createState() => _ProductPageState(); +} + +class _ProductPageState extends State { + // 当前显示内容:0=主菜单 1=产品亮点 2=功能介绍 3=产品参数 4=应用场景 + int currentPage = 0; + + // 功能介绍列表 + final List funcList = const [ + '两轮驱动履带式行走系统,超强地形适应能力,无极变速,轻松爬坡越障,履带爬坡角度≤35°,履带前仰角40°,垂直越障≤15cm。', + '3kW闭环无刷电机独立驱动行走系统,高速传动装置效率达到95%以上,整机扭矩达到175N·m,轻松爬坡越障。闭环车速控制和检测系统,0-4km/h无级变速。', + '坦克履型设计,接近角达到45°,超强地形适应能力,爬坡角度≤40°,垂直越障≤15cm;复合材料履带,抗拉强度高、抗切能力好,抗磨损;接地比压小,轻松通过烂泥沼泽地。', + '方向速度手动操作模式(MT)和方向速度自动操作模式(AT)满足不同操作者使用习惯和场景使用方式。', + '自主研发自适应增程系统,根据负载变化调整输出功率,节省能耗,尖峰抑制稳流系统,实现稳定电力输出配备大功率风冷发电机和过滤系统,可实现大功率交直流放电,满足长时间使用要求。', + '户外等级控制箱,操作面板集中布置,防护等级IP55,操作、故障排査信息和注意事项醒目张贴,方便操作和检修。', + '工业级遥控器,防护等级IP65,有跌落防误碰保护;标准433MHz波段,无需无线电备案,直接使用;遥控信号绕障和穿越能力强,抗干扰能力强,同一空间至少支持100台机器同时作业,遥控距离达到500m;锂离子充电电池,可充可换,满足各种需要。', + '整机三色灯显示机器各种状态,包括油量预警、电量预警、直行保持、电控箱温度预警和安全倾角预警等;遥控实现整机操控,包括发动机一键启停、行走操控、速度操控、割盘升降和割刀转速等;遥控显示屏实时显示作业状态,包括作业时间、保养提醒、驱动器情况、作业模式、行驶模式、安全倾角检测、障碍物检测车灯状态、信号强度和遥控器电量等。', + ]; + + // 产品亮点 + final List> starPoints = const [ + {'title': '整机设计', 'content': '承载式车身、高效大功率行走系统、坦克履型、油电混动、防溜坡系统、主被动溜坡浮动式刀盘、空气辅助式提升机构、自适应割草装置'}, + {'title': '控制系统', 'content': '车规级控制和电气系统、工业级防干扰遥控系统、车速控制和检测系统、牵引力控制系统、手动和自动行驶操作模式、整机状态液晶显示。'}, + {'title': '动力系统', 'content': '自适应增程系统、本田顶级发动机、大功率交直流放电功能。'}, + {'title': '安全配置', 'content': '车身稳定系统、ABS刹车装置、电子防翻滚功能、倾斜保护功能、急速、坠落和翻滚停机功能、无操作自动关机。'}, + {'title': '智能功能', 'content': '直行保持功能、防碰撞功能、碰撞停机功能。'}, + {'title': '扩展功能', 'content': '路径规划、巡检探测、物联控制。'}, + ]; + + // 应用场景 + final List> sceneList = const [ + {'img': 'assets/images/scene4.png', 'title': '果园林业'}, + {'img': 'assets/images/scene1.png', 'title': '光伏储能'}, + {'img': 'assets/images/scene2.png', 'title': '公路路政'}, + {'img': 'assets/images/scene3.png', 'title': '农业生产'}, + {'img': 'assets/images/scene5.png', 'title': '市政河堤'}, + {'img': 'assets/images/scene6.png', 'title': '机场场所'}, + ]; + + // 产品参数 markdown + final String productParamMd = ''' +# 整车信息 +| 项目 | 单位 | MC700 | +| ---- | ---- | ---- | +| 外形尺寸(长×宽×高) | mm | 1429×1090×650 | +| 车体重量 | kg | 320 | +| 工作效率 | m²/h | 2500¹ | +| 最低离地高度 | mm | 90 | +| 续航能力(纯电) | h | 0.5 | +| 水平运动额定负载 | kg | 500 | +| 动力源 | | 混合动力 | +| 整机显示信息 | | 电量、电压、温度 | +| 遥控器显示信息 | | 电量、失联报警 | +| 电池品牌 | | 理士 | +| 电芯成分 | | SLA(密封铅酸可充电电池) | +| 电池容量(20小时率) | Ah/hr | 23 | +| 低电量报警 | | 显示屏和三色灯 | +| 增程器 | | 自适应 | +| 发电机保护 | | 过滤网 | +| 外放电源 | V/W | 24/480(直流) | +| 外放电源接口 | | 点烟器 | +| 按钮 | | 304不锈钢带灯 | +| 箱内电气 | | 防震端子 | +| 箱外电气 | | 车规级线束 | +| 防护等级 | | IP55 | + +# 控制系统 +| 项目 | 单位 | MC700 | +| ---- | ---- | ---- | +| 控制器 | | ECU | +| 遥控器 | | 工业级数显屏 | +| 遥控距离 | m | ≤500 | +| 遥控信号 | MHz | 433 | +| 通信频率 | Hz | 10 | +| 发动机维护提醒 | | 屏幕图标提醒 | +| 自动驻车 | kg | 有 | +| 坡道辅助起步 | | 有 | +| 车身倾斜检测| | 有 | +| 车速检测系统 | | 有 | +| ABS刹车系统 | | 有 | +| 电子防御滚系统 | | 有 | +| 牵引力控制系统| | 有 | +| 车身稳定系统| | 有 | +| 抗干扰 || 百台设备互不干扰 | +| 传感器安装预留 | |LIDAR/CPS/RIDAR | +| 防护等级| |车规级ip65 | +'''; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + // 顶部标题栏 + _buildHeader(), + + // 内容区域 + Expanded(child: _buildContent()), + ], + ), + ); + } + + Widget _buildHeader() { + String title = '产品介绍'; + if (currentPage == 1) title = '产品亮点'; + if (currentPage == 2) title = '功能介绍'; + if (currentPage == 3) title = '产品参数'; + if (currentPage == 4) title = '应用场景'; + + return Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + // 返回按钮 —— 只在非主页显示 + if (currentPage != 0) + IconButton( + onPressed: () { + setState(() { + currentPage = 0; + }); + }, + icon: const Icon(Icons.arrow_back_ios, size: 16), + ), + + // 标题居中 + Expanded( + child: Text( + title, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500), + ), + ), + + // 占位用,让标题绝对居中(对称占位) + if (currentPage != 0) const SizedBox(width: 48), + ], + ), + ); + } + + /// 内容区域 + Widget _buildContent() { + switch (currentPage) { + case 1: + return _buildStarPoint(); + case 2: + return _buildFunction(); + case 3: + return _buildParam(); + case 4: + return _buildScene(); + default: + return _buildMainMenu(); + } + } + + /// 主菜单 + Widget _buildMainMenu() { + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: Column( + children: [ + _menuItem('产品亮点', Icons.highlight_outlined, 1), + _menuItem('功能介绍', Icons.widgets_outlined, 2), + _menuItem('产品参数', Icons.table_chart_outlined, 3), + _menuItem('应用场景', Icons.place_outlined, 4), + ], + ), + ); + } + + /// 菜单条目 + Widget _menuItem(String title, IconData icon, int page) { + return InkWell( + onTap: () { + setState(() { + currentPage = page; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey, width: 0.5)), + ), + child: Row( + children: [ + Icon(icon, size: 24, color: Colors.grey[700]), + const SizedBox(width: 8), + Text(title, style: const TextStyle(fontSize: 16)), + ], + ), + ), + ); + } + + /// 产品亮点 + Widget _buildStarPoint() { + return SingleChildScrollView( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...starPoints.map((item) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.check_circle_outline, size: 20, color: Colors.grey), + const SizedBox(width: 6), + Text(item['title']!, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + ], + ), + const SizedBox(height: 4), + Text(item['content']!), + ], + ), + ); + }), + // 图片(替换为你的资源) + // Image.asset('assets/startpoint.png'), + const SizedBox(height: 20), + ], + ), + ); + } + + /// 功能介绍 + Widget _buildFunction() { + return SingleChildScrollView( + padding: const EdgeInsets.all(15), + child: Column( + children: [ + ...funcList.map((text) { + return Padding( + padding: const EdgeInsets.only(left: 15, bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('• ', style: TextStyle(fontSize: 16)), + Expanded(child: Text(text)), + ], + ), + ); + }), + const SizedBox(height: 16), + // Image.asset('assets/card.png'), + ], + ), + ); + } + + /// 产品参数(markdown) + Widget _buildParam() { + return SingleChildScrollView( + padding: const EdgeInsets.all(10), + child: MarkdownWidget( + data: productParamMd, + config: MarkdownConfig( + configs: [ + TableConfig( + wrapper: (child) => Container(margin: const EdgeInsets.symmetric(vertical: 8), child: child), + ), + ], + ), + ), + ); + } + + /// 应用场景 + Widget _buildScene() { + return GridView.builder( + padding: const EdgeInsets.all(10), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.0), + itemCount: sceneList.length, + itemBuilder: (ctx, index) { + final item = sceneList[index]; + return Column( + children: [ + // 图片(请自行替换为本地资源) + Image.asset(item['img']!, height: 100, fit: BoxFit.cover), + //Container( + // height: 100, + // color: Colors.grey[200], + // child: const Center(child: Text('场景图')), + //), + const SizedBox(height: 6), + Text(item['title']!), + ], + ); + }, + ); + } +} diff --git a/lib/features/home/presentation/routes/home_routes.dart b/lib/features/home/presentation/routes/home_routes.dart index 4575ee5e..487ca2f7 100644 --- a/lib/features/home/presentation/routes/home_routes.dart +++ b/lib/features/home/presentation/routes/home_routes.dart @@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:maibu_satabot_v2/features/home/presentation/pages/dj_page.dart'; import 'package:maibu_satabot_v2/features/home/presentation/pages/home_page.dart'; +import 'package:maibu_satabot_v2/features/home/presentation/pages/productDesc.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/router/route_paths.dart'; @@ -25,6 +26,7 @@ class HomeRoutes { ), GoRoute(path: RoutePaths.routePlan, builder: (context, state) => const RoutePlanPage()), GoRoute(path: RoutePaths.djMap, builder: (context, state) => const DjPage()), + GoRoute(path: RoutePaths.productDesc, builder: (context, state) => const ProductPage()), GoRoute( path: RoutePaths.runningStatus, // builder: (context, state) => const RunningStatusPage(), diff --git a/lib/features/machine_details/presentation/pages/machine_details_page.dart b/lib/features/machine_details/presentation/pages/machine_details_page.dart index 19846f60..d9b66615 100644 --- a/lib/features/machine_details/presentation/pages/machine_details_page.dart +++ b/lib/features/machine_details/presentation/pages/machine_details_page.dart @@ -30,9 +30,7 @@ class _MachineDetailsPageState extends State { @override void initState() { super.initState(); - _deviceName = (widget.device?.deviceAlias?.trim().isEmpty ?? true) - ? '未知设备' - : widget.device!.deviceAlias!; + _deviceName = (widget.device?.deviceAlias?.trim().isEmpty ?? true) ? '未知设备' : widget.device!.deviceAlias!; _deviceId = widget.device?.deviceName ?? ''; _nameController = TextEditingController(text: _deviceName); } @@ -41,19 +39,16 @@ class _MachineDetailsPageState extends State { void dispose() { _nameController.dispose(); // 兜底关闭所有弹窗 - if (_loadingDialogContext != null && - Navigator.canPop(_loadingDialogContext!)) { + if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) { Navigator.pop(_loadingDialogContext!); } - if (_updateLoadingContext != null && - Navigator.canPop(_updateLoadingContext!)) { + if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) { Navigator.pop(_updateLoadingContext!); } if (_dialogContext != null && Navigator.canPop(_dialogContext!)) { Navigator.pop(_dialogContext!); } - if (_editNameSheetContext != null && - Navigator.canPop(_editNameSheetContext!)) { + if (_editNameSheetContext != null && Navigator.canPop(_editNameSheetContext!)) { Navigator.pop(_editNameSheetContext!); } super.dispose(); @@ -65,35 +60,21 @@ class _MachineDetailsPageState extends State { Future bottomSheetFuture = showModalBottomSheet( context: context, isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), builder: (ctx) { _editNameSheetContext = ctx; return Padding( - padding: EdgeInsets.only( - left: 20, - right: 20, - top: 20, - bottom: MediaQuery.of(ctx).viewInsets.bottom + 20, - ), + padding: EdgeInsets.only(left: 20, right: 20, top: 20, bottom: MediaQuery.of(ctx).viewInsets.bottom + 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - "修改设备名称", - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), + const Text("修改设备名称", style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600)), const SizedBox(height: 16), TextField( controller: _nameController, autofocus: true, maxLength: 20, - decoration: const InputDecoration( - hintText: "请输入设备名称", - border: OutlineInputBorder(), - counterText: "", - ), + decoration: const InputDecoration(hintText: "请输入设备名称", border: OutlineInputBorder(), counterText: ""), ), const SizedBox(height: 10), Row( @@ -116,9 +97,7 @@ class _MachineDetailsPageState extends State { final text = _nameController.text.trim(); if (text.isEmpty) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("名称不能为空")), - ); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("名称不能为空"))); } return; } @@ -130,14 +109,7 @@ class _MachineDetailsPageState extends State { await _executeUpdateDeviceName(text); }, child: _isUpdatingName - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) + ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text("确认修改"), ), ), @@ -171,8 +143,7 @@ class _MachineDetailsPageState extends State { // 核心:返回上一页 Navigator.pop(context); if (context.mounted) { - final username = - context.read().state.user?.username ?? ""; + final username = context.read().state.user?.username ?? ""; context.read().fetchAllDevices(username); } }, @@ -196,17 +167,13 @@ class _MachineDetailsPageState extends State { backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( centerTitle: true, - title: const Text( - '设备详情', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), + title: const Text('设备详情', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.pop(context); if (context.mounted) { - final username = - context.read().state.user?.username ?? ""; + final username = context.read().state.user?.username ?? ""; context.read().fetchAllDevices(username); } }, @@ -240,13 +207,7 @@ class _MachineDetailsPageState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column( children: [ @@ -255,30 +216,19 @@ class _MachineDetailsPageState extends State { width: 300, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { - return const Icon( - Icons.device_hub, - size: 100, - color: Colors.grey, - ); + return const Icon(Icons.device_hub, size: 100, color: Colors.grey); }, ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( - color: device.isOnline - ? const Color(0xFF00C853).withOpacity(0.1) - : const Color(0xFF999999).withOpacity(0.1), + color: device.isOnline ? const Color(0xFF00C853).withOpacity(0.1) : const Color(0xFF999999).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( device.isOnline ? '在线' : '离线', - style: TextStyle( - fontSize: 14, - color: device.isOnline - ? const Color(0xFF00C853) - : const Color(0xFF999999), - ), + style: TextStyle(fontSize: 14, color: device.isOnline ? const Color(0xFF00C853) : const Color(0xFF999999)), ), ), ], @@ -294,23 +244,14 @@ class _MachineDetailsPageState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(bottom: 16), - child: Text( - '基本信息', - style: TextStyle(fontSize: 14, color: Color(0xFF999999)), - ), + child: Text('基本信息', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), ), _buildInfoRow( context: context, @@ -346,21 +287,14 @@ class _MachineDetailsPageState extends State { textAlign: TextAlign.right, softWrap: true, overflow: TextOverflow.visible, - style: const TextStyle( - fontSize: 14, - color: Colors.black87, - ), + style: const TextStyle(fontSize: 14, color: Colors.black87), ), ), ), const SizedBox(height: 4), GestureDetector( onTap: () => _copyToClipboard(deviceId, '设备ID已复制到剪贴板'), - child: const Icon( - Icons.copy, - size: 16, - color: Color(0xFF999999), - ), + child: const Icon(Icons.copy, size: 16, color: Color(0xFF999999)), ), ], ), @@ -377,23 +311,14 @@ class _MachineDetailsPageState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(bottom: 16), - child: Text( - '资源中心', - style: TextStyle(fontSize: 14, color: Color(0xFF999999)), - ), + child: Text('资源中心', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), ), _buildResourceItem('使用说明', Icons.description_outlined), const Divider(height: 32, color: Color(0xFFF0F0F0)), @@ -404,12 +329,7 @@ class _MachineDetailsPageState extends State { } /// 通用信息行组件(保持不变) - Widget _buildInfoRow({ - required BuildContext context, - required IconData icon, - required String label, - required Widget trailing, - }) { + Widget _buildInfoRow({required BuildContext context, required IconData icon, required String label, required Widget trailing}) { return Row( children: [ Icon(icon, size: 18, color: const Color(0xFF999999)), @@ -425,9 +345,11 @@ class _MachineDetailsPageState extends State { Widget _buildResourceItem(String title, IconData icon) { return InkWell( onTap: () { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('暂未开放$title功能'))); + if (title == "使用说明") { + context.go(RoutePaths.productDesc); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('暂未开放$title功能'))); + } }, child: Row( children: [ @@ -435,11 +357,7 @@ class _MachineDetailsPageState extends State { const SizedBox(width: 12), Text(title, style: const TextStyle(fontSize: 16)), const Spacer(), - const Icon( - Icons.arrow_forward_ios, - size: 16, - color: Color(0xFF999999), - ), + const Icon(Icons.arrow_forward_ios, size: 16, color: Color(0xFF999999)), ], ), ); @@ -454,9 +372,7 @@ class _MachineDetailsPageState extends State { style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: const Color(0xFFFF3B30), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), side: const BorderSide(color: Color(0xFFFF3B30), width: 1), padding: const EdgeInsets.symmetric(vertical: 8), textStyle: const TextStyle(height: 1.2), @@ -468,11 +384,7 @@ class _MachineDetailsPageState extends State { children: [ Icon(Icons.unarchive_outlined, size: 18), SizedBox(width: 8), - Text( - '解绑设备', - style: TextStyle(fontSize: 16, height: 1.0), - overflow: TextOverflow.visible, - ), + Text('解绑设备', style: TextStyle(fontSize: 16, height: 1.0), overflow: TextOverflow.visible), ], ), ), @@ -488,18 +400,11 @@ class _MachineDetailsPageState extends State { return AlertDialog( title: const Text('确认解绑'), content: Text('确定要解绑【$_deviceName】吗?解绑后将无法管理该设备'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), actions: [ + TextButton(onPressed: () => Navigator.pop(dialogContext), child: const Text('取消')), TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: const Text('取消'), - ), - TextButton( - style: TextButton.styleFrom( - foregroundColor: const Color(0xFFFF3B30), - ), + style: TextButton.styleFrom(foregroundColor: const Color(0xFFFF3B30)), onPressed: _executeUnbind, child: const Text('确认'), ), @@ -512,12 +417,7 @@ class _MachineDetailsPageState extends State { Future _executeUnbind() async { if (_deviceId.isEmpty || _deviceName.isEmpty) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("设备信息异常,无法解绑"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("设备信息异常,无法解绑"), backgroundColor: Colors.red)); } return; } @@ -535,15 +435,7 @@ class _MachineDetailsPageState extends State { barrierDismissible: false, builder: (ctx) { _loadingDialogContext = ctx; - return const AlertDialog( - content: Row( - children: [ - CircularProgressIndicator(strokeWidth: 2), - SizedBox(width: 16), - Text("正在解绑设备..."), - ], - ), - ); + return const AlertDialog(content: Row(children: [CircularProgressIndicator(strokeWidth: 2), SizedBox(width: 16), Text("正在解绑设备...")])); }, ); } @@ -552,22 +444,15 @@ class _MachineDetailsPageState extends State { await context.read().unbindDevice(_deviceId, _deviceName); // 主动关闭加载弹窗 - if (_loadingDialogContext != null && - Navigator.canPop(_loadingDialogContext!)) { + if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) { Navigator.pop(_loadingDialogContext!); _loadingDialogContext = null; } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("解绑异常:${e.toString()}"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("解绑异常:${e.toString()}"), backgroundColor: Colors.red)); // 关闭加载弹窗 - if (_loadingDialogContext != null && - Navigator.canPop(_loadingDialogContext!)) { + if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) { Navigator.pop(_loadingDialogContext!); _loadingDialogContext = null; } @@ -580,12 +465,7 @@ class _MachineDetailsPageState extends State { // 基础校验 if (_deviceId.isEmpty || newDeviceName.isEmpty) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("设备信息异常,无法修改名称"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("设备信息异常,无法修改名称"), backgroundColor: Colors.red)); } setState(() => _isUpdatingName = false); return; @@ -599,31 +479,17 @@ class _MachineDetailsPageState extends State { barrierDismissible: false, builder: (ctx) { _updateLoadingContext = ctx; - return const AlertDialog( - content: Row( - children: [ - CircularProgressIndicator(strokeWidth: 2), - SizedBox(width: 16), - Text("正在修改名称..."), - ], - ), - ); + return const AlertDialog(content: Row(children: [CircularProgressIndicator(strokeWidth: 2), SizedBox(width: 16), Text("正在修改名称...")])); }, ); } - print( - "调用updateDeviceName,deviceId: $_deviceId, newDeviceName: $newDeviceName", - ); + print("调用updateDeviceName,deviceId: $_deviceId, newDeviceName: $newDeviceName"); // 调用Cubit修改方法 - await context.read().updateDeviceName( - _deviceId, - newDeviceName, - ); + await context.read().updateDeviceName(_deviceId, newDeviceName); // 主动关闭修改名称加载弹窗 - if (_updateLoadingContext != null && - Navigator.canPop(_updateLoadingContext!)) { + if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) { Navigator.pop(_updateLoadingContext!); _updateLoadingContext = null; } @@ -632,24 +498,13 @@ class _MachineDetailsPageState extends State { // 兜底:强制将Cubit的isLoading置为false if (context.mounted) { - context.read().emit( - context.read().state.copyWith( - isLoading: false, - operationType: DeviceOperationType.none, - ), - ); + context.read().emit(context.read().state.copyWith(isLoading: false, operationType: DeviceOperationType.none)); } } catch (e) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("修改异常:${e.toString()}"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("修改异常:${e.toString()}"), backgroundColor: Colors.red)); // 关闭修改名称加载弹窗 - if (_updateLoadingContext != null && - Navigator.canPop(_updateLoadingContext!)) { + if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) { Navigator.pop(_updateLoadingContext!); _updateLoadingContext = null; } @@ -658,12 +513,7 @@ class _MachineDetailsPageState extends State { // 兜底:强制重置Cubit的loading状态 if (context.mounted) { - context.read().emit( - context.read().state.copyWith( - isLoading: false, - operationType: DeviceOperationType.none, - ), - ); + context.read().emit(context.read().state.copyWith(isLoading: false, operationType: DeviceOperationType.none)); } } } @@ -671,38 +521,25 @@ class _MachineDetailsPageState extends State { /// 处理解绑结果(保持不变) void _handleUnbindResult(DevicesState state) { // 强制关闭加载弹窗 - if (_loadingDialogContext != null && - Navigator.canPop(_loadingDialogContext!)) { + if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) { Navigator.pop(_loadingDialogContext!); _loadingDialogContext = null; - } else if (!state.isLoading && - context.mounted && - Navigator.canPop(context)) { + } else if (!state.isLoading && context.mounted && Navigator.canPop(context)) { Navigator.pop(context); } if (state.errorMessage?.isEmpty == true && !state.isLoading) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("设备「$_deviceName」解绑成功"), - backgroundColor: Colors.green, - duration: const Duration(seconds: 2), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("设备「$_deviceName」解绑成功"), backgroundColor: Colors.green, duration: const Duration(seconds: 2))); - final username = - context.read().state.user?.username ?? ""; + final username = context.read().state.user?.username ?? ""; context.read().fetchAllDevices(username); } } else if (state.errorMessage?.isNotEmpty == true && !state.isLoading) { if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("解绑失败:${state.errorMessage ?? '未知错误'}"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("解绑失败:${state.errorMessage ?? '未知错误'}"), backgroundColor: Colors.red)); } } context.go(RoutePaths.home); @@ -711,8 +548,7 @@ class _MachineDetailsPageState extends State { /// 处理修改名称结果(核心修改:放宽判断条件) void _handleUpdateNameResult(DevicesState state) { // 强制关闭修改名称加载弹窗(优先用专属上下文) - if (_updateLoadingContext != null && - Navigator.canPop(_updateLoadingContext!)) { + if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) { Navigator.pop(_updateLoadingContext!); _updateLoadingContext = null; } @@ -735,45 +571,25 @@ class _MachineDetailsPageState extends State { }); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("设备名称已修改为:$_deviceName"), - backgroundColor: Colors.green, - duration: const Duration(seconds: 2), - ), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("设备名称已修改为:$_deviceName"), backgroundColor: Colors.green, duration: const Duration(seconds: 2))); // 关闭修改名称的底部抽屉(如果还在) - if (_editNameSheetContext != null && - Navigator.canPop(_editNameSheetContext!)) { + if (_editNameSheetContext != null && Navigator.canPop(_editNameSheetContext!)) { Navigator.pop(_editNameSheetContext!); _editNameSheetContext = null; } // 强制重置Cubit的loading状态 - context.read().emit( - state.copyWith( - isLoading: false, - operationType: DeviceOperationType.none, - ), - ); + context.read().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none)); } } else if (state.errorMessage?.isNotEmpty == true) { setState(() => _isUpdatingName = false); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("修改失败:${state.errorMessage ?? '未知错误'}"), - backgroundColor: Colors.red, - ), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("修改失败:${state.errorMessage ?? '未知错误'}"), backgroundColor: Colors.red)); // 强制重置Cubit的loading状态 - context.read().emit( - state.copyWith( - isLoading: false, - operationType: DeviceOperationType.none, - ), - ); + context.read().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none)); } } } @@ -782,9 +598,7 @@ class _MachineDetailsPageState extends State { void _copyToClipboard(String text, String tip) { Clipboard.setData(ClipboardData(text: text)); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(tip), duration: const Duration(seconds: 1)), - ); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(tip), duration: const Duration(seconds: 1))); } } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e24fb1eb..4d3447a0 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = @@ -24,4 +25,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1394e659..e72c54c5 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_webrtc isar_community_flutter_libs sentry_flutter + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index bf451370..279b8b73 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,6 +14,7 @@ import mobile_scanner import package_info_plus import sentry_flutter import shared_preferences_foundation +import url_launcher_macos import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -26,5 +27,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 5c1e32e1..e03bc0bc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -365,6 +365,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "9.1.1" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -535,6 +543,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" + highlight: + dependency: transitive + description: + name: highlight + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" hooks: dependency: transitive description: @@ -767,6 +783,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.0" + markdown_widget: + dependency: "direct main" + description: + name: markdown_widget + sha256: b52c13d3ee4d0e60c812e15b0593f142a3b8a2003cde1babb271d001a1dbdc1c + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2+8" matcher: dependency: transitive description: @@ -1055,6 +1087,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" + scroll_to_index: + dependency: transitive + description: + name: scroll_to_index + sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" sentry: dependency: transitive description: @@ -1268,6 +1308,70 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.3.1" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.5" uuid: dependency: transitive description: @@ -1308,6 +1412,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0+2" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d6b1c970..72df7f76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -121,6 +121,8 @@ dependencies: fl_chart: ^0.66.0 syncfusion_flutter_gauges: ^32.2.8 + markdown_widget: ^2.0.0 # 产品参数用的 markdown 渲染 + #qr_code_scanner: ^1.0.1 # 用于扫描二维码 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7ac7377e..bf0b1810 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( @@ -26,4 +27,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index ebca70b5..b63b2b8a 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST isar_community_flutter_libs permission_handler_windows sentry_flutter + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST