使用说明 完成

This commit is contained in:
mmc
2026-03-16 09:29:21 +08:00
parent 5b814d065f
commit 0dccd8c5e1
17 changed files with 497 additions and 251 deletions

BIN
assets/images/scene1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
assets/images/scene2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/images/scene3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/images/scene4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
assets/images/scene5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
assets/images/scene6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -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';
}

View File

@@ -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<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
// 当前显示内容:0=主菜单 1=产品亮点 2=功能介绍 3=产品参数 4=应用场景
int currentPage = 0;
// 功能介绍列表
final List<String> funcList = const [
'两轮驱动履带式行走系统,超强地形适应能力,无极变速,轻松爬坡越障,履带爬坡角度≤35°,履带前仰角40°,垂直越障≤15cm。',
'3kW闭环无刷电机独立驱动行走系统,高速传动装置效率达到95%以上,整机扭矩达到175N·m,轻松爬坡越障。闭环车速控制和检测系统,0-4km/h无级变速。',
'坦克履型设计,接近角达到45°,超强地形适应能力,爬坡角度≤40°,垂直越障≤15cm;复合材料履带,抗拉强度高、抗切能力好,抗磨损;接地比压小,轻松通过烂泥沼泽地。',
'方向速度手动操作模式(MT)和方向速度自动操作模式(AT)满足不同操作者使用习惯和场景使用方式。',
'自主研发自适应增程系统,根据负载变化调整输出功率,节省能耗,尖峰抑制稳流系统,实现稳定电力输出配备大功率风冷发电机和过滤系统,可实现大功率交直流放电,满足长时间使用要求。',
'户外等级控制箱,操作面板集中布置,防护等级IP55,操作、故障排査信息和注意事项醒目张贴,方便操作和检修。',
'工业级遥控器,防护等级IP65,有跌落防误碰保护;标准433MHz波段,无需无线电备案,直接使用;遥控信号绕障和穿越能力强,抗干扰能力强,同一空间至少支持100台机器同时作业,遥控距离达到500m;锂离子充电电池,可充可换,满足各种需要。',
'整机三色灯显示机器各种状态,包括油量预警、电量预警、直行保持、电控箱温度预警和安全倾角预警等;遥控实现整机操控,包括发动机一键启停、行走操控、速度操控、割盘升降和割刀转速等;遥控显示屏实时显示作业状态,包括作业时间、保养提醒、驱动器情况、作业模式、行驶模式、安全倾角检测、障碍物检测车灯状态、信号强度和遥控器电量等。',
];
// 产品亮点
final List<Map<String, String>> starPoints = const [
{'title': '整机设计', 'content': '承载式车身、高效大功率行走系统、坦克履型、油电混动、防溜坡系统、主被动溜坡浮动式刀盘、空气辅助式提升机构、自适应割草装置'},
{'title': '控制系统', 'content': '车规级控制和电气系统、工业级防干扰遥控系统、车速控制和检测系统、牵引力控制系统、手动和自动行驶操作模式、整机状态液晶显示。'},
{'title': '动力系统', 'content': '自适应增程系统、本田顶级发动机、大功率交直流放电功能。'},
{'title': '安全配置', 'content': '车身稳定系统、ABS刹车装置、电子防翻滚功能、倾斜保护功能、急速、坠落和翻滚停机功能、无操作自动关机。'},
{'title': '智能功能', 'content': '直行保持功能、防碰撞功能、碰撞停机功能。'},
{'title': '扩展功能', 'content': '路径规划、巡检探测、物联控制。'},
];
// 应用场景
final List<Map<String, String>> 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']!),
],
);
},
);
}
}

View File

@@ -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(),

View File

@@ -30,9 +30,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
@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<MachineDetailsPage> {
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<MachineDetailsPage> {
Future<void> 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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
// 核心:返回上一页
Navigator.pop(context);
if (context.mounted) {
final username =
context.read<AppUserCubit>().state.user?.username ?? "";
final username = context.read<AppUserCubit>().state.user?.username ?? "";
context.read<DevicesCubit>().fetchAllDevices(username);
}
},
@@ -196,17 +167,13 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
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<AppUserCubit>().state.user?.username ?? "";
final username = context.read<AppUserCubit>().state.user?.username ?? "";
context.read<DevicesCubit>().fetchAllDevices(username);
}
},
@@ -240,13 +207,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
}
/// 通用信息行组件(保持不变)
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
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<MachineDetailsPage> {
Future<void> _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<MachineDetailsPage> {
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<MachineDetailsPage> {
await context.read<DevicesCubit>().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<MachineDetailsPage> {
// 基础校验
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<MachineDetailsPage> {
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<DevicesCubit>().updateDeviceName(
_deviceId,
newDeviceName,
);
await context.read<DevicesCubit>().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<MachineDetailsPage> {
// 兜底:强制将Cubit的isLoading置为false
if (context.mounted) {
context.read<DevicesCubit>().emit(
context.read<DevicesCubit>().state.copyWith(
isLoading: false,
operationType: DeviceOperationType.none,
),
);
context.read<DevicesCubit>().emit(context.read<DevicesCubit>().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<MachineDetailsPage> {
// 兜底:强制重置Cubit的loading状态
if (context.mounted) {
context.read<DevicesCubit>().emit(
context.read<DevicesCubit>().state.copyWith(
isLoading: false,
operationType: DeviceOperationType.none,
),
);
context.read<DevicesCubit>().emit(context.read<DevicesCubit>().state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
}
}
}
@@ -671,38 +521,25 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
/// 处理解绑结果(保持不变)
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<AppUserCubit>().state.user?.username ?? "";
final username = context.read<AppUserCubit>().state.user?.username ?? "";
context.read<DevicesCubit>().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<MachineDetailsPage> {
/// 处理修改名称结果(核心修改:放宽判断条件)
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<MachineDetailsPage> {
});
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<DevicesCubit>().emit(
state.copyWith(
isLoading: false,
operationType: DeviceOperationType.none,
),
);
context.read<DevicesCubit>().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<DevicesCubit>().emit(
state.copyWith(
isLoading: false,
operationType: DeviceOperationType.none,
),
);
context.read<DevicesCubit>().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
}
}
}
@@ -782,9 +598,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
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)));
}
}
}

View File

@@ -10,6 +10,7 @@
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <isar_community_flutter_libs/isar_flutter_libs_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
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);
}

View File

@@ -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

View File

@@ -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"))
}

View File

@@ -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:

View File

@@ -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 # 用于扫描二维码

View File

@@ -12,6 +12,7 @@
#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>
#include <url_launcher_windows/url_launcher_windows.h>
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"));
}

View File

@@ -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