commit 5d28bf631b232814304c4c4cdabe45a825019212 Author: Developer <91611@user.local> Date: Thu Jun 4 11:19:44 2026 +0800 chore(project): 初始化项目基础配置文件 - 添加 CodeGraph、Android 和通用 gitignore 配置 - 创建项目元数据文件跟踪 Flutter 项目属性 - 添加 Codex AI 指导文档 AGENTS.md 说明项目架构 - 配置代码分析选项 analysis_options.yaml - 设置 Android 应用清单权限和 Kiosk 模式配置 - 实现中英文国际化支持 AppLocalizations - 配置 GoRouter 应用路由导航 - 创建明亮工业控制风格的主题配置 AppTheme diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..9de0f16 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,16 @@ +# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty diff --git a/.codegraph/daemon.pid b/.codegraph/daemon.pid new file mode 100644 index 0000000..4fd6e3b --- /dev/null +++ b/.codegraph/daemon.pid @@ -0,0 +1,6 @@ +{ + "pid": 67540, + "version": "0.9.7", + "socketPath": "\\\\.\\pipe\\codegraph-7209b3ccb3579134", + "startedAt": 1780364708996 +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36bf422 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +.planning/ +.omc/ \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..0587820 --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "cc0734ac716fbb8b90f3f9db8020958b1553afa7" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: android + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: web + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8dc1c64 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,166 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +## 项目概述 + +污水毒品快检一体机控制软件(kuaishai2),用于控制设备运行程序、管理程序配置、监控运行状态。 + +**包名**: com.xiarui.kuaishai2 + +## 开发命令 + +```bash +# 获取依赖 +flutter pub get + +# 运行应用 (调试模式) +flutter run + +# 构建 APK +flutter build apk + +# 构建发布版 APK +flutter build apk --release + +# 代码分析 +flutter analyze + +# 运行测试 +flutter test + +# 清理构建缓存 +flutter clean +``` + +## 代码架构 + +项目采用 **feature-first** 分层架构,结构如下: + +``` +lib/ +├── core/ # 核心模块 +│ ├── database/ # SQLite 数据库服务 (单例) +│ ├── router/ # GoRouter 路由配置 +│ ├── theme/ # AppTheme 主题定义 +│ └── localization/ # AppLocalizations 国际化 +├── features/ # 功能模块 +│ ├── home/ # 首页(设备控制面板) +│ ├── programs/ # 程序管理 +│ ├── program_detail/ # 程序详情/步骤配置 +│ ├── device/ # 设备运行控制 +│ └── settings/ # 系统设置 +└── shared/ # 共享组件 + ├── widgets/ # 通用组件 (CommonButton, CommonCard, StatusIndicator) + └── utils/ # 常量定义 (Constants) +``` + +### 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 状态管理 | flutter_riverpod | StateNotifier + Provider 模式 | +| 路由 | go_router | 声明式路由,支持路径参数 | +| 数据持久化 | sqflite | SQLite 本地数据库 | +| 国际化 | intl + flutter_localizations | 中/英双语 | + +### 核心数据模型 + +**Program** (程序): `id`, `code`, `name`, `createdAt`, `status(1启用/0停用)` +**Step** (步骤): `id`, `programId`, `stepNo`, `position`, `name`, `mixTime`, `magnetTime`, `volume`, `mixSpeed`, `blowSpeed`, `blowTime`, `needleSpeed` + +### 关键 Provider + +- `programsProvider` - 程序列表状态 (StateNotifier) +- `runStateProvider` - 设备运行状态 (RunStatus: idle/running/paused/completed/error) +- `goRouterProvider` - 路由配置 + +### 数据库 + +DatabaseService 为单例模式,表结构: +- `programs` - 程序表 +- `steps` - 步骤表 (外键关联 program_id) + +### 国际化 + +AppLocalizations 支持中文(zh)和英文(en),使用 `AppLocalizations.of(context)` 获取翻译。 + +## 代码规范 + +- Dart SDK 版本: ^3.11.5 +- 使用 `flutter_lints` 包的推荐 lint 规则 +- 代码风格遵循 Flutter 官方最佳实践 + +## 功能模块 + +根据需求文档,应用包含以下核心模块: + +### 1. 首页模块(设备控制面板) +- 状态栏:设备名称、实时时钟、系统状态、照明控制 +- 程序列表:卡片展示、程序选择、查看详情 +- 运行控制:启动/暂停/停止程序 +- 运行状态监控:当前步骤、剩余时间、总进度 + +### 2. 程序管理模块 +- 程序列表:编号、名称、创建时间、状态 +- CRUD 操作:新增、编辑、删除、导入程序 + +### 3. 程序详情模块 +- 步骤管理:步骤列表、排序、增删改 +- 步骤参数:孔位、混合时间、吸磁时间、容积、速度等 + +### 4. 系统设置模块 +- 软件升级、语言切换(中文/英文)、密码修改、U盘导入 + +## 步骤参数说明 + +| 参数 | 说明 | 取值范围 | +|------|------|----------| +| 孔位 | 操作位置 | A1, A2, B1... | +| 混合时间 | 混合持续时间 | 正整数(秒) | +| 吸磁时间 | 磁珠吸附时间 | 正整数(秒) | +| 容积 | 液体体积 | 正整数(μL) | +| 混合/吹气速度 | 操作速度 | 低速/中速/高速 | +| 下针速度 | 针头下移速度 | 1-10档 | + +## 当前实现状态 + +已完成基础架构搭建,实现了: +- 首页:状态栏、程序列表、运行控制面板、运行状态监控 +- 程序管理:列表展示、新增/编辑/删除、状态切换 +- 程序详情:步骤列表、步骤参数编辑 +- 运行控制:启动/暂停/停止、进度监控(使用 MockRunner 模拟) +- 系统设置:基础页面框架 +- 国际化:中/英文翻译配置 + +待完善:设备通信对接、实际硬件控制、U盘导入功能。 + +## UI 设计稿 + +**Stitch 项目链接**: https://stitch.withgoogle.com/projects/16230138564963723693 + +### 页面截图链接 + +| 页面 | 功能描述 | 截图链接 | +|------|----------|----------| +| 首页 - 待机状态 | 状态栏 + 程序列表 + 开始运行按钮 + 瓷套棒确认 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujbe00elAXxTbD-KomnaatYJ40X-ubwTDSaI--jR_FRjPow3DJn7S61WpX9NxIMfgAyup6KXC_5qQWeZi0G9qQakQpvszL-OiaTU0C_yQ5FB-E1UMrystcYL0inmiky1Z2Ai-18NSYAdr0dJ6bbTp_dmJ4-JgmK0bjqdoUSnqlzo_Q1SwyNmbJb8WdNTKIUoXNudgLOCZURLWzONviyPgs9CqlxS1KUvopuxJymQF26kwxFO33lY6_vdLzD) | +| 首页 - 运行状态 | 实时监控:孔位、步骤、倒计时、进度条、暂停/停止控制 | [查看](https://lh3.googleusercontent.com/aida/ADBb0uj1bjMG6Fu-6-yvfL-Y51mJws-NBwlkX-GCbBBjLjb-4gNvMB_y8AESOu7X-UF_SHggCttMNpY6g88WgGOSJjw-1NOyTyigDBRInEwawYt8aXSGIGknLVw6zK_99aFnfturjzoYySuHBhKP2r34XWvfAO1g0e-as3hWBOaJW0gb2w8DSLmv3MpGLvsedJNq4SQFhl50LtNFM_zCMKjv-NtqyCE3y1vndNxnxCrJUy_BHvh4XHCahf18UnOU) | +| 程序管理 - 列表视图 | 程序表格:复选框、编号、名称、状态、批量操作、分页 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujE1ZBh5_Gn-yyy1bfU-U_xCeGHREsycgxyAlWBKzWbhFzvlusSWhCEY1u6RBRqVk5XZ49x8Ljpe4yXcS5e3q_jgGMGpcd1CN0RxX0nU6D75O056euNV7fY9cBNtTVGXibmM7Am1uj5uxV7TLbW8c_ix8WubRhn0gYFU6L87b80N6Zl-2T3lmBZsp98jwSsUXP3RqHk6CwMLoQ9LYcLRQTU5Dh5XD1e9aXus9I9pDgj-ZMUsFcZJA7XnJ0y) | +| 程序详情 - 步骤配置 | 左侧步骤列表 + 右侧参数表单(孔位、时间、速度等) | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujEyurJOmwUkZx3ysQMSCk4pXU9_6Jh6bsEMhwlwyC88SQZ0hCFRHMhKADFecHdLXG3D-A5Hp3Avw25j1ivrP2CyTTdeXgd4QPCARNZetrezcL1iy0b7RYsVTTnXnzIFX846pRC_rxgbh8DdqMAiHPz8Q94JciAFCAokQfduFmDWw3Wzhj_P0KtlL8UmEIJNzGFi9ySTGo6eKmz6lfdfQ6VMowhYR5Qy8VGZi8qTLlXqFSU9f-dZepaJWs) | +| 系统设置 - 控制面板 | 软件升级、语言切换、密码修改、U盘导入 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ugIEqBuSXmwYtQkPXXIJ7qBdL69VyQdTRzTyXjc5M6THuTBnbJlLyIDmvo7qfTz_RwjwZE_czzN6dyR_ydqk2Er5hNYduPLypOLqqR7I2sHaKDIvCeole91oJIVkAcnfJkWQJJPgaIhqfysMV4mrsHD0rhYJpngLorfE9WMl11tG9MPz9_E8XcmwaFqXqtYvxHeBsnvSNtIQfkX9S5QfnYtOiAsnUKioY3GpVvjK6bV4PYIfCMMHXv5koS8) | +| 运行完成 - 操作指引 | 成功提示 + 样本滴入说明 + 返回/重运行按钮 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujh5-3mBPpjWkSRHGOzNRQuusc4O7p_Kov2dbd3iiJmOHlElBb9-gPWC5bnfnRuDbhIbZDhxQhTfVkRpUhQU5nMGa5y7NpV4efvG6ZF0Bx4aVdm4HJ-o2Qca-alJuSfgsggJgbZ3QptA_cAXx-VfG79sHB1zUvRDHpPkLSu0lePhmjaR6m6-pwR42Pib7OLSNqS6V9208w7HoGCX1cPC6pH-hi8rZrMbMmbk2p-pTd2tJebNsow9aLb6bLJ) | + +### 设计规范 + +- **主色调**: #2196F3(蓝色) +- **字体**: Inter +- **圆角**: 4px(工业控制风格) +- **配色模式**: Light + Tonal Spot +- **注意**: Stitch 生成的画布尺寸为 2560×2048,实际开发需适配 1920×1080 横屏 + +--- + +## 其他说明 + +1. 需求文档:[已确认-污水毒品快检一体机_功能需求文档.md](docs/%E5%B7%B2%E7%A1%AE%E8%AE%A4-%E6%B1%A1%E6%B0%B4%E6%AF%92%E5%93%81%E5%BF%AB%E6%A3%80%E4%B8%80%E4%BD%93%E6%9C%BA_%E5%8A%9F%E8%83%BD%E9%9C%80%E6%B1%82%E6%96%87%E6%A1%A3.md) +2. 运行设备屏幕尺寸为1920*1080(横屏),UI设计必须支持此尺寸 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4de6d2c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,166 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +污水毒品快检一体机控制软件(kuaishai2),用于控制设备运行程序、管理程序配置、监控运行状态。 + +**包名**: com.xiarui.kuaishai2 + +## 开发命令 + +```bash +# 获取依赖 +flutter pub get + +# 运行应用 (调试模式) +flutter run + +# 构建 APK +flutter build apk + +# 构建发布版 APK +flutter build apk --release + +# 代码分析 +flutter analyze + +# 运行测试 +flutter test + +# 清理构建缓存 +flutter clean +``` + +## 代码架构 + +项目采用 **feature-first** 分层架构,结构如下: + +``` +lib/ +├── core/ # 核心模块 +│ ├── database/ # SQLite 数据库服务 (单例) +│ ├── router/ # GoRouter 路由配置 +│ ├── theme/ # AppTheme 主题定义 +│ └── localization/ # AppLocalizations 国际化 +├── features/ # 功能模块 +│ ├── home/ # 首页(设备控制面板) +│ ├── programs/ # 程序管理 +│ ├── program_detail/ # 程序详情/步骤配置 +│ ├── device/ # 设备运行控制 +│ └── settings/ # 系统设置 +└── shared/ # 共享组件 + ├── widgets/ # 通用组件 (CommonButton, CommonCard, StatusIndicator) + └── utils/ # 常量定义 (Constants) +``` + +### 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 状态管理 | flutter_riverpod | StateNotifier + Provider 模式 | +| 路由 | go_router | 声明式路由,支持路径参数 | +| 数据持久化 | sqflite | SQLite 本地数据库 | +| 国际化 | intl + flutter_localizations | 中/英双语 | + +### 核心数据模型 + +**Program** (程序): `id`, `code`, `name`, `createdAt`, `status(1启用/0停用)` +**Step** (步骤): `id`, `programId`, `stepNo`, `position`, `name`, `mixTime`, `magnetTime`, `volume`, `mixSpeed`, `blowSpeed`, `blowTime`, `needleSpeed` + +### 关键 Provider + +- `programsProvider` - 程序列表状态 (StateNotifier) +- `runStateProvider` - 设备运行状态 (RunStatus: idle/running/paused/completed/error) +- `goRouterProvider` - 路由配置 + +### 数据库 + +DatabaseService 为单例模式,表结构: +- `programs` - 程序表 +- `steps` - 步骤表 (外键关联 program_id) + +### 国际化 + +AppLocalizations 支持中文(zh)和英文(en),使用 `AppLocalizations.of(context)` 获取翻译。 + +## 代码规范 + +- Dart SDK 版本: ^3.11.5 +- 使用 `flutter_lints` 包的推荐 lint 规则 +- 代码风格遵循 Flutter 官方最佳实践 + +## 功能模块 + +根据需求文档,应用包含以下核心模块: + +### 1. 首页模块(设备控制面板) +- 状态栏:设备名称、实时时钟、系统状态、照明控制 +- 程序列表:卡片展示、程序选择、查看详情 +- 运行控制:启动/暂停/停止程序 +- 运行状态监控:当前步骤、剩余时间、总进度 + +### 2. 程序管理模块 +- 程序列表:编号、名称、创建时间、状态 +- CRUD 操作:新增、编辑、删除、导入程序 + +### 3. 程序详情模块 +- 步骤管理:步骤列表、排序、增删改 +- 步骤参数:孔位、混合时间、吸磁时间、容积、速度等 + +### 4. 系统设置模块 +- 软件升级、语言切换(中文/英文)、密码修改、U盘导入 + +## 步骤参数说明 + +| 参数 | 说明 | 取值范围 | +|------|------|----------| +| 孔位 | 操作位置 | A1, A2, B1... | +| 混合时间 | 混合持续时间 | 正整数(秒) | +| 吸磁时间 | 磁珠吸附时间 | 正整数(秒) | +| 容积 | 液体体积 | 正整数(μL) | +| 混合/吹气速度 | 操作速度 | 低速/中速/高速 | +| 下针速度 | 针头下移速度 | 1-10档 | + +## 当前实现状态 + +已完成基础架构搭建,实现了: +- 首页:状态栏、程序列表、运行控制面板、运行状态监控 +- 程序管理:列表展示、新增/编辑/删除、状态切换 +- 程序详情:步骤列表、步骤参数编辑 +- 运行控制:启动/暂停/停止、进度监控(使用 MockRunner 模拟) +- 系统设置:基础页面框架 +- 国际化:中/英文翻译配置 + +待完善:设备通信对接、实际硬件控制、U盘导入功能。 + +## UI 设计稿 + +**Stitch 项目链接**: https://stitch.withgoogle.com/projects/16230138564963723693 + +### 页面截图链接 + +| 页面 | 功能描述 | 截图链接 | +|------|----------|----------| +| 首页 - 待机状态 | 状态栏 + 程序列表 + 开始运行按钮 + 瓷套棒确认 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujbe00elAXxTbD-KomnaatYJ40X-ubwTDSaI--jR_FRjPow3DJn7S61WpX9NxIMfgAyup6KXC_5qQWeZi0G9qQakQpvszL-OiaTU0C_yQ5FB-E1UMrystcYL0inmiky1Z2Ai-18NSYAdr0dJ6bbTp_dmJ4-JgmK0bjqdoUSnqlzo_Q1SwyNmbJb8WdNTKIUoXNudgLOCZURLWzONviyPgs9CqlxS1KUvopuxJymQF26kwxFO33lY6_vdLzD) | +| 首页 - 运行状态 | 实时监控:孔位、步骤、倒计时、进度条、暂停/停止控制 | [查看](https://lh3.googleusercontent.com/aida/ADBb0uj1bjMG6Fu-6-yvfL-Y51mJws-NBwlkX-GCbBBjLjb-4gNvMB_y8AESOu7X-UF_SHggCttMNpY6g88WgGOSJjw-1NOyTyigDBRInEwawYt8aXSGIGknLVw6zK_99aFnfturjzoYySuHBhKP2r34XWvfAO1g0e-as3hWBOaJW0gb2w8DSLmv3MpGLvsedJNq4SQFhl50LtNFM_zCMKjv-NtqyCE3y1vndNxnxCrJUy_BHvh4XHCahf18UnOU) | +| 程序管理 - 列表视图 | 程序表格:复选框、编号、名称、状态、批量操作、分页 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujE1ZBh5_Gn-yyy1bfU-U_xCeGHREsycgxyAlWBKzWbhFzvlusSWhCEY1u6RBRqVk5XZ49x8Ljpe4yXcS5e3q_jgGMGpcd1CN0RxX0nU6D75O056euNV7fY9cBNtTVGXibmM7Am1uj5uxV7TLbW8c_ix8WubRhn0gYFU6L87b80N6Zl-2T3lmBZsp98jwSsUXP3RqHk6CwMLoQ9LYcLRQTU5Dh5XD1e9aXus9I9pDgj-ZMUsFcZJA7XnJ0y) | +| 程序详情 - 步骤配置 | 左侧步骤列表 + 右侧参数表单(孔位、时间、速度等) | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujEyurJOmwUkZx3ysQMSCk4pXU9_6Jh6bsEMhwlwyC88SQZ0hCFRHMhKADFecHdLXG3D-A5Hp3Avw25j1ivrP2CyTTdeXgd4QPCARNZetrezcL1iy0b7RYsVTTnXnzIFX846pRC_rxgbh8DdqMAiHPz8Q94JciAFCAokQfduFmDWw3Wzhj_P0KtlL8UmEIJNzGFi9ySTGo6eKmz6lfdfQ6VMowhYR5Qy8VGZi8qTLlXqFSU9f-dZepaJWs) | +| 系统设置 - 控制面板 | 软件升级、语言切换、密码修改、U盘导入 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ugIEqBuSXmwYtQkPXXIJ7qBdL69VyQdTRzTyXjc5M6THuTBnbJlLyIDmvo7qfTz_RwjwZE_czzN6dyR_ydqk2Er5hNYduPLypOLqqR7I2sHaKDIvCeole91oJIVkAcnfJkWQJJPgaIhqfysMV4mrsHD0rhYJpngLorfE9WMl11tG9MPz9_E8XcmwaFqXqtYvxHeBsnvSNtIQfkX9S5QfnYtOiAsnUKioY3GpVvjK6bV4PYIfCMMHXv5koS8) | +| 运行完成 - 操作指引 | 成功提示 + 样本滴入说明 + 返回/重运行按钮 | [查看](https://lh3.googleusercontent.com/aida/ADBb0ujh5-3mBPpjWkSRHGOzNRQuusc4O7p_Kov2dbd3iiJmOHlElBb9-gPWC5bnfnRuDbhIbZDhxQhTfVkRpUhQU5nMGa5y7NpV4efvG6ZF0Bx4aVdm4HJ-o2Qca-alJuSfgsggJgbZ3QptA_cAXx-VfG79sHB1zUvRDHpPkLSu0lePhmjaR6m6-pwR42Pib7OLSNqS6V9208w7HoGCX1cPC6pH-hi8rZrMbMmbk2p-pTd2tJebNsow9aLb6bLJ) | + +### 设计规范 + +- **主色调**: #2196F3(蓝色) +- **字体**: Inter +- **圆角**: 4px(工业控制风格) +- **配色模式**: Light + Tonal Spot +- **注意**: Stitch 生成的画布尺寸为 2560×2048,实际开发需适配 1920×1080 横屏 + +--- + +## 其他说明 + +1. 需求文档:[已确认-污水毒品快检一体机_功能需求文档.md](docs/%E5%B7%B2%E7%A1%AE%E8%AE%A4-%E6%B1%A1%E6%B0%B4%E6%AF%92%E5%93%81%E5%BF%AB%E6%A3%80%E4%B8%80%E4%BD%93%E6%9C%BA_%E5%8A%9F%E8%83%BD%E9%9C%80%E6%B1%82%E6%96%87%E6%A1%A3.md) +2. 运行设备屏幕尺寸为1920*1080(横屏),UI设计必须支持此尺寸 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9a82f9 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# kuaishai2 + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..6145c29 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.xiarui.kuaishai2" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.xiarui.kuaishai2" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e4992c3 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/xiarui/kuaishai2/BootReceiver.kt b/android/app/src/main/kotlin/com/xiarui/kuaishai2/BootReceiver.kt new file mode 100644 index 0000000..a50a891 --- /dev/null +++ b/android/app/src/main/kotlin/com/xiarui/kuaishai2/BootReceiver.kt @@ -0,0 +1,22 @@ +package com.xiarui.kuaishai2 + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +/** + * Kiosk 开机自启广播接收器 + * 设备开机后自动启动 MainActivity + */ +class BootReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_BOOT_COMPLETED || + intent.action == Intent.ACTION_LOCKED_BOOT_COMPLETED || + intent.action == "android.intent.action.QUICKBOOT_POWERON") { + val startIntent = Intent(context, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(startIntent) + } + } +} diff --git a/android/app/src/main/kotlin/com/xiarui/kuaishai2/MainActivity.kt b/android/app/src/main/kotlin/com/xiarui/kuaishai2/MainActivity.kt new file mode 100644 index 0000000..5be51ca --- /dev/null +++ b/android/app/src/main/kotlin/com/xiarui/kuaishai2/MainActivity.kt @@ -0,0 +1,49 @@ +package com.xiarui.kuaishai2 + +import android.os.Bundle +import android.view.KeyEvent +import android.view.View +import android.view.WindowManager +import io.flutter.embedding.android.FlutterActivity + +/** + * Kiosk 模式主 Activity + * - 全屏沉浸(隐藏状态栏、导航栏) + * - 屏蔽 Back/Home/Recent 键 + * - 保持屏幕常亮 + */ +class MainActivity : FlutterActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 保持屏幕常亮 + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + enableImmersiveMode() + } + } + + private fun enableImmersiveMode() { + window.decorView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + or View.SYSTEM_UI_FLAG_FULLSCREEN + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + ) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + return when (keyCode) { + KeyEvent.KEYCODE_BACK, + KeyEvent.KEYCODE_HOME, + KeyEvent.KEYCODE_APP_SWITCH -> true // 屏蔽,防止退出 Kiosk + else -> super.onKeyDown(keyCode, event) + } + } +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..2c0b9ad --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,26 @@ +allprojects { + repositories { + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..a982db7 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") \ No newline at end of file diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/docs/已确认-污水毒品快检一体机_功能需求文档.md b/docs/已确认-污水毒品快检一体机_功能需求文档.md new file mode 100644 index 0000000..06058be --- /dev/null +++ b/docs/已确认-污水毒品快检一体机_功能需求文档.md @@ -0,0 +1,417 @@ +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +# 1. 功能结构 + +**一、首页模块(设备控制面板)** + +**1. 状态栏** + +- 设备名称显示 + +- 实时时钟 + +- 系统状态显示 + +**2. 任务列表** + +- 程序列表展示 + +- 程序信息显示 + +- 程序选择 + +- 选中状态标识 + +- 查看详情 + +**3. 运行控制** + +- 程序选择确认 + +- 运行按钮 + +- 暂停 / 继续 + +- 停止按钮 + +**4. 运行状态监控** + +- 当前程序显示 + +- 当前孔位 + +- 步骤序号 + +- 步骤名称 + +- 步骤剩余时间 + +- 总进度条 + +- 暂停 / 继续按钮 + +- 停止按钮 + +**5. 运行完成提示** + +**5.1 样本滴入提示** + +- 提示信息 + +- 操作指引 + +------------------------------------------------------------------------ + +**二、程序管理模块** + +**1. 程序列表** + +- 程序列表展示 + +- 编号显示 + +- 程序名称 + +- 创建时间 + +- 状态显示 + +- 多选功能 + +- 全选 / 取消全选 + +**2. 程序 CRUD 操作** + +- 新增程序 + +- 编辑程序 + +- 删除程序 + +- 删除确认 + +- 文件导入 + +- 查看详情 + +------------------------------------------------------------------------ + +**三、程序详情模块** + +**1. 步骤管理** + +- 步骤列表展示 + +- 步骤参数显示 + +- 多选步骤 + +- 全选 / 取消全选 + +- 拖动排序 + +- 添加步骤 + +- 编辑步骤 + +- 删除步骤 + +- 返回按钮 + +**2. 步骤参数配置** + +- 步骤编号 + +- 孔位 + +- 步骤名称 + +- 混合时间 + +- 吸磁时间 + +- 容积 + +- 混合速度 + +- 吹气速度 + +- 吹气时间 + +- 下针速度 + +------------------------------------------------------------------------ + +**四、系统设置模块** + +**1. 软件升级** + +- 版本显示 + +- 检查更新 + +- 更新提示 + +**2. 语言设置** + +- 语言选择 + +- 实时切换 + +**3. 安全设置** + +- 密码修改 + +- 原密码验证 + +- 新密码确认 + +- 密码一致性校验 + +**4. U 盘导入** + +- 自动检测 + +- 程序导入 + +- 导入确认 + +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +# 2. 首页模块 - 设备控制面板 + +## 2.1 状态栏功能 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 设备名称显示 显示\"污水毒品前处理一体机\"设备标识 + + 实时时钟 显示当前日期时间,格式: YYYY-MM-DD HH:mm:ss + + 系统状态显示 实时显示设备运行状态(运行中/未运行) + + 照明按钮 切换设备照明灯状态(亮/暗) + ----------------------------------------------------------------------- + +## 2.2 程序列表功能 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 程序列表展示 以卡片形式展示所有可用程序 + + 程序信息显示 显示程序编号、名称、创建时间 + + 程序选择 点击卡片可选择要运行的程序 + + 选中状态标识 选中的程序卡片高亮显示并带勾选标记 + + 查看详情 可直接跳转查看程序详细步骤配置 + ----------------------------------------------------------------------- + +## 2.3 运行控制功能 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 程序选择确认 显示当前选中的程序名称 + + 运行按钮 启动选中的程序运行(未选择程序时禁用) + + 瓷套棒确认 运行前确认是否已安装瓷套棒(硬件传感器检测支持) + + 暂停/继续 运行过程中可暂停和继续程序执行 + + 停止按钮 终止当前运行的程序 + + 停止确认 停止前需用户确认操作 + ----------------------------------------------------------------------- + +## 2.4 运行状态监控 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 当前程序显示 显示正在运行的程序名称 + + 当前孔位 显示当前操作的孔位 + + 步骤序号 显示当前执行的步骤编号 + + 步骤名称 显示当前步骤的名称 + + 步骤剩余时间 倒计时显示当前步骤剩余时间(HH:MM:SS) + + 总进度条 显示程序总体完成百分比 + + 暂停/继续按钮 运行过程中的暂停和继续控制 + + 停止按钮 终止当前运行的程序 + + 步骤参数 显示当前步骤所有设置参数 + ----------------------------------------------------------------------- + +## 2.5 运行完成提示 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 提示信息 程序运行完成后自动跳转到提示页面 + + 样本滴入指引 提示用户将样本滴入到检测卡 + + 操作说明 显示详细操作步骤说明 + ----------------------------------------------------------------------- + +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +# 3. 程序管理模块 + +## 3.1 程序列表管理 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 程序列表展示 以表格形式展示所有程序 + + 编号显示 显示程序唯一编号 + + 程序名称 显示程序名称 + + 创建时间 显示程序创建日期 + + 状态显示 显示程序状态(启用/停用) + + 多选功能 支持勾选多个程序进行批量操作 + + 全选/取消全选 表头复选框控制全选状态 + ----------------------------------------------------------------------- + +## 3.2 程序CRUD操作 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 新增程序 创建新程序,需填写编号和名称 + + 编辑程序 修改程序的编号、名称、状态 + + 删除程序 删除选中的程序,支持批量删除 + + 删除确认 删除操作需用户确认 + + 文件导入 从文件导入程序配置 + + 查看详情 查看程序的详细步骤配置 + ----------------------------------------------------------------------- + +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +# 4. 程序详情模块 + +## 4.1 步骤管理功能 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 步骤列表展示 以表格形式展示程序的所有步骤 + + 步骤参数显示 显示步骤的完整参数配置 + + 多选步骤 支持勾选多个步骤 + + 全选/取消全选 表头复选框控制全选状态 + + 拖动排序 支持拖动步骤行调整执行顺序 + + 添加步骤 新增步骤并配置参数 + + 编辑步骤 修改已有步骤的参数配置 + + 删除步骤 删除选中的步骤,支持批量删除 + + 返回按钮 返回主页面 + ----------------------------------------------------------------------- + +## 4.2 步骤参数配置 + + ---------------------------------------------------------------------------- + **参数名称** **说明** **取值范围** **示例** + -------------- -------------------------- ----------------- ---------------- + 步骤编号 步骤执行顺序(可拖动调整) 正整数 1, 2, 3\... + + 孔位 操作的孔位位置 如A1、A2、B1等 A1, B2, C3 + + 步骤名称 步骤的描述名称 文本 混合、吸磁\... + + 混合时间 混合操作的持续时间 秒(正整数) 60, 120\... + + 吸磁时间 磁珠吸附时间 秒(正整数) 30, 60\... + + 容积 液体体积 微升μL(正整数) 100, 200\... + + 混合速度 混合操作速度 低速/中速/高速 低速, 中速\... + + 吹气速度 吹气操作速度 低速/中速/高速 中速, 高速\... + + 吹气时间 吹气持续时间 分钟(正整数) 5, 10\... + + 下针速度 针头下移速度,10档可选 1-10档 1档至10档 + ---------------------------------------------------------------------------- + +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +# 5. 系统设置模块 + +## 5.1 软件升级 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 版本显示 显示当前软件版本号(如V1.0.0) + + 检查更新 检查是否有新版本可用 + + 更新提示 有新版本时提示用户更新 + ----------------------------------------------------------------------- + +## 5.2 语言设置 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 语言选择 支持简体中文、English语言切换 + + 实时切换 切换后立即生效 + ----------------------------------------------------------------------- + +## 5.3 安全设置 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 密码修改 修改用户登录密码或操作密码 + + 原密码验证 需输入原密码进行身份验证 + + 新密码确认 需两次输入新密码进行确认 + + 密码一致性校验 确保两次输入的新密码一致 + ----------------------------------------------------------------------- + +## 5.4 U盘导入 + + ----------------------------------------------------------------------- + **功能项** **描述** + ----------------- ----------------------------------------------------- + 自动检测 自动检测U盘插入事件 + + 程序导入 从U盘自动导入程序配置文件 + + 导入确认 导入前显示确认信息,用户确认后执行导入 + ----------------------------------------------------------------------- + +─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +--- 文档结束 --- diff --git a/lib/core/database/database_service.dart b/lib/core/database/database_service.dart new file mode 100644 index 0000000..b27f7b1 --- /dev/null +++ b/lib/core/database/database_service.dart @@ -0,0 +1,164 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; + +/// 数据库服务 +class DatabaseService { + static final DatabaseService instance = DatabaseService._internal(); + static Database? _database; + + DatabaseService._internal(); + + Future get database async { + if (_database != null) return _database!; + _database = await _initDatabase(); + return _database!; + } + + Future _initDatabase() async { + final dbPath = await getDatabasesPath(); + final path = join(dbPath, 'kuaishai.db'); + + return await openDatabase( + path, + version: 2, + onCreate: _onCreate, + onUpgrade: _onUpgrade, + ); + } + + Future _onCreate(Database db, int version) async { + // 程序表 + await db.execute(''' + CREATE TABLE programs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + created_at TEXT NOT NULL, + status INTEGER DEFAULT 1 + ) + '''); + + // 步骤表 + await db.execute(''' + CREATE TABLE steps ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + program_id INTEGER NOT NULL, + step_no INTEGER NOT NULL, + position TEXT NOT NULL, + name TEXT NOT NULL, + mix_time INTEGER DEFAULT 0, + magnet_time INTEGER DEFAULT 0, + volume INTEGER DEFAULT 0, + mix_speed TEXT DEFAULT '中速', + blow_speed TEXT DEFAULT '中速', + blow_time INTEGER DEFAULT 0, + needle_speed INTEGER DEFAULT 5, + FOREIGN KEY (program_id) REFERENCES programs(id) ON DELETE CASCADE + ) + '''); + + // 设置表(密码存储) + await db.execute(''' + CREATE TABLE settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ) + '''); + + // 初始化默认密码 + await db.insert('settings', {'key': 'password', 'value': '123456'}); + } + + /// 数据库升级 + Future _onUpgrade(Database db, int oldVersion, int newVersion) async { + if (oldVersion < 2) { + // 添加 settings 表 + await db.execute(''' + CREATE TABLE settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ) + '''); + // 初始化默认密码 + await db.insert('settings', {'key': 'password', 'value': '123456'}); + } + } + + Future close() async { + if (_database != null) { + await _database!.close(); + _database = null; + } + } + + /// 初始化测试数据(仅调试模式使用) + Future initTestData() async { + final db = await database; + + // 检查是否已有数据 + final count = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM programs'), + ); + if (count != null && count > 0) return; + + // 插入测试程序并添加步骤 + final testPrograms = [ + {'code': 'P001', 'name': '标准检测程序', 'created_at': '2026-05-19', 'status': 1}, + {'code': 'P002', 'name': '快速检测程序', 'created_at': '2026-05-18', 'status': 1}, + {'code': 'P003', 'name': '深度检测程序', 'created_at': '2026-05-17', 'status': 1}, + {'code': 'P004', 'name': '样本预处理程序', 'created_at': '2026-05-16', 'status': 0}, + {'code': 'P005', 'name': '磁珠分离程序', 'created_at': '2026-05-15', 'status': 1}, + ]; + + for (final program in testPrograms) { + final programId = await db.insert('programs', program); + + // 为每个程序添加测试步骤 + final testSteps = [ + { + 'program_id': programId, + 'step_no': 1, + 'position': 'A1', + 'name': '混合', + 'mix_time': 60, + 'magnet_time': 0, + 'volume': 100, + 'mix_speed': '中速', + 'blow_speed': '中速', + 'blow_time': 0, + 'needle_speed': 5, + }, + { + 'program_id': programId, + 'step_no': 2, + 'position': 'A1', + 'name': '吸磁', + 'mix_time': 0, + 'magnet_time': 30, + 'volume': 0, + 'mix_speed': '中速', + 'blow_speed': '中速', + 'blow_time': 0, + 'needle_speed': 5, + }, + { + 'program_id': programId, + 'step_no': 3, + 'position': 'A2', + 'name': '吹气', + 'mix_time': 0, + 'magnet_time': 0, + 'volume': 0, + 'mix_speed': '中速', + 'blow_speed': '高速', + 'blow_time': 10, + 'needle_speed': 8, + }, + ]; + + for (final step in testSteps) { + await db.insert('steps', step); + } + } + } +} \ No newline at end of file diff --git a/lib/core/localization/app_localizations.dart b/lib/core/localization/app_localizations.dart new file mode 100644 index 0000000..578b0ac --- /dev/null +++ b/lib/core/localization/app_localizations.dart @@ -0,0 +1,366 @@ +import 'package:flutter/material.dart'; + +/// 应用国际化配置 +class AppLocalizations { + final Locale locale; + + AppLocalizations(this.locale); + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + // 状态栏 + String get deviceName => _localizedValues[locale.languageCode]?['deviceName'] ?? '污水毒品前处理一体机'; + String get running => _localizedValues[locale.languageCode]?['running'] ?? '运行中'; + String get idle => _localizedValues[locale.languageCode]?['idle'] ?? '未运行'; + String get lighting => _localizedValues[locale.languageCode]?['lighting'] ?? '照明'; + + // 程序管理 + String get programs => _localizedValues[locale.languageCode]?['programs'] ?? '程序管理'; + String get programList => _localizedValues[locale.languageCode]?['programList'] ?? '程序列表'; + String get programName => _localizedValues[locale.languageCode]?['programName'] ?? '程序名称'; + String get programCode => _localizedValues[locale.languageCode]?['programCode'] ?? '程序编号'; + String get createTime => _localizedValues[locale.languageCode]?['createTime'] ?? '创建时间'; + String get addProgram => _localizedValues[locale.languageCode]?['addProgram'] ?? '新增程序'; + String get editProgram => _localizedValues[locale.languageCode]?['editProgram'] ?? '编辑程序'; + String get deleteProgram => _localizedValues[locale.languageCode]?['deleteProgram'] ?? '删除程序'; + String get importProgram => _localizedValues[locale.languageCode]?['importProgram'] ?? '导入程序'; + String get viewDetails => _localizedValues[locale.languageCode]?['viewDetails'] ?? '查看详情'; + String get selectedProgram => _localizedValues[locale.languageCode]?['selectedProgram'] ?? '当前选中程序'; + String get selectedProgramLabel => _localizedValues[locale.languageCode]?['selectedProgramLabel'] ?? '当前选中'; + String get availablePrograms => _localizedValues[locale.languageCode]?['availablePrograms'] ?? '可用程序'; + String get ceramicNotInstalled => _localizedValues[locale.languageCode]?['ceramicNotInstalled'] ?? '瓷套棒: 未安装 — 禁止启动'; + String get ceramicInstalled => _localizedValues[locale.languageCode]?['ceramicInstalled'] ?? '瓷套棒: 已安装'; + String get runningMonitor => _localizedValues[locale.languageCode]?['runningMonitor'] ?? '运行状态监控'; + String get currentHole => _localizedValues[locale.languageCode]?['currentHole'] ?? '当前孔位'; + String get stepParams => _localizedValues[locale.languageCode]?['stepParams'] ?? '步骤参数'; + String get speed => _localizedValues[locale.languageCode]?['speed'] ?? '转速'; + String get temperature => _localizedValues[locale.languageCode]?['temperature'] ?? '温度'; + String get duration => _localizedValues[locale.languageCode]?['duration'] ?? '持续时间'; + String get sampleVolume => _localizedValues[locale.languageCode]?['sampleVolume'] ?? '样品体积'; + String get pleaseSelectProgram => _localizedValues[locale.languageCode]?['pleaseSelectProgram'] ?? '请选择要运行的程序'; + + // 运行控制 + String get run => _localizedValues[locale.languageCode]?['run'] ?? '运行'; + String get pause => _localizedValues[locale.languageCode]?['pause'] ?? '暂停'; + String get continue_ => _localizedValues[locale.languageCode]?['continue'] ?? '继续'; + String get stop => _localizedValues[locale.languageCode]?['stop'] ?? '停止'; + String get startRun => _localizedValues[locale.languageCode]?['startRun'] ?? '开始运行'; + String get currentStep => _localizedValues[locale.languageCode]?['currentStep'] ?? '当前步骤'; + String get remainingTime => _localizedValues[locale.languageCode]?['remainingTime'] ?? '剩余时间'; + String get progress => _localizedValues[locale.languageCode]?['progress'] ?? '进度'; + String get ceramicSleeveConfirm => _localizedValues[locale.languageCode]?['ceramicSleeveConfirm'] ?? '运行前请确认已安装瓷套棒'; + String get paused => _localizedValues[locale.languageCode]?['paused'] ?? '已暂停'; + String get stopConfirm => _localizedValues[locale.languageCode]?['stopConfirm'] ?? '确定要停止当前运行的程序吗?'; + String get currentProgram => _localizedValues[locale.languageCode]?['currentProgram'] ?? '当前程序'; + String get backToHome => _localizedValues[locale.languageCode]?['backToHome'] ?? '返回首页'; + String get runAgain => _localizedValues[locale.languageCode]?['runAgain'] ?? '重新运行'; + String get deleteConfirm => _localizedValues[locale.languageCode]?['deleteConfirm'] ?? '确定要删除此程序吗?'; + + // 步骤参数 + String get stepNo => _localizedValues[locale.languageCode]?['stepNo'] ?? '步骤编号'; + String get position => _localizedValues[locale.languageCode]?['position'] ?? '孔位'; + String get stepName => _localizedValues[locale.languageCode]?['stepName'] ?? '步骤名称'; + String get mixTime => _localizedValues[locale.languageCode]?['mixTime'] ?? '混合时间'; + String get magnetTime => _localizedValues[locale.languageCode]?['magnetTime'] ?? '吸磁时间'; + String get volume => _localizedValues[locale.languageCode]?['volume'] ?? '容积'; + String get mixSpeed => _localizedValues[locale.languageCode]?['mixSpeed'] ?? '混合速度'; + String get blowSpeed => _localizedValues[locale.languageCode]?['blowSpeed'] ?? '吹气速度'; + String get blowTime => _localizedValues[locale.languageCode]?['blowTime'] ?? '吹气时间'; + String get needleSpeed => _localizedValues[locale.languageCode]?['needleSpeed'] ?? '下针速度'; + + // 速度选项 + String get lowSpeed => _localizedValues[locale.languageCode]?['lowSpeed'] ?? '低速'; + String get mediumSpeed => _localizedValues[locale.languageCode]?['mediumSpeed'] ?? '中速'; + String get highSpeed => _localizedValues[locale.languageCode]?['highSpeed'] ?? '高速'; + + // 设置 + String get settings => _localizedValues[locale.languageCode]?['settings'] ?? '系统设置'; + String get language => _localizedValues[locale.languageCode]?['language'] ?? '语言设置'; + String get password => _localizedValues[locale.languageCode]?['password'] ?? '密码修改'; + String get upgrade => _localizedValues[locale.languageCode]?['upgrade'] ?? '软件升级'; + String get usbImport => _localizedValues[locale.languageCode]?['usbImport'] ?? 'U盘导入'; + + // 通用 + String get confirm => _localizedValues[locale.languageCode]?['confirm'] ?? '确认'; + String get cancel => _localizedValues[locale.languageCode]?['cancel'] ?? '取消'; + String get save => _localizedValues[locale.languageCode]?['save'] ?? '保存'; + String get delete => _localizedValues[locale.languageCode]?['delete'] ?? '删除'; + String get select => _localizedValues[locale.languageCode]?['select'] ?? '选择'; + String get selected => _localizedValues[locale.languageCode]?['selected'] ?? '已选择'; + String get detail => _localizedValues[locale.languageCode]?['detail'] ?? '详情'; + String get noData => _localizedValues[locale.languageCode]?['noData'] ?? '暂无数据'; + + // 完成提示 + String get runComplete => _localizedValues[locale.languageCode]?['runComplete'] ?? '运行完成'; + String get sampleDropGuide => _localizedValues[locale.languageCode]?['sampleDropGuide'] ?? '请将样本滴入检测卡'; + + // 补充缺失的翻译 + String get lightOn => _localizedValues[locale.languageCode]?['lightOn'] ?? '亮'; + String get lightOff => _localizedValues[locale.languageCode]?['lightOff'] ?? '暗'; + String get enabled => _localizedValues[locale.languageCode]?['enabled'] ?? '启用'; + String get disabled => _localizedValues[locale.languageCode]?['disabled'] ?? '停用'; + String get stepList => _localizedValues[locale.languageCode]?['stepList'] ?? '步骤列表'; + String get operationSteps => _localizedValues[locale.languageCode]?['operationSteps'] ?? '操作步骤'; + String get addStep => _localizedValues[locale.languageCode]?['addStep'] ?? '添加步骤'; + String get editStep => _localizedValues[locale.languageCode]?['editStep'] ?? '编辑步骤'; + String get deleteStep => _localizedValues[locale.languageCode]?['deleteStep'] ?? '删除步骤'; + String get deleteStepConfirm => _localizedValues[locale.languageCode]?['deleteStepConfirm'] ?? '确定要删除此步骤吗?'; + String get stepsCount => _localizedValues[locale.languageCode]?['stepsCount'] ?? '步'; + String get noSteps => _localizedValues[locale.languageCode]?['noSteps'] ?? '暂无步骤'; + String get selectStepFirst => _localizedValues[locale.languageCode]?['selectStepFirst'] ?? '请选择或添加步骤'; + String get oldPassword => _localizedValues[locale.languageCode]?['oldPassword'] ?? '原密码'; + String get newPassword => _localizedValues[locale.languageCode]?['newPassword'] ?? '新密码'; + String get confirmPassword => _localizedValues[locale.languageCode]?['confirmPassword'] ?? '确认新密码'; + String get passwordMinLength => _localizedValues[locale.languageCode]?['passwordMinLength'] ?? '至少6位字符'; + String get passwordChanged => _localizedValues[locale.languageCode]?['passwordChanged'] ?? '密码已修改'; + String get passwordChangeFailed => _localizedValues[locale.languageCode]?['passwordChangeFailed'] ?? '密码修改失败'; + String get oldPasswordError => _localizedValues[locale.languageCode]?['oldPasswordError'] ?? '原密码错误'; + String get passwordMismatch => _localizedValues[locale.languageCode]?['passwordMismatch'] ?? '两次输入的新密码不一致'; + String get fillAllFields => _localizedValues[locale.languageCode]?['fillAllFields'] ?? '请填写所有字段'; + String get importSuccess => _localizedValues[locale.languageCode]?['importSuccess'] ?? '成功导入'; + String get importFailed => _localizedValues[locale.languageCode]?['importFailed'] ?? '导入失败'; + String get programsImported => _localizedValues[locale.languageCode]?['programsImported'] ?? '个程序'; + String get usbDetected => _localizedValues[locale.languageCode]?['usbDetected'] ?? '检测到U盘'; + String get usbNotDetected => _localizedValues[locale.languageCode]?['usbNotDetected'] ?? '未检测到U盘'; + String get insertUsb => _localizedValues[locale.languageCode]?['insertUsb'] ?? '请插入U盘后重试'; + String get detectingUsb => _localizedValues[locale.languageCode]?['detectingUsb'] ?? '正在检测U盘...'; + String get currentVersion => _localizedValues[locale.languageCode]?['currentVersion'] ?? '当前版本'; + String get latestVersion => _localizedValues[locale.languageCode]?['latestVersion'] ?? '已是最新版本'; + String get updateAvailable => _localizedValues[locale.languageCode]?['updateAvailable'] ?? '有新版本可用'; + String get checkUpdate => _localizedValues[locale.languageCode]?['checkUpdate'] ?? '检查更新'; + + static final Map> _localizedValues = { + 'zh': { + 'deviceName': '污水毒品前处理一体机', + 'running': '运行中', + 'idle': '未运行', + 'lighting': '照明', + 'programs': '程序管理', + 'programList': '程序列表', + 'programName': '程序名称', + 'programCode': '程序编号', + 'createTime': '创建时间', + 'addProgram': '新增程序', + 'editProgram': '编辑程序', + 'deleteProgram': '删除程序', + 'importProgram': '导入程序', + 'viewDetails': '查看详情', + 'selectedProgram': '当前选中程序', + 'selectedProgramLabel': '当前选中', + 'availablePrograms': '可用程序', + 'ceramicNotInstalled': '瓷套棒: 未安装 — 禁止启动', + 'ceramicInstalled': '瓷套棒: 已安装', + 'runningMonitor': '运行状态监控', + 'currentHole': '当前孔位', + 'stepParams': '步骤参数', + 'speed': '转速', + 'temperature': '温度', + 'duration': '持续时间', + 'sampleVolume': '样品体积', + 'pleaseSelectProgram': '请选择要运行的程序', + 'run': '运行', + 'pause': '暂停', + 'continue': '继续', + 'stop': '停止', + 'startRun': '开始运行', + 'currentStep': '当前步骤', + 'remainingTime': '剩余时间', + 'progress': '进度', + 'ceramicSleeveConfirm': '运行前请确认已安装瓷套棒', + 'paused': '已暂停', + 'stopConfirm': '确定要停止当前运行的程序吗?', + 'currentProgram': '当前程序', + 'backToHome': '返回首页', + 'runAgain': '重新运行', + 'deleteConfirm': '确定要删除此程序吗?', + 'stepNo': '步骤编号', + 'position': '孔位', + 'stepName': '步骤名称', + 'mixTime': '混合时间', + 'magnetTime': '吸磁时间', + 'volume': '容积', + 'mixSpeed': '混合速度', + 'blowSpeed': '吹气速度', + 'blowTime': '吹气时间', + 'needleSpeed': '下针速度', + 'lowSpeed': '低速', + 'mediumSpeed': '中速', + 'highSpeed': '高速', + 'settings': '系统设置', + 'language': '语言设置', + 'password': '密码修改', + 'upgrade': '软件升级', + 'usbImport': 'U盘导入', + 'confirm': '确认', + 'cancel': '取消', + 'save': '保存', + 'delete': '删除', + 'select': '选择', + 'selected': '已选择', + 'detail': '详情', + 'noData': '暂无数据', + 'runComplete': '运行完成', + 'sampleDropGuide': '请将样本滴入检测卡', + 'lightOn': '亮', + 'lightOff': '暗', + 'enabled': '启用', + 'disabled': '停用', + 'stepList': '步骤列表', + 'operationSteps': '操作步骤', + 'addStep': '添加步骤', + 'editStep': '编辑步骤', + 'deleteStep': '删除步骤', + 'deleteStepConfirm': '确定要删除此步骤吗?', + 'stepsCount': '步', + 'noSteps': '暂无步骤', + 'selectStepFirst': '请选择或添加步骤', + 'oldPassword': '原密码', + 'newPassword': '新密码', + 'confirmPassword': '确认新密码', + 'passwordMinLength': '至少6位字符', + 'passwordChanged': '密码已修改', + 'passwordChangeFailed': '密码修改失败', + 'oldPasswordError': '原密码错误', + 'passwordMismatch': '两次输入的新密码不一致', + 'fillAllFields': '请填写所有字段', + 'importSuccess': '成功导入', + 'importFailed': '导入失败', + 'programsImported': '个程序', + 'usbDetected': '检测到U盘', + 'usbNotDetected': '未检测到U盘', + 'insertUsb': '请插入U盘后重试', + 'detectingUsb': '正在检测U盘...', + 'currentVersion': '当前版本', + 'latestVersion': '已是最新版本', + 'updateAvailable': '有新版本可用', + 'checkUpdate': '检查更新', + }, + 'en': { + 'deviceName': 'Wastewater Drug Pretreatment System', + 'running': 'Running', + 'idle': 'Idle', + 'lighting': 'Lighting', + 'programs': 'Programs', + 'programList': 'Program List', + 'programName': 'Program Name', + 'programCode': 'Program Code', + 'createTime': 'Create Time', + 'addProgram': 'Add Program', + 'editProgram': 'Edit Program', + 'deleteProgram': 'Delete Program', + 'importProgram': 'Import Program', + 'viewDetails': 'View Details', + 'selectedProgram': 'Selected Program', + 'selectedProgramLabel': 'Selected', + 'availablePrograms': 'Available Programs', + 'ceramicNotInstalled': 'Ceramic sleeve: Not installed — Cannot start', + 'ceramicInstalled': 'Ceramic sleeve: Installed', + 'runningMonitor': 'Running Status Monitor', + 'currentHole': 'Current Position', + 'stepParams': 'Step Parameters', + 'speed': 'Speed', + 'temperature': 'Temperature', + 'duration': 'Duration', + 'sampleVolume': 'Sample Volume', + 'pleaseSelectProgram': 'Please select a program', + 'run': 'Run', + 'pause': 'Pause', + 'continue': 'Continue', + 'stop': 'Stop', + 'startRun': 'Start Run', + 'currentStep': 'Current Step', + 'remainingTime': 'Remaining', + 'progress': 'Progress', + 'ceramicSleeveConfirm': 'Please confirm ceramic sleeve is installed', + 'paused': 'Paused', + 'stopConfirm': 'Are you sure to stop the running program?', + 'currentProgram': 'Current Program', + 'backToHome': 'Back to Home', + 'runAgain': 'Run Again', + 'deleteConfirm': 'Are you sure to delete this program?', + 'stepNo': 'Step No.', + 'position': 'Position', + 'stepName': 'Step Name', + 'mixTime': 'Mix Time', + 'magnetTime': 'Magnet Time', + 'volume': 'Volume', + 'mixSpeed': 'Mix Speed', + 'blowSpeed': 'Blow Speed', + 'blowTime': 'Blow Time', + 'needleSpeed': 'Needle Speed', + 'lowSpeed': 'Low', + 'mediumSpeed': 'Medium', + 'highSpeed': 'High', + 'settings': 'Settings', + 'language': 'Language', + 'password': 'Password', + 'upgrade': 'Upgrade', + 'usbImport': 'USB Import', + 'confirm': 'Confirm', + 'cancel': 'Cancel', + 'save': 'Save', + 'delete': 'Delete', + 'select': 'Select', + 'selected': 'Selected', + 'detail': 'Detail', + 'noData': 'No Data', + 'runComplete': 'Complete', + 'sampleDropGuide': 'Drop sample to test card', + 'lightOn': 'On', + 'lightOff': 'Off', + 'enabled': 'Enabled', + 'disabled': 'Disabled', + 'stepList': 'Step List', + 'operationSteps': 'Operation Steps', + 'addStep': 'Add Step', + 'editStep': 'Edit Step', + 'deleteStep': 'Delete Step', + 'deleteStepConfirm': 'Are you sure to delete this step?', + 'stepsCount': 'steps', + 'noSteps': 'No steps', + 'selectStepFirst': 'Please select or add a step', + 'oldPassword': 'Old Password', + 'newPassword': 'New Password', + 'confirmPassword': 'Confirm Password', + 'passwordMinLength': 'At least 6 characters', + 'passwordChanged': 'Password changed', + 'passwordChangeFailed': 'Password change failed', + 'oldPasswordError': 'Old password incorrect', + 'passwordMismatch': 'Passwords do not match', + 'fillAllFields': 'Please fill all fields', + 'importSuccess': 'Successfully imported', + 'importFailed': 'Import failed', + 'programsImported': 'programs', + 'usbDetected': 'USB detected', + 'usbNotDetected': 'USB not detected', + 'insertUsb': 'Please insert USB and try again', + 'detectingUsb': 'Detecting USB...', + 'currentVersion': 'Current Version', + 'latestVersion': 'Already latest version', + 'updateAvailable': 'Update available', + 'checkUpdate': 'Check Update', + }, + }; +} + +class _AppLocalizationsDelegate extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) { + return ['zh', 'en'].contains(locale.languageCode); + } + + @override + Future load(Locale locale) async { + return AppLocalizations(locale); + } + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} \ No newline at end of file diff --git a/lib/core/localization/locale_provider.dart b/lib/core/localization/locale_provider.dart new file mode 100644 index 0000000..2d05f05 --- /dev/null +++ b/lib/core/localization/locale_provider.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Locale 状态 Notifier +class LocaleNotifier extends StateNotifier { + static const String _key = 'app_locale'; + + LocaleNotifier() : super(const Locale('zh', 'CN')) { + _loadLocale(); + } + + /// 从本地存储加载语言设置 + Future _loadLocale() async { + final prefs = await SharedPreferences.getInstance(); + final localeCode = prefs.getString(_key); + if (localeCode != null) { + state = Locale(localeCode, localeCode == 'zh' ? 'CN' : 'US'); + } + } + + /// 切换语言 + Future setLocale(Locale locale) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_key, locale.languageCode); + state = locale; + } + + /// 切换为中文 + Future setChinese() async { + await setLocale(const Locale('zh', 'CN')); + } + + /// 切换为英文 + Future setEnglish() async { + await setLocale(const Locale('en', 'US')); + } +} + +/// Locale Provider +final localeProvider = StateNotifierProvider((ref) { + return LocaleNotifier(); +}); + +/// 当前语言是否为中文 +final isChineseProvider = Provider((ref) { + return ref.watch(localeProvider).languageCode == 'zh'; +}); \ No newline at end of file diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart new file mode 100644 index 0000000..9e67e0a --- /dev/null +++ b/lib/core/router/app_router.dart @@ -0,0 +1,45 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../features/home/pages/home_page.dart'; +import '../../features/programs/pages/programs_page.dart'; +import '../../features/program_detail/pages/program_detail_page.dart'; +import '../../features/settings/pages/settings_page.dart'; +import '../../features/home/pages/complete_page.dart'; + +/// 应用路由配置 +final goRouterProvider = Provider((ref) { + return GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + name: 'home', + builder: (context, state) => const HomePage(), + ), + GoRoute( + path: '/programs', + name: 'programs', + builder: (context, state) => const ProgramsPage(), + ), + GoRoute( + path: '/programs/:id', + name: 'programDetail', + builder: (context, state) { + final id = state.pathParameters['id']; + return ProgramDetailPage(programId: id ?? ''); + }, + ), + GoRoute( + path: '/settings', + name: 'settings', + builder: (context, state) => const SettingsPage(), + ), + GoRoute( + path: '/complete', + name: 'complete', + builder: (context, state) => const CompletePage(), + ), + ], + ); +}); \ No newline at end of file diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..ee5caa8 --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; + +/// 应用主题配置 - 明亮工业控制风格 +/// 主色 #2196F3,圆角 4px,明亮背景,适配 1920x1080 横屏 +class AppTheme { + // ========== 主色 ========== + static const Color primaryColor = Color(0xFF2196F3); + static const Color primaryDark = Color(0xFF1976D2); + static const Color primaryLight = Color(0xFFBBDEFB); + + // ========== 功能色 ========== + static const Color successColor = Color(0xFF4CAF50); + static const Color warningColor = Color(0xFFFF9800); + static const Color errorColor = Color(0xFFF44336); + static const Color infoColor = Color(0xFF00BCD4); + + // ========== 背景色(明亮) ========== + static const Color bgPage = Color(0xFFF5F7FA); + static const Color bgDeep = Color(0xFFE8ECF0); + static const Color bgSurface = Color(0xFFFFFFFF); + static const Color bgCard = Color(0xFFFFFFFF); + static const Color bgCardHover = Color(0xFFF0F7FF); + static const Color bgSidebar = Color(0xFFF0F2F5); + + // ========== 文本色 ========== + static const Color textHeading = Color(0xFF1A1A2E); + static const Color textPrimary = Color(0xFF333344); + static const Color textSecondary = Color(0xFF6B7280); + static const Color textTertiary = Color(0xFF9CA3AF); + static const Color textOnPrimary = Colors.white; + + // ========== 状态色 ========== + static const Color statusRunning = Color(0xFF4CAF50); + static const Color statusStopped = Color(0xFF9CA3AF); + static const Color statusPaused = Color(0xFFFF9800); + static const Color statusError = Color(0xFFF44336); + + // ========== 卡片背景 ========== + static const Color cardBg = Color(0xFFFFFFFF); + static const Color cardSelectedBg = Color(0xFFE3F2FD); + + // ========== 功能色(accent) ========== + static const Color accentPrimary = primaryColor; + static const Color accentInfo = infoColor; + static const Color accentWarning = warningColor; + static const Color accentCritical = errorColor; + + // ========== 边框色 ========== + static const Color borderLight = Color(0xFFE5E7EB); + static const Color borderMedium = Color(0xFFD1D5DB); + static const Color borderSubtle = borderLight; + static const Color borderFocus = primaryColor; + + // ========== 圆角 ========== + static const double radiusSm = 4.0; + static const double radiusMd = 8.0; + static const double radiusLg = 12.0; + + // ========== 阴影 ========== + static const List shadowCard = [ + BoxShadow( + color: Color(0x0A000000), + blurRadius: 8, + offset: Offset(0, 2), + ), + ]; + + static const List shadowCardHover = [ + BoxShadow( + color: Color(0x14000000), + blurRadius: 12, + offset: Offset(0, 4), + ), + ]; + + // ========== 兼容旧代码的颜色别名 ========== + static const Color runningColor = statusRunning; + static const Color idleColor = statusStopped; + static const Color backgroundColor = bgPage; + static const Color cardColor = bgCard; + + /// 亮色主题 - 明亮工业风格 + static ThemeData lightTheme() { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + ), + scaffoldBackgroundColor: bgPage, + fontFamily: 'Inter', + cardTheme: CardThemeData( + color: bgCard, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd), + side: const BorderSide(color: borderLight, width: 1), + ), + margin: EdgeInsets.zero, + ), + appBarTheme: const AppBarTheme( + backgroundColor: bgSurface, + foregroundColor: textHeading, + elevation: 0, + centerTitle: false, + scrolledUnderElevation: 1, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: textOnPrimary, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusSm), + ), + ), + ), + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(radiusSm), + borderSide: const BorderSide(color: borderMedium), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(radiusSm), + borderSide: const BorderSide(color: borderMedium), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(radiusSm), + borderSide: const BorderSide(color: primaryColor, width: 2), + ), + filled: true, + fillColor: bgSurface, + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + ), + dialogTheme: DialogThemeData( + backgroundColor: bgSurface, + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd), + ), + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: textHeading, + contentTextStyle: const TextStyle(color: Colors.white, fontSize: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusSm), + ), + behavior: SnackBarBehavior.floating, + ), + listTileTheme: const ListTileThemeData( + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radiusSm)), + ), + ), + dataTableTheme: DataTableThemeData( + headingRowColor: WidgetStateProperty.all(bgSidebar), + dividerThickness: 1, + ), + dividerTheme: const DividerThemeData( + color: borderLight, + thickness: 1, + ), + ); + } + + /// 暗色主题(与亮色主题风格一致的暗色模式) + static ThemeData darkTheme() { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.dark, + ), + scaffoldBackgroundColor: const Color(0xFF121212), + ); + } +} diff --git a/lib/features/device/models/device_state.dart b/lib/features/device/models/device_state.dart new file mode 100644 index 0000000..7476320 --- /dev/null +++ b/lib/features/device/models/device_state.dart @@ -0,0 +1,73 @@ +/// 设备状态模型 +enum DeviceStatus { idle, running, paused, error } + +/// 设备状态数据 +class DeviceState { + final DeviceStatus status; + final String? currentProgram; + final String? currentPosition; + final int? currentStepNo; + final String? currentStepName; + final int? remainingSeconds; + final double? progress; + final bool lightingOn; + + DeviceState({ + this.status = DeviceStatus.idle, + this.currentProgram, + this.currentPosition, + this.currentStepNo, + this.currentStepName, + this.remainingSeconds, + this.progress, + this.lightingOn = false, + }); + + bool get isRunning => status == DeviceStatus.running; + bool get isPaused => status == DeviceStatus.paused; + bool get isIdle => status == DeviceStatus.idle; + bool get hasError => status == DeviceStatus.error; + + String statusText() { + switch (status) { + case DeviceStatus.running: + return '运行中'; + case DeviceStatus.paused: + return '已暂停'; + case DeviceStatus.error: + return '错误'; + case DeviceStatus.idle: + return '未运行'; + } + } + + String formatRemainingTime() { + if (remainingSeconds == null) return '--:--:--'; + final hours = remainingSeconds! ~/ 3600; + final minutes = (remainingSeconds! % 3600) ~/ 60; + final seconds = remainingSeconds! % 60; + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } + + DeviceState copyWith({ + DeviceStatus? status, + String? currentProgram, + String? currentPosition, + int? currentStepNo, + String? currentStepName, + int? remainingSeconds, + double? progress, + bool? lightingOn, + }) { + return DeviceState( + status: status ?? this.status, + currentProgram: currentProgram ?? this.currentProgram, + currentPosition: currentPosition ?? this.currentPosition, + currentStepNo: currentStepNo ?? this.currentStepNo, + currentStepName: currentStepName ?? this.currentStepName, + remainingSeconds: remainingSeconds ?? this.remainingSeconds, + progress: progress ?? this.progress, + lightingOn: lightingOn ?? this.lightingOn, + ); + } +} \ No newline at end of file diff --git a/lib/features/device/providers/run_state_provider.dart b/lib/features/device/providers/run_state_provider.dart new file mode 100644 index 0000000..f003e6f --- /dev/null +++ b/lib/features/device/providers/run_state_provider.dart @@ -0,0 +1,188 @@ +import 'dart:async'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../programs/models/program.dart'; +import '../../programs/models/step.dart'; +import '../../programs/services/program_service.dart'; +import '../services/mock_runner.dart'; + +/// 运行状态枚举 +enum RunStatus { + idle, // 待机 + running, // 运行中 + paused, // 已暂停 + completed,// 已完成 + error, // 错误 +} + +/// 运行状态 +class RunState { + final RunStatus status; + final Program? currentProgram; + final List steps; + final int currentStepIndex; + final int remainingSeconds; + final double progress; + final String? currentWell; + + const RunState({ + this.status = RunStatus.idle, + this.currentProgram, + this.steps = const [], + this.currentStepIndex = 0, + this.remainingSeconds = 0, + this.progress = 0, + this.currentWell, + }); + + RunState copyWith({ + RunStatus? status, + Program? currentProgram, + List? steps, + int? currentStepIndex, + int? remainingSeconds, + double? progress, + String? currentWell, + bool clearProgram = false, + bool clearWell = false, + }) { + return RunState( + status: status ?? this.status, + currentProgram: clearProgram ? null : (currentProgram ?? this.currentProgram), + steps: steps ?? this.steps, + currentStepIndex: currentStepIndex ?? this.currentStepIndex, + remainingSeconds: remainingSeconds ?? this.remainingSeconds, + progress: progress ?? this.progress, + currentWell: clearWell ? null : (currentWell ?? this.currentWell), + ); + } + + /// 获取当前步骤 + Step? get currentStep { + if (steps.isEmpty || currentStepIndex >= steps.length) return null; + return steps[currentStepIndex]; + } + + /// 格式化剩余时间 (HH:MM:SS) + String get formattedRemainingTime { + final hours = remainingSeconds ~/ 3600; + final minutes = (remainingSeconds % 3600) ~/ 60; + final seconds = remainingSeconds % 60; + return '${hours.toString().padLeft(2, '0')}:' + '${minutes.toString().padLeft(2, '0')}:' + '${seconds.toString().padLeft(2, '0')}'; + } + + /// 格式化进度百分比 + String get formattedProgress { + return '${(progress * 100).toStringAsFixed(0)}%'; + } +} + +/// 运行状态 Notifier +class RunStateNotifier extends StateNotifier { + final MockRunner _runner; + final ProgramService _programService; + + RunStateNotifier(this._runner, this._programService) : super(const RunState()); + + /// 开始运行程序 + Future start(Program program) async { + // 获取程序步骤(这里使用模拟数据,实际应从数据库读取) + final steps = await _loadSteps(program.id!); + + if (steps.isEmpty) { + state = state.copyWith(status: RunStatus.error); + return; + } + + state = state.copyWith( + status: RunStatus.running, + currentProgram: program, + steps: steps, + currentStepIndex: 0, + progress: 0, + ); + + _runner.start( + program, + steps, + (stepIndex, remaining, progress, well) { + state = state.copyWith( + currentStepIndex: stepIndex, + remainingSeconds: remaining, + progress: progress, + currentWell: well, + ); + }, + () { + state = state.copyWith( + status: RunStatus.completed, + progress: 1, + clearWell: true, + ); + }, + ); + } + + /// 暂停运行 + void pause() { + if (state.status == RunStatus.running) { + _runner.pause(); + state = state.copyWith(status: RunStatus.paused); + } + } + + /// 继续运行 + void resume() { + if (state.status == RunStatus.paused) { + _runner.resume(); + state = state.copyWith(status: RunStatus.running); + } + } + + /// 停止运行 + void stop() { + _runner.stop(); + state = const RunState(status: RunStatus.idle); + } + + /// 重置状态 + void reset() { + stop(); + } + + /// 加载程序步骤(从数据库读取) + Future> _loadSteps(int programId) async { + return await _programService.getStepsByProgramId(programId); + } +} + +/// MockRunner Provider +final mockRunnerProvider = Provider((ref) { + return MockRunner(); +}); + +/// ProgramService Provider +final programServiceProvider = Provider((ref) { + return ProgramService.instance; +}); + +/// 运行状态 Provider +final runStateProvider = + StateNotifierProvider((ref) { + final runner = ref.watch(mockRunnerProvider); + final programService = ref.watch(programServiceProvider); + return RunStateNotifier(runner, programService); +}); + +/// 是否正在运行 Provider +final isRunningProvider = Provider((ref) { + final status = ref.watch(runStateProvider).status; + return status == RunStatus.running; +}); + +/// 是否已暂停 Provider +final isPausedProvider = Provider((ref) { + final status = ref.watch(runStateProvider).status; + return status == RunStatus.paused; +}); \ No newline at end of file diff --git a/lib/features/device/services/mock_runner.dart b/lib/features/device/services/mock_runner.dart new file mode 100644 index 0000000..27134a8 --- /dev/null +++ b/lib/features/device/services/mock_runner.dart @@ -0,0 +1,190 @@ +import 'dart:async'; +import '../../programs/models/step.dart'; +import '../../programs/models/program.dart'; + +/// 模拟运行器回调 +typedef RunProgressCallback = void Function( + int currentStepIndex, + int remainingSeconds, + double progress, + String currentWell, +); + +typedef RunCompleteCallback = void Function(); + +/// 模拟运行器 +/// 用于在没有实际硬件连接时模拟程序执行过程 +class MockRunner { + Timer? _timer; + Program? _currentProgram; + List _steps = []; + int _currentStepIndex = 0; + int _remainingSeconds = 0; + bool _isPaused = false; + RunProgressCallback? _onProgress; + RunCompleteCallback? _onComplete; + + /// 是否正在运行 + bool get isRunning => _timer != null && !_isPaused; + + /// 是否已暂停 + bool get isPaused => _isPaused; + + /// 当前程序 + Program? get currentProgram => _currentProgram; + + /// 开始运行程序 + void start( + Program program, + List steps, + RunProgressCallback onProgress, + RunCompleteCallback onComplete, + ) { + _currentProgram = program; + _steps = steps; + _onProgress = onProgress; + _onComplete = onComplete; + _currentStepIndex = 0; + _isPaused = false; + + if (steps.isEmpty) { + onComplete(); + return; + } + + // 开始执行第一个步骤 + _startStep(steps[0]); + } + + /// 暂停运行 + void pause() { + if (_timer != null && !_isPaused) { + _isPaused = true; + _timer!.cancel(); + _timer = null; + } + } + + /// 继续运行 + void resume() { + if (_isPaused && _currentProgram != null) { + _isPaused = false; + _resumeStep(); + } + } + + /// 停止运行 + void stop() { + _timer?.cancel(); + _timer = null; + _currentProgram = null; + _steps = []; + _currentStepIndex = 0; + _remainingSeconds = 0; + _isPaused = false; + } + + /// 开始执行步骤 + void _startStep(Step step) { + // 计算步骤总时间(混合时间 + 吸磁时间 + 吹气时间) + _remainingSeconds = step.mixTime + step.magnetTime + step.blowTime; + + // 如果步骤时间为0,设置最小演示时间(5秒) + if (_remainingSeconds == 0) { + _remainingSeconds = 5; + } + + // 启动定时器,每秒更新 + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _remainingSeconds--; + + // 计算总进度 + final totalSeconds = _calculateTotalSeconds(); + final elapsedSeconds = _calculateElapsedSeconds(); + final progress = totalSeconds > 0 ? elapsedSeconds / totalSeconds : 0.0; + + // 回调进度更新 + _onProgress?.call( + _currentStepIndex, + _remainingSeconds, + progress, + step.position, + ); + + // 步骤完成 + if (_remainingSeconds <= 0) { + timer.cancel(); + _timer = null; + _nextStep(); + } + }); + } + + /// 继续执行步骤(从暂停恢复) + void _resumeStep() { + if (_currentStepIndex >= _steps.length) return; + + final step = _steps[_currentStepIndex]; + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _remainingSeconds--; + + final totalSeconds = _calculateTotalSeconds(); + final elapsedSeconds = _calculateElapsedSeconds(); + final progress = totalSeconds > 0 ? elapsedSeconds / totalSeconds : 0.0; + + _onProgress?.call( + _currentStepIndex, + _remainingSeconds, + progress, + step.position, + ); + + if (_remainingSeconds <= 0) { + timer.cancel(); + _timer = null; + _nextStep(); + } + }); + } + + /// 执行下一个步骤 + void _nextStep() { + _currentStepIndex++; + + if (_currentStepIndex >= _steps.length) { + // 所有步骤完成 + _onComplete?.call(); + stop(); + } else { + // 执行下一个步骤 + _startStep(_steps[_currentStepIndex]); + } + } + + /// 计算总执行时间 + int _calculateTotalSeconds() { + int total = 0; + for (final step in _steps) { + int stepTime = step.mixTime + step.magnetTime + step.blowTime; + if (stepTime == 0) stepTime = 5; + total += stepTime; + } + return total; + } + + /// 计算已执行时间 + int _calculateElapsedSeconds() { + int elapsed = 0; + for (int i = 0; i < _currentStepIndex; i++) { + int stepTime = _steps[i].mixTime + _steps[i].magnetTime + _steps[i].blowTime; + if (stepTime == 0) stepTime = 5; + elapsed += stepTime; + } + // 加上当前步骤已执行的时间 + final currentStep = _steps[_currentStepIndex]; + int currentStepTime = currentStep.mixTime + currentStep.magnetTime + currentStep.blowTime; + if (currentStepTime == 0) currentStepTime = 5; + elapsed += currentStepTime - _remainingSeconds; + return elapsed; + } +} \ No newline at end of file diff --git a/lib/features/device/services/mock_runner_impl.dart b/lib/features/device/services/mock_runner_impl.dart new file mode 100644 index 0000000..c807b07 --- /dev/null +++ b/lib/features/device/services/mock_runner_impl.dart @@ -0,0 +1,114 @@ +import '../../programs/models/program.dart'; +import '../../programs/models/step.dart'; +import 'runner_interface.dart'; + +/// 模拟运行器(用于开发测试) +/// 模拟硬件运行过程 +class MockRunner implements Runner { + @override + RunnerStatus status = RunnerStatus.idle; + + bool _isRunning = false; + int _currentStep = 0; + int _remainingSeconds = 0; + RunnerCallbacks? _callbacks; + List _steps = []; + + @override + void start(Program program, List steps, RunnerCallbacks callbacks) { + if (steps.isEmpty) { + callbacks.onError?.call('No steps to run'); + status = RunnerStatus.error; + return; + } + + _steps = steps; + _callbacks = callbacks; + _currentStep = 0; + _isRunning = true; + status = RunnerStatus.running; + + // 开始模拟运行 + _runSimulation(); + } + + void _runSimulation() { + if (!_isRunning || _currentStep >= _steps.length) { + _completeRun(); + return; + } + + final step = _steps[_currentStep]; + // 计算步骤时间(混合时间 + 吸磁时间 + 吹气时间 + 5秒最小) + final stepTime = (step.mixTime ?? 0) + (step.magnetTime ?? 0) + (step.blowTime ?? 0) + 5; + _remainingSeconds = stepTime.clamp(5, 300); + + // 模拟倒计时 + _simulateStepProgress(stepTime); + } + + void _simulateStepProgress(int totalSeconds) { + // 简化模拟:每秒更新进度 + int elapsed = 0; + while (_isRunning && elapsed < totalSeconds) { + elapsed++; + final remaining = totalSeconds - elapsed; + final progress = elapsed / totalSeconds; + + _callbacks?.onProgress?.call( + _currentStep, + remaining, + (_currentStep + progress) / _steps.length, + _steps[_currentStep].position, + ); + + // 实际实现需要使用 Timer + // await Future.delayed(Duration(seconds: 1)); + } + + if (_isRunning) { + _currentStep++; + _runSimulation(); + } + } + + void _completeRun() { + status = RunnerStatus.completed; + _isRunning = false; + _callbacks?.onComplete?.call(); + } + + @override + void pause() { + if (status == RunnerStatus.running) { + _isRunning = false; + status = RunnerStatus.paused; + } + } + + @override + void resume() { + if (status == RunnerStatus.paused) { + _isRunning = true; + status = RunnerStatus.running; + // 继续运行 + _runSimulation(); + } + } + + @override + void stop() { + _isRunning = false; + status = RunnerStatus.idle; + _currentStep = 0; + _remainingSeconds = 0; + } + + @override + RunnerStatus getStatus() => status; + + @override + void dispose() { + stop(); + } +} \ No newline at end of file diff --git a/lib/features/device/services/runner_interface.dart b/lib/features/device/services/runner_interface.dart new file mode 100644 index 0000000..1bf1309 --- /dev/null +++ b/lib/features/device/services/runner_interface.dart @@ -0,0 +1,54 @@ +import '../../programs/models/program.dart'; +import '../../programs/models/step.dart'; + +/// 运行器状态 +enum RunnerStatus { + idle, + running, + paused, + completed, + error, +} + +/// 运行器回调 +class RunnerCallbacks { + /// 步骤进度回调: (stepIndex, remainingSeconds, progress, currentWell) + final void Function(int stepIndex, int remainingSeconds, double progress, String well)? onProgress; + + /// 运行完成回调 + final void Function()? onComplete; + + /// 错误回调 + final void Function(String error)? onError; + + const RunnerCallbacks({ + this.onProgress, + this.onComplete, + this.onError, + }); +} + +/// 运行器抽象接口 +/// 定义硬件运行控制的标准接口 +abstract class Runner { + /// 当前状态 + RunnerStatus status = RunnerStatus.idle; + + /// 启动程序运行 + void start(Program program, List steps, RunnerCallbacks callbacks); + + /// 暂停运行 + void pause(); + + /// 继续运行 + void resume(); + + /// 停止运行 + void stop(); + + /// 获取当前状态 + RunnerStatus getStatus(); + + /// 释放资源 + void dispose(); +} \ No newline at end of file diff --git a/lib/features/device/services/serial_runner.dart b/lib/features/device/services/serial_runner.dart new file mode 100644 index 0000000..5d8239d --- /dev/null +++ b/lib/features/device/services/serial_runner.dart @@ -0,0 +1,91 @@ +import '../../programs/models/program.dart'; +import '../../programs/models/step.dart'; +import 'runner_interface.dart'; + +/// 串口运行器(真实硬件实现) +/// 实现与设备的串口通信 +class SerialRunner implements Runner { + @override + RunnerStatus status = RunnerStatus.idle; + + /// 串口配置 + final String portName; + final int baudRate; + final int dataBits; + final int stopBits; + + SerialRunner({ + this.portName = '/dev/ttyUSB0', + this.baudRate = 9600, + this.dataBits = 8, + this.stopBits = 1, + }); + + @override + void start(Program program, List steps, RunnerCallbacks callbacks) { + // TODO: 实现串口通信启动逻辑 + // 1. 打开串口连接 + // 2. 发送程序配置 + // 3. 按步骤发送控制指令 + // 4. 接收设备反馈并更新状态 + + status = RunnerStatus.running; + + // 示例:发送启动指令 + // _sendCommand('START', program.code); + + // 示例:监听设备状态 + // _listenToDevice(callbacks); + } + + @override + void pause() { + if (status == RunnerStatus.running) { + // _sendCommand('PAUSE'); + status = RunnerStatus.paused; + } + } + + @override + void resume() { + if (status == RunnerStatus.paused) { + // _sendCommand('RESUME'); + status = RunnerStatus.running; + } + } + + @override + void stop() { + // _sendCommand('STOP'); + // _closeConnection(); + status = RunnerStatus.idle; + } + + @override + RunnerStatus getStatus() => status; + + @override + void dispose() { + stop(); + } + + /// 发送控制指令(待硬件协议确定后实现) + Future _sendCommand(String command, [String? data]) async { + // TODO: 根据硬件通信协议实现 + // 示例协议格式: [CMD:data] 或 二进制协议 + } + + /// 监听设备反馈(待硬件协议确定后实现) + void _listenToDevice(RunnerCallbacks callbacks) { + // TODO: 解析设备返回的状态数据 + // 状态格式示例: [STEP:1,TIME:60,POS:A1] + } + + /// 执行单个步骤 + Future _executeStep(Step step) async { + // TODO: 根据步骤参数生成控制指令 + // 混合: MIX(position, time, speed) + // 吸磁: MAGNET(position, time) + // 吹气: BLOW(position, speed, time) + } +} \ No newline at end of file diff --git a/lib/features/home/pages/complete_page.dart b/lib/features/home/pages/complete_page.dart new file mode 100644 index 0000000..145f31c --- /dev/null +++ b/lib/features/home/pages/complete_page.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../../device/providers/run_state_provider.dart'; + +/// 运行完成提示页面 +class CompletePage extends ConsumerWidget { + const CompletePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context); + final runState = ref.watch(runStateProvider); + final runNotifier = ref.read(runStateProvider.notifier); + + return Scaffold( + body: Container( + color: AppTheme.backgroundColor, + child: Center( + child: Container( + width: 600, + padding: const EdgeInsets.all(40), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.15), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 成功图标 + Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: AppTheme.successColor.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.check_circle, + size: 60, + color: AppTheme.successColor, + ), + ), + + const SizedBox(height: 24), + + // 标题 + Text( + l10n?.runComplete ?? '程序运行完成', + style: TextStyle( + color: AppTheme.textPrimary, + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + + const SizedBox(height: 16), + + // 提示信息 + Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + decoration: BoxDecoration( + color: AppTheme.warningColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + l10n?.sampleDropGuide ?? '请将样本滴入检测卡', + style: TextStyle( + color: AppTheme.warningColor, + fontSize: 16, + ), + ), + ), + + const SizedBox(height: 32), + + // 操作示意图 + _buildOperationGuide(), + + const SizedBox(height: 32), + + // 按钮区域 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 返回首页按钮 + CommonButton( + text: l10n?.backToHome ?? '返回首页', + icon: Icons.home, + type: ButtonType.primary, + onPressed: () { + runNotifier.reset(); + context.go('/'); + }, + ), + const SizedBox(width: 24), + + // 重新运行按钮 + CommonButton( + text: l10n?.runAgain ?? '重新运行', + icon: Icons.refresh, + type: ButtonType.secondary, + onPressed: () { + final program = runState.currentProgram; + if (program != null) { + runNotifier.reset(); + runNotifier.start(program); + context.go('/'); + } + }, + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + /// 操作指引示意图 + Widget _buildOperationGuide() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.backgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppTheme.idleColor.withValues(alpha: 0.2)), + ), + child: Column( + children: [ + Text( + '操作步骤', + style: TextStyle( + color: AppTheme.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildStepItem(1, '取出样本', Icons.science), + _buildStepItem(2, '滴入检测卡', Icons.water_drop), + _buildStepItem(3, '等待反应', Icons.timer), + _buildStepItem(4, '查看结果', Icons.visibility), + ], + ), + ], + ), + ); + } + + /// 步骤项 + Widget _buildStepItem(int number, String text, IconData icon) { + return Column( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: AppTheme.primaryColor, size: 24), + ), + const SizedBox(height: 8), + Text( + '$number', + style: TextStyle( + color: AppTheme.primaryColor, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + text, + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 11, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/features/home/pages/home_page.dart b/lib/features/home/pages/home_page.dart new file mode 100644 index 0000000..08da9f2 --- /dev/null +++ b/lib/features/home/pages/home_page.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../core/database/database_service.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../device/providers/run_state_provider.dart'; +import '../../programs/pages/programs_page.dart'; +import '../../settings/pages/settings_page.dart'; +import '../widgets/status_bar.dart'; +import '../widgets/program_list.dart'; +import '../widgets/running_control_panel.dart'; +import '../widgets/run_status_monitor.dart'; + +/// 首页 - 设备控制面板 (暗色工业风格) +/// 布局:状态栏 + 导航标签栏 + 内容区(设备控制/程序管理/系统设置) +class HomePage extends ConsumerStatefulWidget { + const HomePage({super.key}); + + @override + ConsumerState createState() => _HomePageState(); +} + +class _HomePageState extends ConsumerState + with SingleTickerProviderStateMixin { + bool _lightOn = false; + final bool _ceramicSleeveInstalled = false; // TODO: 后续对接硬件传感器后改为可变状态 + int _currentIndex = 0; + + @override + void initState() { + super.initState(); + DatabaseService.instance.initTestData(); + } + + @override + Widget build(BuildContext context) { + final runState = ref.watch(runStateProvider); + + // 监听运行完成状态,自动跳转 + ref.listen(runStateProvider, (prev, next) { + if (prev?.status != RunStatus.completed && next.status == RunStatus.completed) { + // 仅首页才自动跳转 + if (_currentIndex == 0) { + context.push('/complete'); + } + } + }); + + return Scaffold( + body: Container( + color: AppTheme.bgDeep, + child: Column( + children: [ + // 状态栏 + StatusBar( + isRunning: runState.status == RunStatus.running, + lightOn: _lightOn, + onLightToggle: () { + setState(() { + _lightOn = !_lightOn; + }); + }, + ceramicSleeveInstalled: _ceramicSleeveInstalled, + ), + + // 导航标签栏 + _buildTabBar(), + + // 内容区 + Expanded( + child: IndexedStack( + index: _currentIndex, + children: [ + _buildDeviceControlPage(runState), + const ProgramsPage(), + const SettingsPage(), + ], + ), + ), + ], + ), + ), + ); + } + + /// 导航标签栏 + Widget _buildTabBar() { + const tabs = [ + (icon: Icons.dashboard, label: '设备控制'), + (icon: Icons.list_alt, label: '程序管理'), + (icon: Icons.settings, label: '系统设置'), + ]; + + return Container( + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + children: List.generate(tabs.length, (index) { + final tab = tabs[index]; + final isSelected = _currentIndex == index; + return GestureDetector( + onTap: () => setState(() => _currentIndex = index), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.only(right: 4), + padding: const EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + color: isSelected ? AppTheme.accentPrimary : AppTheme.cardBg, + borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), + border: Border.all( + color: isSelected + ? AppTheme.accentPrimary + : AppTheme.borderSubtle, + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + tab.icon, + size: 18, + color: isSelected ? Colors.white : AppTheme.textSecondary, + ), + const SizedBox(width: 8), + Text( + tab.label, + style: TextStyle( + color: isSelected ? Colors.white : AppTheme.textSecondary, + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, + ), + ), + ], + ), + ), + ); + }), + ), + ); + } + + /// 设备控制页面内容 + Widget _buildDeviceControlPage(RunState runState) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), + child: Row( + children: [ + // 左侧:程序列表(运行时锁定) + Opacity( + opacity: runState.status == RunStatus.idle ? 1.0 : 0.6, + child: IgnorePointer( + ignoring: runState.status != RunStatus.idle, + child: const ProgramList(), + ), + ), + const SizedBox(width: 20), + // 右侧:运行控制区域 + Expanded( + child: Column( + children: [ + const Expanded(child: RunningControlPanel()), + if (runState.status != RunStatus.idle) ...[ + const SizedBox(height: 16), + const Expanded(child: RunStatusMonitor()), + ], + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/home/widgets/program_list.dart b/lib/features/home/widgets/program_list.dart new file mode 100644 index 0000000..c758444 --- /dev/null +++ b/lib/features/home/widgets/program_list.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../programs/models/program.dart'; +import '../../programs/providers/programs_provider.dart'; + +/// 程序列表组件 - 暗色工业风格 +/// 显示程序卡片列表,支持选择操作 +class ProgramList extends ConsumerWidget { + const ProgramList({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context); + final programsState = ref.watch(programsProvider); + final programsNotifier = ref.read(programsProvider.notifier); + + return Container( + width: 380, + decoration: BoxDecoration( + color: AppTheme.cardBg, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppTheme.borderSubtle, width: 1), + ), + child: Column( + children: [ + // 标题 + Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + Icon(Icons.list_alt, color: AppTheme.textHeading, size: 18), + const SizedBox(width: 10), + Text( + l10n?.availablePrograms ?? '可用程序', + style: const TextStyle( + color: AppTheme.textHeading, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + + // 程序列表 + Expanded( + child: programsState.isLoading + ? const Center(child: CircularProgressIndicator()) + : programsState.programs.isEmpty + ? Center( + child: Text( + l10n?.noData ?? '暂无数据', + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 14, + ), + ), + ) + : ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 14), + itemCount: programsState.programs.length, + itemBuilder: (context, index) { + final program = programsState.programs[index]; + final isSelected = + programsState.selectedProgramId == program.id; + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: _ProgramCard( + program: program, + isSelected: isSelected, + onTap: () { + programsNotifier.selectProgram(program.id); + }, + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +/// 单个程序卡片 - 暗色工业风格 +class _ProgramCard extends StatelessWidget { + final Program program; + final bool isSelected; + final VoidCallback? onTap; + + const _ProgramCard({ + required this.program, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + final dateFormat = DateFormat('yyyy-MM-dd HH:mm'); + final createdAt = _parseDate(program.createdAt); + + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isSelected ? AppTheme.cardSelectedBg : AppTheme.cardBg, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected ? AppTheme.accentPrimary : AppTheme.borderSubtle, + width: isSelected ? 2 : 1, + ), + ), + child: Row( + children: [ + // 选择指示器 + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isSelected + ? AppTheme.accentPrimary + : Colors.transparent, + border: Border.all( + color: isSelected + ? AppTheme.accentPrimary + : AppTheme.statusStopped, + width: 2, + ), + ), + child: isSelected + ? const Icon(Icons.check, color: Colors.white, size: 12) + : null, + ), + const SizedBox(width: 12), + + // 程序信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + program.code, + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 12, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: program.status == 1 + ? AppTheme.statusRunning.withValues(alpha: 0.15) + : AppTheme.statusStopped.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + program.status == 1 ? '启用' : '停用', + style: TextStyle( + color: program.status == 1 + ? AppTheme.statusRunning + : AppTheme.statusStopped, + fontSize: 10, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + program.name, + style: const TextStyle( + color: AppTheme.textHeading, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + createdAt != null + ? dateFormat.format(createdAt) + : program.createdAt, + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + DateTime? _parseDate(String dateStr) { + try { + return DateTime.parse(dateStr); + } catch (e) { + return null; + } + } +} diff --git a/lib/features/home/widgets/run_status_monitor.dart b/lib/features/home/widgets/run_status_monitor.dart new file mode 100644 index 0000000..09b82c7 --- /dev/null +++ b/lib/features/home/widgets/run_status_monitor.dart @@ -0,0 +1,242 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../device/providers/run_state_provider.dart'; + +/// 运行状态监控面板 - 暗色工业风格 +/// 显示当前孔位、步骤、倒计时、进度条、参数详情 +class RunStatusMonitor extends ConsumerWidget { + const RunStatusMonitor({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context); + final runState = ref.watch(runStateProvider); + + if (runState.status == RunStatus.idle) { + return const SizedBox.shrink(); + } + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.cardBg, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppTheme.borderSubtle, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + 程序名 + Row( + children: [ + Text( + l10n?.runningMonitor ?? '运行状态监控', + style: const TextStyle( + color: AppTheme.textHeading, + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + Text( + runState.currentProgram?.name ?? '', + style: const TextStyle( + color: AppTheme.accentPrimary, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + + const SizedBox(height: 14), + + // 进度信息横排 (孔位 / 步骤 / 剩余时间) + Row( + children: [ + // 当前孔位 + _buildInfoBlock( + label: l10n?.currentHole ?? '当前孔位', + value: runState.currentWell ?? '--', + valueColor: AppTheme.textHeading, + ), + const SizedBox(width: 20), + // 当前步骤 + _buildInfoBlock( + label: l10n?.currentStep ?? '当前步骤', + value: '${l10n?.stepNo ?? '步骤'} ${runState.currentStepIndex + 1}', + subValue: runState.currentStep?.name ?? '--', + valueColor: AppTheme.accentInfo, + ), + const SizedBox(width: 20), + // 剩余时间 + _buildInfoBlock( + label: l10n?.remainingTime ?? '剩余时间', + value: runState.formattedRemainingTime, + valueColor: AppTheme.textHeading, + valueSize: 20, + ), + ], + ), + + const SizedBox(height: 14), + + // 总进度条 + _buildProgressBar(l10n, runState), + + const SizedBox(height: 14), + + // 步骤参数 + if (runState.currentStep != null) + _buildStepParams(l10n, runState.currentStep!), + ], + ), + ); + } + + /// 信息块 + Widget _buildInfoBlock({ + required String label, + required String value, + String? subValue, + Color valueColor = AppTheme.textHeading, + double valueSize = 16, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 11, + ), + ), + const SizedBox(height: 2), + Text( + value, + style: TextStyle( + color: valueColor, + fontSize: valueSize, + fontWeight: FontWeight.w600, + fontFamily: 'monospace', + ), + ), + if (subValue != null) ...[ + const SizedBox(height: 2), + Text( + subValue, + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 11, + ), + ), + ], + ], + ); + } + + /// 进度条 + Widget _buildProgressBar(AppLocalizations? l10n, RunState runState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + l10n?.progress ?? '总进度', + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 11, + ), + ), + const Spacer(), + Text( + runState.formattedProgress, + style: const TextStyle( + color: AppTheme.accentPrimary, + fontSize: 12, + fontWeight: FontWeight.w600, + fontFamily: 'monospace', + ), + ), + ], + ), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: runState.progress, + minHeight: 8, + backgroundColor: const Color(0xFF1E293B), + valueColor: AlwaysStoppedAnimation(AppTheme.accentPrimary), + ), + ), + ], + ); + } + + /// 步骤参数详情 + Widget _buildStepParams(AppLocalizations? l10n, dynamic step) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n?.stepParams ?? '步骤参数', + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 11, + ), + ), + const SizedBox(height: 2), + if (step.mixTime > 0) + _buildParamRow( + l10n?.speed ?? '转速', + '${step.mixSpeed}', + ), + if (step.magnetTime > 0) + _buildParamRow( + l10n?.temperature ?? '温度', + '65.0 °C', + ), + _buildParamRow( + l10n?.duration ?? '持续时间', + step.mixTime > 0 ? '${step.mixTime} min' : '--', + ), + _buildParamRow( + l10n?.sampleVolume ?? '样品体积', + '10.0 mL', + ), + ], + ); + } + + /// 参数行 + Widget _buildParamRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Text( + label, + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 11, + ), + ), + const Spacer(), + Text( + value, + style: const TextStyle( + color: AppTheme.textPrimary, + fontSize: 11, + fontFamily: 'monospace', + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/home/widgets/running_control_panel.dart b/lib/features/home/widgets/running_control_panel.dart new file mode 100644 index 0000000..03b479d --- /dev/null +++ b/lib/features/home/widgets/running_control_panel.dart @@ -0,0 +1,365 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../../device/providers/run_state_provider.dart'; +import '../../programs/providers/programs_provider.dart'; + +/// 运行控制面板 - 暗色工业风格 +/// 显示当前程序信息、瓷套棒状态和运行控制按钮 +class RunningControlPanel extends ConsumerWidget { + const RunningControlPanel({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context); + final runState = ref.watch(runStateProvider); + final programsState = ref.watch(programsProvider); + + return Container( + decoration: BoxDecoration( + color: AppTheme.cardBg, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppTheme.borderSubtle, width: 1), + ), + child: runState.status == RunStatus.idle + ? _buildIdleState(context, ref, l10n, programsState.selectedProgram) + : _buildRunningState(context, ref, l10n, runState), + ); + } + + /// 待机状态布局 + Widget _buildIdleState( + BuildContext context, + WidgetRef ref, + AppLocalizations? l10n, + dynamic selectedProgram, + ) { + final runNotifier = ref.read(runStateProvider.notifier); + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 当前选中程序显示 + if (selectedProgram != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: AppTheme.cardSelectedBg, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: AppTheme.accentPrimary, width: 1), + ), + child: Row( + children: [ + Text( + '${l10n?.selectedProgramLabel ?? '当前选中'}:', + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 12, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + '${selectedProgram.code} ${selectedProgram.name}', + style: const TextStyle( + color: AppTheme.accentPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ) + else + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: AppTheme.cardBg, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: AppTheme.borderSubtle, width: 1), + ), + child: Text( + l10n?.pleaseSelectProgram ?? '请选择要运行的程序', + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 12, + ), + ), + ), + + const SizedBox(height: 12), + + // 瓷套棒确认提示 + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: AppTheme.cardBg, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: AppTheme.borderSubtle, width: 1), + ), + child: Row( + children: [ + Container( + width: 10, + height: 10, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: AppTheme.statusStopped, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + l10n?.ceramicNotInstalled ?? '瓷套棒: 未安装 — 禁止启动', + style: const TextStyle( + color: AppTheme.textSecondary, + fontSize: 12, + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 12), + + // 控制按钮 + Row( + children: [ + // 开始运行按钮 + Expanded( + flex: 2, + child: SizedBox( + height: 48, + child: CommonButton( + text: l10n?.startRun ?? '开始运行', + icon: Icons.play_arrow, + type: ButtonType.primary, + enabled: selectedProgram != null, + onPressed: selectedProgram != null + ? () => runNotifier.start(selectedProgram) + : null, + ), + ), + ), + const SizedBox(width: 12), + // 暂停/继续按钮(待机态禁用) + Expanded( + child: SizedBox( + height: 48, + child: CommonButton( + text: l10n?.pause ?? '暂停', + icon: Icons.pause, + type: ButtonType.secondary, + enabled: false, + onPressed: null, + ), + ), + ), + const SizedBox(width: 12), + // 停止按钮(待机态禁用) + Expanded( + child: SizedBox( + height: 48, + child: CommonButton( + text: l10n?.stop ?? '停止', + icon: Icons.stop, + type: ButtonType.danger, + enabled: false, + onPressed: null, + ), + ), + ), + ], + ), + ], + ), + ); + } + + /// 运行状态布局 + Widget _buildRunningState( + BuildContext context, + WidgetRef ref, + AppLocalizations? l10n, + RunState runState, + ) { + final runNotifier = ref.read(runStateProvider.notifier); + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // 当前程序名称 + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: AppTheme.cardSelectedBg, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: AppTheme.accentPrimary, width: 1), + ), + child: Row( + children: [ + Text( + '${l10n?.selectedProgramLabel ?? '当前选中'}:', + style: const TextStyle( + color: AppTheme.textTertiary, + fontSize: 12, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + runState.currentProgram?.name ?? '', + style: const TextStyle( + color: AppTheme.accentPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + + const SizedBox(height: 12), + + // 控制按钮 + Row( + children: [ + // 开始/继续按钮 + Expanded( + flex: 2, + child: SizedBox( + height: 48, + child: CommonButton( + text: runState.status == RunStatus.paused + ? (l10n?.continue_ ?? '继续') + : (l10n?.run ?? '运行'), + icon: runState.status == RunStatus.paused + ? Icons.play_arrow + : Icons.play_arrow, + type: ButtonType.primary, + onPressed: () => runNotifier.resume(), + ), + ), + ), + const SizedBox(width: 12), + // 暂停按钮 + Expanded( + child: SizedBox( + height: 48, + child: CommonButton( + text: l10n?.pause ?? '暂停', + icon: Icons.pause, + type: ButtonType.warning, + onPressed: runState.status == RunStatus.paused + ? null + : () => runNotifier.pause(), + ), + ), + ), + const SizedBox(width: 12), + // 停止按钮 + Expanded( + child: SizedBox( + height: 48, + child: CommonButton( + text: l10n?.stop ?? '停止', + icon: Icons.stop, + type: ButtonType.danger, + onPressed: () => _showStopConfirm(context, runNotifier, l10n), + ), + ), + ), + ], + ), + + const SizedBox(height: 12), + + // 状态指示 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: runState.status == RunStatus.paused + ? AppTheme.accentWarning + : AppTheme.statusRunning, + ), + ), + const SizedBox(width: 8), + Text( + runState.status == RunStatus.paused + ? (l10n?.paused ?? '已暂停') + : (l10n?.running ?? '运行中'), + style: TextStyle( + color: runState.status == RunStatus.paused + ? AppTheme.accentWarning + : AppTheme.statusRunning, + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ), + ], + ), + ], + ), + ); + } + + /// 显示停止确认对话框 + void _showStopConfirm( + BuildContext context, + RunStateNotifier runNotifier, + AppLocalizations? l10n, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: AppTheme.cardBg, + title: Text( + l10n?.confirm ?? '确认', + style: const TextStyle(color: AppTheme.textHeading), + ), + content: Text( + l10n?.stopConfirm ?? '确定要停止当前运行的程序吗?', + style: const TextStyle(color: AppTheme.textPrimary), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + l10n?.cancel ?? '取消', + style: const TextStyle(color: AppTheme.textSecondary), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.accentCritical, + foregroundColor: Colors.white, + ), + onPressed: () { + runNotifier.stop(); + Navigator.of(context).pop(); + }, + child: Text(l10n?.confirm ?? '确认'), + ), + ], + ), + ); + } +} diff --git a/lib/features/home/widgets/status_bar.dart b/lib/features/home/widgets/status_bar.dart new file mode 100644 index 0000000..c8d64bb --- /dev/null +++ b/lib/features/home/widgets/status_bar.dart @@ -0,0 +1,181 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/status_indicator.dart'; + +/// 状态栏组件 - 明亮工业风格 +/// 显示设备名称、实时时钟、系统状态、照明控制、瓷套棒状态 +class StatusBar extends StatefulWidget { + final bool isRunning; + final bool lightOn; + final VoidCallback? onLightToggle; + final bool ceramicSleeveInstalled; + + const StatusBar({ + super.key, + this.isRunning = false, + this.lightOn = false, + this.onLightToggle, + this.ceramicSleeveInstalled = false, + }); + + @override + State createState() => _StatusBarState(); +} + +class _StatusBarState extends State { + String _currentTime = ''; + Timer? _timer; + + @override + void initState() { + super.initState(); + _updateTime(); + _timer = Timer.periodic(const Duration(seconds: 1), (_) => _updateTime()); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + void _updateTime() { + final now = DateTime.now(); + _currentTime = + '${now.year}-${_twoDigits(now.month)}-${_twoDigits(now.day)} ' + '${_twoDigits(now.hour)}:${_twoDigits(now.minute)}:${_twoDigits(now.second)}'; + if (mounted) setState(() {}); + } + + String _twoDigits(int n) => n.toString().padLeft(2, '0'); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + + return Container( + height: 56, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + decoration: BoxDecoration( + color: AppTheme.primaryColor, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.08), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.precision_manufacturing, color: Colors.white, size: 22), + const SizedBox(width: 10), + Text( + l10n?.deviceName ?? '污水毒品前处理一体机', + style: const TextStyle( + color: Colors.white, + fontSize: 17, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + const Spacer(), + _LightToggleButton(isOn: widget.lightOn, onTap: widget.onLightToggle), + const SizedBox(width: 16), + _CeramicSleeveStatus(installed: widget.ceramicSleeveInstalled), + const SizedBox(width: 20), + StatusIndicator( + text: widget.isRunning + ? (l10n?.running ?? '运行中') + : (l10n?.idle ?? '未运行'), + status: widget.isRunning + ? DeviceStatusType.running + : DeviceStatusType.idle, + ), + const SizedBox(width: 20), + Text( + _currentTime, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontFamily: 'monospace', + fontWeight: FontWeight.normal, + ), + ), + ], + ), + ); + } +} + +class _CeramicSleeveStatus extends StatelessWidget { + final bool installed; + const _CeramicSleeveStatus({required this.installed}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: installed ? Colors.greenAccent : Colors.redAccent, + ), + ), + const SizedBox(width: 6), + Text( + installed ? '瓷套棒: 已安装' : '瓷套棒: 未安装', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.9), + fontSize: 12, + ), + ), + ], + ); + } +} + +class _LightToggleButton extends StatelessWidget { + final bool isOn; + final VoidCallback? onTap; + const _LightToggleButton({this.isOn = false, this.onTap}); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: isOn + ? Colors.white.withValues(alpha: 0.25) + : Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Icon( + isOn ? Icons.lightbulb : Icons.lightbulb_outline_rounded, + color: isOn ? Colors.yellowAccent : Colors.white.withValues(alpha: 0.8), + size: 20, + ), + ), + ), + ); + } +} diff --git a/lib/features/program_detail/pages/program_detail_page.dart b/lib/features/program_detail/pages/program_detail_page.dart new file mode 100644 index 0000000..d0ade1c --- /dev/null +++ b/lib/features/program_detail/pages/program_detail_page.dart @@ -0,0 +1,199 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../providers/steps_provider.dart'; +import '../widgets/step_list.dart'; +import '../widgets/step_form.dart'; +import '../../programs/providers/programs_provider.dart'; + +/// 程序详情页面 +/// 左侧步骤列表 + 右侧参数表单 +class ProgramDetailPage extends ConsumerStatefulWidget { + final String programId; + + const ProgramDetailPage({super.key, required this.programId}); + + @override + ConsumerState createState() => _ProgramDetailPageState(); +} + +class _ProgramDetailPageState extends ConsumerState { + late int _programIdInt; + + @override + void initState() { + super.initState(); + _programIdInt = int.tryParse(widget.programId) ?? 0; + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final programsState = ref.watch(programsProvider); + final program = programsState.programs.where((p) => p.id == _programIdInt).firstOrNull; + final stepsState = ref.watch(stepsProvider(_programIdInt)); + + return Scaffold( + body: Container( + color: AppTheme.backgroundColor, + child: Column( + children: [ + // 顶部导航栏 + Container( + height: 60, + padding: const EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // 返回按钮 + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.go('/programs'), + ), + const SizedBox(width: 16), + // 程序名称 + Text( + program?.name ?? (l10n?.detail ?? '程序详情'), + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + // 保存按钮 + CommonButton( + text: l10n?.save ?? '保存', + icon: Icons.save, + type: ButtonType.primary, + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('已保存'), + backgroundColor: AppTheme.successColor, + ), + ); + }, + ), + ], + ), + ), + + // 主内容区域 + Expanded( + child: stepsState.isLoading + ? const Center(child: CircularProgressIndicator()) + : Row( + children: [ + // 左侧:步骤列表 + SizedBox( + width: 400, + child: StepList( + programId: _programIdInt, + steps: stepsState.steps, + selectedStepId: stepsState.selectedStepId, + onStepSelected: (stepId) { + ref.read(stepsProvider(_programIdInt).notifier).selectStep(stepId); + }, + onAddStep: () => _showAddStepDialog(context, ref), + onReorder: (oldIndex, newIndex) { + ref.read(stepsProvider(_programIdInt).notifier).reorderSteps(oldIndex, newIndex); + }, + onDeleteSteps: (stepIds) { + ref.read(stepsProvider(_programIdInt).notifier).deleteSteps(stepIds); + }, + ), + ), + + // 分隔线 + Container( + width: 1, + color: AppTheme.idleColor.withValues(alpha: 0.3), + ), + + // 右侧:步骤参数表单 + Expanded( + child: stepsState.selectedStep != null + ? StepForm( + programId: _programIdInt, + step: stepsState.selectedStep!, + onSave: (step) async { + final success = await ref + .read(stepsProvider(_programIdInt).notifier) + .updateStep(step); + if (success) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('步骤已更新'), + backgroundColor: AppTheme.successColor, + ), + ); + } + }, + ) + : Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.edit_note, + size: 64, + color: AppTheme.idleColor, + ), + const SizedBox(height: 16), + Text( + '请选择或添加步骤', + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + /// 显示添加步骤对话框 + void _showAddStepDialog(BuildContext context, WidgetRef ref) { + showDialog( + context: context, + builder: (context) => Dialog( + child: Container( + width: 600, + padding: const EdgeInsets.all(24), + child: StepForm( + programId: _programIdInt, + isNew: true, + onSave: (step) async { + final success = await ref + .read(stepsProvider(_programIdInt).notifier) + .addStep(step); + if (success) { + Navigator.of(context).pop(); + } + }, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/program_detail/providers/steps_provider.dart b/lib/features/program_detail/providers/steps_provider.dart new file mode 100644 index 0000000..85d5358 --- /dev/null +++ b/lib/features/program_detail/providers/steps_provider.dart @@ -0,0 +1,161 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../programs/models/step.dart'; +import '../../programs/services/program_service.dart'; + +/// 步骤状态 +class StepsState { + final List steps; + final int? selectedStepId; + final bool isLoading; + final String? error; + + const StepsState({ + this.steps = const [], + this.selectedStepId, + this.isLoading = false, + this.error, + }); + + StepsState copyWith({ + List? steps, + int? selectedStepId, + bool? isLoading, + String? error, + bool clearSelection = false, + bool clearError = false, + }) { + return StepsState( + steps: steps ?? this.steps, + selectedStepId: clearSelection ? null : (selectedStepId ?? this.selectedStepId), + isLoading: isLoading ?? this.isLoading, + error: clearError ? null : (error ?? this.error), + ); + } + + /// 获取选中的步骤 + Step? get selectedStep { + if (selectedStepId == null) return null; + return steps.where((s) => s.id == selectedStepId).firstOrNull; + } +} + +/// 步骤 Notifier +class StepsNotifier extends StateNotifier { + final ProgramService _service; + final int programId; + + StepsNotifier(this._service, this.programId) : super(const StepsState()) { + loadSteps(); + } + + /// 加载步骤 + Future loadSteps() async { + state = state.copyWith(isLoading: true, clearError: true); + try { + final steps = await _service.getStepsByProgramId(programId); + state = state.copyWith(steps: steps, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + /// 选择步骤 + void selectStep(int? stepId) { + state = state.copyWith(selectedStepId: stepId); + } + + /// 清除选择 + void clearSelection() { + state = state.copyWith(clearSelection: true); + } + + /// 添加步骤 + Future addStep(Step step) async { + try { + await _service.addStep(step); + await loadSteps(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 更新步骤 + Future updateStep(Step step) async { + if (step.id == null) return false; + try { + await _service.updateStep(step); + await loadSteps(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 删除步骤 + Future deleteStep(int stepId) async { + try { + await _service.deleteStep(stepId); + if (state.selectedStepId == stepId) { + state = state.copyWith(clearSelection: true); + } + await loadSteps(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 批量删除步骤 + Future deleteSteps(List stepIds) async { + try { + await _service.deleteSteps(stepIds); + if (stepIds.contains(state.selectedStepId)) { + state = state.copyWith(clearSelection: true); + } + await loadSteps(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 重新排序步骤 + Future reorderSteps(int oldIndex, int newIndex) async { + final steps = List.from(state.steps); + final step = steps.removeAt(oldIndex); + steps.insert(newIndex, step); + + // 更新 step_no + for (int i = 0; i < steps.length; i++) { + steps[i] = steps[i].copyWith(stepNo: i + 1); + } + + state = state.copyWith(steps: steps); + + // 持久化排序 + await _service.reorderSteps(programId, steps.map((s) => s.id!).toList()); + } +} + +/// 程序服务 Provider +final programServiceProvider = Provider((ref) { + return ProgramService.instance; +}); + +/// 步骤 Provider(按程序ID) +final stepsProvider = StateNotifierProvider.family( + (ref, programId) { + final service = ref.watch(programServiceProvider); + return StepsNotifier(service, programId); + }, +); + +/// 选中的步骤 Provider +final selectedStepProvider = Provider.family((ref, programId) { + return ref.watch(stepsProvider(programId)).selectedStep; +}); \ No newline at end of file diff --git a/lib/features/program_detail/widgets/step_form.dart b/lib/features/program_detail/widgets/step_form.dart new file mode 100644 index 0000000..a66388f --- /dev/null +++ b/lib/features/program_detail/widgets/step_form.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/utils/constants.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../../programs/models/step.dart' as models; + +/// 步骤参数表单 +class StepForm extends StatefulWidget { + final int programId; + final models.Step? step; + final bool isNew; + final void Function(models.Step) onSave; + + const StepForm({ + super.key, + required this.programId, + this.step, + this.isNew = false, + required this.onSave, + }); + + @override + State createState() => _StepFormState(); +} + +class _StepFormState extends State { + final _formKey = GlobalKey(); + + late TextEditingController _nameController; + late TextEditingController _mixTimeController; + late TextEditingController _magnetTimeController; + late TextEditingController _volumeController; + late TextEditingController _blowTimeController; + + String _position = 'A1'; + String _mixSpeed = '中速'; + String _blowSpeed = '中速'; + int _needleSpeed = 5; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: widget.step?.name ?? ''); + _mixTimeController = TextEditingController(text: '${widget.step?.mixTime ?? 0}'); + _magnetTimeController = TextEditingController(text: '${widget.step?.magnetTime ?? 0}'); + _volumeController = TextEditingController(text: '${widget.step?.volume ?? 0}'); + _blowTimeController = TextEditingController(text: '${widget.step?.blowTime ?? 0}'); + + _position = widget.step?.position ?? 'A1'; + _mixSpeed = widget.step?.mixSpeed ?? '中速'; + _blowSpeed = widget.step?.blowSpeed ?? '中速'; + _needleSpeed = widget.step?.needleSpeed ?? 5; + } + + @override + void dispose() { + _nameController.dispose(); + _mixTimeController.dispose(); + _magnetTimeController.dispose(); + _volumeController.dispose(); + _blowTimeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + + return Padding( + padding: const EdgeInsets.all(24), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + Text( + widget.isNew ? '添加步骤' : '编辑步骤', + style: TextStyle( + color: AppTheme.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 24), + + // 步骤名称 + TextFormField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n?.stepName ?? '步骤名称', + hintText: '例如: 混合、吸磁、吹气', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return '请输入步骤名称'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // 孔位选择 + Row( + children: [ + Text(l10n?.position ?? '孔位', style: TextStyle(color: AppTheme.textPrimary)), + const SizedBox(width: 16), + Expanded( + child: DropdownButtonFormField( + value: _position, + decoration: InputDecoration( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + items: Constants.positions.map((p) => DropdownMenuItem(value: p, child: Text(p))).toList(), + onChanged: (value) { + if (value != null) setState(() => _position = value); + }, + ), + ), + ], + ), + const SizedBox(height: 16), + + // 时间参数行 + Row( + children: [ + Expanded( + child: TextFormField( + controller: _mixTimeController, + decoration: InputDecoration( + labelText: '${l10n?.mixTime ?? '混合时间'} (${Constants.timeUnitSeconds})', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextFormField( + controller: _magnetTimeController, + decoration: InputDecoration( + labelText: '${l10n?.magnetTime ?? '吸磁时间'} (${Constants.timeUnitSeconds})', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + keyboardType: TextInputType.number, + ), + ), + ], + ), + const SizedBox(height: 16), + + // 容积和吹气时间 + Row( + children: [ + Expanded( + child: TextFormField( + controller: _volumeController, + decoration: InputDecoration( + labelText: '${l10n?.volume ?? '容积'} (${Constants.volumeUnit})', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextFormField( + controller: _blowTimeController, + decoration: InputDecoration( + labelText: '${l10n?.blowTime ?? '吹气时间'} (${Constants.timeUnitMinutes})', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + keyboardType: TextInputType.number, + ), + ), + ], + ), + const SizedBox(height: 16), + + // 速度选择 + Row( + children: [ + Expanded( + child: DropdownButtonFormField( + value: _mixSpeed, + decoration: InputDecoration( + labelText: l10n?.mixSpeed ?? '混合速度', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + items: Constants.speedOptions.map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(), + onChanged: (value) { + if (value != null) setState(() => _mixSpeed = value); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: DropdownButtonFormField( + value: _blowSpeed, + decoration: InputDecoration( + labelText: l10n?.blowSpeed ?? '吹气速度', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + items: Constants.speedOptions.map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(), + onChanged: (value) { + if (value != null) setState(() => _blowSpeed = value); + }, + ), + ), + ], + ), + const SizedBox(height: 16), + + // 下针速度滑块 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${l10n?.needleSpeed ?? '下针速度'}: $_needleSpeed 档', style: TextStyle(color: AppTheme.textPrimary)), + Slider( + value: _needleSpeed.toDouble(), + min: 1, + max: 10, + divisions: 9, + activeColor: AppTheme.primaryColor, + onChanged: (value) { + setState(() => _needleSpeed = value.round()); + }, + ), + ], + ), + + const SizedBox(height: 24), + + // 保存按钮 + CommonButton( + text: l10n?.save ?? '保存', + icon: Icons.save, + type: ButtonType.primary, + onPressed: _saveStep, + ), + ], + ), + ), + ); + } + + /// 保存步骤 + void _saveStep() { + if (!_formKey.currentState!.validate()) return; + + final step = models.Step( + id: widget.step?.id, + programId: widget.programId, + stepNo: widget.step?.stepNo ?? 1, + position: _position, + name: _nameController.text.trim(), + mixTime: int.tryParse(_mixTimeController.text) ?? 0, + magnetTime: int.tryParse(_magnetTimeController.text) ?? 0, + volume: int.tryParse(_volumeController.text) ?? 0, + mixSpeed: _mixSpeed, + blowSpeed: _blowSpeed, + blowTime: int.tryParse(_blowTimeController.text) ?? 0, + needleSpeed: _needleSpeed, + ); + + widget.onSave(step); + } +} \ No newline at end of file diff --git a/lib/features/program_detail/widgets/step_list.dart b/lib/features/program_detail/widgets/step_list.dart new file mode 100644 index 0000000..3286dbc --- /dev/null +++ b/lib/features/program_detail/widgets/step_list.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../../programs/models/step.dart' as models; + +/// 步骤列表组件 +class StepList extends StatefulWidget { + final int programId; + final List steps; + final int? selectedStepId; + final void Function(int?) onStepSelected; + final void Function() onAddStep; + final void Function(int oldIndex, int newIndex)? onReorder; + final void Function(List stepIds)? onDeleteSteps; + + const StepList({ + super.key, + required this.programId, + required this.steps, + this.selectedStepId, + required this.onStepSelected, + required this.onAddStep, + this.onReorder, + this.onDeleteSteps, + }); + + @override + State createState() => _StepListState(); +} + +class _StepListState extends State { + final Set _selectedIds = {}; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final allSelected = _selectedIds.length == widget.steps.length && widget.steps.isNotEmpty; + + return Container( + color: Colors.white, + child: Column( + children: [ + // 标题 + Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + ), + child: Row( + children: [ + Icon(Icons.list, color: AppTheme.primaryColor, size: 20), + const SizedBox(width: 12), + Text( + '步骤列表', + style: TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + Text( + '${widget.steps.length} 步', + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 12, + ), + ), + ], + ), + ), + + // 表头 + Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: AppTheme.idleColor.withValues(alpha: 0.2)), + ), + ), + child: Row( + children: [ + SizedBox( + width: 40, + child: Checkbox( + value: allSelected, + onChanged: (value) { + setState(() { + if (value == true) { + _selectedIds.clear(); + _selectedIds.addAll(widget.steps.map((s) => s.id!)); + } else { + _selectedIds.clear(); + } + }); + }, + ), + ), + SizedBox(width: 40, child: Text('#', style: TextStyle(fontSize: 12))), + Expanded(child: Text(l10n?.stepName ?? '名称', style: TextStyle(fontSize: 12))), + SizedBox(width: 60, child: Text(l10n?.position ?? '孔位', style: TextStyle(fontSize: 12))), + ], + ), + ), + + // 步骤列表(可拖拽排序) + Expanded( + child: widget.steps.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add_circle_outline, size: 48, color: AppTheme.idleColor), + const SizedBox(height: 12), + Text('暂无步骤', style: TextStyle(color: AppTheme.textSecondary)), + ], + ), + ) + : ReorderableListView.builder( + padding: const EdgeInsets.all(8), + itemCount: widget.steps.length, + onReorder: (oldIndex, newIndex) { + if (widget.onReorder != null) { + // 调整 newIndex(ReorderableListView 的特殊行为) + if (newIndex > oldIndex) newIndex -= 1; + widget.onReorder!(oldIndex, newIndex); + } + }, + itemBuilder: (context, index) { + final step = widget.steps[index]; + final isSelected = widget.selectedStepId == step.id || _selectedIds.contains(step.id); + return _buildStepItem(step, isSelected, index); + }, + ), + ), + + // 底部操作栏 + Container( + height: 60, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: AppTheme.idleColor.withValues(alpha: 0.2)), + ), + ), + child: Row( + children: [ + // 添加按钮 + CommonButton( + text: '添加', + icon: Icons.add, + type: ButtonType.primary, + onPressed: widget.onAddStep, + ), + const SizedBox(width: 12), + // 删除按钮 + if (_selectedIds.isNotEmpty) + CommonButton( + text: '删除', + icon: Icons.delete, + type: ButtonType.danger, + onPressed: () => _showDeleteConfirmDialog(context), + ), + ], + ), + ), + ], + ), + ); + } + + /// 步骤项 + Widget _buildStepItem(models.Step step, bool isSelected, int index) { + return Container( + key: ValueKey(step.id), + margin: const EdgeInsets.symmetric(vertical: 2), + decoration: BoxDecoration( + color: isSelected ? AppTheme.primaryLight.withValues(alpha: 0.3) : Colors.white, + borderRadius: BorderRadius.circular(4), + border: isSelected ? Border.all(color: AppTheme.primaryColor, width: 2) : null, + ), + child: ListTile( + dense: true, + leading: Checkbox( + value: _selectedIds.contains(step.id), + onChanged: (value) { + setState(() { + if (value == true) { + _selectedIds.add(step.id!); + } else { + _selectedIds.remove(step.id!); + } + }); + }, + ), + title: Row( + children: [ + Container( + width: 30, + alignment: Alignment.center, + child: Text( + '${step.stepNo}', + style: TextStyle( + color: AppTheme.primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded(child: Text(step.name)), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + step.position, + style: TextStyle( + color: AppTheme.primaryColor, + fontSize: 12, + ), + ), + ), + ], + ), + trailing: Icon(Icons.drag_handle, color: AppTheme.idleColor), + onTap: () => widget.onStepSelected(step.id), + ), + ); + } + + /// 显示删除确认对话框 + void _showDeleteConfirmDialog(BuildContext context) { + final l10n = AppLocalizations.of(context); + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(l10n?.confirm ?? '确认'), + content: Text( + _selectedIds.length == 1 + ? '确定要删除此步骤吗?' + : '确定要删除选中的 ${_selectedIds.length} 个步骤吗?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: Text(l10n?.cancel ?? '取消'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.errorColor, + foregroundColor: Colors.white, + ), + onPressed: () { + Navigator.of(ctx).pop(); + if (widget.onDeleteSteps != null) { + widget.onDeleteSteps!(_selectedIds.toList()); + } + setState(() { + _selectedIds.clear(); + }); + }, + child: Text(l10n?.confirm ?? '确认'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/programs/models/program.dart b/lib/features/programs/models/program.dart new file mode 100644 index 0000000..aaa967f --- /dev/null +++ b/lib/features/programs/models/program.dart @@ -0,0 +1,52 @@ +/// 程序模型 +class Program { + final int? id; + final String code; + final String name; + final String createdAt; + final int status; // 1: 启用, 0: 停用 + + Program({ + this.id, + required this.code, + required this.name, + required this.createdAt, + this.status = 1, + }); + + Map toMap() { + return { + 'id': id, + 'code': code, + 'name': name, + 'created_at': createdAt, + 'status': status, + }; + } + + factory Program.fromMap(Map map) { + return Program( + id: map['id'] as int?, + code: map['code'] as String, + name: map['name'] as String, + createdAt: map['created_at'] as String, + status: map['status'] as int? ?? 1, + ); + } + + Program copyWith({ + int? id, + String? code, + String? name, + String? createdAt, + int? status, + }) { + return Program( + id: id ?? this.id, + code: code ?? this.code, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + status: status ?? this.status, + ); + } +} \ No newline at end of file diff --git a/lib/features/programs/models/step.dart b/lib/features/programs/models/step.dart new file mode 100644 index 0000000..ce8635b --- /dev/null +++ b/lib/features/programs/models/step.dart @@ -0,0 +1,94 @@ +/// 步骤模型 +class Step { + final int? id; + final int programId; + final int stepNo; + final String position; + final String name; + final int mixTime; + final int magnetTime; + final int volume; + final String mixSpeed; + final String blowSpeed; + final int blowTime; + final int needleSpeed; + + Step({ + this.id, + required this.programId, + required this.stepNo, + required this.position, + required this.name, + this.mixTime = 0, + this.magnetTime = 0, + this.volume = 0, + this.mixSpeed = '中速', + this.blowSpeed = '中速', + this.blowTime = 0, + this.needleSpeed = 5, + }); + + Map toMap() { + return { + 'id': id, + 'program_id': programId, + 'step_no': stepNo, + 'position': position, + 'name': name, + 'mix_time': mixTime, + 'magnet_time': magnetTime, + 'volume': volume, + 'mix_speed': mixSpeed, + 'blow_speed': blowSpeed, + 'blow_time': blowTime, + 'needle_speed': needleSpeed, + }; + } + + factory Step.fromMap(Map map) { + return Step( + id: map['id'] as int?, + programId: map['program_id'] as int, + stepNo: map['step_no'] as int, + position: map['position'] as String, + name: map['name'] as String, + mixTime: map['mix_time'] as int? ?? 0, + magnetTime: map['magnet_time'] as int? ?? 0, + volume: map['volume'] as int? ?? 0, + mixSpeed: map['mix_speed'] as String? ?? '中速', + blowSpeed: map['blow_speed'] as String? ?? '中速', + blowTime: map['blow_time'] as int? ?? 0, + needleSpeed: map['needle_speed'] as int? ?? 5, + ); + } + + Step copyWith({ + int? id, + int? programId, + int? stepNo, + String? position, + String? name, + int? mixTime, + int? magnetTime, + int? volume, + String? mixSpeed, + String? blowSpeed, + int? blowTime, + int? needleSpeed, + }) { + return Step( + id: id ?? this.id, + programId: programId ?? this.programId, + stepNo: stepNo ?? this.stepNo, + position: position ?? this.position, + name: name ?? this.name, + mixTime: mixTime ?? this.mixTime, + magnetTime: magnetTime ?? this.magnetTime, + volume: volume ?? this.volume, + mixSpeed: mixSpeed ?? this.mixSpeed, + blowSpeed: blowSpeed ?? this.blowSpeed, + blowTime: blowTime ?? this.blowTime, + needleSpeed: needleSpeed ?? this.needleSpeed, + ); + } +} \ No newline at end of file diff --git a/lib/features/programs/pages/programs_page.dart b/lib/features/programs/pages/programs_page.dart new file mode 100644 index 0000000..75934c1 --- /dev/null +++ b/lib/features/programs/pages/programs_page.dart @@ -0,0 +1,509 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:file_picker/file_picker.dart'; +import 'dart:io'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../models/program.dart'; +import '../providers/programs_provider.dart'; +import '../widgets/program_form_dialog.dart'; +import '../services/program_import_service.dart'; + +/// 程序管理页面 +class ProgramsPage extends ConsumerStatefulWidget { + const ProgramsPage({super.key}); + + @override + ConsumerState createState() => _ProgramsPageState(); +} + +class _ProgramsPageState extends ConsumerState { + final Set _selectedIds = {}; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final programsState = ref.watch(programsProvider); + + return Scaffold( + body: Container( + color: AppTheme.backgroundColor, + child: Column( + children: [ + // 顶部导航栏 + Container( + height: 60, + padding: const EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // 返回按钮 + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.go('/'), + ), + const SizedBox(width: 16), + Text( + l10n?.programs ?? '程序管理', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + // 新增按钮 + CommonButton( + text: l10n?.addProgram ?? '新增', + icon: Icons.add, + type: ButtonType.primary, + onPressed: () => _showAddDialog(context, ref), + ), + const SizedBox(width: 12), + // 导入按钮 + CommonButton( + text: l10n?.importProgram ?? '导入', + icon: Icons.file_upload, + type: ButtonType.secondary, + onPressed: () => _importPrograms(context, ref), + ), + ], + ), + ), + + // 程序列表表格 + Expanded( + child: programsState.isLoading + ? const Center(child: CircularProgressIndicator()) + : programsState.programs.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.folder_open, + size: 64, + color: AppTheme.idleColor, + ), + const SizedBox(height: 16), + Text( + l10n?.noData ?? '暂无数据', + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 16, + ), + ), + ], + ), + ) + : Container( + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 8, + offset: const Offset(2, 2), + ), + ], + ), + child: Column( + children: [ + // 表头 + _buildTableHeader(l10n, programsState.programs), + // 表格内容 + Expanded( + child: ListView.builder( + itemCount: programsState.programs.length, + itemBuilder: (context, index) { + final program = programsState.programs[index]; + final isSelected = _selectedIds.contains(program.id); + return _buildTableRow( + context, + ref, + l10n, + program, + isSelected, + index == programsState.programs.length - 1, + ); + }, + ), + ), + ], + ), + ), + ), + + // 底部操作栏 + if (programsState.programs.isNotEmpty) + Container( + height: 60, + padding: const EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, -2), + ), + ], + ), + child: Row( + children: [ + Text( + '${l10n?.selected ?? '已选择'}: ${_selectedIds.length}', + style: TextStyle(color: AppTheme.textSecondary), + ), + const Spacer(), + if (_selectedIds.isNotEmpty) + CommonButton( + text: l10n?.deleteProgram ?? '删除', + icon: Icons.delete, + type: ButtonType.danger, + onPressed: () => _showDeleteConfirmDialog( + context, + ref, + l10n, + _selectedIds.toList(), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + /// 表头 + Widget _buildTableHeader(AppLocalizations? l10n, List programs) { + final allSelected = _selectedIds.length == programs.length && programs.isNotEmpty; + + return Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), + ), + child: Row( + children: [ + // 复选框 + SizedBox( + width: 50, + child: Checkbox( + value: allSelected, + onChanged: (value) { + setState(() { + if (value == true) { + _selectedIds.clear(); + _selectedIds.addAll(programs.map((p) => p.id!)); + } else { + _selectedIds.clear(); + } + }); + }, + ), + ), + // 编号 + SizedBox( + width: 100, + child: Text( + l10n?.programCode ?? '编号', + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppTheme.textPrimary, + ), + ), + ), + // 名称 + Expanded( + flex: 2, + child: Text( + l10n?.programName ?? '名称', + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppTheme.textPrimary, + ), + ), + ), + // 创建时间 + Expanded( + child: Text( + l10n?.createTime ?? '创建时间', + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppTheme.textPrimary, + ), + ), + ), + // 状态 + SizedBox( + width: 80, + child: Text( + '状态', + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppTheme.textPrimary, + ), + ), + ), + // 操作 + SizedBox( + width: 150, + child: Text( + l10n?.detail ?? '操作', + style: TextStyle( + fontWeight: FontWeight.w600, + color: AppTheme.textPrimary, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + } + + /// 表格行 + Widget _buildTableRow( + BuildContext context, + WidgetRef ref, + AppLocalizations? l10n, + Program program, + bool isSelected, + bool isLast, + ) { + return Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: isSelected ? AppTheme.primaryLight.withValues(alpha: 0.2) : null, + border: isLast + ? null + : Border( + bottom: BorderSide( + color: AppTheme.idleColor.withValues(alpha: 0.2), + ), + ), + ), + child: Row( + children: [ + // 复选框 + SizedBox( + width: 50, + child: Checkbox( + value: isSelected, + onChanged: (value) { + setState(() { + if (value == true) { + _selectedIds.add(program.id!); + } else { + _selectedIds.remove(program.id!); + } + }); + }, + ), + ), + // 编号 + SizedBox( + width: 100, + child: Text( + program.code, + style: TextStyle(color: AppTheme.textPrimary), + ), + ), + // 名称 + Expanded( + flex: 2, + child: Text( + program.name, + style: TextStyle(color: AppTheme.textPrimary), + ), + ), + // 创建时间 + Expanded( + child: Text( + program.createdAt, + style: TextStyle(color: AppTheme.textSecondary), + ), + ), + // 状态 + SizedBox( + width: 80, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: program.status == 1 + ? AppTheme.successColor.withValues(alpha: 0.1) + : AppTheme.idleColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + program.status == 1 ? '启用' : '停用', + style: TextStyle( + color: program.status == 1 + ? AppTheme.successColor + : AppTheme.idleColor, + fontSize: 12, + ), + textAlign: TextAlign.center, + ), + ), + ), + // 操作按钮 + SizedBox( + width: 150, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon(Icons.edit, size: 20), + color: AppTheme.primaryColor, + onPressed: () => _showEditDialog(context, ref, program), + ), + IconButton( + icon: const Icon(Icons.delete, size: 20), + color: AppTheme.errorColor, + onPressed: () => _showDeleteConfirmDialog( + context, + ref, + l10n, + [program.id!], + ), + ), + IconButton( + icon: const Icon(Icons.visibility, size: 20), + color: AppTheme.textSecondary, + onPressed: () => context.go('/programs/${program.id}'), + ), + ], + ), + ), + ], + ), + ); + } + + /// 显示新增对话框 + void _showAddDialog(BuildContext context, WidgetRef ref) { + showDialog( + context: context, + builder: (context) => const ProgramFormDialog(), + ); + } + + /// 导入程序 + Future _importPrograms(BuildContext context, WidgetRef ref) async { + try { + // 选择文件 + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + allowMultiple: false, + ); + + if (result == null || result.files.isEmpty) { + return; + } + + final file = result.files.first; + if (file.path == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('无法读取文件'), + backgroundColor: AppTheme.errorColor, + ), + ); + return; + } + + // 读取文件内容 + final jsonContent = await File(file.path!).readAsString(); + + // 导入程序 + final importedCount = await ProgramImportService.instance.importFromJson(jsonContent); + + // 刷新程序列表 + ref.read(programsProvider.notifier).loadPrograms(); + + // 显示结果 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('成功导入 $importedCount 个程序'), + backgroundColor: importedCount > 0 ? AppTheme.successColor : AppTheme.warningColor, + ), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('导入失败: ${e.toString()}'), + backgroundColor: AppTheme.errorColor, + ), + ); + } + } + + /// 显示编辑对话框 + void _showEditDialog(BuildContext context, WidgetRef ref, Program program) { + showDialog( + context: context, + builder: (context) => ProgramFormDialog(program: program), + ); + } + + /// 显示删除确认对话框 + void _showDeleteConfirmDialog( + BuildContext context, + WidgetRef ref, + AppLocalizations? l10n, + List ids, + ) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n?.confirm ?? '确认'), + content: Text( + ids.length == 1 + ? '确定要删除此程序吗?' + : '确定要删除选中的 ${ids.length} 个程序吗?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(l10n?.cancel ?? '取消'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.errorColor, + foregroundColor: Colors.white, + ), + onPressed: () async { + final notifier = ref.read(programsProvider.notifier); + await notifier.deletePrograms(ids); + setState(() { + _selectedIds.removeAll(ids); + }); + Navigator.of(context).pop(); + }, + child: Text(l10n?.confirm ?? '确认'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/programs/providers/programs_provider.dart b/lib/features/programs/providers/programs_provider.dart new file mode 100644 index 0000000..290b411 --- /dev/null +++ b/lib/features/programs/providers/programs_provider.dart @@ -0,0 +1,192 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/database/database_service.dart'; +import '../models/program.dart'; + +/// 程序列表状态 +class ProgramsState { + final List programs; + final int? selectedProgramId; + final bool isLoading; + final String? error; + + const ProgramsState({ + this.programs = const [], + this.selectedProgramId, + this.isLoading = false, + this.error, + }); + + ProgramsState copyWith({ + List? programs, + int? selectedProgramId, + bool? isLoading, + String? error, + bool clearSelection = false, + bool clearError = false, + }) { + return ProgramsState( + programs: programs ?? this.programs, + selectedProgramId: clearSelection ? null : (selectedProgramId ?? this.selectedProgramId), + isLoading: isLoading ?? this.isLoading, + error: clearError ? null : (error ?? this.error), + ); + } + + /// 获取选中的程序 + Program? get selectedProgram { + if (selectedProgramId == null) return null; + return programs.where((p) => p.id == selectedProgramId).firstOrNull; + } +} + +/// 程序列表 Notifier +class ProgramsNotifier extends StateNotifier { + final DatabaseService _db; + + ProgramsNotifier(this._db) : super(const ProgramsState()) { + loadPrograms(); + } + + /// 加载所有程序 + Future loadPrograms() async { + state = state.copyWith(isLoading: true, clearError: true); + + try { + final db = await _db.database; + final maps = await db.query('programs', orderBy: 'created_at DESC'); + final programs = maps.map((m) => Program.fromMap(m)).toList(); + + state = state.copyWith(programs: programs, isLoading: false); + } catch (e) { + state = state.copyWith(isLoading: false, error: e.toString()); + } + } + + /// 选择程序 + void selectProgram(int? programId) { + state = state.copyWith(selectedProgramId: programId); + } + + /// 清除选择 + void clearSelection() { + state = state.copyWith(clearSelection: true); + } + + /// 新增程序 + Future addProgram(Program program) async { + try { + final db = await _db.database; + await db.insert('programs', program.toMap()); + await loadPrograms(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 更新程序 + Future updateProgram(Program program) async { + if (program.id == null) return false; + + try { + final db = await _db.database; + await db.update( + 'programs', + program.toMap(), + where: 'id = ?', + whereArgs: [program.id], + ); + await loadPrograms(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 删除程序 + Future deleteProgram(int programId) async { + try { + final db = await _db.database; + await db.delete('programs', where: 'id = ?', whereArgs: [programId]); + + // 如果删除的是选中的程序,清除选择 + if (state.selectedProgramId == programId) { + state = state.copyWith(clearSelection: true); + } + + await loadPrograms(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 批量删除程序 + Future deletePrograms(List programIds) async { + try { + final db = await _db.database; + await db.delete( + 'programs', + where: 'id IN (${programIds.map((_) => '?').join(',')})', + whereArgs: programIds, + ); + + // 如果删除的是选中的程序,清除选择 + if (programIds.contains(state.selectedProgramId)) { + state = state.copyWith(clearSelection: true); + } + + await loadPrograms(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } + + /// 切换程序状态 + Future toggleStatus(int programId) async { + try { + final db = await _db.database; + final program = state.programs.where((p) => p.id == programId).firstOrNull; + if (program == null) return false; + + await db.update( + 'programs', + {'status': program.status == 1 ? 0 : 1}, + where: 'id = ?', + whereArgs: [programId], + ); + await loadPrograms(); + return true; + } catch (e) { + state = state.copyWith(error: e.toString()); + return false; + } + } +} + +/// 数据库服务 Provider +final databaseServiceProvider = Provider((ref) { + return DatabaseService.instance; +}); + +/// 程序列表 Provider +final programsProvider = + StateNotifierProvider((ref) { + final db = ref.watch(databaseServiceProvider); + return ProgramsNotifier(db); +}); + +/// 选中的程序 Provider +final selectedProgramProvider = Provider((ref) { + return ref.watch(programsProvider).selectedProgram; +}); + +/// 启用的程序列表 Provider +final enabledProgramsProvider = Provider>((ref) { + return ref.watch(programsProvider).programs.where((p) => p.status == 1).toList(); +}); \ No newline at end of file diff --git a/lib/features/programs/services/program_import_service.dart b/lib/features/programs/services/program_import_service.dart new file mode 100644 index 0000000..0dceecf --- /dev/null +++ b/lib/features/programs/services/program_import_service.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; +import '../../programs/models/program.dart'; +import '../../programs/models/step.dart'; +import '../../programs/services/program_service.dart'; + +/// 程序导入服务 +class ProgramImportService { + static final ProgramImportService instance = ProgramImportService._internal(); + final ProgramService _programService = ProgramService.instance; + + ProgramImportService._internal(); + + /// 从 JSON 字符串导入程序 + /// 返回导入的程序数量 + Future importFromJson(String jsonContent) async { + final data = jsonDecode(jsonContent); + + // 支持单个程序或程序数组 + final List programsData; + if (data is List) { + programsData = data; + } else if (data is Map && data.containsKey('programs')) { + programsData = data['programs'] as List; + } else { + programsData = [data]; + } + + int importedCount = 0; + + for (final programData in programsData) { + try { + // 验证必填字段 + if (!_validateProgramData(programData)) { + continue; + } + + // 检查编号是否已存在 + final existingPrograms = await _programService.getAllPrograms(); + final code = programData['code'] as String; + if (existingPrograms.any((p) => p.code == code)) { + // 编号已存在,跳过或使用新编号 + continue; + } + + // 创建程序 + final program = Program( + code: code, + name: programData['name'] as String, + createdAt: programData['createdAt'] ?? DateTime.now().toString().split('.')[0], + status: programData['status'] ?? 1, + ); + + final programId = await _programService.addProgram(program); + + // 导入步骤 + final stepsData = programData['steps'] as List?; + if (stepsData != null) { + for (int i = 0; i < stepsData.length; i++) { + final stepData = stepsData[i]; + final step = Step( + programId: programId, + stepNo: i + 1, + position: stepData['position'] as String? ?? 'A1', + name: stepData['name'] as String? ?? '步骤${i + 1}', + mixTime: stepData['mixTime'] as int? ?? 0, + magnetTime: stepData['magnetTime'] as int? ?? 0, + volume: stepData['volume'] as int? ?? 0, + mixSpeed: stepData['mixSpeed'] as String? ?? '中速', + blowSpeed: stepData['blowSpeed'] as String? ?? '中速', + blowTime: stepData['blowTime'] as int? ?? 0, + needleSpeed: stepData['needleSpeed'] as int? ?? 5, + ); + await _programService.addStep(step); + } + } + + importedCount++; + } catch (e) { + // 忽略单个程序导入错误 + continue; + } + } + + return importedCount; + } + + /// 验证程序数据 + bool _validateProgramData(Map data) { + return data.containsKey('code') && + data.containsKey('name') && + data['code'] is String && + data['name'] is String; + } + + /// 导出程序为 JSON + Future exportToJson(List programIds) async { + final programs = []; + + for (final id in programIds) { + final program = await _programService.getProgramById(id); + if (program == null) continue; + + final steps = await _programService.getStepsByProgramId(id); + + programs.add({ + 'code': program.code, + 'name': program.name, + 'createdAt': program.createdAt, + 'status': program.status, + 'steps': steps.map((s) => { + 'position': s.position, + 'name': s.name, + 'mixTime': s.mixTime, + 'magnetTime': s.magnetTime, + 'volume': s.volume, + 'mixSpeed': s.mixSpeed, + 'blowSpeed': s.blowSpeed, + 'blowTime': s.blowTime, + 'needleSpeed': s.needleSpeed, + }).toList(), + }); + } + + return jsonEncode({'programs': programs}); + } +} \ No newline at end of file diff --git a/lib/features/programs/services/program_service.dart b/lib/features/programs/services/program_service.dart new file mode 100644 index 0000000..c4d0624 --- /dev/null +++ b/lib/features/programs/services/program_service.dart @@ -0,0 +1,156 @@ +import '../../../core/database/database_service.dart'; +import '../models/program.dart'; +import '../models/step.dart'; + +/// 程序服务 +/// 封装程序和步骤的数据库操作 +class ProgramService { + static final ProgramService instance = ProgramService._internal(); + final DatabaseService _db = DatabaseService.instance; + + ProgramService._internal(); + + /// 获取所有程序 + Future> getAllPrograms() async { + final database = await _db.database; + final maps = await database.query('programs', orderBy: 'created_at DESC'); + return maps.map((m) => Program.fromMap(m)).toList(); + } + + /// 根据ID获取程序 + Future getProgramById(int id) async { + final database = await _db.database; + final maps = await database.query( + 'programs', + where: 'id = ?', + whereArgs: [id], + ); + if (maps.isEmpty) return null; + return Program.fromMap(maps.first); + } + + /// 新增程序 + Future addProgram(Program program) async { + final database = await _db.database; + return await database.insert('programs', program.toMap()); + } + + /// 更新程序 + Future updateProgram(Program program) async { + if (program.id == null) return false; + final database = await _db.database; + final count = await database.update( + 'programs', + program.toMap(), + where: 'id = ?', + whereArgs: [program.id], + ); + return count > 0; + } + + /// 删除程序(含步骤) + Future deleteProgram(int id) async { + final database = await _db.database; + // 先删除关联的步骤 + await database.delete('steps', where: 'program_id = ?', whereArgs: [id]); + // 再删除程序 + final count = await database.delete('programs', where: 'id = ?', whereArgs: [id]); + return count > 0; + } + + /// 批量删除程序 + Future deletePrograms(List ids) async { + if (ids.isEmpty) return true; + final database = await _db.database; + // 先删除关联的步骤 + await database.delete( + 'steps', + where: 'program_id IN (${ids.map((_) => '?').join(',')})', + whereArgs: ids, + ); + // 再删除程序 + final count = await database.delete( + 'programs', + where: 'id IN (${ids.map((_) => '?').join(',')})', + whereArgs: ids, + ); + return count > 0; + } + + /// 切换程序状态 + Future toggleProgramStatus(int id) async { + final database = await _db.database; + final program = await getProgramById(id); + if (program == null) return false; + final count = await database.update( + 'programs', + {'status': program.status == 1 ? 0 : 1}, + where: 'id = ?', + whereArgs: [id], + ); + return count > 0; + } + + /// 获取程序的步骤列表 + Future> getStepsByProgramId(int programId) async { + final database = await _db.database; + final maps = await database.query( + 'steps', + where: 'program_id = ?', + whereArgs: [programId], + orderBy: 'step_no ASC', + ); + return maps.map((m) => Step.fromMap(m)).toList(); + } + + /// 新增步骤 + Future addStep(Step step) async { + final database = await _db.database; + return await database.insert('steps', step.toMap()); + } + + /// 更新步骤 + Future updateStep(Step step) async { + if (step.id == null) return false; + final database = await _db.database; + final count = await database.update( + 'steps', + step.toMap(), + where: 'id = ?', + whereArgs: [step.id], + ); + return count > 0; + } + + /// 删除步骤 + Future deleteStep(int id) async { + final database = await _db.database; + final count = await database.delete('steps', where: 'id = ?', whereArgs: [id]); + return count > 0; + } + + /// 批量删除步骤 + Future deleteSteps(List ids) async { + if (ids.isEmpty) return true; + final database = await _db.database; + final count = await database.delete( + 'steps', + where: 'id IN (${ids.map((_) => '?').join(',')})', + whereArgs: ids, + ); + return count > 0; + } + + /// 更新步骤排序 + Future reorderSteps(int programId, List stepIds) async { + final database = await _db.database; + for (int i = 0; i < stepIds.length; i++) { + await database.update( + 'steps', + {'step_no': i + 1}, + where: 'id = ? AND program_id = ?', + whereArgs: [stepIds[i], programId], + ); + } + } +} \ No newline at end of file diff --git a/lib/features/programs/widgets/program_form_dialog.dart b/lib/features/programs/widgets/program_form_dialog.dart new file mode 100644 index 0000000..8d21402 --- /dev/null +++ b/lib/features/programs/widgets/program_form_dialog.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../models/program.dart'; +import '../providers/programs_provider.dart'; + +/// 程序表单弹窗 +/// 用于新增和编辑程序 +class ProgramFormDialog extends ConsumerStatefulWidget { + final Program? program; + + const ProgramFormDialog({super.key, this.program}); + + @override + ConsumerState createState() => _ProgramFormDialogState(); +} + +class _ProgramFormDialogState extends ConsumerState { + final _formKey = GlobalKey(); + late TextEditingController _codeController; + late TextEditingController _nameController; + bool _isEnabled = true; + bool _isSaving = false; + + @override + void initState() { + super.initState(); + _codeController = TextEditingController(text: widget.program?.code ?? ''); + _nameController = TextEditingController(text: widget.program?.name ?? ''); + _isEnabled = widget.program?.status == 1; + } + + @override + void dispose() { + _codeController.dispose(); + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final isEditing = widget.program != null; + + return AlertDialog( + title: Text( + isEditing + ? (l10n?.editProgram ?? '编辑程序') + : (l10n?.addProgram ?? '新增程序'), + ), + content: SizedBox( + width: 400, + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 编号输入 + TextFormField( + controller: _codeController, + decoration: InputDecoration( + labelText: l10n?.programCode ?? '编号', + hintText: '例如: P001', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return '请输入编号'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // 名称输入 + TextFormField( + controller: _nameController, + decoration: InputDecoration( + labelText: l10n?.programName ?? '名称', + hintText: '请输入程序名称', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return '请输入名称'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // 状态开关 + Row( + children: [ + Text( + '状态', + style: TextStyle(color: AppTheme.textPrimary), + ), + const Spacer(), + Switch( + value: _isEnabled, + onChanged: (value) { + setState(() { + _isEnabled = value; + }); + }, + activeColor: AppTheme.successColor, + ), + Text( + _isEnabled ? '启用' : '停用', + style: TextStyle( + color: _isEnabled ? AppTheme.successColor : AppTheme.idleColor, + ), + ), + ], + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: _isSaving ? null : () => Navigator.of(context).pop(), + child: Text(l10n?.cancel ?? '取消'), + ), + CommonButton( + text: l10n?.save ?? '保存', + icon: Icons.save, + type: ButtonType.primary, + isLoading: _isSaving, + onPressed: _isSaving ? null : () => _saveProgram(context, ref, l10n), + ), + ], + ); + } + + /// 保存程序 + Future _saveProgram( + BuildContext context, + WidgetRef ref, + AppLocalizations? l10n, + ) async { + if (!_formKey.currentState!.validate()) return; + + setState(() { + _isSaving = true; + }); + + final notifier = ref.read(programsProvider.notifier); + final now = DateTime.now().toString().substring(0, 10); + + final program = Program( + id: widget.program?.id, + code: _codeController.text.trim(), + name: _nameController.text.trim(), + createdAt: widget.program?.createdAt ?? now, + status: _isEnabled ? 1 : 0, + ); + + bool success; + if (widget.program != null) { + success = await notifier.updateProgram(program); + } else { + success = await notifier.addProgram(program); + } + + setState(() { + _isSaving = false; + }); + + if (success) { + Navigator.of(context).pop(); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('保存失败,请检查编号是否重复'), + backgroundColor: AppTheme.errorColor, + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/features/settings/pages/settings_page.dart b/lib/features/settings/pages/settings_page.dart new file mode 100644 index 0000000..e1e9c01 --- /dev/null +++ b/lib/features/settings/pages/settings_page.dart @@ -0,0 +1,382 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../core/localization/app_localizations.dart'; +import '../../../core/localization/locale_provider.dart'; +import '../../../core/theme/app_theme.dart'; +import '../../../shared/widgets/common_button.dart'; +import '../services/settings_service.dart'; + +/// 系统设置页面 +class SettingsPage extends ConsumerStatefulWidget { + const SettingsPage({super.key}); + + @override + ConsumerState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends ConsumerState { + String _currentVersion = 'V1.0.0'; + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + // locale 用于语言切换,通过 ref.watch 保持监听 + + return Scaffold( + body: Container( + color: AppTheme.backgroundColor, + child: Row( + children: [ + // 左侧导航菜单 + SizedBox( + width: 280, + child: Container( + color: Colors.white, + child: Column( + children: [ + // 返回按钮 + Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back), + color: AppTheme.textPrimary, + onPressed: () => context.go('/'), + ), + Text( + '返回首页', + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), + // 设置标题 + Container( + height: 60, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.primaryColor.withValues(alpha: 0.1), + ), + child: Row( + children: [ + Icon(Icons.settings, color: AppTheme.primaryColor, size: 24), + const SizedBox(width: 12), + Text( + l10n?.settings ?? '系统设置', + style: TextStyle( + color: AppTheme.primaryColor, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + // 软件升级 + _buildMenuItem( + icon: Icons.system_update, + title: l10n?.upgrade ?? '软件升级', + onTap: () {}, + ), + // 语言设置 + _buildMenuItem( + icon: Icons.language, + title: l10n?.language ?? '语言设置', + onTap: () => _showLanguageDialog(), + ), + // 安全设置 + _buildMenuItem( + icon: Icons.lock, + title: l10n?.password ?? '密码修改', + onTap: () => _showPasswordDialog(), + ), + // U盘导入 + _buildMenuItem( + icon: Icons.usb, + title: l10n?.usbImport ?? 'U盘导入', + onTap: () => _showUsbImportDialog(), + ), + ], + ), + ), + ), + + // 右侧内容区域 + Expanded( + child: Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n?.upgrade ?? '软件升级', + style: TextStyle( + color: AppTheme.textPrimary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 24), + + // 版本信息 + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.backgroundColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(Icons.info_outline, color: AppTheme.primaryColor), + const SizedBox(width: 12), + Text( + '当前版本: $_currentVersion', + style: TextStyle(color: AppTheme.textPrimary), + ), + ], + ), + ), + const SizedBox(height: 24), + + // 检查更新按钮 + CommonButton( + text: '检查更新', + icon: Icons.refresh, + type: ButtonType.primary, + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('已是最新版本'), + backgroundColor: AppTheme.successColor, + ), + ); + }, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + /// 导航菜单项 + Widget _buildMenuItem({ + required IconData icon, + required String title, + required VoidCallback onTap, + }) { + return ListTile( + leading: Icon(icon, color: AppTheme.textSecondary), + title: Text(title, style: TextStyle(color: AppTheme.textPrimary)), + trailing: Icon(Icons.chevron_right, color: AppTheme.idleColor), + onTap: onTap, + ); + } + + /// 显示语言选择对话框 + void _showLanguageDialog() { + final locale = ref.read(localeProvider); + final currentLang = locale.languageCode; + + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text('语言设置'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioListTile( + title: Text('简体中文'), + value: 'zh', + groupValue: currentLang, + onChanged: (value) { + ref.read(localeProvider.notifier).setChinese(); + Navigator.of(ctx).pop(); + }, + ), + RadioListTile( + title: Text('English'), + value: 'en', + groupValue: currentLang, + onChanged: (value) { + ref.read(localeProvider.notifier).setEnglish(); + Navigator.of(ctx).pop(); + }, + ), + ], + ), + ), + ); + } + + /// 显示密码修改对话框 + void _showPasswordDialog() { + final oldPasswordController = TextEditingController(); + final newPasswordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + String? errorMessage; + + showDialog( + context: context, + builder: (ctx) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: Text('密码修改'), + content: SizedBox( + width: 300, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: oldPasswordController, + decoration: InputDecoration( + labelText: '原密码', + errorText: null, + ), + obscureText: true, + ), + const SizedBox(height: 12), + TextField( + controller: newPasswordController, + decoration: InputDecoration( + labelText: '新密码', + helperText: '至少6位字符', + ), + obscureText: true, + ), + const SizedBox(height: 12), + TextField( + controller: confirmPasswordController, + decoration: InputDecoration(labelText: '确认新密码'), + obscureText: true, + ), + if (errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 12), + child: Text( + errorMessage!, + style: TextStyle(color: AppTheme.errorColor, fontSize: 12), + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: Text('取消'), + ), + ElevatedButton( + onPressed: () async { + // 验证逻辑 + final oldPassword = oldPasswordController.text.trim(); + final newPassword = newPasswordController.text.trim(); + final confirmPassword = confirmPasswordController.text.trim(); + + // 检查空值 + if (oldPassword.isEmpty || newPassword.isEmpty || confirmPassword.isEmpty) { + setState(() => errorMessage = '请填写所有字段'); + return; + } + + // 检查新密码长度 + if (newPassword.length < 6) { + setState(() => errorMessage = '新密码至少6位字符'); + return; + } + + // 检查新密码一致性 + if (newPassword != confirmPassword) { + setState(() => errorMessage = '两次输入的新密码不一致'); + return; + } + + // 验证原密码 + final isValid = await SettingsService.instance.verifyPassword(oldPassword); + if (!isValid) { + setState(() => errorMessage = '原密码错误'); + return; + } + + // 保存新密码 + final success = await SettingsService.instance.setPassword(newPassword); + Navigator.of(ctx).pop(); + + if (success) { + ScaffoldMessenger.of(this.context).showSnackBar( + SnackBar( + content: Text('密码已修改'), + backgroundColor: AppTheme.successColor, + ), + ); + } else { + ScaffoldMessenger.of(this.context).showSnackBar( + SnackBar( + content: Text('密码修改失败'), + backgroundColor: AppTheme.errorColor, + ), + ); + } + }, + child: Text('确认'), + ), + ], + ), + ), + ); + } + + /// 显示U盘导入对话框 + void _showUsbImportDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('U盘导入'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.usb, size: 48, color: AppTheme.warningColor), + const SizedBox(height: 16), + Text('未检测到U盘'), + const SizedBox(height: 8), + Text( + '请插入U盘后重试', + style: TextStyle(color: AppTheme.textSecondary, fontSize: 12), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('关闭'), + ), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('正在检测U盘...'), + backgroundColor: AppTheme.primaryColor, + ), + ); + }, + child: Text('重新检测'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/settings/services/settings_service.dart b/lib/features/settings/services/settings_service.dart new file mode 100644 index 0000000..d754606 --- /dev/null +++ b/lib/features/settings/services/settings_service.dart @@ -0,0 +1,63 @@ +import '../../../core/database/database_service.dart'; + +/// 设置服务 +/// 管理系统设置(密码、语言偏好等) +class SettingsService { + static final SettingsService instance = SettingsService._internal(); + final DatabaseService _db = DatabaseService.instance; + + SettingsService._internal(); + + /// 获取密码 + Future getPassword() async { + final database = await _db.database; + final results = await database.query( + 'settings', + where: 'key = ?', + whereArgs: ['password'], + ); + if (results.isEmpty) return '123456'; // 默认密码 + return results.first['value'] as String; + } + + /// 设置密码 + Future setPassword(String newPassword) async { + final database = await _db.database; + final count = await database.update( + 'settings', + {'value': newPassword}, + where: 'key = ?', + whereArgs: ['password'], + ); + return count > 0; + } + + /// 验证密码 + Future verifyPassword(String password) async { + final storedPassword = await getPassword(); + return password == storedPassword; + } + + /// 获取设置值 + Future getSetting(String key) async { + final database = await _db.database; + final results = await database.query( + 'settings', + where: 'key = ?', + whereArgs: [key], + ); + if (results.isEmpty) return null; + return results.first['value'] as String; + } + + /// 设置值 + Future setSetting(String key, String value) async { + final database = await _db.database; + // 使用 insert 或 replace + await database.execute( + 'INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)', + [key, value], + ); + return true; + } +} \ No newline at end of file diff --git a/lib/features/settings/services/usb_detection_service.dart b/lib/features/settings/services/usb_detection_service.dart new file mode 100644 index 0000000..fa5bd1f --- /dev/null +++ b/lib/features/settings/services/usb_detection_service.dart @@ -0,0 +1,99 @@ +import 'dart:async'; + +/// USB 检测服务 +/// 监听 U盘插入/拔出事件 +class UsbDetectionService { + static final UsbDetectionService instance = UsbDetectionService._internal(); + + UsbDetectionService._internal(); + + /// USB 状态 + bool _isUsbConnected = false; + String? _usbPath; + + /// 状态流 + final StreamController _stateController = StreamController.broadcast(); + + /// 监听 USB 状态变化 + Stream get stateStream => _stateController.stream; + + /// 当前 USB 是否连接 + bool get isConnected => _isUsbConnected; + + /// USB 路径 + String? get usbPath => _usbPath; + + /// 开始监听 USB 事件 + void startMonitoring() { + // TODO: 实现平台特定的 USB 监听 + // Android: 使用 BroadcastReceiver 监听 ACTION_MEDIA_MOUNTED + // Linux: 监听 /dev/disk/by-path/ 或使用 udev + // Windows: 监听 WM_DEVICECHANGE + + // 模拟实现:定时检测 + _startPolling(); + } + + /// 停止监听 + void stopMonitoring() { + // _stopPolling(); + } + + /// 模拟轮询检测(待平台实现) + void _startPolling() { + // TODO: 根据平台实现真实的 USB 检测 + // 定时检测 /mnt/usb 或 /media/*/ 目录 + } + + /// 手动检测 USB + Future detectUsb() async { + // TODO: 实现平台特定的 USB 检测 + // Android: 检查 getExternalFilesDir 或 mount points + // Linux: 检查 /mnt, /media 目录 + // Windows: 检查 D:, E: 等驱动器 + + // 返回检测结果 + return _isUsbConnected; + } + + /// 获取 USB 上的程序文件列表 + Future> listProgramFiles() async { + if (!_isUsbConnected || _usbPath == null) { + return []; + } + + // TODO: 扫描 USB 目录中的 .json 程序文件 + // 示例路径: $_usbPath/programs/*.json + + return []; + } + + /// 模拟 USB 连接(用于测试) + void simulateConnection(String path) { + _isUsbConnected = true; + _usbPath = path; + _stateController.add(UsbState.connected(path)); + } + + /// 模拟 USB 断开(用于测试) + void simulateDisconnection() { + _isUsbConnected = false; + _usbPath = null; + _stateController.add(UsbState.disconnected()); + } + + /// 释放资源 + void dispose() { + stopMonitoring(); + _stateController.close(); + } +} + +/// USB 状态 +class UsbState { + final bool isConnected; + final String? path; + + const UsbState.connected(String path) : isConnected = true, path = path; + const UsbState.disconnected() : isConnected = false, path = null; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..9378274 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +import 'core/router/app_router.dart'; +import 'core/theme/app_theme.dart'; +import 'core/localization/app_localizations.dart'; +import 'core/localization/locale_provider.dart'; +import 'core/database/database_service.dart'; + +/// 应用入口 +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Kiosk 模式:隐藏系统状态栏和导航栏 + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + + // 固定横屏 + SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); + + final db = DatabaseService.instance; + await db.database; + await db.initTestData(); + runApp(const ProviderScope(child: KuaishaiApp())); +} + +/// 应用主体 +class KuaishaiApp extends ConsumerWidget { + const KuaishaiApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(goRouterProvider); + final locale = ref.watch(localeProvider); + + return MaterialApp.router( + title: '污水毒品快检一体机', + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme(), + darkTheme: AppTheme.darkTheme(), + themeMode: ThemeMode.light, + + // 国际化配置 + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('zh', 'CN'), + Locale('en', 'US'), + ], + locale: locale, + + // 路由配置 + routerConfig: router, + ); + } +} \ No newline at end of file diff --git a/lib/shared/services/toast_service.dart b/lib/shared/services/toast_service.dart new file mode 100644 index 0000000..24bf80e --- /dev/null +++ b/lib/shared/services/toast_service.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import '../../core/theme/app_theme.dart'; + +/// Toast 服务 +/// 统一的消息提示管理 +class ToastService { + /// 显示成功提示 + static void showSuccess(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.check_circle, color: Colors.white, size: 20), + const SizedBox(width: 12), + Expanded(child: Text(message)), + ], + ), + backgroundColor: AppTheme.successColor, + duration: const Duration(seconds: 3), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.all(16), + ), + ); + } + + /// 显示错误提示 + static void showError(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.error, color: Colors.white, size: 20), + const SizedBox(width: 12), + Expanded(child: Text(message)), + ], + ), + backgroundColor: AppTheme.errorColor, + duration: const Duration(seconds: 4), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.all(16), + ), + ); + } + + /// 显示警告提示 + static void showWarning(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.warning, color: Colors.white, size: 20), + const SizedBox(width: 12), + Expanded(child: Text(message)), + ], + ), + backgroundColor: AppTheme.warningColor, + duration: const Duration(seconds: 3), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.all(16), + ), + ); + } + + /// 显示信息提示 + static void showInfo(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.info, color: Colors.white, size: 20), + const SizedBox(width: 12), + Expanded(child: Text(message)), + ], + ), + backgroundColor: AppTheme.primaryColor, + duration: const Duration(seconds: 3), + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.all(16), + ), + ); + } +} \ No newline at end of file diff --git a/lib/shared/utils/constants.dart b/lib/shared/utils/constants.dart new file mode 100644 index 0000000..2d21963 --- /dev/null +++ b/lib/shared/utils/constants.dart @@ -0,0 +1,24 @@ +/// 常量定义 +class Constants { + // 速度选项 + static const List speedOptions = ['低速', '中速', '高速']; + + // 下针速度档位 + static const List needleSpeedLevels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + // 孔位列表 + static const List positions = [ + 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', + 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', + 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', + 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', + ]; + + // 默认步骤名称 + static const List defaultStepNames = ['混合', '吸磁', '吹气', '下针']; + + // 时间单位 + static const String timeUnitSeconds = '秒'; + static const String timeUnitMinutes = '分钟'; + static const String volumeUnit = 'μL'; +} \ No newline at end of file diff --git a/lib/shared/utils/responsive_layout.dart b/lib/shared/utils/responsive_layout.dart new file mode 100644 index 0000000..a2298fe --- /dev/null +++ b/lib/shared/utils/responsive_layout.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +/// 响应式布局工具类 +/// 目标屏幕: 1920x1080 +class ResponsiveLayout { + static const double targetWidth = 1920; + static const double targetHeight = 1080; + + /// 获取屏幕宽度比例 + static double widthPercent(BuildContext context, double percent) { + return MediaQuery.of(context).size.width * percent; + } + + /// 获取屏幕高度比例 + static double heightPercent(BuildContext context, double percent) { + return MediaQuery.of(context).size.height * percent; + } + + /// 基于目标屏幕缩放宽度 + static double scaleWidth(BuildContext context, double targetValue) { + final screenWidth = MediaQuery.of(context).size.width; + return targetValue * (screenWidth / targetWidth); + } + + /// 基于目标屏幕缩放高度 + static double scaleHeight(BuildContext context, double targetValue) { + final screenHeight = MediaQuery.of(context).size.height; + return targetValue * (screenHeight / targetHeight); + } + + /// 基于目标屏幕缩放字体 + static double scaleFont(BuildContext context, double targetFontSize) { + return scaleWidth(context, targetFontSize); + } + + /// 预设布局尺寸 + static double sidebarWidth(BuildContext context) => widthPercent(context, 0.25); // 480px on 1920 + static double detailWidth(BuildContext context) => widthPercent(context, 0.21); // 400px on 1920 + static double navWidth(BuildContext context) => widthPercent(context, 0.15); // 280px on 1920 + static double cardWidth(BuildContext context) => widthPercent(context, 0.30); // ~576px +} + +/// 响应式间距 +class ResponsiveSpacing { + static double small(BuildContext context) => ResponsiveLayout.scaleWidth(context, 8); + static double medium(BuildContext context) => ResponsiveLayout.scaleWidth(context, 16); + static double large(BuildContext context) => ResponsiveLayout.scaleWidth(context, 24); + static double xlarge(BuildContext context) => ResponsiveLayout.scaleWidth(context, 32); +} \ No newline at end of file diff --git a/lib/shared/widgets/common_button.dart b/lib/shared/widgets/common_button.dart new file mode 100644 index 0000000..2aa6ea6 --- /dev/null +++ b/lib/shared/widgets/common_button.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import '../../core/theme/app_theme.dart'; + +/// 通用按钮组件 - 明亮工业风格 +class CommonButton extends StatelessWidget { + final String text; + final VoidCallback? onPressed; + final bool enabled; + final Color? backgroundColor; + final Color? textColor; + final IconData? icon; + final bool isLoading; + final ButtonType type; + + const CommonButton({ + super.key, + required this.text, + this.onPressed, + this.enabled = true, + this.backgroundColor, + this.textColor, + this.icon, + this.isLoading = false, + this.type = ButtonType.primary, + }); + + @override + Widget build(BuildContext context) { + final bgColor = backgroundColor ?? _getDefaultBackgroundColor(); + final fgColor = textColor ?? _getDefaultTextColor(); + + Widget content; + if (isLoading) { + content = Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + color: fgColor, + ), + ), + const SizedBox(width: 8), + Text(text, style: TextStyle(fontWeight: FontWeight.w500)), + ], + ); + } else if (icon != null) { + content = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18), + const SizedBox(width: 8), + Text(text, style: TextStyle(fontWeight: FontWeight.w500)), + ], + ); + } else { + content = Text(text, style: TextStyle(fontWeight: FontWeight.w500)); + } + + return ElevatedButton( + onPressed: enabled && !isLoading ? onPressed : null, + style: ElevatedButton.styleFrom( + backgroundColor: bgColor, + foregroundColor: fgColor, + disabledBackgroundColor: AppTheme.statusStopped.withValues(alpha: 0.3), + disabledForegroundColor: AppTheme.textTertiary, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppTheme.radiusSm), + ), + ), + child: content, + ); + } + + Color _getDefaultBackgroundColor() { + switch (type) { + case ButtonType.primary: + return AppTheme.primaryColor; + case ButtonType.success: + return AppTheme.successColor; + case ButtonType.warning: + return AppTheme.warningColor; + case ButtonType.danger: + return AppTheme.errorColor; + case ButtonType.secondary: + return AppTheme.bgSurface; + } + } + + Color _getDefaultTextColor() { + switch (type) { + case ButtonType.secondary: + return AppTheme.textPrimary; + default: + return AppTheme.textOnPrimary; + } + } +} + +enum ButtonType { primary, success, warning, danger, secondary } diff --git a/lib/shared/widgets/common_card.dart b/lib/shared/widgets/common_card.dart new file mode 100644 index 0000000..bd426a7 --- /dev/null +++ b/lib/shared/widgets/common_card.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import '../../core/theme/app_theme.dart'; + +/// 通用卡片组件 - 明亮工业风格 +class CommonCard extends StatelessWidget { + final Widget child; + final VoidCallback? onTap; + final bool selected; + final EdgeInsetsGeometry? padding; + final EdgeInsetsGeometry? margin; + + const CommonCard({ + super.key, + required this.child, + this.onTap, + this.selected = false, + this.padding, + this.margin, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: margin ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Material( + color: selected ? AppTheme.bgCardHover : AppTheme.bgCard, + borderRadius: BorderRadius.circular(AppTheme.radiusMd), + elevation: 0, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(AppTheme.radiusMd), + child: Container( + padding: padding ?? const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(AppTheme.radiusMd), + border: selected + ? Border.all(color: AppTheme.primaryColor, width: 2) + : Border.all(color: AppTheme.borderLight, width: 1), + boxShadow: AppTheme.shadowCard, + ), + child: child, + ), + ), + ), + ); + } +} diff --git a/lib/shared/widgets/common_dialog.dart b/lib/shared/widgets/common_dialog.dart new file mode 100644 index 0000000..8ac738f --- /dev/null +++ b/lib/shared/widgets/common_dialog.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; + +/// 确认对话框组件 +class CommonDialog { + /// 显示确认对话框 + static Future showConfirm({ + required BuildContext context, + required String title, + required String content, + String confirmText = '确认', + String cancelText = '取消', + bool isDestructive = false, + }) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(cancelText), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + style: ElevatedButton.styleFrom( + backgroundColor: isDestructive ? Colors.red : null, + ), + child: Text(confirmText), + ), + ], + ), + ); + } + + /// 显示信息对话框 + static Future showInfo({ + required BuildContext context, + required String title, + required String content, + String confirmText = '确认', + }) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: Text(confirmText), + ), + ], + ), + ); + } + + /// 显示输入对话框 + static Future showInput({ + required BuildContext context, + required String title, + String? hintText, + String? initialValue, + String confirmText = '确认', + String cancelText = '取消', + }) { + final controller = TextEditingController(text: initialValue); + + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: TextField( + decoration: InputDecoration( + hintText: hintText, + border: const OutlineInputBorder(), + ), + controller: controller, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(cancelText), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, controller.text), + child: Text(confirmText), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/shared/widgets/empty_state_widget.dart b/lib/shared/widgets/empty_state_widget.dart new file mode 100644 index 0000000..4539800 --- /dev/null +++ b/lib/shared/widgets/empty_state_widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import '../../../core/theme/app_theme.dart'; + +/// 空状态组件 +/// 统一的空数据展示样式 +class EmptyStateWidget extends StatelessWidget { + final IconData icon; + final String message; + final String? actionText; + final VoidCallback? onAction; + + const EmptyStateWidget({ + super.key, + required this.icon, + required this.message, + this.actionText, + this.onAction, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 64, + color: AppTheme.idleColor, + ), + const SizedBox(height: 16), + Text( + message, + style: TextStyle( + color: AppTheme.textSecondary, + fontSize: 16, + ), + ), + if (actionText != null && onAction != null) ...[ + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: onAction, + icon: const Icon(Icons.add, size: 20), + label: Text(actionText!), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + foregroundColor: Colors.white, + ), + ), + ], + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/shared/widgets/status_indicator.dart b/lib/shared/widgets/status_indicator.dart new file mode 100644 index 0000000..8b46cde --- /dev/null +++ b/lib/shared/widgets/status_indicator.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import '../../core/theme/app_theme.dart'; + +/// 状态指示器组件 - 明亮工业风格 +class StatusIndicator extends StatelessWidget { + final String text; + final DeviceStatusType status; + final double size; + + const StatusIndicator({ + super.key, + required this.text, + required this.status, + this.size = 10, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getStatusColor(), + ), + ), + const SizedBox(width: 6), + Text( + text, + style: TextStyle( + color: _getStatusColor(), + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ], + ); + } + + Color _getStatusColor() { + switch (status) { + case DeviceStatusType.running: + return AppTheme.statusRunning; + case DeviceStatusType.idle: + return AppTheme.statusStopped; + case DeviceStatusType.paused: + return AppTheme.statusPaused; + case DeviceStatusType.error: + return AppTheme.statusError; + case DeviceStatusType.success: + return AppTheme.statusRunning; + } + } +} + +enum DeviceStatusType { running, idle, paused, error, success } diff --git a/pencil/images/Untitled.fig b/pencil/images/Untitled.fig new file mode 100644 index 0000000..b125c6a Binary files /dev/null and b/pencil/images/Untitled.fig differ diff --git a/pencil/images/image-import.png b/pencil/images/image-import.png new file mode 100644 index 0000000..2244d97 Binary files /dev/null and b/pencil/images/image-import.png differ diff --git a/pencil/untitled.pen b/pencil/untitled.pen new file mode 100644 index 0000000..23bc3f1 --- /dev/null +++ b/pencil/untitled.pen @@ -0,0 +1,12973 @@ +{ + "version": "2.11", + "children": [ + { + "type": "frame", + "id": "ZtBwm", + "x": 1151.646094282759, + "y": 199.01134255946727, + "name": "Section 1", + "width": 5416, + "height": 1280, + "fill": "#ffffffff", + "cornerRadius": 2, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#0000001a" + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "VmWXJ", + "x": 100, + "y": 100, + "name": "系统设置中心 - 桌面端", + "width": 1280, + "height": 1024, + "fill": [ + "#ffffffff", + "#0f172aff" + ], + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "E31vFr", + "name": "Header - Top Navigation Bar", + "width": "fill_container", + "height": 64, + "fill": "#1b1b1dff", + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "gap": 812.4500122070312, + "padding": [ + 0, + 32 + ], + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "hxwTy", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "iMACV", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "m4fwc", + "name": "Icon", + "geometry": "M1.25 23.13235l0-3.75 3.875 0-3.1875-10.4375c-0.5625-0.3125-1.02604-0.77083-1.39063-1.375-0.36458-0.60417-0.54688-1.25-0.54687-1.9375 0-1.04167 0.36458-1.92708 1.09375-2.65625 0.72917-0.72917 1.61458-1.09375 2.65625-1.09375 0.8125 0 1.53646 0.23437 2.17188 0.70313 0.63542 0.46875 1.07813 1.06771 1.32812 1.79687l4 0 0-1.25c0-0.35417 0.11979-0.65104 0.35938-0.89062 0.23958-0.23958 0.53646-0.35937 0.89062-0.35938 0.1875 0 0.36979 0.04167 0.54688 0.125 0.17708 0.08333 0.32813 0.20833 0.45312 0.375l0 0 2.125-2c0.1875-0.1875 0.41146-0.30729 0.67188-0.35937 0.26042-0.05208 0.51563-0.01562 0.76562 0.10937l4.875 2.25c0.25 0.125 0.42188 0.30729 0.51563 0.54688 0.09375 0.23958 0.08854 0.47396-0.01563 0.70312-0.125 0.25-0.30729 0.41146-0.54688 0.48438-0.23958 0.07292-0.47396 0.05729-0.70312-0.04688l-4.5-2.0625-2.9375 2.75 0 1.75 2.9375 2.6875 4.5-2.0625c0.22917-0.10417 0.46875-0.11458 0.71875-0.03125 0.25 0.08333 0.42708 0.23958 0.53125 0.46875 0.125 0.25 0.13542 0.48958 0.03125 0.71875-0.10417 0.22917-0.28125 0.40625-0.53125 0.53125l-4.875 2.3125c-0.25 0.125-0.50521 0.16146-0.76563 0.10938-0.26042-0.05208-0.48438-0.17188-0.67187-0.35938l-2.125-2 0 0c-0.125 0.125-0.27604 0.23958-0.45313 0.34375-0.17708 0.10417-0.35938 0.15625-0.54687 0.15625-0.35417 0-0.65104-0.11979-0.89063-0.35937-0.23958-0.23958-0.35938-0.53646-0.35937-0.89063l0-1.25-4 0c-0.0625 0.16667-0.13021 0.32292-0.20313 0.46875-0.07292 0.14583-0.17188 0.30208-0.29687 0.46875l6.25 11.5625 4.5 0 0 3.75-16.25 0 0 0m2.5-16.25c0.35417 0 0.65104-0.11979 0.89063-0.35937 0.23958-0.23958 0.35938-0.53646 0.35937-0.89063 0-0.35417-0.11979-0.65104-0.35938-0.89062-0.23958-0.23958-0.53646-0.35938-0.89062-0.35938-0.35417 0-0.65104 0.11979-0.89063 0.35938-0.23958 0.23958-0.35938 0.53646-0.35937 0.89062 0 0.35417 0.11979 0.65104 0.35937 0.89063 0.23958 0.23958 0.53646 0.35938 0.89063 0.35937l0 0m3.9375 12.5l2.4375 0-5.375-10c0 0-0.02083 0-0.0625 0-0.04167 0-0.0625 0-0.0625 0l3.0625 10 0 0m2.4375 0l0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 22.539772033691406, + "height": 23.132352828979492 + } + ] + }, + { + "type": "frame", + "id": "K8vKa3", + "name": "Margin", + "width": 17, + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 8 + ], + "children": [ + { + "type": "rectangle", + "id": "R2Em8a", + "name": "Vertical Divider", + "fill": "#45464dff", + "width": 1, + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + }, + { + "type": "frame", + "id": "EucI5", + "name": "Heading 1", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "wxmRa", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "系统设置中心", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 24, + "fontWeight": "500", + "letterSpacing": -0.6000000238418579 + } + ] + } + ] + }, + { + "type": "frame", + "id": "DXwKO", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "jjvaI", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": -0.5, + "alignItems": "end", + "children": [ + { + "type": "frame", + "id": "W6YurD", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "MTvDp", + "name": "Text", + "fill": "#909097ff", + "content": "设备状态", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "P0SeJa", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 6, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "z84Ql", + "name": "Background", + "fill": "#10b981ff", + "width": 6, + "height": 6, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "text", + "id": "NUBHH", + "name": "Text", + "fill": "#10b981ff", + "content": "运行中", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.699999988079071 + } + ] + } + ] + }, + { + "type": "frame", + "id": "N8Va2B", + "name": "Button", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "Yidcz", + "name": "Icon", + "geometry": "M7.3 20l-0.4-3.2c-0.21667-0.08333-0.42083-0.18333-0.6125-0.3-0.19167-0.11667-0.37917-0.24167-0.5625-0.375l-2.975 1.25-2.75-4.75 2.575-1.95c-0.01667-0.11667-0.025-0.22917-0.025-0.3375 0-0.10833 0-0.22083 0-0.3375 0-0.11667 0-0.22917 0-0.3375 0-0.10833 0.00833-0.22083 0.025-0.3375l-2.575-1.95 2.75-4.75 2.975 1.25c0.18333-0.13333 0.375-0.25833 0.575-0.375 0.2-0.11667 0.4-0.21667 0.6-0.3l0.4-3.2 5.5 0 0.4 3.2c0.21667 0.08333 0.42083 0.18333 0.6125 0.3 0.19167 0.11667 0.37917 0.24167 0.5625 0.375l2.975-1.25 2.75 4.75-2.575 1.95c0.01667 0.11667 0.025 0.22917 0.025 0.3375 0 0.10833 0 0.22083 0 0.3375 0 0.11667 0 0.22917 0 0.3375 0 0.10833-0.01667 0.22083-0.05 0.3375l2.575 1.95-2.75 4.75-2.95-1.25c-0.18333 0.13333-0.375 0.25833-0.575 0.375-0.2 0.11667-0.4 0.21666-0.6 0.3l-0.4 3.2-5.5 0 0 0m1.75-2l1.975 0 0.35-2.65c0.51667-0.13333 0.99583-0.32917 1.4375-0.5875 0.44167-0.25833 0.84583-0.57083 1.2125-0.9375l2.475 1.025 0.975-1.7-2.15-1.625c0.08333-0.23333 0.14167-0.47917 0.175-0.7375 0.03333-0.25833 0.05-0.52083 0.05-0.7875 0-0.26667-0.01667-0.52917-0.05-0.7875-0.03333-0.25833-0.09167-0.50417-0.175-0.7375l2.15-1.625-0.975-1.7-2.475 1.05c-0.36667-0.38333-0.77083-0.70417-1.2125-0.9625-0.44167-0.25833-0.92083-0.45417-1.4375-0.5875l-0.325-2.65-1.975 0-0.35 2.65c-0.51667 0.13333-0.99583 0.32917-1.4375 0.5875-0.44167 0.25833-0.84583 0.57083-1.2125 0.9375l-2.475-1.025-0.975 1.7 2.15 1.6c-0.08333 0.25-0.14167 0.5-0.175 0.75-0.03333 0.25-0.05 0.51667-0.05 0.8 0 0.26667 0.01667 0.525 0.05 0.775 0.03333 0.25 0.09167 0.5 0.175 0.75l-2.15 1.625 0.975 1.7 2.475-1.05c0.36667 0.38333 0.77083 0.70417 1.2125 0.9625 0.44167 0.25833 0.92083 0.45417 1.4375 0.5875l0.325 2.65 0 0m1.05-4.5c0.96667 0 1.79167-0.34167 2.475-1.025 0.68333-0.68333 1.025-1.50833 1.025-2.475 0-0.96667-0.34167-1.79167-1.025-2.475-0.68333-0.68333-1.50833-1.025-2.475-1.025-0.98333 0-1.8125 0.34167-2.4875 1.025-0.675 0.68333-1.0125 1.50833-1.0125 2.475 0 0.96667 0.3375 1.79167 1.0125 2.475 0.675 0.68333 1.50417 1.025 2.4875 1.025l0 0m-0.05-3.5l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#c6c6cdff", + "width": 20.100000381469727, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "n0ycGs", + "name": "Button", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "hvCMa", + "name": "Icon", + "geometry": "M2 18c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-14c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l7 0 0 2-7 0 0 0 0 0 0 14 0 0 0 0 7 0 0 2-7 0 0 0m11-4l-1.375-1.45 2.55-2.55-8.175 0 0-2 8.175 0-2.55-2.55 1.375-1.45 5 5-5 5 0 0", + "fill": "#c6c6cdff", + "width": 18, + "height": 18 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "pUP4M", + "name": "Container", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "children": [ + { + "type": "frame", + "id": "IBSOD", + "name": "Aside - Sidebar Navigation", + "width": 278.6099853515625, + "height": "fill_container", + "fill": "#0e0e10ff", + "stroke": { + "align": "inside", + "thickness": { + "right": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "iG6vc", + "name": "Nav", + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "padding": 16, + "children": [ + { + "type": "frame", + "id": "kg6AK", + "name": "Link", + "width": "fill_container", + "fill": "#0f172aff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "KI8NS", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "SBjRS", + "name": "Icon", + "geometry": "M7.3 20l-0.4-3.2c-0.21667-0.08333-0.42083-0.18333-0.6125-0.3-0.19167-0.11667-0.37917-0.24167-0.5625-0.375l-2.975 1.25-2.75-4.75 2.575-1.95c-0.01667-0.11667-0.025-0.22917-0.025-0.3375 0-0.10833 0-0.22083 0-0.3375 0-0.11667 0-0.22917 0-0.3375 0-0.10833 0.00833-0.22083 0.025-0.3375l-2.575-1.95 2.75-4.75 2.975 1.25c0.18333-0.13333 0.375-0.25833 0.575-0.375 0.2-0.11667 0.4-0.21667 0.6-0.3l0.4-3.2 5.5 0 0.4 3.2c0.21667 0.08333 0.42083 0.18333 0.6125 0.3 0.19167 0.11667 0.37917 0.24167 0.5625 0.375l2.975-1.25 2.75 4.75-2.575 1.95c0.01667 0.11667 0.025 0.22917 0.025 0.3375 0 0.10833 0 0.22083 0 0.3375 0 0.11667 0 0.22917 0 0.3375 0 0.10833-0.01667 0.22083-0.05 0.3375l2.575 1.95-2.75 4.75-2.95-1.25c-0.18333 0.13333-0.375 0.25833-0.575 0.375-0.2 0.11667-0.4 0.21666-0.6 0.3l-0.4 3.2-5.5 0 0 0m1.75-2l1.975 0 0.35-2.65c0.51667-0.13333 0.99583-0.32917 1.4375-0.5875 0.44167-0.25833 0.84583-0.57083 1.2125-0.9375l2.475 1.025 0.975-1.7-2.15-1.625c0.08333-0.23333 0.14167-0.47917 0.175-0.7375 0.03333-0.25833 0.05-0.52083 0.05-0.7875 0-0.26667-0.01667-0.52917-0.05-0.7875-0.03333-0.25833-0.09167-0.50417-0.175-0.7375l2.15-1.625-0.975-1.7-2.475 1.05c-0.36667-0.38333-0.77083-0.70417-1.2125-0.9625-0.44167-0.25833-0.92083-0.45417-1.4375-0.5875l-0.325-2.65-1.975 0-0.35 2.65c-0.51667 0.13333-0.99583 0.32917-1.4375 0.5875-0.44167 0.25833-0.84583 0.57083-1.2125 0.9375l-2.475-1.025-0.975 1.7 2.15 1.6c-0.08333 0.25-0.14167 0.5-0.175 0.75-0.03333 0.25-0.05 0.51667-0.05 0.8 0 0.26667 0.01667 0.525 0.05 0.775 0.03333 0.25 0.09167 0.5 0.175 0.75l-2.15 1.625 0.975 1.7 2.475-1.05c0.36667 0.38333 0.77083 0.70417 1.2125 0.9625 0.44167 0.25833 0.92083 0.45417 1.4375 0.5875l0.325 2.65 0 0m1.05-4.5c0.96667 0 1.79167-0.34167 2.475-1.025 0.68333-0.68333 1.025-1.50833 1.025-2.475 0-0.96667-0.34167-1.79167-1.025-2.475-0.68333-0.68333-1.50833-1.025-2.475-1.025-0.98333 0-1.8125 0.34167-2.4875 1.025-0.675 0.68333-1.0125 1.50833-1.0125 2.475 0 0.96667 0.3375 1.79167 1.0125 2.475 0.675 0.68333 1.50417 1.025 2.4875 1.025l0 0m-0.05-3.5l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 20.100000381469727, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "Vm2yL", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "y9NITN", + "name": "Text", + "fill": "#bec6e0ff", + "content": "通用设置", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "wMbhg", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "wncy9", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "qEgF9", + "name": "Icon", + "geometry": "M2 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-12c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l16 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 12c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-16 0 0 0m0-2l16 0 0 0 0 0 0-10-16 0 0 10 0 0 0 0 0 0m3.5-1l-1.4-1.4 2.575-2.6-2.6-2.6 1.425-1.4 4 4-4 4 0 0m4.5 0l0-2 6 0 0 2-6 0 0 0", + "fill": "#c6c6cdff", + "width": 20, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "N3Ncd", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "m6uTOS", + "name": "Text", + "fill": "#c6c6cdff", + "content": "系统日志", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "cvsyN", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "bpEhD", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "hspAg", + "name": "Icon", + "geometry": "M0 16l0-2 2.725 0c-0.85-0.73333-1.51667-1.61667-2-2.65-0.48333-1.03333-0.725-2.15-0.725-3.35 0-1.86667 0.56667-3.5125 1.7-4.9375 1.13333-1.425 2.56667-2.3625 4.3-2.8125l0 2.1c-1.16667 0.41667-2.125 1.1375-2.875 2.1625-0.75 1.025-1.125 2.1875-1.125 3.4875 0 0.9 0.17917 1.72917 0.5375 2.4875 0.35833 0.75833 0.84583 1.4125 1.4625 1.9625l0-2.45 2 0 0 6-6 0 0 0m11 0c-0.83333 0-1.54167-0.29167-2.125-0.875-0.58333-0.58333-0.875-1.29167-0.875-2.125 0-0.8 0.275-1.4875 0.825-2.0625 0.55-0.575 1.225-0.87917 2.025-0.9125 0.28333-0.6 0.70417-1.0875 1.2625-1.4625 0.55833-0.375 1.1875-0.5625 1.8875-0.5625 0.88333 0 1.64583 0.2875 2.2875 0.8625 0.64167 0.575 1.02917 1.2875 1.1625 2.1375l0 0c0.7 0 1.3 0.24167 1.8 0.725 0.5 0.48333 0.75 1.06667 0.75 1.75 0 0.7-0.24167 1.29583-0.725 1.7875-0.48333 0.49167-1.075 0.7375-1.775 0.7375l-6.5 0 0 0m2.9-9c-0.11667-0.68333-0.34167-1.31667-0.675-1.9-0.33333-0.58333-0.74167-1.1-1.225-1.55l0 2.45-2 0 0-6 6 0 0 2-2.725 0c0.71667 0.63333 1.30417 1.375 1.7625 2.225 0.45833 0.85 0.75417 1.775 0.8875 2.775l-2.025 0 0 0m-2.9 7l6.5 0c0.13333 0 0.25-0.05 0.35-0.15 0.1-0.1 0.15-0.21667 0.15-0.35 0-0.13333-0.05-0.25-0.15-0.35-0.1-0.1-0.21667-0.15-0.35-0.15l-1.75 0 0-1.25c0-0.48333-0.17083-0.89583-0.5125-1.2375-0.34167-0.34167-0.75417-0.5125-1.2375-0.5125-0.48333 0-0.89583 0.17083-1.2375 0.5125-0.34167 0.34167-0.5125 0.75417-0.5125 1.2375l0 0.25-1.25 0c-0.28333 0-0.52083 0.09583-0.7125 0.2875-0.19167 0.19167-0.2875 0.42917-0.2875 0.7125 0 0.28333 0.09583 0.52083 0.2875 0.7125 0.19167 0.19167 0.42917 0.2875 0.7125 0.2875l0 0m3-2l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#c6c6cdff", + "width": 20, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "bX1H0", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "YHQ3h", + "name": "Text", + "fill": "#c6c6cdff", + "content": "远程维护", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "mcdMR", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "KlfcG", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "OKKXX", + "name": "Icon", + "geometry": "M9 18c-1.25 0-2.42083-0.2375-3.5125-0.7125-1.09167-0.475-2.04167-1.11667-2.85-1.925-0.80833-0.80833-1.45-1.75833-1.925-2.85-0.475-1.09167-0.7125-2.2625-0.7125-3.5125 0-1.25 0.2375-2.42083 0.7125-3.5125 0.475-1.09167 1.11667-2.04167 1.925-2.85 0.80833-0.80833 1.75833-1.45 2.85-1.925 1.09167-0.475 2.2625-0.7125 3.5125-0.7125 1.36667 0 2.6625 0.29167 3.8875 0.875 1.225 0.58333 2.2625 1.40833 3.1125 2.475l0-2.35 2 0 0 6-6 0 0-2 2.75 0c-0.68333-0.93333-1.525-1.66667-2.525-2.2-1-0.53333-2.075-0.8-3.225-0.8-1.95 0-3.60417 0.67917-4.9625 2.0375-1.35833 1.35833-2.0375 3.0125-2.0375 4.9625 0 1.95 0.67917 3.60417 2.0375 4.9625 1.35833 1.35833 3.0125 2.0375 4.9625 2.0375 1.75 0 3.27917-0.56667 4.5875-1.7 1.30833-1.13333 2.07917-2.56667 2.3125-4.3l2.05 0c-0.25 2.28333-1.22917 4.1875-2.9375 5.7125-1.70833 1.525-3.7125 2.2875-6.0125 2.2875l0 0m2.8-4.8l-3.8-3.8 0-5.4 2 0 0 4.6 3.2 3.2-1.4 1.4 0 0", + "fill": "#c6c6cdff", + "width": 18, + "height": 18 + } + ] + }, + { + "type": "frame", + "id": "vRG8D", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "rA048", + "name": "Text", + "fill": "#c6c6cdff", + "content": "固件更新", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "F3rz4", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "DfgOe", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "K0Os89", + "name": "Icon", + "geometry": "M10.0125 20c-1.375 0-2.67083-0.2625-3.8875-0.7875-1.21667-0.525-2.27917-1.24167-3.1875-2.15-0.90833-0.90833-1.625-1.97083-2.15-3.1875-0.525-1.21667-0.7875-2.5125-0.7875-3.8875 0-1.375 0.2625-2.66667 0.7875-3.875 0.525-1.20833 1.24167-2.26667 2.15-3.175 0.90833-0.90833 1.97083-1.625 3.1875-2.15 1.21667-0.525 2.5125-0.7875 3.8875-0.7875 1.375 0 2.66667 0.2625 3.875 0.7875 1.20833 0.525 2.26667 1.24167 3.175 2.15 0.90833 0.90833 1.625 1.96667 2.15 3.175 0.525 1.20833 0.7875 2.5 0.7875 3.875 0 1.375-0.2625 2.67083-0.7875 3.8875-0.525 1.21667-1.24167 2.27917-2.15 3.1875-0.90833 0.90833-1.96667 1.625-3.175 2.15-1.20833 0.525-2.5 0.7875-3.875 0.7875l0 0m-0.0125-2.05c0.43333-0.6 0.80833-1.225 1.125-1.875 0.31667-0.65 0.575-1.34167 0.775-2.075l-3.8 0c0.2 0.73333 0.45833 1.425 0.775 2.075 0.31667 0.65 0.69167 1.275 1.125 1.875l0 0m-2.6-0.4c-0.3-0.55-0.5625-1.12083-0.7875-1.7125-0.225-0.59167-0.4125-1.20417-0.5625-1.8375l-2.95 0c0.48333 0.83333 1.0875 1.55833 1.8125 2.175 0.725 0.61667 1.55417 1.075 2.4875 1.375l0 0m5.2 0c0.93333-0.3 1.7625-0.75833 2.4875-1.375 0.725-0.61667 1.32917-1.34167 1.8125-2.175l-2.95 0c-0.15 0.63333-0.3375 1.24583-0.5625 1.8375-0.225 0.59167-0.4875 1.1625-0.7875 1.7125l0 0m-10.35-5.55l3.4 0c-0.05-0.33333-0.0875-0.6625-0.1125-0.9875-0.025-0.325-0.0375-0.6625-0.0375-1.0125 0-0.35 0.0125-0.6875 0.0375-1.0125 0.025-0.325 0.0625-0.65417 0.1125-0.9875l-3.4 0c-0.08333 0.33333-0.14583 0.6625-0.1875 0.9875-0.04167 0.325-0.0625 0.6625-0.0625 1.0125 0 0.35 0.02083 0.6875 0.0625 1.0125 0.04167 0.325 0.10417 0.65417 0.1875 0.9875l0 0m5.4 0l4.7 0c0.05-0.33333 0.0875-0.6625 0.1125-0.9875 0.025-0.325 0.0375-0.6625 0.0375-1.0125 0-0.35-0.0125-0.6875-0.0375-1.0125-0.025-0.325-0.0625-0.65417-0.1125-0.9875l-4.7 0c-0.05 0.33333-0.0875 0.6625-0.1125 0.9875-0.025 0.325-0.0375 0.6625-0.0375 1.0125 0 0.35 0.0125 0.6875 0.0375 1.0125 0.025 0.325 0.0625 0.65417 0.1125 0.9875l0 0m6.7 0l3.4 0c0.08333-0.33333 0.14583-0.6625 0.1875-0.9875 0.04167-0.325 0.0625-0.6625 0.0625-1.0125 0-0.35-0.02083-0.6875-0.0625-1.0125-0.04167-0.325-0.10417-0.65417-0.1875-0.9875l-3.4 0c0.05 0.33333 0.0875 0.6625 0.1125 0.9875 0.025 0.325 0.0375 0.6625 0.0375 1.0125 0 0.35-0.0125 0.6875-0.0375 1.0125-0.025 0.325-0.0625 0.65417-0.1125 0.9875l0 0m-0.4-6l2.95 0c-0.48333-0.83333-1.0875-1.55833-1.8125-2.175-0.725-0.61667-1.55417-1.075-2.4875-1.375 0.3 0.55 0.5625 1.12083 0.7875 1.7125 0.225 0.59167 0.4125 1.20417 0.5625 1.8375l0 0m-5.85 0l3.8 0c-0.2-0.73333-0.45833-1.425-0.775-2.075-0.31667-0.65-0.69167-1.275-1.125-1.875-0.43333 0.6-0.80833 1.225-1.125 1.875-0.31667 0.65-0.575 1.34167-0.775 2.075l0 0m-5 0l2.95 0c0.15-0.63333 0.3375-1.24583 0.5625-1.8375 0.225-0.59167 0.4875-1.1625 0.7875-1.7125-0.93333 0.3-1.7625 0.75833-2.4875 1.375-0.725 0.61667-1.32917 1.34167-1.8125 2.175l0 0", + "fill": "#c6c6cdff", + "width": 20, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "VkDnw", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "EXVzp", + "name": "Text", + "fill": "#c6c6cdff", + "content": "系统语言", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "kG9R5", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "exx3J", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "EcRy8", + "name": "Icon", + "geometry": "M2 21c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-10c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l1 0 0-2c0-1.38333 0.4875-2.5625 1.4625-3.5375 0.975-0.975 2.15417-1.4625 3.5375-1.4625 1.38333 0 2.5625 0.4875 3.5375 1.4625 0.975 0.975 1.4625 2.15417 1.4625 3.5375l0 2 1 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 10c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-12 0 0 0m0-2l12 0 0 0 0 0 0-10 0 0 0 0-12 0 0 0 0 0 0 10 0 0 0 0 0 0m6-3c0.55 0 1.02083-0.19583 1.4125-0.5875 0.39167-0.39167 0.5875-0.8625 0.5875-1.4125 0-0.55-0.19583-1.02083-0.5875-1.4125-0.39167-0.39167-0.8625-0.5875-1.4125-0.5875-0.55 0-1.02083 0.19583-1.4125 0.5875-0.39167 0.39167-0.5875 0.8625-0.5875 1.4125 0 0.55 0.19583 1.02083 0.5875 1.4125 0.39167 0.39167 0.8625 0.5875 1.4125 0.5875l0 0m-3-9l6 0 0-2c0-0.83333-0.29167-1.54167-0.875-2.125-0.58333-0.58333-1.29167-0.875-2.125-0.875-0.83333 0-1.54167 0.29167-2.125 0.875-0.58333 0.58333-0.875 1.29167-0.875 2.125l0 2 0 0m-3 12l0 0 0 0 0-10 0 0 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0", + "fill": "#c6c6cdff", + "width": 16, + "height": 21 + } + ] + }, + { + "type": "frame", + "id": "FC41R", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "vicoV", + "name": "Text", + "fill": "#c6c6cdff", + "content": "安全设置", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "UGkvn", + "name": "Link", + "width": "fill_container", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "G1b1Nn", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "lK3Rn", + "name": "Icon", + "geometry": "M7 20c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.35 0.09167-0.675 0.275-0.975 0.18333-0.3 0.425-0.54167 0.725-0.725l0-2.3-3 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-2.3c-0.3-0.15-0.54167-0.375-0.725-0.675-0.18333-0.3-0.275-0.64167-0.275-1.025 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.38333-0.09167 0.71667-0.275 1-0.18333 0.28333-0.425 0.51667-0.725 0.7l0 2.3 0 0 0 0 3 0 0-8-2 0 3-4 3 4-2 0 0 8 3 0 0 0 0 0 0-2-1 0 0-4 4 0 0 4-1 0 0 2c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-3 0 0 2.3c0.31667 0.16667 0.5625 0.4 0.7375 0.7 0.175 0.3 0.2625 0.63333 0.2625 1 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0", + "fill": "#c6c6cdff", + "width": 14, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "X6QcJR", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "nAqaH", + "name": "Text", + "fill": "#c6c6cdff", + "content": "U盘数据导入", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "h7ZDm", + "name": "Background+HorizontalBorder", + "width": "fill_container", + "fill": "#1b1b1dff", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "YMOLH", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "k0eis", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "dSN8H", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Gvqm0", + "name": "设备序列号", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "设备序列号", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "zlGCO", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "cr4Ji", + "name": "SN-2024-X990-WTDS", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "SN-2024-X990-WTDS", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.699999988079071 + } + ] + } + ] + }, + { + "type": "frame", + "id": "ekfoF", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "yA0hx", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "DBm6S", + "name": "最后在线时间", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "最后在线时间", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "J4THqh", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "E7Iym4", + "name": "2024-10-27 14:22:05", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "2024-10-27 14:22:05", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.699999988079071 + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "KBVNr", + "name": "Main Content Area", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "fill": "#131315ff", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 40, + "children": [ + { + "type": "frame", + "id": "CVvMm", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "Gbs1N", + "name": "Section: Update & Maintenance", + "width": "fill_container", + "height": 202, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "mAadj", + "x": 0, + "y": 0, + "name": "Section", + "width": 452.69500732421875, + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "gap": 18, + "padding": 32, + "justifyContent": "space_between", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "ov7BB", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 3.8499999046325684, + "padding": [ + 0, + 0.009999999776482582, + 0, + 0 + ], + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "d4uIr", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "xvVIq", + "name": "Overlay", + "width": 48, + "height": 48, + "fill": "#bec6e01a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "RZnX7", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "vcTeZ", + "name": "Icon", + "geometry": "M2 22c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-18c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l10 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 3.1c0.3 0.11667 0.54167 0.3 0.725 0.55 0.18333 0.25 0.275 0.53333 0.275 0.85l0 2c0 0.31667-0.09167 0.6-0.275 0.85-0.18333 0.25-0.425 0.43333-0.725 0.55l0 10.1c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-10 0 0 0m0-2l10 0 0 0 0 0 0-18 0 0 0 0-10 0 0 0 0 0 0 18 0 0 0 0 0 0m0 0l0 0 0 0 0-18 0 0 0 0 0 0 0 0 0 0 0 18 0 0 0 0 0 0 0 0m5-5l4-4-1.4-1.4-1.6 1.55 0-4.15-2 0 0 4.15-1.6-1.55-1.4 1.4 4 4 0 0", + "fill": "#bec6e0ff", + "width": 15, + "height": 22 + } + ] + } + ] + }, + { + "type": "frame", + "id": "oUuLt", + "name": "Container", + "width": "fit_content(218.02000427246094)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "fo3l4", + "name": "Heading 2", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "cTkRx", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "软件系统更新", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "oZc4U", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "W6p9G1", + "name": "Text", + "fill": "#c6c6cdff", + "content": "当前运行版本: V1.2.0-STABLE", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "IzSaH", + "name": "Button", + "fill": "#0566d9ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 10, + 24 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ejSe7", + "name": "Text", + "fill": "#e6ecffff", + "content": "检查更新", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "vGKmA", + "name": "Margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 32, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "IJEZj", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "fEji9", + "name": "Container", + "width": "fill_container", + "height": "fit_content(16)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 265.0799865722656, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "ZtGBV", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "spO4g", + "name": "Text", + "fill": "#909097ff", + "content": "磁盘检查", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "A0p5w9", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "RH1y4", + "name": "Text", + "fill": "#909097ff", + "content": "100% 正常", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "Yb7oa", + "name": "Background", + "clip": true, + "width": "fill_container", + "height": 6, + "fill": "#353436ff", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "justifyContent": "center", + "children": [ + { + "type": "rectangle", + "id": "Gtcjz", + "name": "Background", + "fill": "#10b981ff", + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "XcYft", + "x": 468.69500732421875, + "y": 0, + "name": "Section", + "width": 452.69500732421875, + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "padding": 32, + "justifyContent": "space_between", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "fNrwK", + "name": "Margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 24, + 0 + ], + "children": [ + { + "type": "frame", + "id": "RmD0S", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "K01n7I", + "name": "Overlay", + "width": 48, + "height": 48, + "fill": "#dec29a1a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "iKFbV", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "AxQgx", + "name": "Icon", + "geometry": "M10.0125 20c-1.375 0-2.67083-0.2625-3.8875-0.7875-1.21667-0.525-2.27917-1.24167-3.1875-2.15-0.90833-0.90833-1.625-1.97083-2.15-3.1875-0.525-1.21667-0.7875-2.5125-0.7875-3.8875 0-1.375 0.2625-2.66667 0.7875-3.875 0.525-1.20833 1.24167-2.26667 2.15-3.175 0.90833-0.90833 1.97083-1.625 3.1875-2.15 1.21667-0.525 2.5125-0.7875 3.8875-0.7875 1.375 0 2.66667 0.2625 3.875 0.7875 1.20833 0.525 2.26667 1.24167 3.175 2.15 0.90833 0.90833 1.625 1.96667 2.15 3.175 0.525 1.20833 0.7875 2.5 0.7875 3.875 0 1.375-0.2625 2.67083-0.7875 3.8875-0.525 1.21667-1.24167 2.27917-2.15 3.1875-0.90833 0.90833-1.96667 1.625-3.175 2.15-1.20833 0.525-2.5 0.7875-3.875 0.7875l0 0m-0.0125-2.05c0.43333-0.6 0.80833-1.225 1.125-1.875 0.31667-0.65 0.575-1.34167 0.775-2.075l-3.8 0c0.2 0.73333 0.45833 1.425 0.775 2.075 0.31667 0.65 0.69167 1.275 1.125 1.875l0 0m-2.6-0.4c-0.3-0.55-0.5625-1.12083-0.7875-1.7125-0.225-0.59167-0.4125-1.20417-0.5625-1.8375l-2.95 0c0.48333 0.83333 1.0875 1.55833 1.8125 2.175 0.725 0.61667 1.55417 1.075 2.4875 1.375l0 0m5.2 0c0.93333-0.3 1.7625-0.75833 2.4875-1.375 0.725-0.61667 1.32917-1.34167 1.8125-2.175l-2.95 0c-0.15 0.63333-0.3375 1.24583-0.5625 1.8375-0.225 0.59167-0.4875 1.1625-0.7875 1.7125l0 0m-10.35-5.55l3.4 0c-0.05-0.33333-0.0875-0.6625-0.1125-0.9875-0.025-0.325-0.0375-0.6625-0.0375-1.0125 0-0.35 0.0125-0.6875 0.0375-1.0125 0.025-0.325 0.0625-0.65417 0.1125-0.9875l-3.4 0c-0.08333 0.33333-0.14583 0.6625-0.1875 0.9875-0.04167 0.325-0.0625 0.6625-0.0625 1.0125 0 0.35 0.02083 0.6875 0.0625 1.0125 0.04167 0.325 0.10417 0.65417 0.1875 0.9875l0 0m5.4 0l4.7 0c0.05-0.33333 0.0875-0.6625 0.1125-0.9875 0.025-0.325 0.0375-0.6625 0.0375-1.0125 0-0.35-0.0125-0.6875-0.0375-1.0125-0.025-0.325-0.0625-0.65417-0.1125-0.9875l-4.7 0c-0.05 0.33333-0.0875 0.6625-0.1125 0.9875-0.025 0.325-0.0375 0.6625-0.0375 1.0125 0 0.35 0.0125 0.6875 0.0375 1.0125 0.025 0.325 0.0625 0.65417 0.1125 0.9875l0 0m6.7 0l3.4 0c0.08333-0.33333 0.14583-0.6625 0.1875-0.9875 0.04167-0.325 0.0625-0.6625 0.0625-1.0125 0-0.35-0.02083-0.6875-0.0625-1.0125-0.04167-0.325-0.10417-0.65417-0.1875-0.9875l-3.4 0c0.05 0.33333 0.0875 0.6625 0.1125 0.9875 0.025 0.325 0.0375 0.6625 0.0375 1.0125 0 0.35-0.0125 0.6875-0.0375 1.0125-0.025 0.325-0.0625 0.65417-0.1125 0.9875l0 0m-0.4-6l2.95 0c-0.48333-0.83333-1.0875-1.55833-1.8125-2.175-0.725-0.61667-1.55417-1.075-2.4875-1.375 0.3 0.55 0.5625 1.12083 0.7875 1.7125 0.225 0.59167 0.4125 1.20417 0.5625 1.8375l0 0m-5.85 0l3.8 0c-0.2-0.73333-0.45833-1.425-0.775-2.075-0.31667-0.65-0.69167-1.275-1.125-1.875-0.43333 0.6-0.80833 1.225-1.125 1.875-0.31667 0.65-0.575 1.34167-0.775 2.075l0 0m-5 0l2.95 0c0.15-0.63333 0.3375-1.24583 0.5625-1.8375 0.225-0.59167 0.4875-1.1625 0.7875-1.7125-0.93333 0.3-1.7625 0.75833-2.4875 1.375-0.725 0.61667-1.32917 1.34167-1.8125 2.175l0 0", + "fill": "#dec29aff", + "width": 20, + "height": 20 + } + ] + } + ] + }, + { + "type": "frame", + "id": "RGJ9g", + "name": "Heading 2", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "gVTQJ", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "界面语言设置", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "k8IOw", + "name": "Container", + "width": "fill_container", + "height": 64, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "e5SKZ", + "x": 0, + "y": 0, + "name": "Button", + "fill": "#adc6ff1a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#adc6ffff" + }, + "gap": 12, + "padding": [ + 16, + 42.66999816894531 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "gLZvz", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "TExnY", + "name": "Icon", + "geometry": "M7.16667 12.16667l5.875-5.875-1.16667-1.16667-4.70833 4.70833-2.375-2.375-1.16667 1.16667 3.54167 3.54167 0 0m1.16666 4.5c-1.15278 0-2.23611-0.21875-3.25-0.65625-1.01389-0.4375-1.89583-1.03125-2.64583-1.78125-0.75-0.75-1.34375-1.63195-1.78125-2.64584-0.4375-1.01389-0.65625-2.09722-0.65625-3.25 0-1.15278 0.21875-2.23611 0.65625-3.25 0.4375-1.01389 1.03125-1.89583 1.78125-2.64583 0.75-0.75 1.63194-1.34375 2.64583-1.78125 1.01389-0.4375 2.09722-0.65625 3.25-0.65625 1.15278 0 2.23611 0.21875 3.25 0.65625 1.01389 0.4375 1.89583 1.03125 2.64584 1.78125 0.75 0.75 1.34375 1.63194 1.78125 2.64583 0.4375 1.01389 0.65625 2.09722 0.65625 3.25 0 1.15278-0.21875 2.23611-0.65625 3.25-0.4375 1.01389-1.03125 1.89583-1.78125 2.64584-0.75 0.75-1.63195 1.34375-2.64584 1.78125-1.01389 0.4375-2.09722 0.65625-3.25 0.65625l0 0m0-1.66667c1.86111 0 3.4375-0.64583 4.72917-1.9375 1.29167-1.29167 1.9375-2.86806 1.9375-4.72917 0-1.86111-0.64583-3.4375-1.9375-4.72916-1.29167-1.29167-2.86806-1.9375-4.72917-1.9375-1.86111 0-3.4375 0.64583-4.72916 1.9375-1.29167 1.29167-1.9375 2.86806-1.9375 4.72916 0 1.86111 0.64583 3.4375 1.9375 4.72917 1.29167 1.29167 2.86806 1.9375 4.72916 1.9375l0 0m0-6.66667l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#adc6ffff", + "width": 16.66666603088379, + "height": 16.66666603088379 + } + ] + }, + { + "type": "text", + "id": "oRr4M", + "name": "Text", + "fill": "#adc6ffff", + "content": "简体中文", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "H8Pq8v", + "x": 201.34750366210938, + "y": 0, + "name": "Button", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 19, + 64.4000015258789, + 19, + 64.41000366210938 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "mzBki", + "name": "Text", + "fill": "#c6c6cdff", + "content": "English", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "pyul9", + "name": "Section: Security & Access", + "width": "fill_container", + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "gap": 32, + "padding": 32, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "De2Bb", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "k2o6D", + "name": "Overlay", + "width": 48, + "height": 48, + "fill": "#ffb4ab1a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "T9Ltz", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "x7Er7", + "name": "Icon", + "geometry": "M8 20c-2.31667-0.58333-4.22917-1.9125-5.7375-3.9875-1.50833-2.075-2.2625-4.37917-2.2625-6.9125l0-6.1 8-3 8 3 0 6.1c0 2.53333-0.75417 4.8375-2.2625 6.9125-1.50833 2.075-3.42083 3.40416-5.7375 3.9875l0 0m0-2.1c1.61667-0.5 2.96667-1.4875 4.05-2.9625 1.08333-1.475 1.71667-3.12083 1.9-4.9375l-5.95 0 0-7.875-6 2.25 0 4.725c0 0.18333 0 0.33333 0 0.45 0 0.11667 0.01667 0.26667 0.05 0.45l5.95 0 0 7.9 0 0", + "fill": "#ffb4abff", + "width": 16, + "height": 20 + } + ] + } + ] + }, + { + "type": "frame", + "id": "O4AzN", + "name": "Heading 2", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "AeeOL", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "安全与访问控制", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "ITpQ1", + "name": "Container", + "width": "fill_container", + "height": 90, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "TaLyl", + "x": 0, + "y": 0, + "name": "Background+Border", + "width": 269.1300048828125, + "fill": "#1b1b1dff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "gap": 16, + "padding": 16, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "H55uT", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "r7yT1T", + "name": "管理权限", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "管理权限", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "f78t8", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 115.11000061035156, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "NSN5K", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Zcici", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "修改管理密码", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "ENx9I", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "GDP54", + "name": "Icon", + "geometry": "M2 16l1.425 0 9.775-9.775-1.425-1.425-9.775 9.775 0 1.425 0 0m-2 2l0-4.25 13.2-13.175c0.2-0.18333 0.42083-0.325 0.6625-0.425 0.24167-0.1 0.49583-0.15 0.7625-0.15 0.26667 0 0.525 0.05 0.775 0.15 0.25 0.1 0.46667 0.25 0.65 0.45l1.375 1.4c0.2 0.18333 0.34583 0.4 0.4375 0.65 0.09167 0.25 0.1375 0.5 0.1375 0.75 0 0.26667-0.04583 0.52083-0.1375 0.7625-0.09167 0.24167-0.2375 0.4625-0.4375 0.6625l-13.175 13.175-4.25 0 0 0m16-14.6l0 0-1.4-1.4 0 0 1.4 1.4 0 0m-3.525 2.125l-0.7-0.725 0 0 1.425 1.425 0 0-0.725-0.7 0 0", + "fill": "#bec6e0ff", + "width": 18, + "height": 18 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "smz3n", + "x": 293.1300048828125, + "y": 0, + "name": "Background+Border", + "width": 269.1300048828125, + "fill": "#1b1b1dff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "gap": 16, + "padding": 16, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "mpSbs", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "aIHwV", + "name": "SSH 访问", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "SSH 访问", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "Yvowx", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 143.1300048828125, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "YI6Vn", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "y43ff0", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "已禁用", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "d8G047", + "name": "Label", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "gGgLc", + "name": "Background", + "fill": "#353436ff", + "width": 44, + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "rectangle", + "cornerRadius": 12, + "id": "wVGMf", + "layoutPosition": "absolute", + "x": 2, + "y": 2, + "name": "Background+Border", + "fill": "#ffffffff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#d1d5dbff" + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "r0O2Q", + "x": 586.260009765625, + "y": 0, + "name": "Background+Border", + "width": 269.1300048828125, + "fill": "#1b1b1dff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "gap": 16, + "padding": 16, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "VV9ne", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "NXyhZ", + "name": "登录超时", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "登录超时", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "MalLg", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 154.35000610351562, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "EEEqJ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Qfn9T", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "30 分钟", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + } + ] + }, + { + "type": "frame", + "id": "H5Va6w", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "TWKRm", + "name": "Icon", + "geometry": "M6 7.4l-6-6 1.4-1.4 4.6 4.6 4.6-4.6 1.4 1.4-6 6 0 0", + "fill": "#909097ff", + "width": 12, + "height": 7.400000095367432 + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "ukRDb", + "name": "Section: USB Data Management", + "clip": true, + "width": "fill_container", + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "padding": 32, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "pnqwH", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 32, + "padding": [ + 0, + 1.1368683772161603e-13, + 0, + 0 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "stMG8", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "T6bEP", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "lFPgh", + "name": "Overlay", + "width": 64, + "height": 64, + "fill": "#10b9811a", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "l8vPG", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "oRZAf", + "name": "Icon", + "geometry": "M8.75 25c-0.6875 0-1.27604-0.24479-1.76563-0.73438-0.48958-0.48958-0.73438-1.07813-0.73437-1.76562 0-0.4375 0.11458-0.84375 0.34375-1.21875 0.22917-0.375 0.53125-0.67708 0.90625-0.90625l0-2.875-3.75 0c-0.6875 0-1.27604-0.24479-1.76563-0.73438-0.48958-0.48958-0.73438-1.07813-0.73437-1.76562l0-2.875c-0.375-0.1875-0.67708-0.46875-0.90625-0.84375-0.22917-0.375-0.34375-0.80208-0.34375-1.28125 0-0.6875 0.24479-1.27604 0.73438-1.76563 0.48958-0.48958 1.07813-0.73438 1.76562-0.73437 0.6875 0 1.27604 0.24479 1.76563 0.73438 0.48958 0.48958 0.73438 1.07813 0.73437 1.76562 0 0.47917-0.11458 0.89583-0.34375 1.25-0.22917 0.35417-0.53125 0.64583-0.90625 0.875l0 2.875 0 0 0 0 3.75 0 0-10-2.5 0 3.75-5 3.75 5-2.5 0 0 10 3.75 0 0 0 0 0 0-2.5-1.25 0 0-5 5 0 0 5-1.25 0 0 2.5c0 0.6875-0.24479 1.27604-0.73438 1.76563-0.48958 0.48958-1.07813 0.73438-1.76562 0.73437l-3.75 0 0 2.875c0.39583 0.20833 0.70313 0.5 0.92188 0.875 0.21875 0.375 0.32813 0.79167 0.32812 1.25 0 0.6875-0.24479 1.27604-0.73438 1.76563-0.48958 0.48958-1.07813 0.73438-1.76562 0.73437l0 0", + "fill": "#10b981ff", + "width": 17.5, + "height": 25 + } + ] + } + ] + }, + { + "type": "rectangle", + "cornerRadius": 12, + "id": "TK2rs", + "layoutPosition": "absolute", + "x": 48, + "y": 48, + "name": "Background+Border", + "fill": "#10b981ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 4, + "fill": "#1e293bff" + } + } + ] + }, + { + "type": "frame", + "id": "Fi3PS", + "name": "Container", + "width": "fit_content(353.80999755859375)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "M2EaRg", + "name": "Heading 2", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "fKiSL", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "U盘外部数据导入", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "pbotJ", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Iely7", + "name": "Overlay", + "fill": "#10b98133", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 83.44000244140625, + 4, + 12 + ], + "children": [ + { + "type": "text", + "id": "i6qTDr", + "name": "Text", + "fill": "#10b981ff", + "content": "检测到设备:\nSONY_64GB_ULTRA", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600", + "letterSpacing": 0.699999988079071 + } + ] + }, + { + "type": "frame", + "id": "EkCiZ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 41.939998626708984, + 0, + 0 + ], + "children": [ + { + "type": "text", + "id": "A611NM", + "name": "Text", + "fill": "#c6c6cdff", + "content": "文件系统:\nFAT32", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "o2xfU", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "children": [ + { + "type": "frame", + "id": "YPwaG", + "name": "Button", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#adc6ffff" + }, + "gap": 12.079999923706055, + "padding": [ + 25.5, + 36.09000015258789, + 26.5, + 32 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "MqCNf", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "IeqPi", + "name": "Icon", + "geometry": "M1.66667 13.33333c-0.45833 0-0.85069-0.16319-1.17709-0.48958-0.32639-0.32639-0.48958-0.71875-0.48958-1.17708l0-10c0-0.45833 0.16319-0.85069 0.48958-1.17709 0.32639-0.32639 0.71875-0.48958 1.17709-0.48958l5 0 1.66666 1.66667 6.66667 0c0.45833 0 0.85069 0.16319 1.17708 0.48958 0.32639 0.32639 0.48958 0.71875 0.48959 1.17708l-9.02084 0-1.66666-1.66666-4.3125 0 0 0 0 0 0 10 0 0 0 0 2-6.66667 14.25 0-2.14584 7.14583c-0.11111 0.36111-0.31597 0.64931-0.61458 0.86459-0.29861 0.21528-0.62847 0.32292-0.98958 0.32291l-12.5 0 0 0m1.75-1.66666l10.75 0 1.5-5-10.75 0-1.5 5 0 0m0 0l1.5-5 0 0-1.5 5 0 0 0 0m-1.75-8.33334l0-1.66666 0 0 0 0 0 0 0 1.66666 0 0 0 0", + "fill": "#adc6ffff", + "width": 17.91666603088379, + "height": 13.333333015441895 + } + ] + }, + { + "type": "text", + "id": "ZfDgq", + "name": "Text", + "fill": "#adc6ffff", + "content": "浏览文\n件", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "F6dOj", + "name": "Button", + "fill": "#0566d9ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "padding": [ + 16, + 40 + ], + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "gvVyB", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 213.8000030517578, + "height": 88, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 8.75, + "spread": -6 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 20 + }, + "blur": 21.875, + "spread": -5 + } + ] + }, + { + "type": "frame", + "id": "qCruR", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "p2Btw", + "name": "Icon", + "geometry": "M7 16l0-8.15-2.6 2.6-1.4-1.45 5-5 5 5-1.4 1.45-2.6-2.6 0 8.15-2 0 0 0m-7-11l0-3c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l12 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 3-2 0 0-3 0 0 0 0-12 0 0 0 0 0 0 3-2 0 0 0", + "fill": "#e6ecffff", + "width": 16, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "I7IuM", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 4.889999866485596 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Dzfrt", + "name": "Text", + "fill": "#e6ecffff", + "content": "批量导入程\n序", + "lineHeight": 1.5555555555555556, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 18, + "fontWeight": "500", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "oTqyu", + "name": "Footer Component (Integrated into main content bottom)", + "opacity": 0.6000000238418579, + "width": "fill_container", + "height": 65, + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "qcer1", + "x": 0, + "y": 33, + "name": "Container", + "width": 452.69500732421875, + "height": 32, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "pYdZV", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "YWDa3", + "name": "Icon", + "geometry": "M6.75 11.25l1.5 0 0-4.5-1.5 0 0 4.5 0 0m0.75-6c0.2125 0 0.39063-0.07187 0.53438-0.21562 0.14375-0.14375 0.21562-0.32188 0.21562-0.53438 0-0.2125-0.07187-0.39063-0.21562-0.53437-0.14375-0.14375-0.32188-0.21563-0.53438-0.21563-0.2125 0-0.39063 0.07188-0.53438 0.21563-0.14375 0.14375-0.21562 0.32187-0.21562 0.53437 0 0.2125 0.07187 0.39063 0.21562 0.53438 0.14375 0.14375 0.32188 0.21562 0.53438 0.21562l0 0m0 9.75c-1.0375 0-2.0125-0.19688-2.925-0.59062-0.9125-0.39375-1.70625-0.92813-2.38125-1.60313-0.675-0.675-1.20937-1.46875-1.60313-2.38125-0.39375-0.9125-0.59062-1.8875-0.59062-2.925 0-1.0375 0.19687-2.0125 0.59062-2.925 0.39375-0.9125 0.92813-1.70625 1.60313-2.38125 0.675-0.675 1.46875-1.20937 2.38125-1.60313 0.9125-0.39375 1.8875-0.59062 2.925-0.59062 1.0375 0 2.0125 0.19687 2.925 0.59062 0.9125 0.39375 1.70625 0.92813 2.38125 1.60313 0.675 0.675 1.20937 1.46875 1.60313 2.38125 0.39375 0.9125 0.59062 1.8875 0.59062 2.925 0 1.0375-0.19688 2.0125-0.59062 2.925-0.39375 0.9125-0.92813 1.70625-1.60313 2.38125-0.675 0.675-1.46875 1.20937-2.38125 1.60313-0.9125 0.39375-1.8875 0.59062-2.925 0.59062l0 0m0-1.5c1.675 0 3.09375-0.58125 4.25625-1.74375 1.1625-1.1625 1.74375-2.58125 1.74375-4.25625 0-1.675-0.58125-3.09375-1.74375-4.25625-1.1625-1.1625-2.58125-1.74375-4.25625-1.74375-1.675 0-3.09375 0.58125-4.25625 1.74375-1.1625 1.1625-1.74375 2.58125-1.74375 4.25625 0 1.675 0.58125 3.09375 1.74375 4.25625 1.1625 1.1625 2.58125 1.74375 4.25625 1.74375l0 0m0-6l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#909097ff", + "width": 15, + "height": 15 + } + ] + }, + { + "type": "frame", + "id": "oh9IN", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 23.350000381469727, + 0, + 0 + ], + "children": [ + { + "type": "text", + "id": "n8rEaY", + "name": "Text", + "fill": "#909097ff", + "content": "系统架构: ARM64-V8A | 核心温度: 42°C | 运行内存: 12.4 / 32\nGB", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "OeTzV", + "x": 468.69500732421875, + "y": 33, + "name": "Container", + "width": 452.69500732421875, + "height": 32, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "justifyContent": "end", + "children": [ + { + "type": "frame", + "id": "WwjXE", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "ji8jM", + "name": "Text", + "fill": "#909097ff", + "content": "© 2024 Precision Control Systems", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "hqMOa", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "i8IzzJ", + "name": "Text", + "fill": "#909097ff", + "content": "版本号: V1.2.0-R2410", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Eu7q9", + "x": 1412, + "y": 100, + "name": "程序管理列表 - 桌面端", + "width": 1280, + "fill": [ + "#ffffffff", + "#131315ff" + ], + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 64, + 0, + 96, + 0 + ], + "children": [ + { + "type": "frame", + "id": "nCLsv", + "name": "Main Content Canvas - Optimized for 1920x1080", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 32, + "children": [ + { + "type": "frame", + "id": "pVE1a", + "name": "Action Header Section:margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 32, + 0 + ], + "children": [ + { + "type": "frame", + "id": "K9dbsH", + "name": "Action Header Section", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 182.58999633789062, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "UvXUE", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "OoCtZ", + "name": "Heading 2", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "NPX5C", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "程序管理", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 24, + "fontWeight": "500" + } + ] + }, + { + "type": "rectangle", + "id": "RYjhd", + "name": "Vertical Divider", + "fill": "#45464dff", + "width": 1, + "height": 32, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "frame", + "id": "K5Ki7J", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "mfCNk", + "name": "Input", + "clip": true, + "width": 400, + "fill": "#1f1f21ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 12, + 24, + 10, + 48 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "d6LwR1", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "RsPQP", + "name": "通过程序名称或ID搜索...", + "fill": "#6b7280ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "通过程序名称或ID搜索...", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "VMldB", + "layoutPosition": "absolute", + "x": 16, + "y": 11, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "B7Cga", + "name": "Icon", + "geometry": "M16.6 18l-6.3-6.3c-0.5 0.4-1.075 0.71667-1.725 0.95-0.65 0.23333-1.34167 0.35-2.075 0.35-1.81667 0-3.35417-0.62917-4.6125-1.8875-1.25833-1.25833-1.8875-2.79583-1.8875-4.6125 0-1.81667 0.62917-3.35417 1.8875-4.6125 1.25833-1.25833 2.79583-1.8875 4.6125-1.8875 1.81667 0 3.35417 0.62917 4.6125 1.8875 1.25833 1.25833 1.8875 2.79583 1.8875 4.6125 0 0.73333-0.11667 1.425-0.35 2.075-0.23333 0.65-0.55 1.225-0.95 1.725l6.3 6.3-1.4 1.4 0 0m-10.1-7c1.25 0 2.3125-0.4375 3.1875-1.3125 0.875-0.875 1.3125-1.9375 1.3125-3.1875 0-1.25-0.4375-2.3125-1.3125-3.1875-0.875-0.875-1.9375-1.3125-3.1875-1.3125-1.25 0-2.3125 0.4375-3.1875 1.3125-0.875 0.875-1.3125 1.9375-1.3125 3.1875 0 1.25 0.4375 2.3125 1.3125 3.1875 0.875 0.875 1.9375 1.3125 3.1875 1.3125l0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "hn4iU", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "PRxij", + "name": "Button", + "fill": "#1f1f21ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "gap": 8, + "padding": [ + 10, + 24 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "HaqXN", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "R5vDj", + "name": "Icon", + "geometry": "M7 12l0-8.15-2.6 2.6-1.4-1.45 5-5 5 5-1.4 1.45-2.6-2.6 0 8.15-2 0 0 0m-5 4c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-3 2 0 0 3 0 0 0 0 12 0 0 0 0 0 0-3 2 0 0 3c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-12 0 0 0", + "fill": "#e4e2e4ff", + "width": 16, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "aFBUh", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "A5RgzR", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "IMPORT PROGRAM", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "qffaS", + "name": "Button", + "fill": "#0566d9ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 8, + "padding": [ + 10, + 32 + ], + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "wN34U", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 251.3599853515625, + "height": 36, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 4 + }, + "blur": 5.25, + "spread": -4 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 13.125, + "spread": -3 + } + ] + }, + { + "type": "frame", + "id": "Swt0R", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "P6gTG", + "name": "Icon", + "geometry": "M6 8l-6 0 0-2 6 0 0-6 2 0 0 6 6 0 0 2-6 0 0 6-2 0 0-6 0 0", + "fill": "#e6ecffff", + "width": 14, + "height": 14 + } + ] + }, + { + "type": "frame", + "id": "LVbwG", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Z3kCE", + "name": "Text", + "fill": "#e6ecffff", + "content": "CREATE NEW PROGRAM", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "f4huC", + "name": "Data Table Section", + "clip": true, + "width": "fill_container", + "fill": "#1e293bb2", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#33415580" + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#00000040", + "offset": { + "x": 0, + "y": 25 + }, + "blur": 43.75, + "spread": -12 + }, + { + "type": "background_blur", + "radius": 10.5 + } + ], + "layout": "vertical", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "KL6mZ", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 201, + 0 + ], + "children": [ + { + "type": "frame", + "id": "yu2CB", + "name": "Table", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "kq25N", + "name": "Header", + "width": "fill_container", + "fill": "#2a2a2bff", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#0000000d", + "offset": { + "x": 0, + "y": 1 + }, + "blur": 1.75 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "kEyoY", + "name": "Row", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "frame", + "id": "QfPR5", + "name": "Cell", + "width": 80, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "tfWvu", + "name": "Input", + "fill": "#1f1f21ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + } + } + ] + }, + { + "type": "frame", + "id": "W5NnGW", + "name": "Cell", + "width": 160, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 25.5, + 24, + 26, + 24 + ], + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "v03xKU", + "name": "Text", + "fill": "#909097ff", + "content": "ID", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "ftr8w", + "name": "Cell", + "width": 366, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 25.5, + 24, + 26, + 24 + ], + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "Xnc0a", + "name": "Text", + "fill": "#909097ff", + "content": "PROGRAM NAME", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "c68G8", + "name": "Cell", + "width": 256, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 25.5, + 24, + 26, + 24 + ], + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "U3hdY", + "name": "Text", + "fill": "#909097ff", + "content": "CREATED TIME", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "SYy0W", + "name": "Cell", + "width": 192, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 25.5, + 24, + 26, + 24 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "CYfoz", + "name": "Text", + "fill": "#909097ff", + "content": "STATUS", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "FQQRo", + "name": "Cell", + "width": 160, + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 25.5, + 24, + 26, + 24 + ], + "alignItems": "end", + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "a7e0if", + "name": "Text", + "fill": "#909097ff", + "content": "ACTIONS", + "lineHeight": 1.3333333333333333, + "textAlign": "right", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "KjrA1", + "name": "Body", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": -1, + "children": [ + { + "type": "frame", + "id": "N4JUDk", + "name": "Row 1", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "padding": [ + 0, + 24, + 0, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "WR7Oz", + "name": "Data", + "width": 80, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24 + ], + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "zfAV1", + "name": "Input", + "fill": "#1f1f21ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + } + } + ] + }, + { + "type": "frame", + "id": "TEVnl", + "name": "Data", + "width": 160, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34, + 24, + 35, + 24 + ], + "children": [ + { + "type": "text", + "id": "KKA4R", + "name": "Text", + "fill": "#bec6e0ff", + "content": "PRG-001", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600", + "letterSpacing": 0.699999988079071 + } + ] + }, + { + "type": "frame", + "id": "U9k0D", + "name": "Data", + "width": 342, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 0, + 24 + ], + "children": [ + { + "type": "frame", + "id": "XiVMI", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "GDic0", + "name": "常规城市污水筛查", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "常规城市污水筛查", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "QuXMJ", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Zbi2I", + "name": "标准前处理流程,包含离心与固相萃取", + "fill": "#c6c6cdff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "标准前处理流程,包含离心与固相萃取", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "K6uBoJ", + "name": "Data", + "width": 280, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24, + 34.5, + 48 + ], + "children": [ + { + "type": "text", + "id": "bZuKm", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-10-24 14:20:05", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "R4wkaq", + "name": "Data", + "width": 192, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 29.5, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "OIPBR", + "name": "Overlay+Border", + "fill": "#10b9811a", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#10b9814d" + }, + "gap": 6, + "padding": [ + 6, + 16 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "MPrEz", + "name": "Background", + "fill": "#10b981ff", + "width": 8, + "height": 8, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "text", + "id": "Y2fgU", + "name": "Text", + "fill": "#10b981ff", + "content": "ACTIVE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "e2cU9l", + "name": "Data", + "width": 136, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 11.989999771118164, + "padding": [ + 0, + 0, + 0, + 24 + ], + "justifyContent": "end", + "children": [ + { + "type": "frame", + "id": "oh9tP", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "w7KeD", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "qrmFd", + "name": "Icon", + "geometry": "M2 16l1.425 0 9.775-9.775-1.425-1.425-9.775 9.775 0 1.425 0 0m-2 2l0-4.25 13.2-13.175c0.2-0.18333 0.42083-0.325 0.6625-0.425 0.24167-0.1 0.49583-0.15 0.7625-0.15 0.26667 0 0.525 0.05 0.775 0.15 0.25 0.1 0.46667 0.25 0.65 0.45l1.375 1.4c0.2 0.18333 0.34583 0.4 0.4375 0.65 0.09167 0.25 0.1375 0.5 0.1375 0.75 0 0.26667-0.04583 0.52083-0.1375 0.7625-0.09167 0.24167-0.2375 0.4625-0.4375 0.6625l-13.175 13.175-4.25 0 0 0m16-14.6l0 0-1.4-1.4 0 0 1.4 1.4 0 0m-3.525 2.125l-0.7-0.725 0 0 1.425 1.425 0 0-0.725-0.7 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "D72NeH", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "e032M", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "S1Y6C", + "name": "Icon", + "geometry": "M3 18c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-13-1 0 0-2 5 0 0-1 6 0 0 1 5 0 0 2-1 0 0 13c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-10 0 0 0m10-15l-10 0 0 13 0 0 0 0 10 0 0 0 0 0 0-13 0 0m-8 11l2 0 0-9-2 0 0 9 0 0m4 0l2 0 0-9-2 0 0 9 0 0m-6-11l0 0 0 13 0 0 0 0 0 0 0 0 0 0 0-13 0 0", + "fill": "#909097ff", + "width": 16, + "height": 18 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "P6M4TV", + "name": "Row 2", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464d4d" + }, + "padding": [ + 0, + 24, + 0, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "dkgbH", + "name": "Data", + "width": 80, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24 + ], + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "t7w9L", + "name": "Input", + "fill": "#1f1f21ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + } + } + ] + }, + { + "type": "frame", + "id": "OTGIg", + "name": "Data", + "width": 160, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34, + 24, + 35, + 24 + ], + "children": [ + { + "type": "text", + "id": "YrPyy", + "name": "Text", + "fill": "#bec6e0ff", + "content": "PRG-002", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600", + "letterSpacing": 0.699999988079071 + } + ] + }, + { + "type": "frame", + "id": "W7bGJ", + "name": "Data", + "width": 342, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 0, + 24 + ], + "children": [ + { + "type": "frame", + "id": "bNkPx", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "Fs4sg", + "name": "特定区域深层检测", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "特定区域深层检测", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "aIZzD", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "d12ez", + "name": "高浓度样本稀释处理流程", + "fill": "#c6c6cdff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "高浓度样本稀释处理流程", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "ir7jU", + "name": "Data", + "width": 280, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24, + 34.5, + 48 + ], + "children": [ + { + "type": "text", + "id": "AXC9v", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-11-02 09:15:22", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "E8vRpB", + "name": "Data", + "width": 192, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 29.5, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "XmNHV", + "name": "Overlay+Border", + "fill": "#45464d33", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d80" + }, + "padding": [ + 6, + 16 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "cUm0K", + "name": "Text", + "fill": "#909097ff", + "content": "INACTIVE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "rLTBg", + "name": "Data", + "width": 136, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 11.989999771118164, + "padding": [ + 0, + 0, + 0, + 24 + ], + "justifyContent": "end", + "children": [ + { + "type": "frame", + "id": "wHVf9", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "eDLJw", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "T14MOH", + "name": "Icon", + "geometry": "M2 16l1.425 0 9.775-9.775-1.425-1.425-9.775 9.775 0 1.425 0 0m-2 2l0-4.25 13.2-13.175c0.2-0.18333 0.42083-0.325 0.6625-0.425 0.24167-0.1 0.49583-0.15 0.7625-0.15 0.26667 0 0.525 0.05 0.775 0.15 0.25 0.1 0.46667 0.25 0.65 0.45l1.375 1.4c0.2 0.18333 0.34583 0.4 0.4375 0.65 0.09167 0.25 0.1375 0.5 0.1375 0.75 0 0.26667-0.04583 0.52083-0.1375 0.7625-0.09167 0.24167-0.2375 0.4625-0.4375 0.6625l-13.175 13.175-4.25 0 0 0m16-14.6l0 0-1.4-1.4 0 0 1.4 1.4 0 0m-3.525 2.125l-0.7-0.725 0 0 1.425 1.425 0 0-0.725-0.7 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "aJvsQ", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "keXfe", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "izIRy", + "name": "Icon", + "geometry": "M3 18c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-13-1 0 0-2 5 0 0-1 6 0 0 1 5 0 0 2-1 0 0 13c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-10 0 0 0m10-15l-10 0 0 13 0 0 0 0 10 0 0 0 0 0 0-13 0 0m-8 11l2 0 0-9-2 0 0 9 0 0m4 0l2 0 0-9-2 0 0 9 0 0m-6-11l0 0 0 13 0 0 0 0 0 0 0 0 0 0 0-13 0 0", + "fill": "#909097ff", + "width": 16, + "height": 18 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Dsmuw", + "name": "Row 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464d4d" + }, + "padding": [ + 0, + 24, + 0, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "MaF4C", + "name": "Data", + "width": 80, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24 + ], + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "D8txfj", + "name": "Input", + "fill": "#1f1f21ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + } + } + ] + }, + { + "type": "frame", + "id": "Wf5gf", + "name": "Data", + "width": 160, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34, + 24, + 35, + 24 + ], + "children": [ + { + "type": "text", + "id": "jbCFj", + "name": "Text", + "fill": "#bec6e0ff", + "content": "PRG-003", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600", + "letterSpacing": 0.699999988079071 + } + ] + }, + { + "type": "frame", + "id": "yt2LQ", + "name": "Data", + "width": 342, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 0, + 24 + ], + "children": [ + { + "type": "frame", + "id": "lkCUn", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "WnJs1", + "name": "高精度多参数分析", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "高精度多参数分析", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "cFhLp", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "u09Ye", + "name": "多级洗脱与精确控温模式", + "fill": "#c6c6cdff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "多级洗脱与精确控温模式", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "yAPnY", + "name": "Data", + "width": 280, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24, + 34.5, + 48 + ], + "children": [ + { + "type": "text", + "id": "y0hqQD", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-11-05 16:45:10", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "S7fZ4", + "name": "Data", + "width": 192, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 29.5, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "XDvDO", + "name": "Overlay+Border", + "fill": "#10b9811a", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#10b9814d" + }, + "gap": 6, + "padding": [ + 6, + 16 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "PWHHg", + "name": "Background", + "fill": "#10b981ff", + "width": 8, + "height": 8, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "text", + "id": "V5sljU", + "name": "Text", + "fill": "#10b981ff", + "content": "ACTIVE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "gDWG3", + "name": "Data", + "width": 136, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 11.989999771118164, + "padding": [ + 0, + 0, + 0, + 24 + ], + "justifyContent": "end", + "children": [ + { + "type": "frame", + "id": "NUier", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "HdBxC", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "GZsIT", + "name": "Icon", + "geometry": "M2 16l1.425 0 9.775-9.775-1.425-1.425-9.775 9.775 0 1.425 0 0m-2 2l0-4.25 13.2-13.175c0.2-0.18333 0.42083-0.325 0.6625-0.425 0.24167-0.1 0.49583-0.15 0.7625-0.15 0.26667 0 0.525 0.05 0.775 0.15 0.25 0.1 0.46667 0.25 0.65 0.45l1.375 1.4c0.2 0.18333 0.34583 0.4 0.4375 0.65 0.09167 0.25 0.1375 0.5 0.1375 0.75 0 0.26667-0.04583 0.52083-0.1375 0.7625-0.09167 0.24167-0.2375 0.4625-0.4375 0.6625l-13.175 13.175-4.25 0 0 0m16-14.6l0 0-1.4-1.4 0 0 1.4 1.4 0 0m-3.525 2.125l-0.7-0.725 0 0 1.425 1.425 0 0-0.725-0.7 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "zLbGo", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Kr8IB", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "vDHtr", + "name": "Icon", + "geometry": "M3 18c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-13-1 0 0-2 5 0 0-1 6 0 0 1 5 0 0 2-1 0 0 13c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-10 0 0 0m10-15l-10 0 0 13 0 0 0 0 10 0 0 0 0 0 0-13 0 0m-8 11l2 0 0-9-2 0 0 9 0 0m4 0l2 0 0-9-2 0 0 9 0 0m-6-11l0 0 0 13 0 0 0 0 0 0 0 0 0 0 0-13 0 0", + "fill": "#909097ff", + "width": 16, + "height": 18 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Lq1Hd", + "name": "Row - More rows for desktop display", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464d4d" + }, + "padding": [ + 0, + 24, + 0, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "OcvXJ", + "name": "Data", + "width": 80, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24, + 34, + 24 + ], + "children": [ + { + "type": "rectangle", + "cornerRadius": 2, + "id": "NgVT9", + "name": "Input", + "fill": "#1f1f21ff", + "width": 20, + "height": 20, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + } + } + ] + }, + { + "type": "frame", + "id": "xfewO", + "name": "Data", + "width": 160, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34, + 24, + 34.5, + 24 + ], + "children": [ + { + "type": "text", + "id": "zJuEA", + "name": "Text", + "fill": "#bec6e0ff", + "content": "PRG-004", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600", + "letterSpacing": 0.699999988079071 + } + ] + }, + { + "type": "frame", + "id": "oh8nc", + "name": "Data", + "width": 342, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 0, + 24 + ], + "children": [ + { + "type": "frame", + "id": "MvJWd", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "x7oMzK", + "name": "快速筛查模式-紧急", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "快速筛查模式-紧急", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "f4yM0", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "E71eW", + "name": "缩短反应时间,提高通量", + "fill": "#c6c6cdff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "缩短反应时间,提高通量", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 12, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "PVwC7", + "name": "Data", + "width": 280, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 34.5, + 24, + 34, + 48 + ], + "children": [ + { + "type": "text", + "id": "SDRZn", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-11-08 11:30:00", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "SLXjZ", + "name": "Data", + "width": 192, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 29.5, + 24, + 29, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "X6dpVQ", + "name": "Overlay+Border", + "fill": "#45464d33", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d80" + }, + "padding": [ + 6, + 16 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "wL27M", + "name": "Text", + "fill": "#909097ff", + "content": "INACTIVE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "q734K", + "name": "Data", + "width": 136, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 11.989999771118164, + "padding": [ + 0, + 0, + 0, + 24 + ], + "justifyContent": "end", + "children": [ + { + "type": "frame", + "id": "Hamdz", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "mCrNE", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "irDQk", + "name": "Icon", + "geometry": "M2 16l1.425 0 9.775-9.775-1.425-1.425-9.775 9.775 0 1.425 0 0m-2 2l0-4.25 13.2-13.175c0.2-0.18333 0.42083-0.325 0.6625-0.425 0.24167-0.1 0.49583-0.15 0.7625-0.15 0.26667 0 0.525 0.05 0.775 0.15 0.25 0.1 0.46667 0.25 0.65 0.45l1.375 1.4c0.2 0.18333 0.34583 0.4 0.4375 0.65 0.09167 0.25 0.1375 0.5 0.1375 0.75 0 0.26667-0.04583 0.52083-0.1375 0.7625-0.09167 0.24167-0.2375 0.4625-0.4375 0.6625l-13.175 13.175-4.25 0 0 0m16-14.6l0 0-1.4-1.4 0 0 1.4 1.4 0 0m-3.525 2.125l-0.7-0.725 0 0 1.425 1.425 0 0-0.725-0.7 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "JjkHB", + "name": "Button", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "bZG9n", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "zqYtP", + "name": "Icon", + "geometry": "M3 18c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-13-1 0 0-2 5 0 0-1 6 0 0 1 5 0 0 2-1 0 0 13c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-10 0 0 0m10-15l-10 0 0 13 0 0 0 0 10 0 0 0 0 0 0-13 0 0m-8 11l2 0 0-9-2 0 0 9 0 0m4 0l2 0 0-9-2 0 0 9 0 0m-6-11l0 0 0 13 0 0 0 0 0 0 0 0 0 0 0-13 0 0", + "fill": "#909097ff", + "width": 16, + "height": 18 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "RE4YG", + "name": "Footer Action Bar (Bulk Actions)", + "width": "fill_container", + "fill": "#1f1f21ff", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "gap": 631.97998046875, + "padding": 24, + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "yo7OR", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "qrsy3", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "Selected: 0 items", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + } + ] + }, + { + "type": "frame", + "id": "K91sF", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "vllSx", + "name": "Button", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 8, + "padding": [ + 10, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "a27duJ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "ZRlbm", + "name": "Icon", + "geometry": "M6 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-12c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l9 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 12c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-9 0 0 0m0-2l9 0 0 0 0 0 0-12 0 0 0 0-9 0 0 0 0 0 0 12 0 0 0 0 0 0m-4 6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-14 2 0 0 14 0 0 0 0 11 0 0 2-11 0 0 0m4-6l0 0 0 0 0-12 0 0 0 0 0 0 0 0 0 0 0 12 0 0 0 0 0 0 0 0", + "fill": "#c6c6cdff", + "width": 17, + "height": 20 + } + ] + }, + { + "type": "text", + "id": "oHnYZ", + "name": "Text", + "fill": "#c6c6cdff", + "content": "DUPLICATE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "XHhOI", + "name": "Button", + "opacity": 0.30000001192092896, + "fill": "#ef44441a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#ef44444d" + }, + "gap": 8, + "padding": [ + 10, + 32 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "G5otF", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 222.25, + "height": 38, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 4 + }, + "blur": 5.25, + "spread": -4 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 13.125, + "spread": -3 + } + ] + }, + { + "type": "frame", + "id": "WAhHb", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "jhajy", + "name": "Icon", + "geometry": "M13 13.5l0-2 4 0 0 2-4 0 0 0m0-8l0-2 7 0 0 2-7 0 0 0m0 4l0-2 6 0 0 2-6 0 0 0m-12-6l-1 0 0-2 4 0 0-1.5 4 0 0 1.5 4 0 0 2-1 0 0 9c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-9 0 0m2 0l0 9 0 0 0 0 6 0 0 0 0 0 0-9-6 0 0 0m0 0l0 0 0 9 0 0 0 0 0 0 0 0 0 0 0-9 0 0", + "fill": "#ef4444ff", + "width": 20, + "height": 14.5 + } + ] + }, + { + "type": "frame", + "id": "GiqXP", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Q8dqCs", + "name": "Text", + "fill": "#ef4444ff", + "content": "DELETE SELECTED", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "xe0T8", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Header - Top AppBar", + "width": 1280, + "height": 64, + "fill": "#131315ff", + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "gap": 642.1699829101562, + "padding": [ + 0, + 32 + ], + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "h7u26", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Y8Hurt", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "vNLa5", + "name": "Icon", + "geometry": "M0.83333 15.42157l0-2.5 2.58334 0-2.125-6.95833c-0.375-0.20833-0.68403-0.51389-0.92709-0.91667-0.24306-0.40278-0.36458-0.83333-0.36458-1.29167 0-0.69444 0.24306-1.28472 0.72917-1.77083 0.48611-0.48611 1.07639-0.72917 1.77083-0.72917 0.54167 0 1.02431 0.15625 1.44792 0.46875 0.42361 0.3125 0.71875 0.71181 0.88541 1.19792l2.66667 0 0-0.83333c0-0.23611 0.07986-0.43403 0.23958-0.59375 0.15972-0.15972 0.35764-0.23958 0.59375-0.23959 0.125 0 0.24653 0.02778 0.36459 0.08334 0.11806 0.05556 0.21875 0.13889 0.30208 0.25l0 0 1.41667-1.33334c0.125-0.125 0.27431-0.20486 0.44791-0.23958 0.17361-0.03472 0.34375-0.01042 0.51042 0.07292l3.25 1.5c0.16667 0.08333 0.28125 0.20486 0.34375 0.36458 0.0625 0.15972 0.05903 0.31597-0.01042 0.46875-0.08333 0.16667-0.20486 0.27431-0.36458 0.32292-0.15972 0.04861-0.31597 0.03819-0.46875-0.03125l-3-1.375-1.95833 1.83333 0 1.16667 1.95833 1.79166 3-1.375c0.15278-0.06944 0.3125-0.07639 0.47917-0.02083 0.16667 0.05556 0.28472 0.15972 0.35416 0.3125 0.08333 0.16667 0.09028 0.32639 0.02084 0.47917-0.06944 0.15278-0.1875 0.27083-0.35417 0.35416l-3.25 1.54167c-0.16667 0.08333-0.33681 0.10764-0.51042 0.07292-0.17361-0.03472-0.32292-0.11458-0.44791-0.23959l-1.41667-1.33333 0 0c-0.08333 0.08333-0.18403 0.15972-0.30208 0.22917-0.11806 0.06944-0.23958 0.10417-0.36459 0.10416-0.23611 0-0.43403-0.07986-0.59375-0.23958-0.15972-0.15972-0.23958-0.35764-0.23958-0.59375l0-0.83333-2.66667 0c-0.04167 0.11111-0.08681 0.21528-0.13541 0.3125-0.04861 0.09722-0.11458 0.20139-0.19792 0.3125l4.16667 7.70833 3 0 0 2.5-10.83334 0 0 0m1.66667-10.83333c0.23611 0 0.43403-0.07986 0.59375-0.23959 0.15972-0.15972 0.23958-0.35764 0.23958-0.59375 0-0.23611-0.07986-0.43403-0.23958-0.59375-0.15972-0.15972-0.35764-0.23958-0.59375-0.23958-0.23611 0-0.43403 0.07986-0.59375 0.23958-0.15972 0.15972-0.23958 0.35764-0.23958 0.59375 0 0.23611 0.07986 0.43403 0.23958 0.59375 0.15972 0.15972 0.35764 0.23958 0.59375 0.23959l0 0m2.625 8.33333l1.625 0-3.58333-6.66667c0 0-0.01389 0-0.04167 0-0.02778 0-0.04167 0-0.04167 0l2.04167 6.66667 0 0m1.625 0l0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 15.026515007019043, + "height": 15.421568870544434 + } + ] + }, + { + "type": "frame", + "id": "D4z9IU", + "name": "Heading 1", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "FH6SQ", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "污水毒品前处理一体机", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Yh7mA", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "oAYYb", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "CzOfd", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 4, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "MtS4X", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "yINnu", + "name": "Icon", + "geometry": "M5.83333 11.66667c-0.80694 0-1.56528-0.15313-2.275-0.45938-0.70972-0.30625-1.32708-0.72188-1.85208-1.24687-0.525-0.525-0.94063-1.14236-1.24688-1.85209-0.30625-0.70972-0.45937-1.46806-0.45937-2.275 0-0.80694 0.15312-1.56528 0.45937-2.275 0.30625-0.70972 0.72188-1.32708 1.24688-1.85208 0.525-0.525 1.14236-0.94063 1.85208-1.24688 0.70972-0.30625 1.46806-0.45937 2.275-0.45937 0.80694 0 1.56528 0.15312 2.275 0.45937 0.70972 0.30625 1.32708 0.72188 1.85209 1.24688 0.525 0.525 0.94063 1.14236 1.24687 1.85208 0.30625 0.70972 0.45937 1.46806 0.45938 2.275 0 0.80694-0.15313 1.56528-0.45938 2.275-0.30625 0.70972-0.72188 1.32708-1.24687 1.85209-0.525 0.525-1.14236 0.94063-1.85209 1.24687-0.70972 0.30625-1.46806 0.45937-2.275 0.45938l0 0m0-1.16667c1.30278 0 2.40625-0.45208 3.31042-1.35625 0.90417-0.90417 1.35625-2.00764 1.35625-3.31042 0-1.30278-0.45208-2.40625-1.35625-3.31041-0.90417-0.90417-2.00764-1.35625-3.31042-1.35625-1.30278 0-2.40625 0.45208-3.31041 1.35625-0.90417 0.90417-1.35625 2.00764-1.35625 3.31041 0 1.30278 0.45208 2.40625 1.35625 3.31042 0.90417 0.90417 2.00764 1.35625 3.31041 1.35625l0 0m0-4.66667l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#10b981ff", + "width": 11.666666984558105, + "height": 11.666666984558105 + } + ] + }, + { + "type": "text", + "id": "w3RlV", + "name": "Text", + "fill": "#c6c6cdff", + "content": "系统就绪", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "rectangle", + "id": "LvQed", + "name": "Vertical Divider", + "fill": "#45464dff", + "width": 1, + "height": 16, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "frame", + "id": "B7mdz", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "PcsCB", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023年11月10日 10:30", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "olsdK", + "name": "Button", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "smEYc", + "name": "Icon", + "geometry": "M11 14c0.83333 0 1.54167-0.29167 2.125-0.875 0.58333-0.58333 0.875-1.29167 0.875-2.125 0-0.83333-0.29167-1.54167-0.875-2.125-0.58333-0.58333-1.29167-0.875-2.125-0.875-0.83333 0-1.54167 0.29167-2.125 0.875-0.58333 0.58333-0.875 1.29167-0.875 2.125 0 0.83333 0.29167 1.54167 0.875 2.125 0.58333 0.58333 1.29167 0.875 2.125 0.875l0 0m0 2c-1.38333 0-2.5625-0.4875-3.5375-1.4625-0.975-0.975-1.4625-2.15417-1.4625-3.5375 0-1.38333 0.4875-2.5625 1.4625-3.5375 0.975-0.975 2.15417-1.4625 3.5375-1.4625 1.38333 0 2.5625 0.4875 3.5375 1.4625 0.975 0.975 1.4625 2.15417 1.4625 3.5375 0 1.38333-0.4875 2.5625-1.4625 3.5375-0.975 0.975-2.15417 1.4625-3.5375 1.4625l0 0m-7-4l-4 0 0-2 4 0 0 2 0 0m18 0l-4 0 0-2 4 0 0 2 0 0m-12-8l0-4 2 0 0 4-2 0 0 0m0 18l0-4 2 0 0 4-2 0 0 0m-4.6-15.25l-2.525-2.425 1.425-1.475 2.4 2.5-1.3 1.4 0 0m12.3 12.4l-2.425-2.525 1.325-1.375 2.525 2.425-1.425 1.475 0 0m-2.45-13.75l2.425-2.525 1.475 1.425-2.5 2.4-1.4-1.3 0 0m-12.4 12.3l2.525-2.425 1.375 1.325-2.425 2.525-1.475-1.425 0 0m8.15-6.7l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#c6c6cdff", + "width": 22, + "height": 22 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "V1MPKR", + "layoutPosition": "absolute", + "x": 0, + "y": 935, + "name": "Bottom Navigation Bar (Control Shell)", + "width": 1280, + "fill": "#1b1b1dff", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "padding": [ + 12, + 32 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "id": "ESEjA", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Bottom Navigation Bar (Control Shell):shadow", + "fill": "#ffffff01", + "width": 1280, + "height": 89, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#00000040", + "offset": { + "x": 0, + "y": 25 + }, + "blur": 43.75, + "spread": -12 + } + }, + { + "type": "frame", + "id": "t8rsN", + "name": "Container", + "width": 896, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 41, + "padding": [ + 0, + 4.5 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "HKxGi", + "name": "Button", + "width": 96, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 8, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "pUx6f", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "M52oqw", + "name": "Icon", + "geometry": "M0 14l0-14 11 7-11 7 0 0m2-7l0 0 0 0 0 0 0 0m0 3.35l5.25-3.35-5.25-3.35 0 6.7 0 0", + "fill": "#909097ff", + "width": 11, + "height": 14 + } + ] + }, + { + "type": "frame", + "id": "BIldR", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "D2wyh", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "LUsXq", + "name": "Text", + "fill": "#909097ff", + "content": "START", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "R9Z6a", + "name": "Button", + "width": 96, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 8, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "YGwqA", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "KwU06", + "name": "Icon", + "geometry": "M8 14l0-14 6 0 0 14-6 0 0 0m-8 0l0-14 6 0 0 14-6 0 0 0m10-2l2 0 0-10-2 0 0 10 0 0m-8 0l2 0 0-10-2 0 0 10 0 0m0-10l0 0 0 10 0 0 0-10 0 0m8 0l0 0 0 10 0 0 0-10 0 0", + "fill": "#909097ff", + "width": 14, + "height": 14 + } + ] + }, + { + "type": "frame", + "id": "ysHpF", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "trrh5", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "GqiuQ", + "name": "Text", + "fill": "#909097ff", + "content": "PAUSE", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "MXBxq", + "name": "Button", + "width": 96, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 8, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Z7E1GP", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "NEsGO", + "name": "Icon", + "geometry": "M2 2l0 0 0 8 0 0 0-8 0 0m-2 10l0-12 12 0 0 12-12 0 0 0m2-2l8 0 0-8-8 0 0 8 0 0", + "fill": "#909097ff", + "width": 12, + "height": 12 + } + ] + }, + { + "type": "frame", + "id": "GJQeg", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "jFZ8u", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "gr1jS", + "name": "Text", + "fill": "#909097ff", + "content": "STOP", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "TsOLh", + "name": "Margin", + "width": 33, + "height": 48, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 16 + ], + "children": [ + { + "type": "rectangle", + "id": "M4OvO", + "name": "Vertical Divider", + "fill": "#45464dff", + "width": 1, + "height": 48, + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + }, + { + "type": "frame", + "id": "aPIgi", + "name": "Button", + "width": 96, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 8, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "ap6nH", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "BhWyt", + "name": "Icon", + "geometry": "M10 6l0-6 8 0 0 6-8 0 0 0m-10 4l0-10 8 0 0 10-8 0 0 0m10 8l0-10 8 0 0 10-8 0 0 0m-10 0l0-6 8 0 0 6-8 0 0 0m2-10l4 0 0-6-4 0 0 6 0 0m10 8l4 0 0-6-4 0 0 6 0 0m0-12l4 0 0-2-4 0 0 2 0 0m-10 12l4 0 0-2-4 0 0 2 0 0m4-8l0 0 0 0 0 0 0 0 0 0m6-4l0 0 0 0 0 0 0 0 0 0m0 6l0 0 0 0 0 0 0 0 0 0m-6 4l0 0 0 0 0 0 0 0 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + }, + { + "type": "frame", + "id": "t8WMh", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "SRaQw", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "loduu", + "name": "Text", + "fill": "#909097ff", + "content": "DASHBOARD", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "g1nTIY", + "name": "Button", + "width": 128, + "fill": "#0566d9ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "SjC9V", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 128, + "height": 64, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 8.75, + "spread": -6 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 20 + }, + "blur": 21.875, + "spread": -5 + } + ] + }, + { + "type": "frame", + "id": "tvlUB", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "Iw73a", + "name": "Icon", + "geometry": "M7.3 20l-0.4-3.2c-0.21667-0.08333-0.42083-0.18333-0.6125-0.3-0.19167-0.11667-0.37917-0.24167-0.5625-0.375l-2.975 1.25-2.75-4.75 2.575-1.95c-0.01667-0.11667-0.025-0.22917-0.025-0.3375 0-0.10833 0-0.22083 0-0.3375 0-0.11667 0-0.22917 0-0.3375 0-0.10833 0.00833-0.22083 0.025-0.3375l-2.575-1.95 2.75-4.75 2.975 1.25c0.18333-0.13333 0.375-0.25833 0.575-0.375 0.2-0.11667 0.4-0.21667 0.6-0.3l0.4-3.2 5.5 0 0.4 3.2c0.21667 0.08333 0.42083 0.18333 0.6125 0.3 0.19167 0.11667 0.37917 0.24167 0.5625 0.375l2.975-1.25 2.75 4.75-2.575 1.95c0.01667 0.11667 0.025 0.22917 0.025 0.3375 0 0.10833 0 0.22083 0 0.3375 0 0.11667 0 0.22917 0 0.3375 0 0.10833-0.01667 0.22083-0.05 0.3375l2.575 1.95-2.75 4.75-2.95-1.25c-0.18333 0.13333-0.375 0.25833-0.575 0.375-0.2 0.11667-0.4 0.21666-0.6 0.3l-0.4 3.2-5.5 0 0 0m1.75-2l1.975 0 0.35-2.65c0.51667-0.13333 0.99583-0.32917 1.4375-0.5875 0.44167-0.25833 0.84583-0.57083 1.2125-0.9375l2.475 1.025 0.975-1.7-2.15-1.625c0.08333-0.23333 0.14167-0.47917 0.175-0.7375 0.03333-0.25833 0.05-0.52083 0.05-0.7875 0-0.26667-0.01667-0.52917-0.05-0.7875-0.03333-0.25833-0.09167-0.50417-0.175-0.7375l2.15-1.625-0.975-1.7-2.475 1.05c-0.36667-0.38333-0.77083-0.70417-1.2125-0.9625-0.44167-0.25833-0.92083-0.45417-1.4375-0.5875l-0.325-2.65-1.975 0-0.35 2.65c-0.51667 0.13333-0.99583 0.32917-1.4375 0.5875-0.44167 0.25833-0.84583 0.57083-1.2125 0.9375l-2.475-1.025-0.975 1.7 2.15 1.6c-0.08333 0.25-0.14167 0.5-0.175 0.75-0.03333 0.25-0.05 0.51667-0.05 0.8 0 0.26667 0.01667 0.525 0.05 0.775 0.03333 0.25 0.09167 0.5 0.175 0.75l-2.15 1.625 0.975 1.7 2.475-1.05c0.36667 0.38333 0.77083 0.70417 1.2125 0.9625 0.44167 0.25833 0.92083 0.45417 1.4375 0.5875l0.325 2.65 0 0m1.05-4.5c0.96667 0 1.79167-0.34167 2.475-1.025 0.68333-0.68333 1.025-1.50833 1.025-2.475 0-0.96667-0.34167-1.79167-1.025-2.475-0.68333-0.68333-1.50833-1.025-2.475-1.025-0.98333 0-1.8125 0.34167-2.4875 1.025-0.675 0.68333-1.0125 1.50833-1.0125 2.475 0 0.96667 0.3375 1.79167 1.0125 2.475 0.675 0.68333 1.50417 1.025 2.4875 1.025l0 0m-0.05-3.5l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#e6ecffff", + "width": 20.100000381469727, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "EUUo7", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "nUOd9", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "o14OHc", + "name": "Text", + "fill": "#e6ecffff", + "content": "PROGRAMS", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "WVmgK", + "name": "Button", + "width": 96, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 8, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "GOfQZ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "ACmBa", + "name": "Icon", + "geometry": "M4 14l2 0 0-5-2 0 0 5 0 0m8 0l2 0 0-10-2 0 0 10 0 0m-4 0l2 0 0-3-2 0 0 3 0 0m0-5l2 0 0-2-2 0 0 2 0 0m-6 9c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125l0-14c0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875l14 0c0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125l0 14c0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l-14 0 0 0m0-2l14 0 0 0 0 0 0-14 0 0 0 0-14 0 0 0 0 0 0 14 0 0 0 0 0 0m0-14l0 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 0-14 0 0 0 0 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + }, + { + "type": "frame", + "id": "ZTti4", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 4, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "VAVvF", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "lKHBm", + "name": "Text", + "fill": "#909097ff", + "content": "LOGS", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "EdeJk", + "x": 2724, + "y": 100, + "name": "程序详情配置 - 桌面端", + "width": 1280, + "height": 1080, + "fill": [ + "#ffffffff", + "#131315ff" + ], + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "U2I2a", + "name": "Header - Top Navigation", + "width": "fill_container", + "height": 64, + "fill": "#131315ff", + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "gap": 627.52001953125, + "padding": [ + 0, + 24 + ], + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "o2khEj", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "loqar", + "name": "Button", + "width": 40, + "height": 40, + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "rL4yZ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "SQums", + "name": "Icon", + "geometry": "M3.825 9l5.6 5.6-1.425 1.4-8-8 8-8 1.425 1.4-5.6 5.6 12.175 0 0 2-12.175 0 0 0", + "fill": "#e4e2e4ff", + "width": 16, + "height": 16 + } + ] + } + ] + }, + { + "type": "frame", + "id": "V7id0D", + "name": "Heading 1", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Vsslo", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "程序详情配置: 海洛因快速检测", + "lineHeight": 1.4, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 20, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "xme5V", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "cGMZe", + "name": "Margin", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 16, + 0, + 0 + ], + "children": [ + { + "type": "text", + "id": "nyauC", + "name": "Text", + "fill": "#909097ff", + "content": "DRAFT MODE", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "RBQHD", + "name": "Button", + "height": 40, + "fill": "#0566d9ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 11.5, + 24, + 12.5, + 24 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "VzSjH", + "name": "Text", + "fill": "#e6ecffff", + "content": "SAVE CHANGES", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Ktusd", + "name": "Main Content Area", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 24, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "lD4Oe", + "name": "Top: Program Summary Stats", + "width": "fill_container", + "height": 90, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "HvwAh", + "x": 0, + "y": 0, + "name": "Overlay+Border+OverlayBlur", + "width": 290, + "height": 90, + "fill": "#1e293b66", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": { + "top": 1, + "right": 1, + "bottom": 1, + "left": 4 + }, + "fill": "#bec6e0ff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "gap": 16, + "padding": 20, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "YOydQ", + "name": "Overlay", + "fill": "#bec6e01a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 12, + "children": [ + { + "type": "path", + "id": "zFvZl", + "name": "Icon", + "geometry": "M0 20l0-1.5 2.5 0 0-0.75-1.5 0 0-1.5 1.5 0 0-0.75-2.5 0 0-1.5 3 0c0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 1c0 0.28333-0.09583 0.52083-0.2875 0.7125-0.19167 0.19167-0.42917 0.2875-0.7125 0.2875 0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 1c0 0.28333-0.09583 0.52083-0.2875 0.7125-0.19167 0.19167-0.42917 0.2875-0.7125 0.2875l-3 0 0 0m0-7l0-2.75c0-0.28333 0.09583-0.52083 0.2875-0.7125 0.19167-0.19167 0.42917-0.2875 0.7125-0.2875l1.5 0 0-0.75-2.5 0 0-1.5 3 0c0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 1.75c0 0.28333-0.09583 0.52083-0.2875 0.7125-0.19167 0.19167-0.42917 0.2875-0.7125 0.2875l-1.5 0 0 0.75 2.5 0 0 1.5-4 0 0 0m1.5-7l0-4.5-1.5 0 0-1.5 3 0 0 6-1.5 0 0 0m4.5 11l0-2 12 0 0 2-12 0 0 0m0-6l0-2 12 0 0 2-12 0 0 0m0-6l0-2 12 0 0 2-12 0 0 0", + "fill": "#bec6e0ff", + "width": 18, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "uv2Ho", + "name": "Paragraph", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 3.5, + "children": [ + { + "type": "text", + "id": "NhFBJ", + "name": "Text", + "fill": "#909097ff", + "content": "TOTAL STEPS", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "fn2nQ", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "04", + "lineHeight": 1, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 24, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "tLfIJ", + "x": 314, + "y": 0, + "name": "Overlay+Border+OverlayBlur", + "width": 290, + "height": 90, + "fill": "#1e293b66", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": { + "top": 1, + "right": 1, + "bottom": 1, + "left": 4 + }, + "fill": "#bec6e0ff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "gap": 16, + "padding": 20, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "Upvta", + "name": "Overlay", + "fill": "#bec6e01a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 12, + "children": [ + { + "type": "path", + "id": "uzT46", + "name": "Icon", + "geometry": "M13.3 14.7l1.4-1.4-3.7-3.7 0-4.6-2 0 0 5.4 4.3 4.3 0 0m-3.3 5.3c-1.38333 0-2.68333-0.2625-3.9-0.7875-1.21667-0.525-2.275-1.2375-3.175-2.1375-0.9-0.9-1.6125-1.95833-2.1375-3.175-0.525-1.21667-0.7875-2.51667-0.7875-3.9 0-1.38333 0.2625-2.68333 0.7875-3.9 0.525-1.21667 1.2375-2.275 2.1375-3.175 0.9-0.9 1.95833-1.6125 3.175-2.1375 1.21667-0.525 2.51667-0.7875 3.9-0.7875 1.38333 0 2.68333 0.2625 3.9 0.7875 1.21667 0.525 2.275 1.2375 3.175 2.1375 0.9 0.9 1.6125 1.95833 2.1375 3.175 0.525 1.21667 0.7875 2.51667 0.7875 3.9 0 1.38333-0.2625 2.68333-0.7875 3.9-0.525 1.21667-1.2375 2.275-2.1375 3.175-0.9 0.9-1.95833 1.6125-3.175 2.1375-1.21667 0.525-2.51667 0.7875-3.9 0.7875l0 0m0-10l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0m0 8c2.21667 0 4.10417-0.77917 5.6625-2.3375 1.55833-1.55833 2.3375-3.44583 2.3375-5.6625 0-2.21667-0.77917-4.10417-2.3375-5.6625-1.55833-1.55833-3.44583-2.3375-5.6625-2.3375-2.21667 0-4.10417 0.77917-5.6625 2.3375-1.55833 1.55833-2.3375 3.44583-2.3375 5.6625 0 2.21667 0.77917 4.10417 2.3375 5.6625 1.55833 1.55833 3.44583 2.3375 5.6625 2.3375l0 0", + "fill": "#bec6e0ff", + "width": 20, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "GclXH", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 3.5, + "children": [ + { + "type": "frame", + "id": "yoVPr", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "owYN1", + "name": "Text", + "fill": "#909097ff", + "content": "EST. TIME", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "M5bR4", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "12min 30s", + "lineHeight": 1, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 24, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "UHwfj", + "x": 628, + "y": 0, + "name": "Overlay+Border+OverlayBlur", + "width": 290, + "height": 90, + "fill": "#1e293b66", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": { + "top": 1, + "right": 1, + "bottom": 1, + "left": 4 + }, + "fill": "#bec6e0ff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "gap": 16, + "padding": 20, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "CLYmn", + "name": "Overlay", + "fill": "#bec6e01a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 12, + "children": [ + { + "type": "path", + "id": "ZlSp2", + "name": "Icon", + "geometry": "M8 19c-2.21667 0-4.10417-0.76667-5.6625-2.3-1.55833-1.53333-2.3375-3.4-2.3375-5.6 0-1.08333 0.20833-2.09583 0.625-3.0375 0.41667-0.94167 0.99167-1.77917 1.725-2.5125l5.65-5.55 5.65 5.55c0.73333 0.73333 1.30833 1.57083 1.725 2.5125 0.41667 0.94167 0.625 1.95417 0.625 3.0375 0 2.2-0.77917 4.06667-2.3375 5.6-1.55833 1.53333-3.44583 2.3-5.6625 2.3l0 0m-5.95-7l11.85 0c0.2-1.2 0.0875-2.225-0.3375-3.075-0.425-0.85-0.8625-1.49167-1.3125-1.925l-4.25-4.2-4.25 4.2c-0.45 0.43333-0.89167 1.075-1.325 1.925-0.43333 0.85-0.55833 1.875-0.375 3.075l0 0", + "fill": "#bec6e0ff", + "width": 16, + "height": 19 + } + ] + }, + { + "type": "frame", + "id": "zfDyH", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 3.5, + "children": [ + { + "type": "frame", + "id": "Xphr7", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "TDIzU", + "name": "Text", + "fill": "#909097ff", + "content": "REAGENT VOL.", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "PdqFH", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "850uL", + "lineHeight": 1, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 24, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "MCjQ2", + "x": 942, + "y": 0, + "name": "Overlay+Border+OverlayBlur", + "width": 290, + "height": 90, + "fill": "#1e293b66", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": { + "top": 1, + "right": 1, + "bottom": 1, + "left": 4 + }, + "fill": "#10b981ff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "gap": 16, + "padding": 20, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "pPDdp", + "name": "Overlay", + "fill": "#10b9811a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 12, + "children": [ + { + "type": "path", + "id": "lM8Pe", + "name": "Icon", + "geometry": "M10 20c-1.38333 0-2.68333-0.2625-3.9-0.7875-1.21667-0.525-2.275-1.2375-3.175-2.1375-0.9-0.9-1.6125-1.95833-2.1375-3.175-0.525-1.21667-0.7875-2.51667-0.7875-3.9 0-0.71667 0.075-1.42083 0.225-2.1125 0.15-0.69167 0.36667-1.3625 0.65-2.0125l1.55 1.55c-0.13333 0.43333-0.2375 0.8625-0.3125 1.2875-0.075 0.425-0.1125 0.85417-0.1125 1.2875 0 2.23333 0.775 4.125 2.325 5.675 1.55 1.55 3.44167 2.325 5.675 2.325 2.23333 0 4.125-0.775 5.675-2.325 1.55-1.55 2.325-3.44167 2.325-5.675 0-2.23333-0.775-4.125-2.325-5.675-1.55-1.55-3.44167-2.325-5.675-2.325-0.45 0-0.8875 0.0375-1.3125 0.1125-0.425 0.075-0.84583 0.17917-1.2625 0.3125l-1.525-1.525c0.66667-0.3 1.33333-0.525 2-0.675 0.66667-0.15 1.36667-0.225 2.1-0.225 1.38333 0 2.68333 0.2625 3.9 0.7875 1.21667 0.525 2.275 1.2375 3.175 2.1375 0.9 0.9 1.6125 1.95833 2.1375 3.175 0.525 1.21667 0.7875 2.51667 0.7875 3.9 0 1.38333-0.2625 2.68333-0.7875 3.9-0.525 1.21667-1.2375 2.275-2.1375 3.175-0.9 0.9-1.95833 1.6125-3.175 2.1375-1.21667 0.525-2.51667 0.7875-3.9 0.7875l0 0m-6.5-15c-0.41667 0-0.77083-0.14583-1.0625-0.4375-0.29167-0.29167-0.4375-0.64583-0.4375-1.0625 0-0.41667 0.14583-0.77083 0.4375-1.0625 0.29167-0.29167 0.64583-0.4375 1.0625-0.4375 0.41667 0 0.77083 0.14583 1.0625 0.4375 0.29167 0.29167 0.4375 0.64583 0.4375 1.0625 0 0.41667-0.14583 0.77083-0.4375 1.0625-0.29167 0.29167-0.64583 0.4375-1.0625 0.4375l0 0m6.5 5l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#10b981ff", + "width": 20, + "height": 20 + } + ] + }, + { + "type": "frame", + "id": "tAR3g", + "name": "Container", + "width": "fit_content(96.41000366210938)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "QeOm8", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Q3sDG", + "name": "Text", + "fill": "#909097ff", + "content": "STATUS", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "KxeL0", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "Y9ad5", + "name": "Background", + "fill": "#10b981ff", + "width": 8, + "height": 8, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "frame", + "id": "CewPk", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "F6MiT", + "name": "Text", + "fill": "#10b981ff", + "content": "DRAFT", + "lineHeight": 1, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 24, + "fontWeight": "600" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "fDiFW", + "name": "Middle: Two Column Layout", + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "children": [ + { + "type": "frame", + "id": "N32wl", + "name": "Left Pane: Step Sequence List", + "width": 410.6600036621094, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "cH7fK", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 100.06999969482422, + "padding": [ + 0, + 8.010000228881836, + 0, + 8 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "fonj9", + "name": "Heading 2", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "ZZ1Id", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "V3oIX", + "name": "Icon", + "geometry": "M0 14l0-2 18 0 0 2-18 0 0 0m0-4l0-2 18 0 0 2-18 0 0 0m0-4l0-2 18 0 0 2-18 0 0 0m0-4l0-2 18 0 0 2-18 0 0 0", + "fill": "#bec6e0ff", + "width": 18, + "height": 14 + } + ] + }, + { + "type": "text", + "id": "LkXmj", + "name": "Text", + "fill": "#bec6e0ff", + "content": "STEP SEQUENCE", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "IVXig", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "s5chD", + "name": "Text", + "fill": "#909097ff", + "content": "DRAG TO REORDER", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + }, + { + "type": "frame", + "id": "Yqu0B", + "name": "Container", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "padding": [ + 0, + 8, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "EXsob", + "name": "Step 1 (Active)", + "width": "fill_container", + "fill": "#0f172aff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#bec6e0ff" + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "Xq6Cp", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "k02qM", + "name": "Icon", + "geometry": "M2 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0", + "fill": "#909097ff", + "width": 10, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "omLKE", + "name": "Background", + "width": 40, + "height": 40, + "fill": "#bec6e0ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "UMIMP", + "name": "Text", + "fill": "#283044ff", + "content": "01", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "HEvUO", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "RPQ9z", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "OzR4D", + "name": "Mixing (混合)", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Mixing (混合)", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "JwUmF", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "pqwga", + "name": "A1 | 200UL | MED SPD", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "A1 | 200UL | MED SPD", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "ScZz5", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "QNRCT", + "name": "Icon", + "geometry": "M4.6 6l-4.6-4.6 1.4-1.4 6 6-6 6-1.4-1.4 4.6-4.6 0 0", + "fill": "#bec6e0ff", + "width": 7.400000095367432, + "height": 12 + } + ] + } + ] + }, + { + "type": "frame", + "id": "w2Eeo", + "name": "Step 2", + "width": "fill_container", + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "fM8vT", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "OdQug", + "name": "Icon", + "geometry": "M2 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0", + "fill": "#909097ff", + "width": 10, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "JWQuv", + "name": "Background", + "width": 40, + "height": 40, + "fill": "#353436ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Sa5LQ", + "name": "Text", + "fill": "#909097ff", + "content": "02", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "QJYrT", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "E9SrW4", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "dye00", + "name": "Reagent Addition", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Reagent Addition", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "EIwDZ", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "CmRXe", + "name": "B1 | 150UL | 45S", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "B1 | 150UL | 45S", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "tVwJj", + "name": "Button", + "opacity": 0, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Hrqcb", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "IEnWi", + "name": "Icon", + "geometry": "M2.25 13.5c-0.4125 0-0.76563-0.14687-1.05937-0.44063-0.29375-0.29375-0.44063-0.64687-0.44063-1.05937l0-9.75-0.75 0 0-1.5 3.75 0 0-0.75 4.5 0 0 0.75 3.75 0 0 1.5-0.75 0 0 9.75c0 0.4125-0.14687 0.76563-0.44063 1.05937-0.29375 0.29375-0.64687 0.44063-1.05937 0.44063l-7.5 0 0 0m7.5-11.25l-7.5 0 0 9.75 0 0 0 0 7.5 0 0 0 0 0 0-9.75 0 0m-6 8.25l1.5 0 0-6.75-1.5 0 0 6.75 0 0m3 0l1.5 0 0-6.75-1.5 0 0 6.75 0 0m-4.5-8.25l0 0 0 9.75 0 0 0 0 0 0 0 0 0 0 0-9.75 0 0", + "fill": "#909097ff", + "width": 12, + "height": 13.5 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "V2ryp", + "name": "Step 3", + "width": "fill_container", + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "F2iiT", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "gvD7Z", + "name": "Icon", + "geometry": "M2 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0", + "fill": "#909097ff", + "width": 10, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "kEqTU", + "name": "Background", + "width": 40, + "height": 40, + "fill": "#353436ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "FFk00", + "name": "Text", + "fill": "#909097ff", + "content": "03", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "TJfOF", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "GC4Ci", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "qJ8F6", + "name": "Incubation", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Incubation", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "YzPnk", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "fkMmi", + "name": "C2 | 300S | 37°C", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "C2 | 300S | 37°C", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "AsQ6V", + "name": "Button", + "opacity": 0, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "VJK3I", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "pxoLX", + "name": "Icon", + "geometry": "M2.25 13.5c-0.4125 0-0.76563-0.14687-1.05937-0.44063-0.29375-0.29375-0.44063-0.64687-0.44063-1.05937l0-9.75-0.75 0 0-1.5 3.75 0 0-0.75 4.5 0 0 0.75 3.75 0 0 1.5-0.75 0 0 9.75c0 0.4125-0.14687 0.76563-0.44063 1.05937-0.29375 0.29375-0.64687 0.44063-1.05937 0.44063l-7.5 0 0 0m7.5-11.25l-7.5 0 0 9.75 0 0 0 0 7.5 0 0 0 0 0 0-9.75 0 0m-6 8.25l1.5 0 0-6.75-1.5 0 0 6.75 0 0m3 0l1.5 0 0-6.75-1.5 0 0 6.75 0 0m-4.5-8.25l0 0 0 9.75 0 0 0 0 0 0 0 0 0 0 0-9.75 0 0", + "fill": "#909097ff", + "width": 12, + "height": 13.5 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "v397NG", + "name": "Step 4", + "width": "fill_container", + "fill": "#1e293bff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "gap": 16, + "padding": 16, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "w68RIG", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "FRfhb", + "name": "Icon", + "geometry": "M2 16c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m-6-6c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0m6 0c-0.55 0-1.02083-0.19583-1.4125-0.5875-0.39167-0.39167-0.5875-0.8625-0.5875-1.4125 0-0.55 0.19583-1.02083 0.5875-1.4125 0.39167-0.39167 0.8625-0.5875 1.4125-0.5875 0.55 0 1.02083 0.19583 1.4125 0.5875 0.39167 0.39167 0.5875 0.8625 0.5875 1.4125 0 0.55-0.19583 1.02083-0.5875 1.4125-0.39167 0.39167-0.8625 0.5875-1.4125 0.5875l0 0", + "fill": "#909097ff", + "width": 10, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "dx0Ps", + "name": "Background", + "width": 40, + "height": 40, + "fill": "#353436ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "R7BuwO", + "name": "Text", + "fill": "#909097ff", + "content": "04", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "R8xcV", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "qrNPd", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "lPzjL", + "name": "Detection Scan", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Detection Scan", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "SviCh", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "fQajp", + "name": "D1 | MULTI-WAVE", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "D1 | MULTI-WAVE", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "o7bwY7", + "name": "Button", + "opacity": 0, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "FCcer", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "N4QHyK", + "name": "Icon", + "geometry": "M2.25 13.5c-0.4125 0-0.76563-0.14687-1.05937-0.44063-0.29375-0.29375-0.44063-0.64687-0.44063-1.05937l0-9.75-0.75 0 0-1.5 3.75 0 0-0.75 4.5 0 0 0.75 3.75 0 0 1.5-0.75 0 0 9.75c0 0.4125-0.14687 0.76563-0.44063 1.05937-0.29375 0.29375-0.64687 0.44063-1.05937 0.44063l-7.5 0 0 0m7.5-11.25l-7.5 0 0 9.75 0 0 0 0 7.5 0 0 0 0 0 0-9.75 0 0m-6 8.25l1.5 0 0-6.75-1.5 0 0 6.75 0 0m3 0l1.5 0 0-6.75-1.5 0 0 6.75 0 0m-4.5-8.25l0 0 0 9.75 0 0 0 0 0 0 0 0 0 0 0-9.75 0 0", + "fill": "#909097ff", + "width": 12, + "height": 13.5 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "DXE2L", + "name": "Add Button", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#45464dff" + }, + "layout": "vertical", + "gap": 8, + "padding": [ + 24, + 106.30999755859375, + 24, + 106.30000305175781 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "f4hpU", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "mPJHq", + "name": "Icon", + "geometry": "M11.25 18.75l2.5 0 0-5 5 0 0-2.5-5 0 0-5-2.5 0 0 5-5 0 0 2.5 5 0 0 5 0 0m1.25 6.25c-1.72917 0-3.35417-0.32813-4.875-0.98438-1.52083-0.65625-2.84375-1.54688-3.96875-2.67187-1.125-1.125-2.01563-2.44792-2.67188-3.96875-0.65625-1.52083-0.98438-3.14583-0.98437-4.875 0-1.72917 0.32813-3.35417 0.98437-4.875 0.65625-1.52083 1.54688-2.84375 2.67188-3.96875 1.125-1.125 2.44792-2.01563 3.96875-2.67188 1.52083-0.65625 3.14583-0.98438 4.875-0.98437 1.72917 0 3.35417 0.32813 4.875 0.98437 1.52083 0.65625 2.84375 1.54688 3.96875 2.67188 1.125 1.125 2.01563 2.44792 2.67188 3.96875 0.65625 1.52083 0.98438 3.14583 0.98437 4.875 0 1.72917-0.32813 3.35417-0.98437 4.875-0.65625 1.52083-1.54688 2.84375-2.67188 3.96875-1.125 1.125-2.44792 2.01563-3.96875 2.67188-1.52083 0.65625-3.14583 0.98438-4.875 0.98437l0 0m0-2.5c2.79167 0 5.15625-0.96875 7.09375-2.90625 1.9375-1.9375 2.90625-4.30208 2.90625-7.09375 0-2.79167-0.96875-5.15625-2.90625-7.09375-1.9375-1.9375-4.30208-2.90625-7.09375-2.90625-2.79167 0-5.15625 0.96875-7.09375 2.90625-1.9375 1.9375-2.90625 4.30208-2.90625 7.09375 0 2.79167 0.96875 5.15625 2.90625 7.09375 1.9375 1.9375 4.30208 2.90625 7.09375 2.90625l0 0m0-10l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#909097ff", + "width": 25, + "height": 25 + } + ] + }, + { + "type": "frame", + "id": "uKmRl", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "RXy6p", + "name": "Text", + "fill": "#909097ff", + "content": "ADD NEW PROTOCOL STEP", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "z0KhuP", + "name": "Right Pane: Parameter Editing", + "clip": true, + "width": 797.3400268554688, + "height": "fill_container", + "fill": "#1e293b66", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "layout": "vertical", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "ECZG1", + "name": "Background+HorizontalBorder", + "width": "fill_container", + "fill": "#1f1f21ff", + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#334155ff" + }, + "gap": 312.0899963378906, + "padding": 24, + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "lcVd0", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "t1PIB8", + "name": "Overlay+Border", + "width": 48, + "height": 48, + "fill": "#bec6e033", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#bec6e04d" + }, + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "h6O0z", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "q77BS", + "name": "Icon", + "geometry": "M2.0285 18c-0.85 0-1.45417-0.37917-1.8125-1.1375-0.35833-0.75833-0.27083-1.4625 0.2625-2.1125l5.55-6.75 0-6-1 0c-0.28333 0-0.52083-0.09583-0.7125-0.2875-0.19167-0.19167-0.2875-0.42917-0.2875-0.7125 0-0.28333 0.09583-0.52083 0.2875-0.7125 0.19167-0.19167 0.42917-0.2875 0.7125-0.2875l8 0c0.28333 0 0.52083 0.09583 0.71251 0.2875 0.19167 0.19167 0.2875 0.42917 0.28749 0.7125 0 0.28333-0.09583 0.52083-0.28749 0.7125-0.19167 0.19167-0.42917 0.2875-0.71251 0.2875l-1 0 0 6 5.55001 6.75c0.53333 0.65 0.62083 1.35417 0.26249 2.1125-0.35833 0.75833-0.9625 1.1375-1.8125 1.1375l-14 0 0 0m0-2l14 0-6-7.3 0-6.7-2 0 0 6.7-6 7.3 0 0m7-7l0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 18.057008743286133, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "o9AqGI", + "name": "Container", + "width": "fit_content(223.3000030517578)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "A6EIs", + "name": "Heading 2", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "txzHO", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "Step 01: Mixing (混合)", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "e21xA", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "tmnMG", + "name": "Text", + "fill": "#909097ff", + "content": "EDITING PRIMARY PARAMETERS", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "b01NwP", + "name": "Button", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "padding": [ + 8, + 16 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "YBVEr", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "rHMdp", + "name": "Icon", + "geometry": "M2 22l0-4.2c-0.58333-0.2-1.0625-0.55417-1.4375-1.0625-0.375-0.50833-0.5625-1.0875-0.5625-1.7375l0-10 2 0 0-4c0-0.28333 0.09583-0.52083 0.2875-0.7125 0.19167-0.19167 0.42917-0.2875 0.7125-0.2875 0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 4 2 0 0 10c0 0.65-0.1875 1.22917-0.5625 1.7375-0.375 0.50833-0.85417 0.8625-1.4375 1.0625l0 4.2-2 0 0 0m8 0l0-4.2c-0.58333-0.2-1.0625-0.55417-1.4375-1.0625-0.375-0.50833-0.5625-1.0875-0.5625-1.7375l0-10 2 0 0-4c0-0.28333 0.09583-0.52083 0.2875-0.7125 0.19167-0.19167 0.42917-0.2875 0.7125-0.2875 0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 4 2 0 0 10c0 0.65-0.1875 1.22917-0.5625 1.7375-0.375 0.50833-0.85417 0.8625-1.4375 1.0625l0 4.2-2 0 0 0m8 0l0-4.2c-0.58333-0.2-1.0625-0.55417-1.4375-1.0625-0.375-0.50833-0.5625-1.0875-0.5625-1.7375l0-10 2 0 0-4c0-0.28333 0.09583-0.52083 0.2875-0.7125 0.19167-0.19167 0.42917-0.2875 0.7125-0.2875 0.28333 0 0.52083 0.09583 0.7125 0.2875 0.19167 0.19167 0.2875 0.42917 0.2875 0.7125l0 4 2 0 0 10c0 0.65-0.1875 1.22917-0.5625 1.7375-0.375 0.50833-0.85417 0.8625-1.4375 1.0625l0 4.2-2 0 0 0m-16-15l0 4 2 0 0-4-2 0 0 0m8 0l0 4 2 0 0-4-2 0 0 0m8 0l0 4 2 0 0-4-2 0 0 0m-15 9c0.28333 0 0.52083-0.09583 0.7125-0.2875 0.19167-0.19167 0.2875-0.42917 0.2875-0.7125l0-2-2 0 0 2c0 0.28333 0.09583 0.52083 0.2875 0.7125 0.19167 0.19167 0.42917 0.2875 0.7125 0.2875l0 0m8 0c0.28333 0 0.52083-0.09583 0.7125-0.2875 0.19167-0.19167 0.2875-0.42917 0.2875-0.7125l0-2-2 0 0 2c0 0.28333 0.09583 0.52083 0.2875 0.7125 0.19167 0.19167 0.42917 0.2875 0.7125 0.2875l0 0m8 0c0.28333 0 0.52083-0.09583 0.7125-0.2875 0.19167-0.19167 0.2875-0.42917 0.2875-0.7125l0-2-2 0 0 2c0 0.28333 0.09583 0.52083 0.2875 0.7125 0.19167 0.19167 0.42917 0.2875 0.7125 0.2875l0 0m-16-4l0 0 0 0 0 0 0 0 0 0m8 0l0 0 0 0 0 0 0 0 0 0m8 0l0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 22, + "height": 22 + } + ] + }, + { + "type": "frame", + "id": "n3SKWk", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "u3wzg", + "name": "Text", + "fill": "#bec6e0ff", + "content": "ADVANCED", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "PoIby", + "name": "Container", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 32, + "children": [ + { + "type": "frame", + "id": "K3qYo", + "name": "Container", + "width": "fill_container", + "height": 436, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "zMcQV", + "x": 0, + "y": 0, + "name": "Left Sub-column", + "width": 341.6700134277344, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 32, + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "frame", + "id": "X8Re0V", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "ilyn5", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "i77gJ", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "D4qsLw", + "name": "Icon", + "geometry": "M0 4.66667l0-4.66667 4.66667 0 0 4.66667-4.66667 0 0 0m0 5.83333l0-4.66667 4.66667 0 0 4.66667-4.66667 0 0 0m5.83333-5.83333l0-4.66667 4.66667 0 0 4.66667-4.66667 0 0 0m0 5.83333l0-4.66667 4.66667 0 0 4.66667-4.66667 0 0 0m-4.66666-7l2.33333 0 0-2.33333-2.33333 0 0 2.33333 0 0m5.83333 0l2.33333 0 0-2.33333-2.33333 0 0 2.33333 0 0m0 5.83333l2.33333 0 0-2.33333-2.33333 0 0 2.33333 0 0m-5.83333 0l2.33333 0 0-2.33333-2.33333 0 0 2.33333 0 0m5.83333-5.83333l0 0 0 0 0 0 0 0 0 0m0 3.5l0 0 0 0 0 0 0 0 0 0m-3.5 0l0 0 0 0 0 0 0 0 0 0m0-3.5l0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 10.5, + "height": 10.5 + } + ] + }, + { + "type": "text", + "id": "wgUfa", + "name": "Text", + "fill": "#bec6e0ff", + "content": "HOLE POSITION", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "BAD6s", + "name": "Options", + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "k1ee9v", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "image clip", + "clip": true, + "width": 341.6700134277344, + "height": 50, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 13, + 9, + 13, + 308.6700134277344 + ], + "justifyContent": "center", + "children": [ + { + "type": "frame", + "id": "tHBg2", + "name": "image", + "clip": true, + "width": 24, + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "path", + "id": "K6Vbob", + "x": 7.200000286102295, + "y": 9.600000381469727, + "name": "Vector", + "geometry": "", + "width": 9.600000381469727, + "height": 4.800000190734863, + "stroke": { + "align": "center", + "thickness": 1.8000000715255737, + "join": "round", + "cap": "round", + "fill": "#6b7280ff" + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "bzspc", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "qP8F8", + "name": "A1 (Current Chamber)", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "A1 (Current Chamber)", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "DZ6dh", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "SxN6n", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "amXLd", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "Q19YE", + "name": "Icon", + "geometry": "M0 10.48542l0-2.77084 5.22083-5.22083-0.84583-0.81667 0.84583-0.81666 1.10834 1.10833 1.80833-1.80833c0.04861-0.04861 0.10938-0.0875 0.18229-0.11667 0.07292-0.02917 0.14826-0.04375 0.22604-0.04375 0.07778 0 0.15069 0.01458 0.21875 0.04375 0.06806 0.02917 0.13125 0.06806 0.18959 0.11667l1.37083 1.37083c0.04861 0.05833 0.0875 0.12153 0.11667 0.18958 0.02917 0.06806 0.04375 0.14097 0.04375 0.21875 0 0.07778-0.01458 0.15312-0.04375 0.22605-0.02917 0.07292-0.06806 0.13368-0.11667 0.18229l-1.79375 1.79375 1.10833 1.1375-0.83125 0.83125-0.81666-0.84584-5.22084 5.22084-2.77083 0 0 0m1.16667-1.16667l1.1375 0 4.84166-4.87083-1.10833-1.10834-4.87083 4.84167 0 1.1375 0 0m6.51875-5.97917l1.4-1.4-0.53959-0.53958-1.4 1.4 0.53959 0.53958 0 0m0 0l-0.53959-0.53958 0 0 0.53959 0.53958 0 0 0 0", + "fill": "#bec6e0ff", + "width": 10.485416412353516, + "height": 10.485416412353516 + } + ] + }, + { + "type": "text", + "id": "Yvlyw", + "name": "Text", + "fill": "#bec6e0ff", + "content": "VOLUME (uL)", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "Sjmur", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "PWIdw", + "name": "Input", + "clip": true, + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "XflE2", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 5.684341886080802e-14, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "MEOjD", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "q2wxrX", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "hAh9u", + "name": "200", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "200", + "lineHeight": 1.5555555555555556, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "D7KHgR", + "name": "Rectangle:align-stretch", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "children": [ + { + "type": "rectangle", + "id": "zKJ8W", + "name": "Rectangle", + "opacity": 0, + "width": 15, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Z6YPKd", + "layoutPosition": "absolute", + "x": 214.83999633789062, + "y": 15, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "wzG69", + "name": "Text", + "fill": "#909097ff", + "content": "MICROLITERS", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "trZmA", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "MJvJ8", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "pty1O", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "q5Sax9", + "name": "Icon", + "geometry": "M4.92917 6.70833c0.23333 0.23333 0.53472 0.34757 0.90416 0.34271 0.36944-0.00486 0.64167-0.13854 0.81667-0.40104l3.26667-4.9-4.9 3.26667c-0.2625 0.175-0.40104 0.44236-0.41563 0.80208-0.01458 0.35972 0.09479 0.65625 0.32813 0.88958l0 0m0.90416-6.70833c0.57361 0 1.12535 0.08021 1.65521 0.24062 0.52986 0.16042 1.02813 0.40104 1.49479 0.72188l-1.10833 0.7c-0.32083-0.16528-0.65382-0.28924-0.99896-0.37188-0.34514-0.08264-0.69271-0.12396-1.04271-0.12395-1.29306 0-2.3941 0.45451-3.30312 1.36354-0.90903 0.90903-1.36354 2.01007-1.36354 3.30312 0 0.40833 0.0559 0.81181 0.16771 1.21042 0.11181 0.39861 0.26979 0.77292 0.47395 1.12292l8.05 0c0.22361-0.36944 0.38646-0.75347 0.48855-1.15209 0.10208-0.39861 0.15312-0.81181 0.15312-1.23958 0-0.35-0.04132-0.69028-0.12396-1.02083-0.08264-0.33056-0.2066-0.65139-0.37187-0.9625l0.7-1.10834c0.29167 0.45694 0.52257 0.94306 0.6927 1.45834 0.17014 0.51528 0.26007 1.05 0.2698 1.60416 0.00972 0.55417-0.05347 1.08403-0.18959 1.58959-0.13611 0.50556-0.33542 0.9868-0.59791 1.44375-0.10694 0.175-0.25278 0.31111-0.4375 0.40833-0.18472 0.09722-0.37917 0.14583-0.58334 0.14583l-8.05 0c-0.20417 0-0.39861-0.04861-0.58333-0.14583-0.18472-0.09722-0.33056-0.23333-0.4375-0.40833-0.25278-0.4375-0.44722-0.90174-0.58333-1.39271-0.13611-0.49097-0.20417-1.00868-0.20417-1.55313 0-0.80694 0.15312-1.56285 0.45937-2.26771 0.30625-0.70486 0.72431-1.32222 1.25417-1.85208 0.52986-0.52986 1.14965-0.94792 1.85938-1.25417 0.70972-0.30625 1.46319-0.45937 2.26041-0.45937l0 0m0.10209 4.56458l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 11.667638778686523, + "height": 9.333333015441895 + } + ] + }, + { + "type": "text", + "id": "KEBQe", + "name": "Text", + "fill": "#bec6e0ff", + "content": "MIXING SPEED", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "VMq4g", + "name": "Background+Border", + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": 4, + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "h2lAty", + "name": "Button", + "width": "fill_container", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "M8moaB", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "LOW", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "oO2O0", + "name": "Button", + "width": "fill_container", + "fill": "#0566d9ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 4, + "id": "NnM6J", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 110.55999755859375, + "height": 40, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 4 + }, + "blur": 5.25, + "spread": -4 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0000001a", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 13.125, + "spread": -3 + } + ] + }, + { + "type": "text", + "id": "ZojvU", + "name": "Text", + "fill": "#e6ecffff", + "content": "MEDIUM", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "xUoC9", + "name": "Button", + "width": "fill_container", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "XfgfH", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "HIGH", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "xrDvJ", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "MyYUV", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "HISx4", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "T6yvDR", + "name": "Icon", + "geometry": "M0.58333 10.7951l0-1.75 1.80834 0-1.4875-4.87084c-0.2625-0.14583-0.47882-0.35972-0.64896-0.64166-0.17014-0.28194-0.25521-0.58333-0.25521-0.90417 0-0.48611 0.17014-0.89931 0.51042-1.23958 0.34028-0.34028 0.75347-0.51042 1.23958-0.51042 0.37917 0 0.71701 0.10937 1.01354 0.32813 0.29653 0.21875 0.50313 0.49826 0.61979 0.83854l1.86667 0 0-0.58334c0-0.16528 0.0559-0.30382 0.16771-0.41562 0.11181-0.11181 0.25035-0.16771 0.41562-0.16771 0.0875 0 0.17257 0.01944 0.25521 0.05833 0.08264 0.03889 0.15313 0.09722 0.21146 0.175l0 0 0.99167-0.93333c0.0875-0.0875 0.19201-0.1434 0.31354-0.16771 0.12153-0.02431 0.24062-0.00729 0.35729 0.05104l2.275 1.05c0.11667 0.05833 0.19687 0.1434 0.24063 0.25521 0.04375 0.11181 0.04132 0.22118-0.0073 0.32813-0.05833 0.11667-0.1434 0.19201-0.25521 0.22604-0.11181 0.03403-0.22118 0.02674-0.32812-0.02188l-2.1-0.9625-1.37083 1.28334 0 0.81666 1.37083 1.25417 2.1-0.9625c0.10694-0.04861 0.21875-0.05347 0.33542-0.01458 0.11667 0.03889 0.19931 0.11181 0.24791 0.21875 0.05833 0.11667 0.06319 0.22847 0.01459 0.33541-0.04861 0.10694-0.13125 0.18958-0.24792 0.24792l-2.275 1.07917c-0.11667 0.05833-0.23576 0.07535-0.35729 0.05104-0.12153-0.02431-0.22604-0.08021-0.31354-0.16771l-0.99167-0.93333 0 0c-0.05833 0.05833-0.12882 0.11181-0.21146 0.16041-0.08264 0.04861-0.16771 0.07292-0.25521 0.07292-0.16528 0-0.30382-0.0559-0.41562-0.16771-0.11181-0.11181-0.16771-0.25035-0.16771-0.41562l0-0.58334-1.86667 0c-0.02917 0.07778-0.06076 0.15069-0.09479 0.21875-0.03403 0.06806-0.08021 0.14097-0.13854 0.21875l2.91667 5.39584 2.1 0 0 1.75-7.58334 0 0 0m1.16667-7.58334c0.16528 0 0.30382-0.0559 0.41563-0.1677 0.11181-0.11181 0.16771-0.25035 0.1677-0.41563 0-0.16528-0.0559-0.30382-0.1677-0.41562-0.11181-0.11181-0.25035-0.16771-0.41563-0.16771-0.16528 0-0.30382 0.0559-0.41563 0.16771-0.11181 0.11181-0.16771 0.25035-0.1677 0.41562 0 0.16528 0.0559 0.30382 0.1677 0.41563 0.11181 0.11181 0.25035 0.16771 0.41563 0.1677l0 0m1.8375 5.83334l1.1375 0-2.50833-4.66667c0 0-0.00972 0-0.02917 0-0.01944 0-0.02917 0-0.02917 0l1.42917 4.66667 0 0m1.1375 0l0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 10.518560409545898, + "height": 10.795098304748535 + } + ] + }, + { + "type": "text", + "id": "zpDmX", + "name": "Text", + "fill": "#bec6e0ff", + "content": "NEEDLE SPEED", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "K3swo", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "padding": [ + 16, + 8, + 0, + 8 + ], + "children": [ + { + "type": "frame", + "id": "oWjqw", + "name": "Input", + "width": "fill_container", + "height": 8, + "fill": "#353436ff", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "dRldT", + "x": 0, + "y": -4, + "name": "Container", + "width": 325.6700134277344, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "QsbJg", + "name": "Container", + "width": "fill_container", + "height": 16, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none" + } + ] + } + ] + }, + { + "type": "frame", + "id": "W02miy", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 76.0999984741211, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "MlLnb", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "pXKhx", + "name": "Text", + "fill": "#909097ff", + "content": "PRECISION (LV 1)", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "hLTYa", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "JgNwh", + "name": "Text", + "fill": "#bec6e0ff", + "content": "LV 5", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "700" + } + ] + }, + { + "type": "frame", + "id": "V2OhRf", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "jYwjg", + "name": "Text", + "fill": "#909097ff", + "content": "FAST (LV 10)", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "700" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "MOMI0", + "x": 389.6700134277344, + "y": 0, + "name": "Right Sub-column", + "width": 341.6700134277344, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 32, + "children": [ + { + "type": "frame", + "id": "mlbWZ", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "gQtGX", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "az0fF", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "z6etiv", + "name": "Icon", + "geometry": "M3.5 1.16667l0-1.16667 3.5 0 0 1.16667-3.5 0 0 0m1.16667 6.41666l1.16666 0 0-3.5-1.16666 0 0 3.5 0 0m0.58333 4.66667c-0.71944 0-1.39757-0.13854-2.03437-0.41563-0.63681-0.27708-1.1934-0.65382-1.6698-1.1302-0.47639-0.47639-0.85312-1.03299-1.1302-1.66979-0.27708-0.63681-0.41563-1.31493-0.41563-2.03438 0-0.71944 0.13854-1.39757 0.41563-2.03438 0.27708-0.63681 0.65382-1.1934 1.1302-1.66979 0.47639-0.47639 1.03299-0.85312 1.6698-1.1302 0.63681-0.27708 1.31493-0.41563 2.03437-0.41563 0.60278 0 1.18125 0.09722 1.73542 0.29167 0.55417 0.19444 1.07431 0.47639 1.56041 0.84583l0.81667-0.81667 0.81667 0.81667-0.81667 0.81667c0.36944 0.48611 0.65139 1.00625 0.84583 1.56041 0.19444 0.55417 0.29167 1.13264 0.29167 1.73542 0 0.71944-0.13854 1.39757-0.41563 2.03438-0.27708 0.63681-0.65382 1.1934-1.1302 1.66979-0.47639 0.47639-1.03299 0.85313-1.66979 1.13021-0.63681 0.27708-1.31493 0.41563-2.03438 0.41562l0 0m0-1.16667c1.12778 0 2.09028-0.39861 2.8875-1.19583 0.79722-0.79722 1.19583-1.75972 1.19583-2.8875 0-1.12778-0.39861-2.09028-1.19583-2.8875-0.79722-0.79722-1.75972-1.19583-2.8875-1.19583-1.12778 0-2.09028 0.39861-2.8875 1.19583-0.79722 0.79722-1.19583 1.75972-1.19583 2.8875 0 1.12778 0.39861 2.09028 1.19583 2.8875 0.79722 0.79722 1.75972 1.19583 2.8875 1.19583l0 0m0-4.08333l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 10.5, + "height": 12.25 + } + ] + }, + { + "type": "text", + "id": "qPpsF", + "name": "Text", + "fill": "#bec6e0ff", + "content": "MIXING TIME", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "F6cr3t", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "TXcfb", + "name": "Input", + "clip": true, + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "N5WwG", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": -5.684341886080802e-14, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "n7YO4", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "JwRK6", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "VdlVX", + "name": "60", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "60", + "lineHeight": 1.5555555555555556, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "D24TpI", + "name": "Rectangle:align-stretch", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "children": [ + { + "type": "rectangle", + "id": "PtqRe", + "name": "Rectangle", + "opacity": 0, + "width": 15, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "jO5Uy", + "layoutPosition": "absolute", + "x": 244, + "y": 15, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "staDs", + "name": "Text", + "fill": "#909097ff", + "content": "SECONDS", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "tQfos", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "WcQaQ", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "R2h3J", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "erg5b", + "name": "Icon", + "geometry": "M4.66667 5.25c0.64167 0 1.19097-0.22847 1.64791-0.68542 0.45694-0.45694 0.68542-1.00625 0.68542-1.64791l0-1.75-4.66667 0 0 1.75c0 0.64167 0.22847 1.19097 0.68542 1.64791 0.45694 0.45694 1.00625 0.68542 1.64792 0.68542l0 0m-4.66667 6.41667l0-1.16667 1.16667 0 0-1.75c0-0.59306 0.13854-1.14965 0.41562-1.66979 0.27708-0.52014 0.66354-0.93576 1.15938-1.24688-0.49583-0.31111-0.88229-0.72674-1.15938-1.24687-0.27708-0.52014-0.41563-1.07674-0.41562-1.66979l0-1.75-1.16667 0 0-1.16667 9.33333 0 0 1.16667-1.16666 0 0 1.75c0 0.59306-0.13854 1.14965-0.41563 1.66979-0.27708 0.52014-0.66354 0.93576-1.15937 1.24687 0.49583 0.31111 0.88229 0.72674 1.15937 1.24688 0.27708 0.52014 0.41563 1.07674 0.41563 1.66979l0 1.75 1.16666 0 0 1.16667-9.33333 0 0 0", + "fill": "#bec6e0ff", + "width": 9.333333015441895, + "height": 11.666666984558105 + } + ] + }, + { + "type": "text", + "id": "iN4nZ", + "name": "Text", + "fill": "#bec6e0ff", + "content": "SUCTION TIME", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "TiZcN", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "t8Fncg", + "name": "Input", + "clip": true, + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "UIuFV", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": -5.684341886080802e-14, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "YJAvp", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "Ukoi5", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Y5YIB", + "name": "30", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "30", + "lineHeight": 1.5555555555555556, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "F1Wg6s", + "name": "Rectangle:align-stretch", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "children": [ + { + "type": "rectangle", + "id": "iX7kK", + "name": "Rectangle", + "opacity": 0, + "width": 15, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "ETlKD", + "layoutPosition": "absolute", + "x": 244, + "y": 15, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "U8t8e", + "name": "Text", + "fill": "#909097ff", + "content": "SECONDS", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "bbMlw", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "UYzPB", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "cidUi", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "wwUZL", + "name": "Icon", + "geometry": "M5.54167 9.91667c-0.48611 0-0.89931-0.17014-1.23959-0.51042-0.34028-0.34028-0.51042-0.75347-0.51041-1.23958l1.16666 0c0 0.16528 0.0559 0.30382 0.16771 0.41562 0.11181 0.11181 0.25035 0.16771 0.41563 0.16771 0.16528 0 0.30382-0.0559 0.41562-0.16771 0.11181-0.11181 0.16771-0.25035 0.16771-0.41562 0-0.16528-0.0559-0.30382-0.16771-0.41563-0.11181-0.11181-0.25035-0.16771-0.41562-0.16771l-5.54167 0 0-1.16666 5.54167 0c0.48611 0 0.89931 0.17014 1.23958 0.51041 0.34028 0.34028 0.51042 0.75347 0.51042 1.23959 0 0.48611-0.17014 0.89931-0.51042 1.23958-0.34028 0.34028-0.75347 0.51042-1.23958 0.51042l0 0m-5.54167-5.83334l0-1.16666 7.875 0c0.25278 0 0.46181-0.08264 0.62708-0.24792 0.16528-0.16528 0.24792-0.37431 0.24792-0.62708 0-0.25278-0.08264-0.46181-0.24792-0.62709-0.16528-0.16528-0.37431-0.24792-0.62708-0.24791-0.25278 0-0.46181 0.08264-0.62708 0.24791-0.16528 0.16528-0.24792 0.37431-0.24792 0.62709l-1.16667 0c0-0.57361 0.19687-1.05729 0.59063-1.45105 0.39375-0.39375 0.87743-0.59062 1.45104-0.59062 0.57361 0 1.05729 0.19687 1.45104 0.59062 0.39375 0.39375 0.59063 0.87743 0.59063 1.45105 0 0.57361-0.19688 1.05729-0.59063 1.45104-0.39375 0.39375-0.87743 0.59063-1.45104 0.59062l-7.875 0 0 0m9.625 4.66667l0-1.16667c0.25278 0 0.46181-0.08264 0.62708-0.24791 0.16528-0.16528 0.24792-0.37431 0.24792-0.62709 0-0.25278-0.08264-0.46181-0.24792-0.62708-0.16528-0.16528-0.37431-0.24792-0.62708-0.24792l-9.625 0 0-1.16666 9.625 0c0.57361 0 1.05729 0.19688 1.45104 0.59062 0.39375 0.39375 0.59063 0.87743 0.59063 1.45104 0 0.57361-0.19688 1.05729-0.59063 1.45105-0.39375 0.39375-0.87743 0.59062-1.45104 0.59062l0 0", + "fill": "#bec6e0ff", + "width": 11.666666984558105, + "height": 9.916666984558105 + } + ] + }, + { + "type": "text", + "id": "n9IB7R", + "name": "Text", + "fill": "#bec6e0ff", + "content": "BLOWING SPEED", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "i4v1cu", + "name": "Options", + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "tqoTA", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "image clip", + "clip": true, + "width": 341.6700134277344, + "height": 50, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 13, + 9, + 13, + 308.6700134277344 + ], + "justifyContent": "center", + "children": [ + { + "type": "frame", + "id": "dOqOb", + "name": "image", + "clip": true, + "width": 24, + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "path", + "id": "ozGc7", + "x": 7.200000286102295, + "y": 9.600000381469727, + "name": "Vector", + "geometry": "", + "width": 9.600000381469727, + "height": 4.800000190734863, + "stroke": { + "align": "center", + "thickness": 1.8000000715255737, + "join": "round", + "cap": "round", + "fill": "#6b7280ff" + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "waVXj", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "CBxmm", + "name": "High Pressure Discharge", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "High Pressure Discharge", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "dHYGU", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "TIK5m", + "name": "Label", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 7.989999771118164, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "yGHgR", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "XTDIG", + "name": "Icon", + "geometry": "M5.25 12.54857c0-0.32083 0.11424-0.59549 0.34271-0.82395 0.22847-0.22847 0.50313-0.34271 0.82396-0.34271l0 0 0-3.03334c-0.11667-0.04861-0.22604-0.10451-0.32813-0.1677-0.10208-0.06319-0.19687-0.13368-0.28437-0.21146l-1.28334 0.81666c-0.13611 0.07778-0.28438 0.12882-0.44479 0.15313-0.16042 0.02431-0.32326 0.01215-0.48854-0.03646l-2.625-0.74375c-0.28194-0.07778-0.51285-0.23576-0.69271-0.47396-0.17986-0.23819-0.26979-0.50799-0.26979-0.80937 0-0.36944 0.12882-0.68299 0.38646-0.94063 0.25764-0.25764 0.57118-0.38646 0.94062-0.38646l4.38959 0c0.09722-0.10694 0.20417-0.19931 0.32083-0.27708 0.11667-0.07778 0.24306-0.14097 0.37917-0.18958l0-1.99792c0-0.16528 0.0316-0.32083 0.09479-0.46667 0.06319-0.14583 0.15312-0.27222 0.26979-0.37916l1.99792-1.86667c0.22361-0.21389 0.48368-0.33542 0.7802-0.36458 0.29653-0.02917 0.57118 0.03403 0.82396 0.18958 0.31111 0.19444 0.51285 0.4691 0.60521 0.82396 0.09236 0.35486 0.04132 0.68785-0.15312 0.99896l-2.31875 3.83541c0.06806 0.11667 0.1191 0.24306 0.15312 0.37917 0.03403 0.13611 0.0559 0.27708 0.06563 0.42292l1.575 0.37916c0.15556 0.03889 0.29653 0.10694 0.42291 0.20417 0.12639 0.09722 0.22847 0.21389 0.30625 0.35l1.32709 2.39167c0.14583 0.2625 0.19931 0.53958 0.16041 0.83125-0.03889 0.29167-0.16528 0.54444-0.37916 0.75833-0.2625 0.2625-0.57604 0.39375-0.94063 0.39375-0.36458 0-0.67813-0.13125-0.94062-0.39375l-2.68334-2.68333 0 0 0 0 0 2.49375 0 0c0.32083 0 0.59549 0.11424 0.82396 0.34271 0.22847 0.22847 0.34271 0.50313 0.34271 0.82395l-3.5 0 0 0m-3.5-10.5l0-1.16666 3.5 0 0 1.16666-3.5 0 0 0m5.83333 1.03542l0 1.99792c0.00972 0 0.01701 0.00243 0.02188 0.00729 0.00486 0.00486 0.01215 0.00729 0.02187 0.00729l2.21667-3.68958c0.01944-0.03889 0.02431-0.08021 0.01458-0.12396-0.00972-0.04375-0.03403-0.07535-0.07291-0.09479-0.02917-0.01944-0.06563-0.02431-0.10938-0.01459-0.04375 0.00972-0.07535 0.02431-0.09479 0.04375l-1.99792 1.86667 0 0m-7.58333 1.29792l0-1.16667 2.91667 0 0 1.16667-2.91667 0 0 0m7 2.91666c0.16528 0 0.30382-0.0559 0.41563-0.1677 0.11181-0.11181 0.16771-0.25035 0.1677-0.41563 0-0.16528-0.0559-0.30382-0.1677-0.41562-0.11181-0.11181-0.25035-0.16771-0.41563-0.16771-0.16528 0-0.30382 0.0559-0.41563 0.16771-0.11181 0.11181-0.16771 0.25035-0.1677 0.41562 0 0.16528 0.0559 0.30382 0.1677 0.41563 0.11181 0.11181 0.25035 0.16771 0.41563 0.1677l0 0m-3.07708 0.49584l1.35625-0.81667c-0.00972-0.04861-0.01458-0.09236-0.01459-0.13125 0-0.03889 0-0.08264 0-0.13125l-3.9375 0c-0.04861 0-0.0875 0.01458-0.11666 0.04375-0.02917 0.02917-0.04375 0.06806-0.04375 0.11667 0 0.03889 0.00972 0.07292 0.02916 0.10208 0.01944 0.02917 0.04861 0.04861 0.0875 0.05833l2.63959 0.75834 0 0m6.11041 0.36458l-1.6625-0.37917c-0.01944 0.01944-0.03889 0.04375-0.05833 0.07292-0.01944 0.02917-0.03889 0.05347-0.05833 0.07292l2.84375 2.82917c0.02917 0.02917 0.06806 0.04375 0.11666 0.04374 0.04861 0 0.0875-0.01458 0.11667-0.04374 0.02917-0.02917 0.04618-0.06076 0.05104-0.0948 0.00486-0.03403-0.00243-0.07049-0.02187-0.10937l-1.32709-2.39167 0 0m-8.86666 3.22292l0-1.16667 2.91666 0 0 1.16667-2.91666 0 0 0m5.83333-4.66667l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0m0.62708-1.61875l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0m-2.3625 1.61875l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0m2.98959 1.21042l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 12.539363861083984, + "height": 12.548574447631836 + } + ] + }, + { + "type": "text", + "id": "g3Zyh8", + "name": "Text", + "fill": "#bec6e0ff", + "content": "BLOWING TIME", + "lineHeight": 1.3333333333333333, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "700", + "letterSpacing": 1.2000000476837158 + } + ] + }, + { + "type": "frame", + "id": "CfoaV", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "ulADw", + "name": "Input", + "clip": true, + "width": "fill_container", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 12, + 20 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "G51TN", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": -5.684341886080802e-14, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "ixSlG", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "V3vDM", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "kXLkA", + "name": "5", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "5", + "lineHeight": 1.5555555555555556, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "TI9m2", + "name": "Rectangle:align-stretch", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "children": [ + { + "type": "rectangle", + "id": "jHcJF", + "name": "Rectangle", + "opacity": 0, + "width": 15, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "BmERI", + "layoutPosition": "absolute", + "x": 248.75, + "y": 15, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "UlEyU", + "name": "Text", + "fill": "#909097ff", + "content": "MINUTES", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "c9ulTq", + "name": "Footer - Bottom Action Bar", + "width": "fill_container", + "height": 96, + "fill": "#1f1f21ff", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "gap": 17.280000686645508, + "padding": [ + 0, + 32 + ], + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "pw1CR", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 48, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "b6UP7O", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "sL6HQ", + "name": "Overlay", + "fill": "#bec6e01a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "children": [ + { + "type": "path", + "id": "Ouhv2", + "name": "Icon", + "geometry": "M8.995 2.45c1.76667 0 3.43333 0.37917 5 1.1375 1.56667 0.75833 2.875 1.85417 3.925 3.2875 0.11667 0.15 0.15416 0.28333 0.1125 0.4-0.04167 0.11667-0.1125 0.21667-0.2125 0.3-0.1 0.08333-0.21667 0.12083-0.35 0.1125-0.13333-0.00833-0.25-0.07917-0.35-0.2125-0.91667-1.3-2.09583-2.29583-3.5375-2.9875-1.44167-0.69167-2.97083-1.0375-4.5875-1.0375-1.61667 0-3.13333 0.34583-4.55 1.0375-1.41667 0.69167-2.59167 1.6875-3.525 2.9875-0.1 0.15-0.21667 0.23333-0.35 0.25-0.13333 0.01667-0.25-0.01667-0.35-0.1-0.11667-0.08333-0.1875-0.1875-0.2125-0.3125-0.025-0.125 0.0125-0.25417 0.1125-0.3875 1.03333-1.41667 2.32917-2.51667 3.8875-3.3 1.55833-0.78333 3.22083-1.175 4.9875-1.175l0 0m0 2.35c2.25 0 4.18333 0.75 5.8 2.25 1.61667 1.5 2.425 3.35833 2.425 5.575 0 0.83333-0.29583 1.52917-0.8875 2.0875-0.59167 0.55833-1.3125 0.8375-2.1625 0.8375-0.85 0-1.57917-0.27917-2.1875-0.8375-0.60833-0.55833-0.9125-1.25417-0.9125-2.0875 0-0.55-0.20417-1.0125-0.6125-1.3875-0.40833-0.375-0.89583-0.5625-1.4625-0.5625-0.56667 0-1.05417 0.1875-1.4625 0.5625-0.40833 0.375-0.6125 0.8375-0.6125 1.3875 0 1.61667 0.47917 2.96667 1.4375 4.05 0.95833 1.08333 2.19583 1.84167 3.7125 2.275 0.15 0.05 0.25 0.13333 0.3 0.25 0.05 0.11667 0.05833 0.24167 0.025 0.375-0.03333 0.11667-0.1 0.21666-0.2 0.3-0.1 0.08333-0.225 0.10833-0.375 0.075-1.73333-0.43333-3.15-1.29584-4.25-2.5875-1.1-1.29167-1.65-2.87083-1.65-4.7375 0-0.83333 0.3-1.53333 0.9-2.1 0.6-0.56667 1.325-0.85 2.175-0.85 0.85 0 1.575 0.28333 2.175 0.85 0.6 0.56667 0.9 1.26667 0.9 2.1 0 0.55 0.20833 1.0125 0.625 1.3875 0.41667 0.375 0.90833 0.5625 1.475 0.5625 0.56667 0 1.05-0.1875 1.45-0.5625 0.4-0.375 0.6-0.8375 0.6-1.3875 0-1.93333-0.70833-3.55833-2.125-4.875-1.41667-1.31667-3.10833-1.975-5.075-1.975-1.96667 0-3.65833 0.65833-5.075 1.975-1.41667 1.31667-2.125 2.93333-2.125 4.85 0 0.4 0.0375 0.9 0.1125 1.5 0.075 0.6 0.25417 1.3 0.5375 2.1 0.05 0.15 0.04583 0.28333-0.0125 0.4-0.05833 0.11667-0.15417 0.2-0.2875 0.25-0.13333 0.05-0.2625 0.04583-0.3875-0.0125-0.125-0.05833-0.2125-0.15417-0.2625-0.2875-0.25-0.65-0.42917-1.29583-0.5375-1.9375-0.10833-0.64167-0.1625-1.30417-0.1625-1.9875 0-2.21667 0.80417-4.075 2.4125-5.575 1.60833-1.5 3.52917-2.25 5.7625-2.25l0 0m0-4.8c1.06667 0 2.10833 0.12917 3.125 0.3875 1.01667 0.25833 2 0.62917 2.95 1.1125 0.15 0.08333 0.2375 0.18333 0.2625 0.3 0.025 0.11667 0.0125 0.23333-0.0375 0.35-0.05 0.11667-0.13333 0.20833-0.25 0.275-0.11667 0.06667-0.25833 0.05833-0.425-0.025-0.88333-0.45-1.79583-0.79583-2.7375-1.0375-0.94167-0.24167-1.90417-0.3625-2.8875-0.3625-0.96667 0-1.91667 0.1125-2.85 0.3375-0.93333 0.225-1.825 0.57917-2.675 1.0625-0.13333 0.08333-0.26667 0.10417-0.4 0.0625-0.13333-0.04167-0.23333-0.12917-0.3-0.2625-0.06667-0.13333-0.08333-0.25417-0.05-0.3625 0.03333-0.10833 0.11667-0.20417 0.25-0.2875 0.93333-0.5 1.90833-0.88333 2.925-1.15 1.01667-0.26667 2.05-0.4 3.1-0.4l0 0m0 7.225c1.55 0 2.88333 0.52083 4 1.5625 1.11667 1.04167 1.675 2.32083 1.675 3.8375 0 0.15-0.04583 0.27083-0.1375 0.3625-0.09167 0.09167-0.2125 0.1375-0.3625 0.1375-0.13333 0-0.25-0.04583-0.35-0.1375-0.1-0.09167-0.15-0.2125-0.15-0.3625 0-1.25-0.4625-2.29583-1.3875-3.1375-0.925-0.84167-2.02083-1.2625-3.2875-1.2625-1.26667 0-2.35417 0.42083-3.2625 1.2625-0.90833 0.84167-1.3625 1.8875-1.3625 3.1375 0 1.35 0.23333 2.49583 0.7 3.4375 0.46667 0.94167 1.15 1.8875 2.05 2.8375 0.1 0.1 0.15 0.21667 0.15 0.35 0 0.13333-0.05 0.25-0.15 0.35-0.1 0.1-0.21667 0.15-0.35 0.15-0.13333 0-0.25-0.05-0.35-0.15-0.98333-1.03333-1.7375-2.0875-2.2625-3.1625-0.525-1.075-0.7875-2.34583-0.7875-3.8125 0-1.51667 0.55-2.79583 1.65-3.8375 1.1-1.04167 2.425-1.5625 3.975-1.5625l0 0m-0.025 4.9c0.15 0 0.27083 0.05 0.3625 0.15 0.09167 0.1 0.1375 0.21667 0.1375 0.35 0 1.25 0.45 2.275 1.35 3.075 0.9 0.8 1.95 1.2 3.15 1.2 0.1 0 0.24167-0.00833 0.425-0.025 0.18333-0.01667 0.375-0.04167 0.575-0.075 0.15-0.03333 0.27917-0.0125 0.3875 0.0625 0.10833 0.075 0.17917 0.1875 0.2125 0.3375 0.03333 0.13333 0.00833 0.25-0.075 0.35-0.08333 0.1-0.19167 0.16667-0.325 0.2-0.3 0.08333-0.5625 0.12917-0.7875 0.1375-0.225 0.00833-0.3625 0.0125-0.4125 0.0125-1.48333 0-2.77083-0.5-3.8625-1.5-1.09167-1-1.6375-2.25833-1.6375-3.775 0-0.13333 0.04583-0.25 0.1375-0.35 0.09167-0.1 0.2125-0.15 0.3625-0.15l0 0", + "fill": "#bec6e0ff", + "width": 18.048948287963867, + "height": 19.964284896850586 + } + ] + }, + { + "type": "frame", + "id": "yPlew", + "name": "Container", + "width": "fit_content(128.97000122070312)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "fIZ0b", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "WZ880", + "name": "Text", + "fill": "#909097ff", + "content": "PROGRAM ID", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "700", + "letterSpacing": 0.5 + } + ] + }, + { + "type": "frame", + "id": "eEbGv", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "LZ1Kt", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "#HRO-992-BETA", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "XfcBW", + "name": "VerticalBorder", + "height": 40, + "stroke": { + "align": "inside", + "thickness": { + "left": 1 + }, + "fill": "#45464dff" + }, + "gap": 12, + "padding": [ + 0, + 0, + 0, + 48 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "mP75i", + "name": "Overlay", + "fill": "#bec6e01a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "children": [ + { + "type": "path", + "id": "G7v99", + "name": "Icon", + "geometry": "M9 18c-2.3 0-4.30417-0.7625-6.0125-2.2875-1.70833-1.525-2.6875-3.42917-2.9375-5.7125l2.05 0c0.23333 1.73333 1.00417 3.16667 2.3125 4.3 1.30833 1.13333 2.8375 1.7 4.5875 1.7 1.95 0 3.60417-0.67917 4.9625-2.0375 1.35833-1.35833 2.0375-3.0125 2.0375-4.9625 0-1.95-0.67917-3.60417-2.0375-4.9625-1.35833-1.35833-3.0125-2.0375-4.9625-2.0375-1.15 0-2.225 0.26667-3.225 0.8-1 0.53333-1.84167 1.26667-2.525 2.2l2.75 0 0 2-6 0 0-6 2 0 0 2.35c0.85-1.06667 1.8875-1.89167 3.1125-2.475 1.225-0.58333 2.52083-0.875 3.8875-0.875 1.25 0 2.42083 0.2375 3.5125 0.7125 1.09167 0.475 2.04167 1.11667 2.85 1.925 0.80833 0.80833 1.45 1.75833 1.925 2.85 0.475 1.09167 0.7125 2.2625 0.7125 3.5125 0 1.25-0.2375 2.42083-0.7125 3.5125-0.475 1.09167-1.11667 2.04167-1.925 2.85-0.80833 0.80833-1.75833 1.45-2.85 1.925-1.09167 0.475-2.2625 0.7125-3.5125 0.7125l0 0m2.8-4.8l-3.8-3.8 0-5.4 2 0 0 4.6 3.2 3.2-1.4 1.4 0 0", + "fill": "#bec6e0ff", + "width": 18, + "height": 18 + } + ] + }, + { + "type": "frame", + "id": "v7R80", + "name": "Container", + "width": "fit_content(156.52999877929688)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "G3xrAO", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "KUQqG", + "name": "Text", + "fill": "#909097ff", + "content": "LAST MODIFIED", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 10, + "fontWeight": "700", + "letterSpacing": 0.5 + } + ] + }, + { + "type": "frame", + "id": "FQBXv", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "ZKRmy", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "2023-11-24 14:20:05", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "pT8mc", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "OKDch", + "name": "Button", + "height": 48, + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#45464dff" + }, + "gap": 8, + "padding": [ + 0, + 32 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "r4cb9", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "vKTtO", + "name": "Icon", + "geometry": "M1.66667 17.5c-0.45833 0-0.85069-0.1632-1.17709-0.48958-0.32639-0.32639-0.48958-0.71875-0.48958-1.17709l0-8.33333c0-0.45833 0.16319-0.85069 0.48958-1.17708 0.32639-0.32639 0.71875-0.48958 1.17709-0.48959l2.5 0 0 1.66667-2.5 0 0 0 0 0 0 8.33333 0 0 0 0 10 0 0 0 0 0 0-8.33333 0 0 0 0-2.5 0 0-1.66667 2.5 0c0.45833 0 0.85069 0.16319 1.17708 0.48959 0.32639 0.32639 0.48958 0.71875 0.48958 1.17708l0 8.33333c0 0.45833-0.16319 0.85069-0.48958 1.17709-0.32639 0.32639-0.71875 0.48958-1.17708 0.48958l-10 0 0 0m4.16666-5l0-9.3125-1.33333 1.33333-1.16667-1.1875 3.33334-3.33333 3.33333 3.33333-1.16667 1.1875-1.33333-1.33333 0 9.3125-1.66667 0 0 0", + "fill": "#e4e2e4ff", + "width": 13.333333015441895, + "height": 17.5 + } + ] + }, + { + "type": "text", + "id": "t7QIH", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "EXPORT CONFIG", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "mwtvY", + "name": "Button", + "height": 48, + "fill": "#0f172aff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#bec6e04d" + }, + "gap": 8, + "padding": [ + 0, + 40 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "jP9Iu", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "rtU1Z", + "name": "Icon", + "geometry": "M1.66667 15c-0.45833 0-0.85069-0.16319-1.17709-0.48958-0.32639-0.32639-0.48958-0.71875-0.48958-1.17709l0-11.66666c0-0.45833 0.16319-0.85069 0.48958-1.17709 0.32639-0.32639 0.71875-0.48958 1.17709-0.48958l13.33333 0c0.45833 0 0.85069 0.16319 1.17708 0.48958 0.32639 0.32639 0.48958 0.71875 0.48959 1.17709l0 11.66666c0 0.45833-0.16319 0.85069-0.48959 1.17709-0.32639 0.32639-0.71875 0.48958-1.17708 0.48958l-13.33333 0 0 0m0-1.66667l13.33333 0 0 0 0 0 0-11.66666 0 0 0 0-13.33333 0 0 0 0 0 0 11.66666 0 0 0 0 0 0m0.83333-1.66666l4.16667 0 0-1.66667-4.16667 0 0 1.66667 0 0m7.95833-1.66667l4.125-4.125-1.1875-1.1875-2.9375 2.95833-1.1875-1.1875-1.16666 1.1875 2.35416 2.35417 0 0m-7.95833-1.66667l4.16667 0 0-1.66666-4.16667 0 0 1.66666 0 0m0-3.33333l4.16667 0 0-1.66667-4.16667 0 0 1.66667 0 0m-0.83333 8.33333l0 0 0 0 0 0 0-11.66666 0 0 0 0 0 0 0 0 0 0 0 11.66666 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 16.66666603088379, + "height": 15 + } + ] + }, + { + "type": "text", + "id": "XfWoZ", + "name": "Text", + "fill": "#bec6e0ff", + "content": "VALIDATE RUN", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Iml7D", + "name": "Button", + "height": 48, + "fill": "#0566d9ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 48 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "m9FvU", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button:shadow", + "fill": "#ffffff01", + "width": 228.6999969482422, + "height": 48, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#0566d933", + "offset": { + "x": 0, + "y": 4 + }, + "blur": 5.25, + "spread": -4 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#0566d933", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 13.125, + "spread": -3 + } + ] + }, + { + "type": "text", + "id": "uq84l", + "name": "Text", + "fill": "#e6ecffff", + "content": "SAVE PROTOCOL", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "vEAdz", + "x": 4036, + "y": 100, + "name": "首页控制面板 - 桌面端", + "width": 1920, + "height": 1080, + "fill": [ + "#ffffffff", + "#131315ff", + { + "type": "gradient", + "gradientType": "radial", + "enabled": true, + "rotation": -90, + "size": { + "width": 2.7105760218975945, + "height": 2.7105760218975945 + }, + "colors": [ + { + "color": "#1e293bff", + "position": 0.00768594304099679 + }, + { + "color": "#1e293b00", + "position": 0.00768594304099679 + } + ], + "center": { + "x": -45.333335487505614, + "y": -45.333335487505614 + } + } + ], + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "dy6Ap", + "name": "Header - Top Navigation Bar / Device Status", + "width": "fill_container", + "height": 80, + "fill": "#131315cc", + "stroke": { + "align": "inside", + "thickness": { + "bottom": 1 + }, + "fill": "#45464dff" + }, + "effect": { + "type": "background_blur", + "radius": 10.5 + }, + "gap": 430.3699951171875, + "padding": [ + 0, + 32 + ], + "justifyContent": "space_between", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "coabH", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "RMBOq", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "rqolb", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "GkSnT", + "name": "Icon", + "geometry": "M1.25 23.13235l0-3.75 3.875 0-3.1875-10.4375c-0.5625-0.3125-1.02604-0.77083-1.39063-1.375-0.36458-0.60417-0.54688-1.25-0.54687-1.9375 0-1.04167 0.36458-1.92708 1.09375-2.65625 0.72917-0.72917 1.61458-1.09375 2.65625-1.09375 0.8125 0 1.53646 0.23437 2.17188 0.70313 0.63542 0.46875 1.07813 1.06771 1.32812 1.79687l4 0 0-1.25c0-0.35417 0.11979-0.65104 0.35938-0.89062 0.23958-0.23958 0.53646-0.35937 0.89062-0.35938 0.1875 0 0.36979 0.04167 0.54688 0.125 0.17708 0.08333 0.32813 0.20833 0.45312 0.375l0 0 2.125-2c0.1875-0.1875 0.41146-0.30729 0.67188-0.35937 0.26042-0.05208 0.51563-0.01562 0.76562 0.10937l4.875 2.25c0.25 0.125 0.42188 0.30729 0.51563 0.54688 0.09375 0.23958 0.08854 0.47396-0.01563 0.70312-0.125 0.25-0.30729 0.41146-0.54688 0.48438-0.23958 0.07292-0.47396 0.05729-0.70312-0.04688l-4.5-2.0625-2.9375 2.75 0 1.75 2.9375 2.6875 4.5-2.0625c0.22917-0.10417 0.46875-0.11458 0.71875-0.03125 0.25 0.08333 0.42708 0.23958 0.53125 0.46875 0.125 0.25 0.13542 0.48958 0.03125 0.71875-0.10417 0.22917-0.28125 0.40625-0.53125 0.53125l-4.875 2.3125c-0.25 0.125-0.50521 0.16146-0.76563 0.10938-0.26042-0.05208-0.48438-0.17188-0.67187-0.35938l-2.125-2 0 0c-0.125 0.125-0.27604 0.23958-0.45313 0.34375-0.17708 0.10417-0.35938 0.15625-0.54687 0.15625-0.35417 0-0.65104-0.11979-0.89063-0.35937-0.23958-0.23958-0.35938-0.53646-0.35937-0.89063l0-1.25-4 0c-0.0625 0.16667-0.13021 0.32292-0.20313 0.46875-0.07292 0.14583-0.17188 0.30208-0.29687 0.46875l6.25 11.5625 4.5 0 0 3.75-16.25 0 0 0m2.5-16.25c0.35417 0 0.65104-0.11979 0.89063-0.35937 0.23958-0.23958 0.35938-0.53646 0.35937-0.89063 0-0.35417-0.11979-0.65104-0.35938-0.89062-0.23958-0.23958-0.53646-0.35938-0.89062-0.35938-0.35417 0-0.65104 0.11979-0.89063 0.35938-0.23958 0.23958-0.35938 0.53646-0.35937 0.89062 0 0.35417 0.11979 0.65104 0.35937 0.89063 0.23958 0.23958 0.53646 0.35938 0.89063 0.35937l0 0m3.9375 12.5l2.4375 0-5.375-10c0 0-0.02083 0-0.0625 0-0.04167 0-0.0625 0-0.0625 0l3.0625 10 0 0m2.4375 0l0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 22.539772033691406, + "height": 23.132352828979492 + } + ] + }, + { + "type": "frame", + "id": "erR8c", + "name": "Heading 1", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "IDYes", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "污水毒品前处理一体机 | 控制面板", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "NCsvM", + "name": "Overlay+Border", + "fill": "#10b9811a", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#10b98133" + }, + "gap": 12, + "padding": [ + 8, + 16 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "omAPB", + "name": "Background", + "fill": "#10b981ff", + "width": 10, + "height": 10, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "frame", + "id": "M7vB52", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "pNv0T", + "name": "Text", + "fill": "#10b981ff", + "content": "设备运行中", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500", + "letterSpacing": 1.600000023841858 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Fbrme", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 40, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Z2YWE", + "name": "Real-time Clock", + "stroke": { + "align": "inside", + "thickness": { + "right": 1 + }, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 0, + 40, + 0, + 0 + ], + "alignItems": "end", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "Mt7bx", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "zqyUR", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2026-05-19", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "DDIHh", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "ilmG1", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "07:10:34", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal", + "letterSpacing": 0.800000011920929 + } + ] + } + ] + }, + { + "type": "frame", + "id": "aHISw", + "name": "Lighting & Theme Controls", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "UQOsO", + "name": "Background+Border", + "fill": "#1b1b1dff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "gap": 7.989999771118164, + "padding": 6, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "Vml1U", + "name": "Button", + "fill": "#353436ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "le4y9", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "A1rUcE", + "name": "Icon", + "geometry": "M11 14c0.83333 0 1.54167-0.29167 2.125-0.875 0.58333-0.58333 0.875-1.29167 0.875-2.125 0-0.83333-0.29167-1.54167-0.875-2.125-0.58333-0.58333-1.29167-0.875-2.125-0.875-0.83333 0-1.54167 0.29167-2.125 0.875-0.58333 0.58333-0.875 1.29167-0.875 2.125 0 0.83333 0.29167 1.54167 0.875 2.125 0.58333 0.58333 1.29167 0.875 2.125 0.875l0 0m0 2c-1.38333 0-2.5625-0.4875-3.5375-1.4625-0.975-0.975-1.4625-2.15417-1.4625-3.5375 0-1.38333 0.4875-2.5625 1.4625-3.5375 0.975-0.975 2.15417-1.4625 3.5375-1.4625 1.38333 0 2.5625 0.4875 3.5375 1.4625 0.975 0.975 1.4625 2.15417 1.4625 3.5375 0 1.38333-0.4875 2.5625-1.4625 3.5375-0.975 0.975-2.15417 1.4625-3.5375 1.4625l0 0m-7-4l-4 0 0-2 4 0 0 2 0 0m18 0l-4 0 0-2 4 0 0 2 0 0m-12-8l0-4 2 0 0 4-2 0 0 0m0 18l0-4 2 0 0 4-2 0 0 0m-4.6-15.25l-2.525-2.425 1.425-1.475 2.4 2.5-1.3 1.4 0 0m12.3 12.4l-2.425-2.525 1.325-1.375 2.525 2.425-1.425 1.475 0 0m-2.45-13.75l2.425-2.525 1.475 1.425-2.5 2.4-1.4-1.3 0 0m-12.4 12.3l2.525-2.425 1.375 1.325-2.425 2.525-1.475-1.425 0 0m8.15-6.7l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 22, + "height": 22 + } + ] + } + ] + }, + { + "type": "frame", + "id": "iRvuv", + "name": "Button", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "sRnlq", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "children": [ + { + "type": "path", + "id": "K9p1g", + "name": "Icon", + "geometry": "M9 18c-2.5 0-4.625-0.875-6.375-2.625-1.75-1.75-2.625-3.875-2.625-6.375 0-2.5 0.875-4.625 2.625-6.375 1.75-1.75 3.875-2.625 6.375-2.625 0.23333 0 0.4625 0.00833 0.6875 0.025 0.225 0.01667 0.44583 0.04167 0.6625 0.075-0.68333 0.48333-1.22917 1.1125-1.6375 1.8875-0.40833 0.775-0.6125 1.6125-0.6125 2.5125 0 1.5 0.525 2.775 1.575 3.825 1.05 1.05 2.325 1.575 3.825 1.575 0.91667 0 1.75833-0.20417 2.525-0.6125 0.76667-0.40833 1.39167-0.95417 1.875-1.6375 0.03333 0.21667 0.05833 0.4375 0.075 0.6625 0.01667 0.225 0.025 0.45417 0.025 0.6875 0 2.5-0.875 4.625-2.625 6.375-1.75 1.75-3.875 2.625-6.375 2.625l0 0m0-2c1.46667 0 2.78333-0.40417 3.95-1.2125 1.16667-0.80833 2.01667-1.8625 2.55-3.1625-0.33333 0.08333-0.66667 0.15-1 0.2-0.33333 0.05-0.66667 0.075-1 0.075-2.05 0-3.79583-0.72083-5.2375-2.1625-1.44167-1.44167-2.1625-3.1875-2.1625-5.2375 0-0.33333 0.025-0.66667 0.075-1 0.05-0.33333 0.11667-0.66667 0.2-1-1.3 0.53333-2.35417 1.38333-3.1625 2.55-0.80833 1.16667-1.2125 2.48333-1.2125 3.95 0 1.93333 0.68333 3.58333 2.05 4.95 1.36667 1.36667 3.01667 2.05 4.95 2.05l0 0m-0.25-6.75l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "I5JSPI", + "name": "Button", + "width": 48, + "height": 48, + "fill": "#2a2a2bff", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "bNLbu", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "IlVwt", + "name": "Icon", + "geometry": "M7.3 20l-0.4-3.2c-0.21667-0.08333-0.42083-0.18333-0.6125-0.3-0.19167-0.11667-0.37917-0.24167-0.5625-0.375l-2.975 1.25-2.75-4.75 2.575-1.95c-0.01667-0.11667-0.025-0.22917-0.025-0.3375 0-0.10833 0-0.22083 0-0.3375 0-0.11667 0-0.22917 0-0.3375 0-0.10833 0.00833-0.22083 0.025-0.3375l-2.575-1.95 2.75-4.75 2.975 1.25c0.18333-0.13333 0.375-0.25833 0.575-0.375 0.2-0.11667 0.4-0.21667 0.6-0.3l0.4-3.2 5.5 0 0.4 3.2c0.21667 0.08333 0.42083 0.18333 0.6125 0.3 0.19167 0.11667 0.37917 0.24167 0.5625 0.375l2.975-1.25 2.75 4.75-2.575 1.95c0.01667 0.11667 0.025 0.22917 0.025 0.3375 0 0.10833 0 0.22083 0 0.3375 0 0.11667 0 0.22917 0 0.3375 0 0.10833-0.01667 0.22083-0.05 0.3375l2.575 1.95-2.75 4.75-2.95-1.25c-0.18333 0.13333-0.375 0.25833-0.575 0.375-0.2 0.11667-0.4 0.21666-0.6 0.3l-0.4 3.2-5.5 0 0 0m1.75-2l1.975 0 0.35-2.65c0.51667-0.13333 0.99583-0.32917 1.4375-0.5875 0.44167-0.25833 0.84583-0.57083 1.2125-0.9375l2.475 1.025 0.975-1.7-2.15-1.625c0.08333-0.23333 0.14167-0.47917 0.175-0.7375 0.03333-0.25833 0.05-0.52083 0.05-0.7875 0-0.26667-0.01667-0.52917-0.05-0.7875-0.03333-0.25833-0.09167-0.50417-0.175-0.7375l2.15-1.625-0.975-1.7-2.475 1.05c-0.36667-0.38333-0.77083-0.70417-1.2125-0.9625-0.44167-0.25833-0.92083-0.45417-1.4375-0.5875l-0.325-2.65-1.975 0-0.35 2.65c-0.51667 0.13333-0.99583 0.32917-1.4375 0.5875-0.44167 0.25833-0.84583 0.57083-1.2125 0.9375l-2.475-1.025-0.975 1.7 2.15 1.6c-0.08333 0.25-0.14167 0.5-0.175 0.75-0.03333 0.25-0.05 0.51667-0.05 0.8 0 0.26667 0.01667 0.525 0.05 0.775 0.03333 0.25 0.09167 0.5 0.175 0.75l-2.15 1.625 0.975 1.7 2.475-1.05c0.36667 0.38333 0.77083 0.70417 1.2125 0.9625 0.44167 0.25833 0.92083 0.45417 1.4375 0.5875l0.325 2.65 0 0m1.05-4.5c0.96667 0 1.79167-0.34167 2.475-1.025 0.68333-0.68333 1.025-1.50833 1.025-2.475 0-0.96667-0.34167-1.79167-1.025-2.475-0.68333-0.68333-1.50833-1.025-2.475-1.025-0.98333 0-1.8125 0.34167-2.4875 1.025-0.675 0.68333-1.0125 1.50833-1.0125 2.475 0 0.96667 0.3375 1.79167 1.0125 2.475 0.675 0.68333 1.50417 1.025 2.4875 1.025l0 0m-0.05-3.5l0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "fill": "#e4e2e4ff", + "width": 20.100000381469727, + "height": 20 + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "iUZWH", + "name": "Main Content Area", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 24, + "padding": 24, + "justifyContent": "center", + "children": [ + { + "type": "frame", + "id": "X1MWhq", + "name": "Section - Left: Task List Area (Wide Grid)", + "width": 862.8599853515625, + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 24, + "children": [ + { + "type": "frame", + "id": "w1BXV", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 217.9499969482422, + "justifyContent": "space_between", + "alignItems": "end", + "children": [ + { + "type": "frame", + "id": "cIXbp", + "name": "Container", + "width": "fit_content(224)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "xfO2F", + "name": "Heading 2", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "lD6fe", + "name": "Text", + "fill": "#bec6e0ff", + "content": "程序库", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "F8WOs5", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "F2wHd", + "name": "Text", + "fill": "#c6c6cdff", + "content": "请选择需要执行的检测任务方案", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "egI3Q", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "eUpHL", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "dWZ7o", + "name": "Input", + "clip": true, + "width": 256, + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "padding": [ + 10, + 16, + 8, + 40 + ], + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "JKn98", + "name": "Container", + "clip": true, + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 2, + 0 + ], + "children": [ + { + "type": "text", + "id": "R3zx2", + "name": "搜索任务...", + "fill": "#6b7280ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "搜索任务...", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "N36YA", + "layoutPosition": "absolute", + "x": 12, + "y": 9, + "name": "Container", + "height": 24, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "OhNqJ", + "name": "Icon", + "geometry": "M16.6 18l-6.3-6.3c-0.5 0.4-1.075 0.71667-1.725 0.95-0.65 0.23333-1.34167 0.35-2.075 0.35-1.81667 0-3.35417-0.62917-4.6125-1.8875-1.25833-1.25833-1.8875-2.79583-1.8875-4.6125 0-1.81667 0.62917-3.35417 1.8875-4.6125 1.25833-1.25833 2.79583-1.8875 4.6125-1.8875 1.81667 0 3.35417 0.62917 4.6125 1.8875 1.25833 1.25833 1.8875 2.79583 1.8875 4.6125 0 0.73333-0.11667 1.425-0.35 2.075-0.23333 0.65-0.55 1.225-0.95 1.725l6.3 6.3-1.4 1.4 0 0m-10.1-7c1.25 0 2.3125-0.4375 3.1875-1.3125 0.875-0.875 1.3125-1.9375 1.3125-3.1875 0-1.25-0.4375-2.3125-1.3125-3.1875-0.875-0.875-1.9375-1.3125-3.1875-1.3125-1.25 0-2.3125 0.4375-3.1875 1.3125-0.875 0.875-1.3125 1.9375-1.3125 3.1875 0 1.25 0.4375 2.3125 1.3125 3.1875 0.875 0.875 1.9375 1.3125 3.1875 1.3125l0 0", + "fill": "#909097ff", + "width": 18, + "height": 18 + } + ] + } + ] + }, + { + "type": "frame", + "id": "G5884", + "name": "Background+Border", + "fill": "#1b1b1dff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": [ + 8, + 16 + ], + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "AyE0s", + "name": "Text", + "fill": "#909097ff", + "content": "共 8 个可用程序", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "wx2Eu", + "name": "Container", + "clip": true, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "s2p0Al", + "x": 290.2866516113281, + "y": 260, + "name": "Add New Program Card", + "width": 274.2866516113281, + "height": 260, + "fill": "#0e0e104d", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#45464d66" + }, + "layout": "vertical", + "padding": 24, + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "YbdsF", + "name": "Margin", + "width": 64, + "height": 80, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 16, + 0 + ], + "children": [ + { + "type": "frame", + "id": "C0hWB4", + "name": "Background", + "width": 64, + "height": 64, + "fill": "#2a2a2bff", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "k0D0BA", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "a8X0E", + "name": "Icon", + "geometry": "M7.5 10l-7.5 0 0-2.5 7.5 0 0-7.5 2.5 0 0 7.5 7.5 0 0 2.5-7.5 0 0 7.5-2.5 0 0-7.5 0 0", + "fill": "#e4e2e4ff", + "width": 17.5, + "height": 17.5 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "dCKjO", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "NiqMz", + "name": "Text", + "fill": "#909097ff", + "content": "新建检测方案", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "yLsNz", + "x": 0, + "y": 0, + "name": "Program Card 1 (Active/Selected)", + "width": 274.2866516113281, + "height": 244, + "fill": "#1f1f21ff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 2, + "fill": "#bec6e0ff" + }, + "layout": "vertical", + "gap": 8, + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "EvXaq", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Program Card 1 (Active/Selected):shadow", + "fill": "#ffffff01", + "width": 274.2799987792969, + "height": 244, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#bec6e00d", + "offset": { + "x": 0, + "y": 8 + }, + "blur": 8.75, + "spread": -6 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#bec6e00d", + "offset": { + "x": 0, + "y": 20 + }, + "blur": 21.875, + "spread": -5 + } + ] + }, + { + "type": "frame", + "id": "bLPdf", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "NI8je", + "name": "ID: P-001", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "ID: P-001", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "WaVyW", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Gh24L", + "name": "海洛因成分检测", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "海洛因成分检测", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "dJLQs", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "padding": [ + 8, + 0, + 16, + 0 + ], + "children": [ + { + "type": "frame", + "id": "N0Mli", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 104.80000305175781, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "R0rW7", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "t2Z9p1", + "name": "Text", + "fill": "#909097ff", + "content": "检测等级", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "mF5yr", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "VJYwU", + "name": "Text", + "fill": "#adc6ffff", + "content": "Standard", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "AMt4H", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 87.08999633789062, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "JYwKQ", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "oghms", + "name": "Text", + "fill": "#909097ff", + "content": "创建日期", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "sHw9T", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "SdmCg", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-09-12", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "QjH8q", + "name": "Button", + "width": "fill_container", + "fill": "#bec6e0ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "l3LMZ", + "name": "Text", + "fill": "#283044ff", + "content": "重新配置方案", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "X48gJq", + "layoutPosition": "absolute", + "x": 231.27999877929688, + "y": 18, + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "ty6MT", + "name": "Icon", + "geometry": "M10.75 18.25l8.8125-8.8125-1.75-1.75-7.0625 7.0625-3.5625-3.5625-1.75 1.75 5.3125 5.3125 0 0m1.75 6.75c-1.72917 0-3.35417-0.32813-4.875-0.98438-1.52083-0.65625-2.84375-1.54688-3.96875-2.67187-1.125-1.125-2.01563-2.44792-2.67188-3.96875-0.65625-1.52083-0.98438-3.14583-0.98437-4.875 0-1.72917 0.32813-3.35417 0.98437-4.875 0.65625-1.52083 1.54688-2.84375 2.67188-3.96875 1.125-1.125 2.44792-2.01563 3.96875-2.67188 1.52083-0.65625 3.14583-0.98438 4.875-0.98437 1.72917 0 3.35417 0.32813 4.875 0.98437 1.52083 0.65625 2.84375 1.54688 3.96875 2.67188 1.125 1.125 2.01563 2.44792 2.67188 3.96875 0.65625 1.52083 0.98438 3.14583 0.98437 4.875 0 1.72917-0.32813 3.35417-0.98437 4.875-0.65625 1.52083-1.54688 2.84375-2.67188 3.96875-1.125 1.125-2.44792 2.01563-3.96875 2.67188-1.52083 0.65625-3.14583 0.98438-4.875 0.98437l0 0", + "fill": "#bec6e0ff", + "width": 25, + "height": 25 + } + ] + } + ] + }, + { + "type": "frame", + "id": "kXeZa", + "x": 290.2866516113281, + "y": 0, + "name": "Program Card 2", + "width": 274.2866516113281, + "height": 244, + "fill": "#1e293bff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "gap": 8, + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "JodWj", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Xw6gS", + "name": "ID: P-002", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "ID: P-002", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "CqPEO", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "h7NvEj", + "name": "冰毒快速筛查", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "冰毒快速筛查", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "fz6eX", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "padding": [ + 8, + 0, + 16, + 0 + ], + "children": [ + { + "type": "frame", + "id": "TbuTf", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 130.02999877929688, + "padding": [ + 0, + 2.842170943040401e-14, + 0, + 0 + ], + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "NxB2w", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "FbVlE", + "name": "Text", + "fill": "#909097ff", + "content": "检测等级", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "N49wR", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "BP8wr", + "name": "Text", + "fill": "#c6c6cdff", + "content": "Rapid", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Nlyl4", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 89.16999816894531, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "zv0FD", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "l1ZcS", + "name": "Text", + "fill": "#909097ff", + "content": "创建日期", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "JcF4L", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Xsay2", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-10-05", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "p2TQe", + "name": "Button", + "opacity": 0, + "width": "fill_container", + "fill": "#353436ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "WWLxW", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "选中此方案", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "qQZfO", + "x": 580.5733032226562, + "y": 0, + "name": "Program Card 3", + "width": 274.2866516113281, + "height": 244, + "fill": "#1e293bff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "gap": 8, + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "XOF1G", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Y3JCf5", + "name": "ID: P-005", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "ID: P-005", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "diUR9", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "m6Y1pW", + "name": "氯胺酮定量分析", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "氯胺酮定量分析", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "cj8gs", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "padding": [ + 8, + 0, + 16, + 0 + ], + "children": [ + { + "type": "frame", + "id": "myONz", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 99.0999984741211, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "kZeWW", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "zumi0", + "name": "Text", + "fill": "#909097ff", + "content": "检测等级", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "Yc5GP", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "V7Z5a", + "name": "Text", + "fill": "#dec29aff", + "content": "Lab Grade", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "fE3sS", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 92.33000183105469, + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "e1pIh", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "WhZgZ", + "name": "Text", + "fill": "#909097ff", + "content": "创建日期", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "T039iA", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "E7PpT", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-10-15", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "HOK7T", + "name": "Button", + "opacity": 0, + "width": "fill_container", + "fill": "#353436ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "FgUgD", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "选中此方案", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "cG6bh", + "x": 0, + "y": 260, + "name": "Program Card 4", + "width": 274.2866516113281, + "height": 260, + "fill": "#1e293bff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#334155ff" + }, + "layout": "vertical", + "gap": 8, + "padding": 24, + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "c7u3De", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "l0p77", + "name": "ID: P-008", + "fill": "#909097ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "ID: P-008", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fgDXz", + "name": "Heading 3", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "KKe3A", + "name": "可卡因代谢物检测", + "fill": "#e4e2e4ff", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "可卡因代谢物检测", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "elqLV", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "padding": [ + 8, + 0, + 16, + 0 + ], + "children": [ + { + "type": "frame", + "id": "gJ8sg", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 104.19999694824219, + "padding": [ + 0, + 2.842170943040401e-14, + 0, + 0 + ], + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "yMX9b", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "Qi3Je", + "name": "Text", + "fill": "#909097ff", + "content": "检测等级", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "kwVQw", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "h1Xx9", + "name": "Text", + "fill": "#c6c6cdff", + "content": "Extended", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "y3nTf", + "name": "Container", + "width": "fill_container", + "height": "fit_content(20)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 89.41999816894531, + "padding": [ + 0, + 2.842170943040401e-14, + 0, + 0 + ], + "justifyContent": "space_between", + "children": [ + { + "type": "frame", + "id": "EIAiE", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "N8JIou", + "name": "Text", + "fill": "#909097ff", + "content": "创建日期", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 14, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "NUqVp", + "name": "Container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "o6p0Ia", + "name": "Text", + "fill": "#c6c6cdff", + "content": "2023-10-20", + "lineHeight": 1.4285714285714286, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "NaGw2", + "name": "Button", + "opacity": 0, + "width": "fill_container", + "fill": "#353436ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "padding": [ + 12, + 0 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Nzist", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "选中此方案", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "C0i7t2", + "name": "Aside - Right: Running Status Sidebar (Detailed)", + "clip": true, + "width": 345.1400146484375, + "height": "fill_container", + "fill": "#1f1f21ff", + "cornerRadius": 24, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "layout": "vertical", + "padding": 32, + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 12, + "id": "n8HoI", + "layoutPosition": "absolute", + "x": 184.13999938964844, + "y": -95, + "name": "Background Decorative Glow", + "fill": "#bec6e00d", + "width": 256, + "height": 256, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": { + "type": "blur", + "radius": 70 + } + }, + { + "type": "frame", + "id": "H6Mw0t", + "name": "Container", + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "Kvvbl", + "name": "Margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 40, + 0 + ], + "children": [ + { + "type": "frame", + "id": "LHClo", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 67.12000274658203, + "justifyContent": "space_between", + "children": [ + { + "type": "text", + "id": "urcVC", + "name": "Heading 2 → 运行状态", + "fill": "#e4e2e4ff", + "content": "运行状态", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "djKB6", + "name": "Overlay+Border", + "fill": "#adc6ff1a", + "cornerRadius": 12, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#adc6ff33" + }, + "layout": "vertical", + "padding": [ + 4, + 12 + ], + "layoutIncludeStroke": true, + "children": [ + { + "type": "text", + "id": "NvmsX", + "name": "Text", + "fill": "#adc6ffff", + "content": "正在处理 A1 模块", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "IZTSM", + "name": "Status Grid:margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 32, + 0 + ], + "children": [ + { + "type": "frame", + "id": "do8sI", + "name": "Status Grid", + "width": "fill_container", + "height": 275, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "OjhHr", + "x": 0, + "y": 0, + "name": "Background+Border", + "width": 279.1400146484375, + "height": 118, + "fill": "#0e0e10ff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d66" + }, + "gap": 24, + "padding": 24, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "Y1LDO", + "name": "Overlay", + "width": 64, + "height": 64, + "fill": "#bec6e01a", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "ZDL38", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "jcXfD", + "name": "Icon", + "geometry": "M0 15l0-12.5c0-0.6875 0.24479-1.27604 0.73438-1.76563 0.48958-0.48958 1.07813-0.73438 1.76562-0.73437l17.5 0c0.6875 0 1.27604 0.24479 1.76563 0.73438 0.48958 0.48958 0.73438 1.07813 0.73437 1.76562l0 12.5c0 0.6875-0.24479 1.27604-0.73438 1.76563-0.48958 0.48958-1.07813 0.73438-1.76562 0.73437l-17.5 0c-0.6875 0-1.27604-0.24479-1.76563-0.73438-0.48958-0.48958-0.73438-1.07813-0.73437-1.76562l0 0m9.15625-7.5l10.84375 0 0 0 0 0 0-5 0 0 0 0-10.84375 0 0 0 0 0 0 5 0 0 0 0 0 0m6.6875 7.5l4.15625 0 0 0 0 0 0-5 0 0 0 0-4.15625 0 0 0 0 0 0 5 0 0 0 0 0 0m-6.6875 0l4.1875 0 0 0 0 0 0-5 0 0 0 0-4.1875 0 0 0 0 0 0 5 0 0 0 0 0 0m-6.65625 0l4.15625 0 0 0 0 0 0-12.5 0 0 0 0-4.15625 0 0 0 0 0 0 12.5 0 0 0 0 0 0", + "fill": "#bec6e0ff", + "width": 22.5, + "height": 17.5 + } + ] + } + ] + }, + { + "type": "frame", + "id": "Y3PvJ", + "name": "Container", + "width": "fit_content(112.61000061035156)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "xtv1v", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "J34MhH", + "name": "Text", + "fill": "#909097ff", + "content": "当前单元 UNIT", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "qZp6f", + "name": "Paragraph", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 8, + "children": [ + { + "type": "text", + "id": "ILjqC", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "A1", + "lineHeight": 1.1111111111111112, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 36, + "fontWeight": "700" + }, + { + "type": "text", + "id": "z0oni", + "name": "Text", + "fill": "#c6c6cdff", + "content": "加样模块", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "v0sQVl", + "x": 0, + "y": 134, + "name": "Background+Border", + "width": 279.1400146484375, + "height": 141, + "fill": "#0e0e10ff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d66" + }, + "gap": 24, + "padding": 24, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "fKMYf", + "name": "Overlay", + "width": 60.91999816894531, + "height": 64, + "fill": "#adc6ff1a", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "hRqJb", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "path", + "id": "gm9LZ", + "name": "Icon", + "geometry": "M21.25 10c-1.0625 0-1.95313-0.35938-2.67188-1.07813-0.71875-0.71875-1.07813-1.60938-1.07812-2.67187 0-1.0625 0.35938-1.95313 1.07812-2.67188 0.71875-0.71875 1.60938-1.07813 2.67188-1.07812 1.0625 0 1.95313 0.35938 2.67188 1.07812 0.71875 0.71875 1.07813 1.60938 1.07812 2.67188 0 1.0625-0.35938 1.95313-1.07812 2.67188-0.71875 0.71875-1.60938 1.07813-2.67188 1.07812l0 0m-12.5 2.5l-1.75-1.78125 3.21875-3.21875-10.21875 0 0-2.5 10.21875 0-3.21875-3.25 1.75-1.75 6.25 6.25-6.25 6.25 0 0", + "fill": "#adc6ffff", + "width": 25, + "height": 12.5 + } + ] + } + ] + }, + { + "type": "frame", + "id": "v7pM9", + "name": "Container", + "width": "fit_content(108.86000061035156)", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "frame", + "id": "qs3x0", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "WBSks", + "name": "Text", + "fill": "#909097ff", + "content": "当前步骤 STEP", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "xjwrj", + "name": "Container", + "width": "fill_container", + "height": 63, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none", + "children": [ + { + "type": "text", + "id": "cHm3d", + "x": 0, + "y": 0, + "name": "Text", + "fill": "#e4e2e4ff", + "content": "03", + "lineHeight": 1.1111111111111112, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 36, + "fontWeight": "700" + }, + { + "type": "frame", + "id": "b3aoM", + "x": 55.52000045776367, + "y": 15, + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 8.699999809265137, + 0, + 0 + ], + "children": [ + { + "type": "text", + "id": "DV7om", + "name": "Text", + "fill": "#c6c6cdff", + "content": "离心搅拌程\n序", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "vjr0A", + "name": "Time Section:margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 0, + 0, + 40, + 0 + ], + "children": [ + { + "type": "frame", + "id": "xxQZw", + "name": "Time Section", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "cGP55", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "J6hU8", + "name": "Text", + "fill": "#909097ff", + "content": "剩余预计时间 ESTIMATED\nREMAINING", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "o24wI", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "umenP", + "name": "Text", + "fill": "#adc6ffff", + "content": "00:01:45", + "lineHeight": 1, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Liberation Mono", + "fontSize": 48, + "fontWeight": "normal", + "letterSpacing": 4.800000190734863 + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "hlhxm", + "name": "Progress Section", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "NbBMx", + "name": "Container", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 179.58999633789062, + "justifyContent": "space_between", + "alignItems": "end", + "children": [ + { + "type": "frame", + "id": "LM4oo", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "wXEJV", + "name": "Text", + "fill": "#e4e2e4ff", + "content": "总体进度", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "cCwSn", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "children": [ + { + "type": "text", + "id": "NgfEn", + "name": "Text", + "fill": "#adc6ffff", + "content": "45%", + "lineHeight": 1.5, + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "HC3iD", + "name": "Background+Border", + "clip": true, + "width": "fill_container", + "height": 32, + "fill": "#353436ff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d4d" + }, + "layout": "vertical", + "padding": 4, + "justifyContent": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "pDu4v", + "name": "Background", + "width": 121.11000061035156, + "height": "fill_container", + "fill": "#adc6ffff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "justifyContent": "center", + "children": [ + { + "type": "rectangle", + "id": "cv231", + "name": "Gradient", + "fill": { + "type": "gradient", + "gradientType": "linear", + "enabled": true, + "rotation": -450, + "size": { + "height": 1 + }, + "colors": [ + { + "color": "#ffffff00", + "position": 0 + }, + { + "color": "#ffffff33", + "position": 0.5 + }, + { + "color": "#ffffff00", + "position": 1 + } + ], + "center": { + "y": 0.49999999999999994 + } + }, + "width": "fill_container", + "height": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "yIlwN", + "name": "Decorative Visual:margin", + "width": "fill_container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "padding": [ + 32, + 0, + 0, + 0 + ], + "children": [ + { + "type": "frame", + "id": "g2FSM", + "name": "Decorative Visual", + "clip": true, + "width": "fill_container", + "height": 89, + "fill": "#1b1b1d80", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464d33" + }, + "layout": "none", + "children": [ + { + "type": "frame", + "id": "C119e", + "x": 1, + "y": 1, + "name": "Interior view of processing unit", + "opacity": 0.30000001192092896, + "clip": true, + "width": 277.1400146484375, + "height": 160, + "fill": [ + { + "type": "image", + "enabled": true, + "url": "images\\image-import.png", + "mode": "stretch" + }, + { + "type": "color", + "color": "#ffffffff", + "enabled": true, + "blendMode": "saturation" + } + ], + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "none" + }, + { + "type": "frame", + "id": "iAn4l", + "x": 1, + "y": 1, + "name": "Container", + "width": 277.1400146484375, + "height": 87, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "zteVN", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "POqCD", + "name": "Icon", + "geometry": "M20 28c-2.2 0-4.08333-0.78333-5.65-2.35-1.56667-1.56667-2.35-3.45-2.35-5.65 0-2.2 0.78333-4.08333 2.35-5.65 1.56667-1.56667 3.45-2.35 5.65-2.35 2.2 0 4.08333 0.78333 5.65 2.35 1.56667 1.56667 2.35 3.45 2.35 5.65 0 2.2-0.78333 4.08333-2.35 5.65-1.56667 1.56667-3.45 2.35-5.65 2.35l0 0m0-4c1.1 0 2.04167-0.39167 2.825-1.175 0.78333-0.78333 1.175-1.725 1.175-2.825 0-1.1-0.39167-2.04167-1.175-2.825-0.78333-0.78333-1.725-1.175-2.825-1.175-1.1 0-2.04167 0.39167-2.825 1.175-0.78333 0.78333-1.175 1.725-1.175 2.825 0 1.1 0.39167 2.04167 1.175 2.825 0.78333 0.78333 1.725 1.175 2.825 1.175l0 0m9.05 16c0.6-1.86666 1.05-3.53333 1.35-5 0.3-1.46667 0.53333-2.63334 0.7-3.5-1.43333 1.4-3.1 2.5-5 3.3-1.9 0.8-3.93333 1.2-6.1 1.2-4.53333 0-8.50833-0.30833-11.925-0.925-3.41667-0.61666-6.10833-1.20834-8.075-1.775l0-4.25c1.86667 0.6 3.53333 1.05 5 1.35 1.46667 0.3 2.63333 0.53333 3.5 0.7-1.4-1.43333-2.5-3.1-3.3-5-0.8-1.9-1.2-3.93333-1.2-6.1 0-4.56667 0.30833-8.55 0.925-11.95 0.61667-3.4 1.20833-6.08333 1.775-8.05l4.25 0c-0.6 1.86667-1.05833 3.53333-1.375 5-0.31667 1.46667-0.54167 2.63333-0.675 3.5 1.43333-1.4 3.1-2.5 5-3.3 1.9-0.8 3.93333-1.2 6.1-1.2 4.56667 0 8.55 0.30833 11.95 0.925 3.4 0.61667 6.08333 1.20833 8.05 1.775l0 4.25c-1.86666-0.6-3.53333-1.05833-5-1.375-1.46667-0.31667-2.63334-0.54167-3.5-0.675 1.4 1.43333 2.5 3.1 3.3 5 0.8 1.9 1.2 3.93333 1.2 6.1 0 4.56667-0.30833 8.55-0.925 11.95-0.61666 3.4-1.20834 6.08333-1.775 8.05l-4.25 0 0 0m-9.05-8c3.33333 0 6.16667-1.16667 8.5-3.5 2.33333-2.33333 3.5-5.16667 3.5-8.5 0-3.33333-1.16667-6.16667-3.5-8.5-2.33333-2.33333-5.16667-3.5-8.5-3.5-3.33333 0-6.16667 1.16667-8.5 3.5-2.33333 2.33333-3.5 5.16667-3.5 8.5 0 3.33333 1.16667 6.16667 3.5 8.5 2.33333 2.33333 5.16667 3.5 8.5 3.5l0 0", + "fill": "#adc6ffff", + "width": 40, + "height": 40 + }, + { + "type": "frame", + "id": "pIZeB", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "dd70o", + "name": "Text", + "fill": "#adc6ffff", + "content": "AGITATOR ACTIVE - 450 RPM", + "lineHeight": 1.3333333333333333, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "P4mkZ", + "name": "Footer - Bottom Controls Bar", + "width": "fill_container", + "height": 112, + "fill": "#0e0e10ff", + "stroke": { + "align": "inside", + "thickness": { + "top": 1 + }, + "fill": "#45464dff" + }, + "padding": [ + 0, + 32 + ], + "justifyContent": "center", + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "h7D5Pn", + "name": "Background+Border", + "fill": "#1b1b1dff", + "cornerRadius": 16, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#45464dff" + }, + "gap": 24, + "padding": 12, + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "n2Gp1z", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Overlay+Shadow", + "fill": "#ffffff01", + "width": 886.030029296875, + "height": 100, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#00000040", + "offset": { + "x": 0, + "y": 25 + }, + "blur": 43.75, + "spread": -12 + } + }, + { + "type": "frame", + "id": "b36Mn", + "name": "Button - Run (Disabled during run)", + "opacity": 0.5, + "fill": "#35343680", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": [ + 16, + 40 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "ZmeBN", + "name": "Background", + "width": 40, + "height": 40, + "fill": "#353436ff", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "y2rcP", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "fo4it", + "name": "Icon", + "geometry": "M0 17.5l0-17.5 13.75 8.75-13.75 8.75 0 0", + "fill": "#909097ff", + "width": 13.75, + "height": 17.5 + } + ] + } + ] + }, + { + "type": "frame", + "id": "spQCy", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "CCvdd", + "name": "Text", + "fill": "#909097ff", + "content": "开始运行 RUN", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "WenQuanYi Zen Hei", + "fontSize": 16, + "fontWeight": "500", + "letterSpacing": 1.600000023841858 + } + ] + } + ] + }, + { + "type": "rectangle", + "id": "FEGmK", + "name": "Vertical Divider", + "fill": "#45464dff", + "width": 1, + "height": 48, + "stroke": { + "align": "inside", + "thickness": 1 + } + }, + { + "type": "frame", + "id": "ril87", + "name": "Button - Pause", + "fill": "#f59e0b1a", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1, + "fill": "#f59e0b33" + }, + "gap": 16, + "padding": [ + 16, + 40 + ], + "alignItems": "center", + "layoutIncludeStroke": true, + "children": [ + { + "type": "frame", + "id": "bFjaZ", + "name": "Overlay", + "width": 40, + "height": 40, + "fill": "#f59e0b33", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "f9KdrY", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "MliCt", + "name": "Icon", + "geometry": "M10 17.5l0-17.5 5 0 0 17.5-5 0 0 0m-10 0l0-17.5 5 0 0 17.5-5 0 0 0", + "fill": "#f59e0bff", + "width": 15, + "height": 17.5 + } + ] + } + ] + }, + { + "type": "frame", + "id": "EltYY", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "R50EpM", + "name": "Text", + "fill": "#f59e0bff", + "content": "暂停任务 PAUSE", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal", + "letterSpacing": 1.600000023841858 + } + ] + } + ] + }, + { + "type": "frame", + "id": "Y9seHh", + "name": "Button - Stop", + "fill": "#ef4444ff", + "cornerRadius": 8, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "gap": 16, + "padding": [ + 16, + 40 + ], + "alignItems": "center", + "children": [ + { + "type": "rectangle", + "cornerRadius": 8, + "id": "OOZC8", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "Button - Stop:shadow", + "fill": "#ffffff01", + "width": 261.4100036621094, + "height": 72, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "effect": [ + { + "type": "shadow", + "shadowType": "outer", + "color": "#ef444433", + "offset": { + "x": 0, + "y": 4 + }, + "blur": 5.25, + "spread": -4 + }, + { + "type": "shadow", + "shadowType": "outer", + "color": "#ef444433", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 13.125, + "spread": -3 + } + ] + }, + { + "type": "frame", + "id": "I2CY0u", + "name": "Overlay", + "width": 40, + "height": 40, + "fill": "#ffffff33", + "cornerRadius": 4, + "stroke": { + "align": "inside", + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Zfv0D", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "path", + "id": "aW83F", + "name": "Icon", + "geometry": "M0 15l0-15 15 0 0 15-15 0 0 0", + "fill": "#ffffffff", + "width": 15, + "height": 15 + } + ] + } + ] + }, + { + "type": "frame", + "id": "CHpWj", + "name": "Container", + "stroke": { + "align": "inside", + "thickness": 1 + }, + "layout": "vertical", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "u2n88", + "name": "Text", + "fill": "#ffffffff", + "content": "停止运行 STOP", + "lineHeight": 1.5, + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "normal", + "letterSpacing": 1.600000023841858 + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "variables": { + "--accent-danger": { + "type": "color", + "value": "#E74C3C" + }, + "--accent-primary": { + "type": "color", + "value": "#635BFF" + }, + "--accent-success": { + "type": "color", + "value": "#27AE60" + }, + "--accent-warning": { + "type": "color", + "value": "#F39C12" + }, + "--bg-card": { + "type": "color", + "value": "#FFFFFF" + }, + "--bg-dark": { + "type": "color", + "value": "#1A1A1A" + }, + "--bg-primary": { + "type": "color", + "value": "#FFFFFF" + }, + "--bg-secondary": { + "type": "color", + "value": "#FFFFFF" + }, + "--border-color": { + "type": "color", + "value": "#E5E7EB" + }, + "--font-muted": { + "type": "color", + "value": "#95A5A6" + }, + "--font-primary": { + "type": "color", + "value": "#1A1A1A" + }, + "--font-secondary": { + "type": "color", + "value": "#4B5563" + }, + "--radius-lg": { + "type": "number", + "value": 0 + }, + "--radius-md": { + "type": "number", + "value": 0 + }, + "--radius-sm": { + "type": "number", + "value": 0 + }, + "--spacing-lg": { + "type": "number", + "value": 24 + }, + "--spacing-md": { + "type": "number", + "value": 16 + }, + "--spacing-sm": { + "type": "number", + "value": 8 + }, + "--spacing-xl": { + "type": "number", + "value": 32 + }, + "--spacing-xs": { + "type": "number", + "value": 4 + }, + "--accent-secondary": { + "type": "color", + "value": "#FF9D00" + }, + "--accent-tertiary": { + "type": "color", + "value": "#FF006E" + }, + "--font-body": { + "type": "string", + "value": "Geist" + }, + "--font-caption": { + "type": "string", + "value": "Inter" + }, + "--font-heading": { + "type": "string", + "value": "Playfair Display" + }, + "--font-inverse": { + "type": "color", + "value": "#FFFFFF" + }, + "--radius-full": { + "type": "number", + "value": 9999 + } + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..53c40a3 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,719 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + url: "https://pub.dev" + source: hosted + version: "7.6.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce + url: "https://pub.dev" + source: hosted + version: "0.13.4" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + custom_lint: + dependency: transitive + description: + name: custom_lint + sha256: "9656925637516c5cf0f5da018b33df94025af2088fe09c8ae2ca54c53f2d9a84" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: "6cdc8e87e51baaaba9c43e283ed8d28e59a0c4732279df62f66f7b5984655414" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.7.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: b453934c36e289cef06525734d1e676c1f91da9e22e2017d9dcab6ce0f999175 + url: "https://pub.dev" + source: hosted + version: "15.1.3" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611" + url: "https://pub.dev" + source: hosted + version: "0.5.10" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35" + url: "https://pub.dev" + source: hosted + version: "2.6.5" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" + url: "https://pub.dev" + source: hosted + version: "2.4.2+1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.dev" + source: hosted + version: "2.4.2+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" + url: "https://pub.dev" + source: hosted + version: "3.4.0+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.11.5 <4.0.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..a79bd94 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,44 @@ +name: kuaishai2 +description: "污水毒品快检一体机控制软件" +publish_to: 'none' + +version: 1.0.0+1 + +environment: + sdk: ^3.11.5 + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + cupertino_icons: ^1.0.8 + + # 状态管理 + flutter_riverpod: ^2.6.0 + + # 路由导航 + go_router: ^15.0.0 + + # 数据存储 + sqflite: ^2.3.0 + path: ^1.9.0 + + # 持久化存储 + shared_preferences: ^2.3.3 + + # 文件选择器 + file_picker: ^8.1.7 + + # 国际化 + intl: ^0.20.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + riverpod_lint: ^2.6.0 + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/test/localization_test.dart b/test/localization_test.dart new file mode 100644 index 0000000..e20bbeb --- /dev/null +++ b/test/localization_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; +import 'package:kuaishai2/core/localization/app_localizations.dart'; + +void main() { + group('AppLocalizations', () { + test('Chinese locale should return Chinese strings', () { + const locale = Locale('zh', 'CN'); + final l10n = AppLocalizations(locale); + + expect(l10n.deviceName, contains('一体机')); + expect(l10n.programs, equals('程序管理')); + expect(l10n.run, equals('运行')); + expect(l10n.settings, equals('系统设置')); + }); + + test('English locale should return English strings', () { + const locale = Locale('en', 'US'); + final l10n = AppLocalizations(locale); + + expect(l10n.deviceName, contains('System')); + expect(l10n.programs, equals('Programs')); + expect(l10n.run, equals('Run')); + expect(l10n.settings, equals('Settings')); + }); + + test('New translation keys should work', () { + const locale = Locale('zh', 'CN'); + final l10n = AppLocalizations(locale); + + expect(l10n.lightOn, equals('亮')); + expect(l10n.lightOff, equals('暗')); + expect(l10n.enabled, equals('启用')); + expect(l10n.disabled, equals('停用')); + expect(l10n.stepList, equals('步骤列表')); + }); + }); +} \ No newline at end of file diff --git a/test/models_test.dart b/test/models_test.dart new file mode 100644 index 0000000..a47806c --- /dev/null +++ b/test/models_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:kuaishai2/features/programs/models/program.dart'; +import 'package:kuaishai2/features/programs/models/step.dart'; + +void main() { + group('Program Model', () { + test('toMap and fromMap should work correctly', () { + final program = Program( + id: 1, + code: 'P001', + name: 'Test Program', + createdAt: '2026-05-20', + status: 1, + ); + + final map = program.toMap(); + final fromMap = Program.fromMap(map); + + expect(fromMap.id, equals(program.id)); + expect(fromMap.code, equals(program.code)); + expect(fromMap.name, equals(program.name)); + expect(fromMap.createdAt, equals(program.createdAt)); + expect(fromMap.status, equals(program.status)); + }); + + test('copyWith should create modified copy', () { + final program = Program( + id: 1, + code: 'P001', + name: 'Test Program', + createdAt: '2026-05-20', + status: 1, + ); + + final copy = program.copyWith(name: 'Updated Name', status: 0); + + expect(copy.id, equals(program.id)); + expect(copy.code, equals(program.code)); + expect(copy.name, equals('Updated Name')); + expect(copy.status, equals(0)); + }); + }); + + group('Step Model', () { + test('toMap and fromMap should work correctly', () { + final step = Step( + id: 1, + programId: 1, + stepNo: 1, + position: 'A1', + name: 'Mix', + mixTime: 60, + magnetTime: 30, + volume: 100, + mixSpeed: '中速', + blowSpeed: '高速', + blowTime: 10, + needleSpeed: 5, + ); + + final map = step.toMap(); + final fromMap = Step.fromMap(map); + + expect(fromMap.id, equals(step.id)); + expect(fromMap.programId, equals(step.programId)); + expect(fromMap.stepNo, equals(step.stepNo)); + expect(fromMap.position, equals(step.position)); + expect(fromMap.name, equals(step.name)); + expect(fromMap.mixTime, equals(step.mixTime)); + expect(fromMap.magnetTime, equals(step.magnetTime)); + expect(fromMap.volume, equals(step.volume)); + }); + + test('copyWith should create modified copy', () { + final step = Step( + id: 1, + programId: 1, + stepNo: 1, + position: 'A1', + name: 'Mix', + mixTime: 60, + ); + + final copy = step.copyWith(stepNo: 2, mixTime: 120); + + expect(copy.id, equals(step.id)); + expect(copy.stepNo, equals(2)); + expect(copy.mixTime, equals(120)); + }); + }); +} \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..b90bad7 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:kuaishai2/main.dart'; + +void main() { + testWidgets('App launches without errors', (WidgetTester tester) async { + // 验证应用能正常启动,不抛出异常 + await tester.pumpWidget(const ProviderScope(child: KuaishaiApp())); + + // 等待异步加载完成 + await tester.pumpAndSettle(const Duration(seconds: 3)); + + // 验证 Scaffold 存在(应用正常渲染) + expect(find.byType(Scaffold), findsWidgets); + }); +} \ No newline at end of file diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..e322719 --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + kuaishai2 + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..987dcd8 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "kuaishai2", + "short_name": "kuaishai2", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}