From d66ba233adf983b72c52c9c968e82c57bdc7458d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=98=9F?= <10947742+xu-xing9@user.noreply.gitee.com> Date: Thu, 16 Apr 2026 08:51:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=92=8C=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend-backend-separation/.config.kiro | 1 + .../requirements.md | 190 +++++++++ .kiro/steering/java-api.md | 127 ++++++ .next/dev/logs/next-development.log | 212 ++++++++++ .next/dev/server/app-paths-manifest.json | 2 + .../page_client-reference-manifest.js | 2 +- .next/dev/trace | 154 ++++++++ .next/dev/types/routes.d.ts | 4 +- .next/dev/types/validator.ts | 18 + src/app/boards/page.tsx | 8 +- src/app/calibration/page.tsx | 278 +++++++------ src/app/calibration/register/page.tsx | 244 ++++++++++++ src/app/components/sidebar.tsx | 8 +- src/app/devices/[sn]/page.tsx | 372 ++++++++++++++++++ src/app/devices/page.tsx | 130 ++++-- src/app/firmware/page.tsx | 6 +- src/app/models/page.tsx | 126 +++++- src/app/registration/page.tsx | 5 +- 18 files changed, 1700 insertions(+), 187 deletions(-) create mode 100644 .kiro/specs/frontend-backend-separation/.config.kiro create mode 100644 .kiro/specs/frontend-backend-separation/requirements.md create mode 100644 .kiro/steering/java-api.md create mode 100644 src/app/calibration/register/page.tsx create mode 100644 src/app/devices/[sn]/page.tsx diff --git a/.kiro/specs/frontend-backend-separation/.config.kiro b/.kiro/specs/frontend-backend-separation/.config.kiro new file mode 100644 index 0000000..97ba296 --- /dev/null +++ b/.kiro/specs/frontend-backend-separation/.config.kiro @@ -0,0 +1 @@ +{"specId": "6761b341-a126-48c6-b6af-57b888694c48", "workflowType": "requirements-first", "specType": "feature"} \ No newline at end of file diff --git a/.kiro/specs/frontend-backend-separation/requirements.md b/.kiro/specs/frontend-backend-separation/requirements.md new file mode 100644 index 0000000..717a37b --- /dev/null +++ b/.kiro/specs/frontend-backend-separation/requirements.md @@ -0,0 +1,190 @@ +# 需求文档:前后端分离 — Java API 后端与 PostgreSQL 数据库 + +## 简介 + +本项目旨在将现有的 Next.js 前端生产管理子系统进行前后端分离改造。当前前端所有页面(设备列表、板卡管理、校准管理、固件库、授权管理、配置文件管理、维修工单、报废管理、设备登记、设备型号管理)均使用硬编码的模拟数据。需要搭建 Java Spring Boot 3.3.6 后端 API 服务和 PostgreSQL 数据库,用真实的 RESTful API 接口和持久化数据替换前端模拟数据。 + +## 术语表 + +- **API_Server**:基于 Spring Boot 3.3.6 的 Java 后端 API 服务,项目路径 `apps/geo-bps-api/` +- **Database**:PostgreSQL 12.14 数据库实例,按模块使用不同 schema 隔离 +- **Frontend**:现有 Next.js 前端应用,位于 `src/app/` 目录 +- **Device_Module**:设备管理业务模块,包名 `com.geomative.bps.device`,数据库 schema `dev` +- **Common_Module**:公共模块,包名 `com.geomative.bps.common`,提供统一响应体、全局异常处理、工具类 +- **API_Admin**:后台管理入口模块,提供需登录鉴权的 API 端点 +- **DDD**:领域驱动设计分层架构(interfaces / application / domain / infrastructure) +- **DO**:数据库映射对象(Data Object) +- **VO**:视图对象(View Object),用于 API 响应 +- **Query**:查询参数对象 +- **Command**:命令对象,用于创建/更新操作 + +## 需求 + +### 需求 1:后端项目骨架搭建 + +**用户故事:** 作为开发者,我希望搭建一个符合技术规范的 Java Spring Boot 多模块 Maven 项目骨架,以便后续各业务模块可以在此基础上开发。 + +#### 验收标准 + +1. THE API_Server SHALL 采用多模块 Maven 项目结构,父 POM 统一管理 Spring Boot 3.3.6、MyBatis-Plus、Lombok 等依赖版本 +2. THE API_Server SHALL 包含以下子模块:api-admin、api-portal、business(含 device 子模块)、common +3. THE Common_Module SHALL 提供统一 JSON 响应格式,包含 code(整数)、message(字符串)、data(泛型)三个字段 +4. THE Common_Module SHALL 提供全局异常处理器,捕获业务异常和参数校验异常并返回统一格式的错误响应 +5. THE Common_Module SHALL 提供 MyBatis-Plus 的 MetaObjectHandler 实现,自动填充 created_at 和 updated_at 审计字段 +6. THE API_Admin SHALL 配置 CORS 策略,允许前端开发服务器(localhost:3000)的跨域请求 +7. THE API_Server SHALL 在 application.yml 中配置 PostgreSQL 数据源连接和 MyBatis-Plus 逻辑删除策略 + +### 需求 2:数据库 Schema 与表结构设计 + +**用户故事:** 作为开发者,我希望设计并创建 PostgreSQL 数据库表结构,以便持久化存储各业务模块的数据。 + +#### 验收标准 + +1. THE Database SHALL 在 `dev` schema 下创建设备管理相关的数据表 +2. THE Database SHALL 包含 `dev.devices` 表,存储设备信息(SN号、型号、状态、固件版本、生产日期、客户名称、批次号) +3. THE Database SHALL 包含 `dev.device_models` 表,存储设备型号信息(型号名称、型号代码、状态、描述) +4. THE Database SHALL 包含 `dev.board_types` 表,存储板卡型号信息(板卡类型、型号、固件版本、生产日期、状态) +5. THE Database SHALL 包含 `dev.firmware_versions` 表,存储固件版本信息(版本号、板卡型号、固件类型、发布日期、状态、文件大小、下载次数、硬件版本范围、升级类型、是否签名、MD5、SHA256、发布说明) +6. THE Database SHALL 包含 `dev.calibration_records` 表,存储校准记录(采集板SN号、板卡型号、校准日期、到期日期、校准人员、状态、通道数、综合偏差) +7. THE Database SHALL 包含 `dev.config_files` 表,存储配置文件信息(配置名称、适配型号、版本、状态、发射参数、采集参数、网络参数) +8. THE Database SHALL 包含 `dev.licenses` 表,存储授权信息(设备型号、授权模块列表、到期时间、状态) +9. THE Database SHALL 包含 `dev.repair_orders` 表,存储维修工单信息(工单号、设备SN、故障类型、状态、优先级、负责人、描述) +10. THE Database SHALL 包含 `dev.scrap_records` 表,存储报废记录(设备SN、型号、报废原因、申请人、状态、来源工单号、残值评估、可回收物料) +11. THE Database SHALL 包含 `dev.checklist_templates` 表和 `dev.checklist_items` 表,存储装配 Checklist 模板及其检查项 +12. WHEN 创建任何业务表时,THE Database SHALL 包含 id(VARCHAR(64) 主键)、created_at、created_by、updated_at、updated_by、deleted 审计字段 + +### 需求 3:设备管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 获取和管理设备数据,以便替换设备列表页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/devices` 时,THE API_Server SHALL 返回分页的设备列表,支持按型号、状态、生产日期、SN号、批次号筛选 +2. WHEN 前端请求 GET `/api/admin/devices/{sn}` 时,THE API_Server SHALL 返回指定设备的详细信息,包含关联的授权信息、子设备列表、固件信息 +3. WHEN 前端请求 POST `/api/admin/devices` 时,THE API_Server SHALL 创建新设备记录(设备登记),包含装机信息和 BOM 清单 +4. WHEN 前端请求 GET `/api/admin/devices/batches` 时,THE API_Server SHALL 返回所有生产批次列表及每个批次的设备数量 +5. IF 请求参数中的 SN 号已存在,THEN THE API_Server SHALL 返回 409 冲突错误码和描述性错误消息 + +### 需求 4:设备型号管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理设备型号和装配 Checklist 模板,以便替换型号管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/device-models` 时,THE API_Server SHALL 返回所有设备型号列表 +2. WHEN 前端请求 POST `/api/admin/device-models` 时,THE API_Server SHALL 创建新设备型号记录 +3. WHEN 前端请求 GET `/api/admin/checklist-templates?modelCode={code}` 时,THE API_Server SHALL 返回指定型号的装配 Checklist 模板及其检查项列表 +4. WHEN 前端请求 POST `/api/admin/checklist-templates` 时,THE API_Server SHALL 创建新的 Checklist 模板,包含检查项列表 +5. IF 请求的型号代码已存在,THEN THE API_Server SHALL 返回 409 冲突错误码 + +### 需求 5:板卡型号管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理板卡型号数据,以便替换板卡管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/board-types` 时,THE API_Server SHALL 返回板卡型号列表,支持按板卡类型(主协板、采集板、发射板、升压板)筛选 +2. WHEN 前端请求 GET `/api/admin/board-types/{id}` 时,THE API_Server SHALL 返回板卡详情,包含升级历史、校准历史、保养历史、维修历史 +3. WHEN 前端请求 POST `/api/admin/board-types` 时,THE API_Server SHALL 创建新板卡型号记录 + +### 需求 6:固件库管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理固件版本数据,以便替换固件库页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/firmware` 时,THE API_Server SHALL 返回固件版本列表,支持按固件类型和板卡型号筛选 +2. WHEN 前端请求 POST `/api/admin/firmware` 时,THE API_Server SHALL 创建新固件版本记录,包含版本号、硬件版本范围、升级类型、固件类型、签名状态、发布说明 +3. WHEN 前端请求 GET `/api/admin/firmware/{id}/download` 时,THE API_Server SHALL 返回固件文件的下载流,并将该固件的下载次数加 1 +4. IF 上传的固件版本号与同一板卡型号的已有版本重复,THEN THE API_Server SHALL 返回 409 冲突错误码 + +### 需求 7:校准管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理采集板校准数据,以便替换校准管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/calibrations` 时,THE API_Server SHALL 返回分页的校准记录列表,支持按采集板SN号、校准状态、校准人员筛选 +2. WHEN 前端请求 GET `/api/admin/calibrations/{id}` 时,THE API_Server SHALL 返回校准详情,包含各通道的校准结果(参考值、测量值、偏差、结果) +3. WHEN 前端请求 POST `/api/admin/calibrations/import` 时,THE API_Server SHALL 支持批量导入校准记录数据 + +### 需求 8:配置文件管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理设备配置文件,以便替换配置文件管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/config-files` 时,THE API_Server SHALL 返回分页的配置文件列表,支持按适配型号、版本、关键字筛选 +2. WHEN 前端请求 GET `/api/admin/config-files/{id}` 时,THE API_Server SHALL 返回配置文件详情,包含发射参数、采集参数、网络参数 +3. WHEN 前端请求 POST `/api/admin/config-files` 时,THE API_Server SHALL 创建新配置文件记录 +4. WHEN 前端请求 PUT `/api/admin/config-files/{id}` 时,THE API_Server SHALL 更新指定配置文件记录 +5. WHEN 前端请求 DELETE `/api/admin/config-files/{id}` 时,THE API_Server SHALL 逻辑删除指定配置文件(设置 deleted=1) + +### 需求 9:授权管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理设备授权数据,以便替换授权管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/licenses` 时,THE API_Server SHALL 返回分页的授权列表,支持按设备型号和状态筛选 +2. WHEN 前端请求 POST `/api/admin/licenses` 时,THE API_Server SHALL 创建新授权记录,包含设备型号、授权模块列表、授权期限 +3. WHEN 前端请求 PUT `/api/admin/licenses/{id}` 时,THE API_Server SHALL 更新指定授权记录 +4. WHEN 前端请求 PUT `/api/admin/licenses/{id}/disable` 时,THE API_Server SHALL 将指定授权记录状态设置为已停用 + +### 需求 10:维修工单管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理维修工单数据,以便替换维修工单页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/repair-orders` 时,THE API_Server SHALL 返回分页的维修工单列表,支持按状态、优先级、负责人、日期范围、设备SN筛选 +2. WHEN 前端请求 GET `/api/admin/repair-orders/{id}` 时,THE API_Server SHALL 返回工单详情,包含设备信息、故障信息、处理记录时间线、板卡更换记录 +3. WHEN 前端请求 POST `/api/admin/repair-orders` 时,THE API_Server SHALL 创建新维修工单 +4. WHEN 前端请求 PUT `/api/admin/repair-orders/{id}/process` 时,THE API_Server SHALL 更新工单处理信息(处理操作、板卡更换、授权处理、处理备注) +5. WHEN 前端请求 PUT `/api/admin/repair-orders/{id}/close` 时,THE API_Server SHALL 关闭工单并将状态设置为已处理 + +### 需求 11:报废管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理报废审批和物料回收数据,以便替换报废管理页面的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/scrap-records` 时,THE API_Server SHALL 返回分页的报废记录列表,支持按设备SN、状态、日期筛选 +2. WHEN 前端请求 GET `/api/admin/scrap-records/{id}` 时,THE API_Server SHALL 返回报废详情,包含设备信息、审批信息、可回收物料列表、审批记录时间线 +3. WHEN 前端请求 PUT `/api/admin/scrap-records/{id}/approve` 时,THE API_Server SHALL 审批通过报废申请,更新状态为已审批 +4. WHEN 前端请求 PUT `/api/admin/scrap-records/{id}/reject` 时,THE API_Server SHALL 驳回报废申请,更新状态为已驳回,记录驳回意见 +5. WHEN 前端请求 PUT `/api/admin/scrap-records/{id}/recover` 时,THE API_Server SHALL 完成物料回收入库,更新状态为已回收,记录回收的物料清单 +6. THE API_Server SHALL 提供 GET `/api/admin/scrap-records/stats` 接口,返回报废统计数据(报废总数、待审批数、已审批待回收数、已回收数) + +### 需求 12:首页 Dashboard 统计 API + +**用户故事:** 作为前端开发者,我希望通过 API 获取首页仪表盘的统计数据,以便替换首页的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/dashboard/metrics` 时,THE API_Server SHALL 返回设备总数、装配中数量、已激活数量、维修中数量、报废数量、授权即将到期数量等统计指标 +2. WHEN 前端请求 GET `/api/admin/dashboard/device-status` 时,THE API_Server SHALL 返回设备状态分布数据(已装配、已出厂、已激活、报废各状态的数量) +3. WHEN 前端请求 GET `/api/admin/dashboard/tasks` 时,THE API_Server SHALL 返回待处理任务列表,包含校准即将到期、维修工单、固件升级通知、授权即将到期四个分组 + +### 需求 13:前端 API 集成层 + +**用户故事:** 作为前端开发者,我希望在前端建立统一的 API 调用层,以便各页面组件可以方便地调用后端接口替换模拟数据。 + +#### 验收标准 + +1. THE Frontend SHALL 创建统一的 API 客户端模块,配置后端 API 基础 URL、请求超时时间(5秒)、统一错误处理 +2. THE Frontend SHALL 为每个业务模块创建独立的 API 服务文件(如 deviceApi.ts、boardApi.ts、firmwareApi.ts 等) +3. WHEN API 请求失败时,THE Frontend SHALL 在页面上展示友好的错误提示信息 +4. WHEN API 请求正在进行时,THE Frontend SHALL 展示加载状态指示器 +5. THE Frontend SHALL 将各页面组件中的硬编码模拟数据替换为 API 调用,使用 React 的 useState 和 useEffect 管理数据获取状态 + +### 需求 14:分页与筛选标准化 + +**用户故事:** 作为前端开发者,我希望前后端采用统一的分页和筛选参数规范,以便各列表页面的数据交互保持一致。 + +#### 验收标准 + +1. THE API_Server SHALL 对所有列表接口采用统一的分页参数格式:page(页码,从1开始)、pageSize(每页条数,默认10) +2. THE API_Server SHALL 对所有列表接口返回统一的分页响应格式,包含 total(总记录数)、page(当前页码)、pageSize(每页条数)、records(数据列表) +3. THE API_Server SHALL 对所有筛选参数进行服务端校验,无效参数返回 400 错误码和描述性错误消息 +4. WHEN 筛选条件为空或为"全部"时,THE API_Server SHALL 返回不带该条件过滤的完整数据集 diff --git a/.kiro/steering/java-api.md b/.kiro/steering/java-api.md new file mode 100644 index 0000000..6c77ab8 --- /dev/null +++ b/.kiro/steering/java-api.md @@ -0,0 +1,127 @@ +# Java API 技术规范 + +## 运行环境 +- JDK 17 + +## 框架选型 +- Spring Boot 3.3.6 (spring-boot-starter-parent) +- ORM:MyBatis-Plus +- Lombok + +## 架构约束 +- API 采用多模块 Maven 项目结构,父项目统一管理依赖版本 +- 项目名: geo-bps +- 包名前缀:com.geomative.bps.* +- 子模块划分: + - `api-admin` 后台管理入口模块:需登录鉴权,引入 `website`、`device`、`library`、`permission` 等模块 + - `api-portal` 前端门户入口模块:无需登录,仅引入对外开放的业务模块 + - `device` 设备管理模块 + - `website` 网站管理后台 + - `library` 资料库模块 + - `permission` 权限管理模块 + - `common` 公共模块:工具类、基础配置、统一响应格式、全局异常处理等共享代码 + +- 项目路径:`apps/geo-bps-api/` +- groupId:`com.geomative.bsp` +- artifactId:`geo-bps-api` +- 项目整体结构: +``` +apps/geo-bps-api/ +├── pom.xml # 父 POM,统一依赖版本管理 +├── api-admin/ # 后台管理入口,Spring Security 鉴权配置 +├── api-portal/ # 前端门户入口,公开接口无需授权 +├── business/ # 业务模块聚合器(pom packaging) +│ ├── pom.xml +│ ├── permission/ # 权限管理模块 +│ ├── website/ # 网站业务模块(被两个入口共享) +│ ├── device/ # 设备管理模块 +│ └── library/ # 资料库模块 +└── common/ # 公共工具、基础配置、统一响应体 +``` + +## 编码规范 + +### 命名规范 +- 类名使用 UpperCamelCase,如 `DeviceService`、`UserDO` +- 方法名、变量名使用 lowerCamelCase,如 `getUserById`、`deviceList` +- 常量全部大写,单词间用下划线,如 `MAX_RETRY_COUNT` +- 包名全部小写,如 `com.example.device.domain` +- 抽象类命名以 `Abstract` 开头;异常类命名以 `Exception` 结尾;测试类命名以被测类名开头、`Test` 结尾 +- POJO 类中布尔类型变量不加 `is` 前缀,避免序列化问题 +- 领域对象命名后缀约定: + - 数据库映射对象:`XxxDO` + - 数据传输对象:`XxxDTO` + - 视图对象:`XxxVO` + - 查询对象:`XxxQuery` + - 命令对象:`XxxCommand` + +### 注释规范 +- 所有 public 类、方法必须有 Javadoc 注释 +- 方法注释需说明功能、参数含义、返回值、可能抛出的异常 +- 代码逻辑复杂处需有行内注释说明意图,而非描述代码本身 +- 禁止保留无意义的注释和注释掉的废弃代码 + +### 异常处理 +- 不允许捕获 `Exception` 等大类异常后不做任何处理(空 catch) +- 业务异常统一使用自定义异常类,继承 `RuntimeException` +- 不允许用异常控制正常业务流程 +- finally 块中不允许使用 return + +### 日志规范 +- 使用 SLF4J + Logback,禁止直接使用 `System.out.println` +- 日志变量声明:`private static final Logger log = LoggerFactory.getLogger(XxxClass.class);` +- 日志输出使用占位符,禁止字符串拼接:`log.info("userId: {}", userId)` +- 异常日志必须输出完整堆栈:`log.error("message", e)` + +### 集合与并发 +- 初始化集合时指定初始容量,如 `new ArrayList<>(16)` +- 禁止在 foreach 循环中对集合进行 add/remove 操作 +- 线程池不允许使用 `Executors` 创建,需通过 `ThreadPoolExecutor` 显式配置参数 + +### 其他 +- 所有方法入参需做非空校验,使用 `Objects.requireNonNull` 或断言 +- 魔法值(Magic Number)禁止直接出现在代码中,需定义为常量 +- 返回值为集合类型时,不允许返回 null,应返回空集合 + +### DDD 分层架构 +- 采用领域驱动设计(DDD)分层架构,每个子模块内部结构如下: + +``` +{module}/ +├── interfaces/ # 接口层:Controller、DTO、Assembler +├── application/ # 应用层:ApplicationService、Command、Query +├── domain/ # 领域层:Entity、ValueObject、DomainService、Repository接口、DomainEvent +└── infrastructure/ # 基础设施层:Repository实现、Mapper、持久化对象(PO)、外部服务适配 +``` + +## 数据库 +- PostgreSQL 12.14 (Debian 12.14-1.pgdg110+1) + +### 审计字段规范 +- 所有业务表必须包含以下审计字段: + - `created_at` TIMESTAMP NOT NULL — 创建时间,INSERT 时自动填充 + - `created_by` VARCHAR(64) NULL — 创建人(用户ID或用户名),字符类型 + - `updated_at` TIMESTAMP NOT NULL — 最后修改时间,INSERT 和 UPDATE 时自动填充 + - `updated_by` VARCHAR(64) NULL — 最后修改人(用户ID或用户名),字符类型 + - `deleted` TINYINT NOT NULL DEFAULT 0 — 删除状态,0=未删除,1=已删除 + +### 主键规范 +- 所有业务表主键类型为 VARCHAR(64),由应用层生成(如雪花算法、UUID 字符串等),不使用数据库自增或 UUID 类型 + +### PostgreSQL Schema 分模块 +- 不同子模块的表使用不同的 PostgreSQL schema 进行隔离: + - `perm` — permission 模块(admin_users、revoked_tokens、admin_menus、admin_roles 等) + - `web` — website 模块(nav_menus、branches、sub_branches、pages、page_contents、page_seo、fixed_page_contents、enabled_locales 等) + - `dev` — device 模块(quotations、access_logs、configurator_steps 等) + - `lib` — library 模块(knowledge_articles 等) + - `common` — 公共表(contact_submissions、system_config 等) +- MyBatis-Plus 通过 `@TableName(schema = "xxx")` 或全局配置指定 schema +- MyBatis-Plus 配置: + - 通过 `MetaObjectHandler` 自动填充 `created_at`、`updated_at` + - 通过 `@TableLogic` 注解标记 `deleted` 字段实现逻辑删除 + - 查询默认过滤 `deleted=1` 的记录 + +## 部署 +- 集成 Nginx 作为前置代理 + - `api-admin`:Nginx 直接流量穿透,转发至单实例后台管理服务 + - `api-portal`:Nginx 4层负载均衡(TCP/stream模块),支持多实例横向扩展 diff --git a/.next/dev/logs/next-development.log b/.next/dev/logs/next-development.log index a3146f8..a1364c5 100644 --- a/.next/dev/logs/next-development.log +++ b/.next/dev/logs/next-development.log @@ -19,3 +19,215 @@ {"timestamp":"117:32:42.259","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: The default export is not a React Component in \"/devices/page\"\u001b[39m"} {"timestamp":"117:33:29.348","source":"Server","level":"LOG","message":"✓ Compiled in 274ms"} {"timestamp":"117:33:30.697","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"118:56:04.251","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"118:56:06.098","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"118:56:21.517","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:13:03.183","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:13:04.798","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:14:07.937","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:18:22.932","source":"Server","level":"LOG","message":"✓ Compiled in 65ms"} +{"timestamp":"119:25:27.462","source":"Server","level":"LOG","message":"✓ Compiled in 29ms"} +{"timestamp":"119:28:26.107","source":"Server","level":"LOG","message":"✓ Compiled in 58ms"} +{"timestamp":"119:28:32.906","source":"Server","level":"LOG","message":"✓ Compiled in 30ms"} +{"timestamp":"119:29:36.058","source":"Server","level":"LOG","message":"✓ Compiled in 49ms"} +{"timestamp":"119:30:08.040","source":"Server","level":"LOG","message":"✓ Compiled in 48ms"} +{"timestamp":"119:36:42.082","source":"Server","level":"LOG","message":"✓ Compiled in 67ms"} +{"timestamp":"119:36:42.377","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: modelsData is not defined"} +{"timestamp":"119:36:42.484","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: modelsData is not defined\\u001b[39m\\n\\u001b[31m at ModelsPage (src/app/models/page.tsx:107:14)\\u001b[39m\\n \\u001b[90m105 |\\u001b[0m \\n \\u001b[90m106 |\\u001b[0m \\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m107 |\\u001b[0m {modelsData.map(model => (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m108 |\\u001b[0m \\n \\u001b[90m109 |\\u001b[0m {model.name}\\n \\u001b[90m110 |\\u001b[0m {model.code}\""} +{"timestamp":"119:36:42.484","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: modelsData is not defined\u001b[39m\n\u001b[31m at ModelsPage (src/app/models/page.tsx:107:14)\u001b[39m\n \u001b[90m105 |\u001b[0m \n \u001b[90m106 |\u001b[0m \n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m107 |\u001b[0m {modelsData.map(model => (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m108 |\u001b[0m \n \u001b[90m109 |\u001b[0m {model.name}\n \u001b[90m110 |\u001b[0m {model.code}"} +{"timestamp":"119:36:56.088","source":"Server","level":"LOG","message":"✓ Compiled in 100ms"} +{"timestamp":"119:36:56.099","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."} +{"timestamp":"119:36:57.229","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:37:03.520","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"} +{"timestamp":"119:37:24.669","source":"Server","level":"LOG","message":"✓ Compiled in 86ms"} +{"timestamp":"119:37:43.557","source":"Server","level":"LOG","message":"✓ Compiled in 47ms"} +{"timestamp":"119:38:59.568","source":"Server","level":"LOG","message":"✓ Compiled in 194ms"} +{"timestamp":"119:39:00.886","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:41:47.398","source":"Server","level":"LOG","message":"✓ Compiled in 30ms"} +{"timestamp":"119:42:31.585","source":"Server","level":"LOG","message":"✓ Compiled in 32ms"} +{"timestamp":"119:45:00.791","source":"Server","level":"LOG","message":"✓ Compiled in 52ms"} +{"timestamp":"119:45:16.637","source":"Server","level":"LOG","message":"✓ Compiled in 86ms"} +{"timestamp":"119:45:16.991","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: batchList is not defined"} +{"timestamp":"119:45:17.051","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: batchList is not defined\\u001b[39m\\n\\u001b[31m at DevicesPage (src/app/devices/page.tsx:198:14)\\u001b[39m\\n \\u001b[90m196 |\\u001b[0m }}>{devicesData.length}\\n \\u001b[90m197 |\\u001b[0m \\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m198 |\\u001b[0m {batchList.map(({ batch, count }) => (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m199 |\\u001b[0m handleBatchSelect(batch)}\""} +{"timestamp":"119:45:17.053","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: batchList is not defined\u001b[39m\n\u001b[31m at DevicesPage (src/app/devices/page.tsx:198:14)\u001b[39m\n \u001b[90m196 |\u001b[0m }}>{devicesData.length}\n \u001b[90m197 |\u001b[0m \n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m198 |\u001b[0m {batchList.map(({ batch, count }) => (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m199 |\u001b[0m handleBatchSelect(batch)}"} +{"timestamp":"119:45:41.032","source":"Server","level":"LOG","message":"✓ Compiled in 52ms"} +{"timestamp":"119:45:41.037","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."} +{"timestamp":"119:45:42.083","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"119:49:42.815","source":"Server","level":"LOG","message":"✓ Compiled in 89ms"} +{"timestamp":"120:11:46.034","source":"Server","level":"LOG","message":"✓ Compiled in 46ms"} +{"timestamp":"120:15:49.161","source":"Server","level":"LOG","message":"✓ Compiled in 38ms"} +{"timestamp":"120:15:54.549","source":"Server","level":"ERROR","message":"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."} +{"timestamp":"120:15:54.759","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:15:54.834","source":"Browser","level":"ERROR","message":"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."} +{"timestamp":"120:15:54.853","source":"Server","level":"ERROR","message":"[browser] \"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.\" \"\""} +{"timestamp":"120:15:54.854","source":"Browser","level":"ERROR","message":"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. \"\""} +{"timestamp":"120:16:05.201","source":"Server","level":"LOG","message":"✓ Compiled in 67ms"} +{"timestamp":"120:21:20.618","source":"Server","level":"LOG","message":"✓ Compiled in 71ms"} +{"timestamp":"120:21:52.821","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:53.298","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:53.333","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload when ./src/app/calibration/page.tsx changed. Read more: https://nextjs.org/docs/messages/fast-refresh-reload"} +{"timestamp":"120:21:53.887","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:54.306","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:54.655","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:54.957","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:55.271","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:55.651","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:56.158","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:56.535","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:56.652","source":"Browser","level":"ERROR","message":"uncaughtError: Error: The default export is not a React Component in \"/calibration/page\""} +{"timestamp":"120:21:56.662","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: The default export is not a React Component in \\\"/calibration/page\\\"\\u001b[39m\""} +{"timestamp":"120:21:56.663","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: The default export is not a React Component in \"/calibration/page\"\u001b[39m"} +{"timestamp":"120:22:31.629","source":"Server","level":"ERROR","message":"⨯ ./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]\n\n"} +{"timestamp":"120:22:31.636","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]"} +{"timestamp":"120:22:31.816","source":"Server","level":"ERROR","message":"⨯ ./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]\n\n"} +{"timestamp":"120:22:31.818","source":"Server","level":"ERROR","message":"[browser] \"./src/app/calibration/page.tsx:138:1\\nExpected ''\\n 136 | \\n 137 | \\n> 138 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:22:31.819","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component] \"\""} +{"timestamp":"120:22:31.821","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]"} +{"timestamp":"120:22:31.822","source":"Server","level":"ERROR","message":"[browser] \"./src/app/calibration/page.tsx:138:1\\nExpected ''\\n 136 | \\n 137 | \\n> 138 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:22:31.822","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component] \"\""} +{"timestamp":"120:22:31.902","source":"Browser","level":"ERROR","message":"uncaughtError: Error: ./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]\n\n"} +{"timestamp":"120:22:31.904","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: ./src/app/calibration/page.tsx:138:1\\nExpected ''\\n 136 | \\n 137 | \\n> 138 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n\\u001b[39m\\n\\u001b[31m at (Error: ./src/app/calibration/page.tsx:138:1)\\n at (Error: (./src/app/calibration/page.tsx:138:1)\\u001b[39m\""} +{"timestamp":"120:22:31.904","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: ./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]\n\n\u001b[39m\n\u001b[31m at (Error: ./src/app/calibration/page.tsx:138:1)\n at (Error: (./src/app/calibration/page.tsx:138:1)\u001b[39m"} +{"timestamp":"120:22:31.923","source":"Server","level":"ERROR","message":"[browser] \"./src/app/calibration/page.tsx:138:1\\nExpected ''\\n 136 | \\n 137 | \\n> 138 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:22:31.923","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component] \"\""} +{"timestamp":"120:22:31.924","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]"} +{"timestamp":"120:22:31.954","source":"Server","level":"ERROR","message":"[browser] \"./src/app/calibration/page.tsx:138:1\\nExpected ''\\n 136 | \\n 137 | \\n> 138 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:22:31.955","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component] \"\""} +{"timestamp":"120:22:31.955","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:138:1\nExpected ''\n 136 | \n 137 | \n> 138 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]"} +{"timestamp":"120:22:55.601","source":"Server","level":"ERROR","message":"[browser] \"./src/app/calibration/page.tsx:192:1\\nExpected ''\\n 190 | \\n 191 | \\n> 192 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/calibration/page.tsx [Client Component Browser]\\n ./src/app/calibration/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/calibration/page.tsx [Client Component SSR]\\n ./src/app/calibration/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:22:55.601","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:192:1\nExpected ''\n 190 | \n 191 | \n> 192 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component] \"\""} +{"timestamp":"120:22:55.602","source":"Browser","level":"ERROR","message":"./src/app/calibration/page.tsx:192:1\nExpected ''\n 190 | \n 191 | \n> 192 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/calibration/page.tsx [Client Component Browser]\n ./src/app/calibration/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/calibration/page.tsx [Client Component SSR]\n ./src/app/calibration/page.tsx [Server Component]"} +{"timestamp":"120:23:28.321","source":"Server","level":"LOG","message":"✓ Compiled in 65ms"} +{"timestamp":"120:23:29.053","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:24:00.938","source":"Server","level":"LOG","message":"✓ Compiled in 76ms"} +{"timestamp":"120:24:01.199","source":"Browser","level":"ERROR","message":"uncaughtError: TypeError: Cannot read properties of undefined (reading 'map')"} +{"timestamp":"120:24:01.332","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught TypeError: Cannot read properties of undefined (reading 'map')\\u001b[39m\\n\\u001b[31m at eval (src/app/components/sidebar.tsx:36:24)\\n at Array.map ()\\n at Sidebar (src/app/components/sidebar.tsx:33:19)\\n at RootLayout (src\\\\app\\\\layout.tsx:14:11)\\u001b[39m\\n \\u001b[90m34 |\\u001b[0m
\\n \\u001b[90m35 |\\u001b[0m
\\u001b[0m \\u001b[90m36 |\\u001b[0m {group.items.map(item => {\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m37 |\\u001b[0m \\u001b[36mconst\\u001b[0m \\u001b[33mIcon\\u001b[0m = item.icon\\n \\u001b[90m38 |\\u001b[0m \\u001b[36mreturn\\u001b[0m (\\n \\u001b[90m39 |\\u001b[0m <\\u001b[33mLink\\u001b[0m key={item.path} href={item.path} className=\\u001b[32m\\\"flex items-center gap-2 px-4 py-2 text-sm transition-c...\\u001b[0m\""} +{"timestamp":"120:24:01.334","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught TypeError: Cannot read properties of undefined (reading 'map')\u001b[39m\n\u001b[31m at eval (src/app/components/sidebar.tsx:36:24)\n at Array.map ()\n at Sidebar (src/app/components/sidebar.tsx:33:19)\n at RootLayout (src\\app\\layout.tsx:14:11)\u001b[39m\n \u001b[90m34 |\u001b[0m
\n \u001b[90m35 |\u001b[0m
\u001b[0m \u001b[90m36 |\u001b[0m {group.items.map(item => {\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m37 |\u001b[0m \u001b[36mconst\u001b[0m \u001b[33mIcon\u001b[0m = item.icon\n \u001b[90m38 |\u001b[0m \u001b[36mreturn\u001b[0m (\n \u001b[90m39 |\u001b[0m <\u001b[33mLink\u001b[0m key={item.path} href={item.path} className=\u001b[32m\"flex items-center gap-2 px-4 py-2 text-sm transition-c...\u001b[0m"} +{"timestamp":"120:24:25.345","source":"Server","level":"LOG","message":"✓ Compiled in 49ms"} +{"timestamp":"120:24:25.347","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."} +{"timestamp":"120:24:25.908","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:26:49.542","source":"Server","level":"LOG","message":"✓ Compiled in 70ms"} +{"timestamp":"120:31:09.492","source":"Browser","level":"ERROR","message":"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."} +{"timestamp":"120:31:09.494","source":"Server","level":"ERROR","message":"[browser] \"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.\" \"\""} +{"timestamp":"120:31:09.494","source":"Browser","level":"ERROR","message":"You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. \"\""} +{"timestamp":"120:31:53.655","source":"Server","level":"LOG","message":"✓ Compiled in 26ms"} +{"timestamp":"120:33:07.451","source":"Server","level":"LOG","message":"✓ Compiled in 31ms"} +{"timestamp":"120:33:49.615","source":"Server","level":"LOG","message":"✓ Compiled in 35ms"} +{"timestamp":"120:33:49.785","source":"Browser","level":"ERROR","message":"A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components"} +{"timestamp":"120:33:49.807","source":"Server","level":"ERROR","message":"[browser] \"A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components\" \"\""} +{"timestamp":"120:33:49.808","source":"Browser","level":"ERROR","message":"A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components \"\""} +{"timestamp":"120:33:54.669","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:35:37.666","source":"Server","level":"LOG","message":"✓ Compiled in 37ms"} +{"timestamp":"120:37:14.496","source":"Server","level":"LOG","message":"✓ Compiled in 28ms"} +{"timestamp":"120:37:47.169","source":"Server","level":"LOG","message":"✓ Compiled in 30ms"} +{"timestamp":"120:37:53.739","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:38:48.262","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:39:20.372","source":"Server","level":"LOG","message":"✓ Compiled in 44ms"} +{"timestamp":"120:40:15.243","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:41:21.738","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:22.130","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:22.971","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:23.735","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:24.485","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:24.972","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:25.720","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:26.046","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:26.173","source":"Browser","level":"ERROR","message":"uncaughtError: Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:26.185","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: The default export is not a React Component in \\\"/devices/[sn]/page\\\"\\u001b[39m\""} +{"timestamp":"120:41:26.186","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: The default export is not a React Component in \"/devices/[sn]/page\"\u001b[39m"} +{"timestamp":"120:41:58.004","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:58.319","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:58.527","source":"Browser","level":"ERROR","message":"uncaughtError: Error: The default export is not a React Component in \"/devices/[sn]/page\""} +{"timestamp":"120:41:58.528","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: The default export is not a React Component in \\\"/devices/[sn]/page\\\"\\u001b[39m\""} +{"timestamp":"120:41:58.529","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: The default export is not a React Component in \"/devices/[sn]/page\"\u001b[39m"} +{"timestamp":"120:42:20.220","source":"Server","level":"ERROR","message":"⨯ ./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 |
\n 153 |
\n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n"} +{"timestamp":"120:42:20.228","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 |
\n 153 |
\n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:42:20.230","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:20.233","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:20.368","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:42:20.368","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:20.368","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:20.370","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:42:20.371","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:20.371","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:20.381","source":"Server","level":"ERROR","message":"⨯ ./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n"} +{"timestamp":"120:42:20.499","source":"Browser","level":"ERROR","message":"uncaughtError: Error: ./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n"} +{"timestamp":"120:42:20.504","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: ./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n\\u001b[39m\\n\\u001b[31m at (Error: ./src/app/devices/[sn]/page.tsx:154:1)\\n at (Error: (./src/app/devices/[sn]/page.tsx:154:1)\\u001b[39m\""} +{"timestamp":"120:42:20.505","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught Error: ./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n\u001b[39m\n\u001b[31m at (Error: ./src/app/devices/[sn]/page.tsx:154:1)\n at (Error: (./src/app/devices/[sn]/page.tsx:154:1)\u001b[39m"} +{"timestamp":"120:42:20.519","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:20.519","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:20.520","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:42:20.587","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:154:1\\nExpected ''\\n 152 | \\n 153 | \\n> 154 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:20.588","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:20.590","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:154:1\nExpected ''\n 152 | \n 153 | \n> 154 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:42:40.514","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:199:1\\nExpected ''\\n 197 | ))}\\n 198 | \\n> 199 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:42:40.515","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:199:1\nExpected ''\n 197 | ))}\n 198 | \n> 199 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:42:40.515","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:199:1\nExpected ''\n 197 | ))}\n 198 | \n> 199 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:43:14.065","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:269:1\\nExpected ''\\n 267 | \\n 268 | )}\\n> 269 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:43:14.065","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:269:1\nExpected ''\n 267 | \n 268 | )}\n> 269 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:43:14.066","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:269:1\nExpected ''\n 267 | \n 268 | )}\n> 269 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:43:45.661","source":"Server","level":"ERROR","message":"[browser] \"./src/app/devices/[sn]/page.tsx:328:1\\nExpected ''\\n 326 | \\n 327 | )}\\n> 328 |\\n | ^\\n\\nParsing ecmascript source code failed\\n\\nImport traces:\\n Client Component Browser:\\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\\n\\n Client Component SSR:\\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\\n ./src/app/devices/[sn]/page.tsx [Server Component]\" \"\""} +{"timestamp":"120:43:45.663","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:328:1\nExpected ''\n 326 | \n 327 | )}\n> 328 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component] \"\""} +{"timestamp":"120:43:45.665","source":"Browser","level":"ERROR","message":"./src/app/devices/[sn]/page.tsx:328:1\nExpected ''\n 326 | \n 327 | )}\n> 328 |\n | ^\n\nParsing ecmascript source code failed\n\nImport traces:\n Client Component Browser:\n ./src/app/devices/[sn]/page.tsx [Client Component Browser]\n ./src/app/devices/[sn]/page.tsx [Server Component]\n\n Client Component SSR:\n ./src/app/devices/[sn]/page.tsx [Client Component SSR]\n ./src/app/devices/[sn]/page.tsx [Server Component]"} +{"timestamp":"120:44:03.932","source":"Server","level":"LOG","message":"✓ Compiled in 145ms"} +{"timestamp":"120:44:04.986","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} +{"timestamp":"120:45:42.964","source":"Server","level":"LOG","message":"✓ Compiled in 38ms"} +{"timestamp":"120:48:15.982","source":"Server","level":"LOG","message":"✓ Compiled in 40ms"} +{"timestamp":"120:52:11.129","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"} +{"timestamp":"120:53:27.719","source":"Server","level":"LOG","message":"✓ Compiled in 43ms"} +{"timestamp":"120:53:57.768","source":"Server","level":"LOG","message":"✓ Compiled in 57ms"} +{"timestamp":"120:54:02.970","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"} +{"timestamp":"120:54:14.218","source":"Server","level":"LOG","message":"✓ Compiled in 30ms"} +{"timestamp":"120:54:30.156","source":"Server","level":"LOG","message":"✓ Compiled in 33ms"} +{"timestamp":"120:54:51.314","source":"Server","level":"LOG","message":"✓ Compiled in 28ms"} +{"timestamp":"120:54:59.763","source":"Server","level":"LOG","message":"✓ Compiled in 53ms"} +{"timestamp":"120:55:14.825","source":"Server","level":"LOG","message":"✓ Compiled in 44ms"} +{"timestamp":"120:58:05.296","source":"Server","level":"LOG","message":"✓ Compiled in 53ms"} +{"timestamp":"120:58:05.328","source":"Server","level":"ERROR","message":"⨯ ./src/app/devices/[sn]/page.tsx:179:7\nExpected ',', got '{'\n 177 | \n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 |
\\n 178 |\\n> 179 | {/* Tabs */}\\n | ^\\n 180 |
\\n 181 | {tabs.map(tab => (\\n 182 |
\n 178 |\n> 179 | {/* Tabs */}\n | ^\n 180 |
\n 181 | {tabs.map(tab => (\n 182 | + + 登记板卡 +
- - {/* Info Banner */} -
- -
- 校准管理仅针对采集板(ACB系列),其他类型板卡无需校准。校准到期前30天系统会自动提醒。 -
+ {/* Stats Cards */} +
+ {[ + { label: '板卡总数', value: stats.total, color: '#4a7c59', bg: '#eef5f0' }, + { label: '在库', value: stats.inStock, color: '#1890FF', bg: '#E6F7FF' }, + { label: '已装配', value: stats.assembled, color: '#52C41A', bg: '#F6FFED' }, + { label: '故障', value: stats.faulty, color: '#FF4D4F', bg: '#FFF1F0' }, + { label: '待校准', value: stats.pendingCalib, color: '#FAAD14', bg: '#FFFBE6' }, + ].map(s => ( +
+
{s.label}
+
{s.value}
+
+ ))}
{/* Filter */}
- - { setFilterSN(e.target.value); setCurrentPage(1) }} placeholder="输入SN号搜索" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> + +
- +
- - { setFilterCalib(e.target.value); setCurrentPage(1) }} style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}> + {calibStatusOptions.map(c => )}
- +
+ + { setSearchText(e.target.value); setCurrentPage(1) }} placeholder="搜索板卡SN或设备SN" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> +
@@ -107,31 +139,33 @@ export default function CalibrationPage() { - {['采集板SN号', '板卡型号', '校准日期', '到期日期', '校准人员', '状态', '操作'].map(h => ( - + {['板卡SN', '类型', '型号', '固件', '状态', '所属设备', '校准状态', '操作'].map(h => ( + ))} {paged.map(row => ( - - - - - - + + + + - + + ))} @@ -140,13 +174,15 @@ export default function CalibrationPage() { {/* Pagination */}
- 显示 {(currentPage - 1) * pageSize + 1}-{Math.min(currentPage * pageSize, filtered.length)} / 共 {filtered.length} 条 + + 显示 {filtered.length > 0 ? (currentPage - 1) * pageSize + 1 : 0}-{Math.min(currentPage * pageSize, filtered.length)} / 共 {filtered.length} 条 +
{Array.from({ length: totalPages }, (_, i) => ( ))} - +
@@ -155,58 +191,60 @@ export default function CalibrationPage() { {detailDrawer && (
setDetailDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} /> -
+
-

校准详情 - {detailDrawer.sn}

+

板卡详情

{/* 基本信息 */}
-

基本信息

+

基本信息

-
采集板SN:{detailDrawer.sn}
-
板卡型号:{detailDrawer.boardModel}
-
通道数:{detailDrawer.channel}
-
综合偏差:{detailDrawer.deviation}
-
校准日期:{detailDrawer.calibDate}
-
到期日期:{detailDrawer.expiryDate}
-
校准人员:{detailDrawer.operator}
-
状态:{detailDrawer.status}
+
板卡SN:{detailDrawer.sn}
+
板卡类型:{detailDrawer.type}
+
型号:{detailDrawer.model}
+
固件版本:{detailDrawer.firmware}
+
生产日期:{detailDrawer.productionDate}
+
+ 状态: + {detailDrawer.status} +
- {/* 通道校准结果 */} + {/* 装配信息 */}
-

通道校准结果

-
{h}{h}
{row.sn}{row.boardModel}{row.calibDate}{row.expiryDate}{row.operator} + {row.sn}{row.type}{row.model}{row.firmware} {row.status} -
- - -
+
{row.deviceSn} + {row.calibStatus !== '-' ? ( + {row.calibStatus} + ) : ( + - + )} + +
- - - {['通道', '参考值', '测量值', '偏差', '结果'].map(h => ( - - ))} - - - - {calibDetailData.calibResults.map((r, i) => ( - - - - - - - - ))} - -
{h}
{r.channel}{r.reference}{r.measured}{r.deviation} - {r.result} -
+

装配信息

+
+ 所属设备: + {detailDrawer.deviceSn === '-' ? ( + 未装配 + ) : ( + {detailDrawer.deviceSn} + )} +
+ + {/* 校准信息 */} + {(detailDrawer.type === '采集板') && ( +
+

校准信息

+
+
+ 校准状态: + {detailDrawer.calibStatus !== '-' ? ( + {detailDrawer.calibStatus} + ) : ( + - + )} +
+
校准日期:{detailDrawer.calibDate}
+
+
+ )} -
- +
diff --git a/src/app/calibration/register/page.tsx b/src/app/calibration/register/page.tsx new file mode 100644 index 0000000..ef75f34 --- /dev/null +++ b/src/app/calibration/register/page.tsx @@ -0,0 +1,244 @@ +'use client' +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import { ArrowLeft, Plus, Trash2, Upload, Info, CheckCircle } from 'lucide-react' + +/** 板卡类型 -> 可选型号 */ +const modelsByType: Record = { + '主协板': [ + { model: 'MB-V1.2', firmware: 'v2.1' }, + { model: 'MB-V2.1', firmware: 'v1.8' }, + ], + '采集板': [ + { model: 'RX-V1.3', firmware: 'v3.0' }, + { model: 'RX-V2.1', firmware: 'v2.5' }, + ], + '发射板': [ + { model: 'TX-V1.5', firmware: 'v1.2' }, + { model: 'TX-V2.1', firmware: 'v1.0' }, + ], + '升压板': [ + { model: 'BO-V2.1', firmware: 'v1.1' }, + { model: 'BO-V2.2', firmware: 'v0.9' }, + ], +} + +const typeOptions = Object.keys(modelsByType) + +interface BoardEntry { + id: number + type: string + model: string + firmware: string + sn: string + productionDate: string + remark: string +} + +let nextId = 1 + +export default function BoardRegisterPage() { + const router = useRouter() + const [entries, setEntries] = useState([createEntry()]) + const [batchMode, setBatchMode] = useState(false) + + function createEntry(): BoardEntry { + return { id: nextId++, type: '采集板', model: 'RX-V2.1', firmware: 'v2.1', sn: '', productionDate: '', remark: '' } + } + + const addEntry = () => { + setEntries(prev => [...prev, createEntry()]) + } + + const removeEntry = (id: number) => { + if (entries.length <= 1) return + setEntries(prev => prev.filter(e => e.id !== id)) + } + + const updateEntry = (id: number, field: keyof BoardEntry, value: string) => { + setEntries(prev => prev.map(e => { + if (e.id !== id) return e + const updated = { ...e, [field]: value } + // 切换类型时自动选第一个型号和固件 + if (field === 'type') { + const models = modelsByType[value] + if (models && models.length > 0) { + updated.model = models[0].model + updated.firmware = models[0].firmware + } + } + // 切换型号时自动填充固件 + if (field === 'model') { + const models = modelsByType[updated.type] + const match = models?.find(m => m.model === value) + if (match) updated.firmware = match.firmware + } + return updated + })) + } + + const isValid = entries.every(e => e.sn.trim() && e.productionDate) + + return ( +
+
+ {/* Header */} +
+ +
+

登记板卡

+

登记新板卡信息,支持单个或批量登记

+
+
+ + {/* Info Banner */} +
+ +
+ 板卡SN号为唯一标识,请确保录入正确。采集板登记后需要进行校准才能用于装配。选择型号后固件版本会自动填充。 +
+
+ + {/* Mode Toggle */} +
+ 登记模式: +
+ + +
+ {batchMode && ( + 当前 {entries.length} 条 + )} +
+ + {/* Entry Cards */} + {entries.map((entry, idx) => ( +
+
+

+ {batchMode ? `板卡 #${idx + 1}` : '板卡信息'} +

+ {batchMode && entries.length > 1 && ( + + )} +
+ +
+ {/* 板卡类型 */} +
+ + +
+ + {/* 板卡型号 */} +
+ + +
+ + {/* 固件版本(自动填充,只读) */} +
+ + +
+ + {/* 板卡SN */} +
+ + updateEntry(entry.id, 'sn', e.target.value)} placeholder={`如 ${entry.model}-20250401001`} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> +
+ + {/* 生产日期 */} +
+ + updateEntry(entry.id, 'productionDate', e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> +
+ + {/* 备注 */} +
+ + updateEntry(entry.id, 'remark', e.target.value)} placeholder="可选" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> +
+
+ + {/* 采集板校准提示 */} + {entry.type === '采集板' && ( +
+ + 采集板登记后状态为"待校准",需完成校准后才能用于设备装配。 +
+ )} +
+ ))} + + {/* Add More Button (batch mode) */} + {batchMode && ( + + )} + + {/* Preview Summary */} + {entries.length > 0 && ( +
+

登记预览

+ + + + {['序号', '类型', '型号', '固件', 'SN号', '生产日期', '状态'].map(h => ( + + ))} + + + + {entries.map((entry, i) => ( + + + + + + + + + + ))} + +
{h}
{i + 1}{entry.type}{entry.model}{entry.firmware}{entry.sn || '未填写'}{entry.productionDate || '未填写'} + + {entry.type === '采集板' ? '待校准' : '在库'} + +
+
+ )} +
+ + {/* Sticky Bottom Bar */} +
+ + 共 {entries.length} 条板卡待登记 + {entries.some(e => e.type === '采集板') && · 含采集板需后续校准} + +
+ + +
+
+
+ ) +} diff --git a/src/app/components/sidebar.tsx b/src/app/components/sidebar.tsx index aec6ba4..7cad2a5 100644 --- a/src/app/components/sidebar.tsx +++ b/src/app/components/sidebar.tsx @@ -1,17 +1,15 @@ 'use client' import Link from 'next/link' import { usePathname } from 'next/navigation' -import { Monitor, Settings2, Key, Cpu, FileCode, Gauge, Wrench, Recycle } from 'lucide-react' +import { Monitor, Settings2, Cpu, Gauge, Wrench, Recycle } from 'lucide-react' const menuGroups = [ { title: '设备', items: [ { path: '/devices', label: '设备列表', icon: Monitor }, { path: '/models', label: '设备型号管理', icon: Settings2 }, - { path: '/boards', label: '板卡型号管理', icon: Cpu }, ]}, - { title: '授权', items: [{ path: '/licenses', label: '授权管理', icon: Key }] }, - { title: '配置', items: [{ path: '/config-files', label: '配置管理', icon: FileCode }] }, - { title: '校准', items: [{ path: '/calibration', label: '校准记录', icon: Gauge }] }, + { title: '板卡', items: [{ path: '/calibration', label: '板卡列表', icon: Gauge }, + { path: '/boards', label: '板卡型号管理', icon: Cpu },] }, { title: '维修', items: [ { path: '/repair', label: '维修工单', icon: Wrench }, { path: '/scrap', label: '报废回收', icon: Recycle }, diff --git a/src/app/devices/[sn]/page.tsx b/src/app/devices/[sn]/page.tsx new file mode 100644 index 0000000..df67643 --- /dev/null +++ b/src/app/devices/[sn]/page.tsx @@ -0,0 +1,372 @@ +'use client' +import { use } from 'react' +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import { ArrowLeft, Cpu, Wifi, Monitor, Key, FileCode, Upload, Clock, Wrench, CheckCircle, AlertTriangle, Package, FileDown } from 'lucide-react' + +/** Mock: 所有设备数据 */ +const allDevices = [ + { id: 1, sn: 'GD30-2025-000001', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-15 14:30', customer: '北京地质研究院', operator: '张工程师' }, + { id: 2, sn: 'GD30-2025-000002', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-18 09:15', customer: '中国地质大学', operator: '张工程师' }, + { id: 3, sn: 'GD30-2024-000056', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-12-20 16:00', customer: '成都理工大学', operator: '李工程师' }, + { id: 4, sn: 'GT20-2025-000045', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-02-10 11:20', customer: '武汉地质调查中心', operator: '王工程师' }, + { id: 5, sn: 'GT20-2025-000046', model: 'GD-20', type: '二维电法仪', status: '装配中', firmware: 'v1.8.5', productionDate: '2025-03-01 08:45', customer: '-', operator: '王工程师' }, + { id: 6, sn: 'GD30-2024-000078', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-11-05 13:30', customer: '长安大学', operator: '张工程师' }, + { id: 7, sn: 'GD10-2024-000033', model: 'GD-10 Supreme', type: '入门级电法仪', status: '已激活', firmware: 'v1.5.2', productionDate: '2024-09-12 10:00', customer: '河海大学', operator: '李工程师' }, + { id: 8, sn: 'GD30-2024-000089', model: 'GD-30 Supreme', type: '高密度电法仪', status: '装配中', firmware: 'v2.3.5', productionDate: '2025-03-05 15:10', customer: '-', operator: '张工程师' }, + { id: 9, sn: 'GT20-2025-000012', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-01-22 09:30', customer: '中南大学', operator: '王工程师' }, + { id: 10, sn: 'GD30-2024-000102', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-10-18 14:00', customer: '吉林大学', operator: '李工程师' }, + { id: 11, sn: 'GD10-2024-000034', model: 'GD-10 Supreme', type: '入门级电法仪', status: '装配中', firmware: 'v1.5.2', productionDate: '2025-03-08 11:45', customer: '-', operator: '王工程师' }, + { id: 12, sn: 'GD30-2024-000145', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2024-08-25 16:20', customer: '同济大学', operator: '张工程师' }, +] + +/** Mock: 装机BOM清单 */ +const bomData: Record = { + 'GD30-2025-000001': [ + { name: '主协板', sn: 'MB25011501', model: 'MB-V2.1', calibration: '-' }, + { name: '采集板', sn: 'RX25011000', model: 'RX-V2.1', calibration: '合格' }, + { name: '采集板', sn: 'RX25011000', model: 'RX-V2.1', calibration: '合格' }, + { name: '发射板', sn: 'TX25012000', model: 'TX-V2.1', calibration: '-' }, + { name: '升压板', sn: 'BO25020100', model: 'BP600-V2.1', calibration: '-' }, + ], +} + +/** Mock: 授权信息 */ +const licenseData: Record = { + 'GD30-2025-000001': { modules: '1D SP, 2D SP, 3D SP, 1D VES, 2D ERT, 3D ERT, 1D IP, 2D IP, 3D IP, 跨孔, 水上', expiry: '2026-01-15', status: '生效' }, + 'GD30-2025-000002': { modules: '2D ERT, 3D ERT, 1D IP, 2D IP, 3D IP, 跨孔, 水上', expiry: '2025-12-31', status: '生效' }, + 'GT20-2025-000045': { modules: '2D ERT, 3D ERT, 1D IP, 2D IP', expiry: '2025-06-30', status: '生效' }, +} + +/** Mock: 配置文件 */ +const configData: Record = { + 'GD30-2025-000001': { name: 'CFG-GD30-v1.3.0', version: 'v1.3.0', uploadDate: '2025-01-15' }, + 'GD30-2025-000002': { name: 'CFG-GD30-v1.3.0', version: 'v1.3.0', uploadDate: '2025-01-18' }, + 'GT20-2025-000045': { name: 'CFG-GD20-v1.1.0', version: 'v1.1.0', uploadDate: '2025-02-10' }, +} + +/** Mock: 操作日志 */ +const operationLogs = [ + { date: '2025-01-15 14:30', action: '设备登记', operator: '张工程师', detail: '完成设备登记,型号 GD-30 Supreme' }, + { date: '2025-01-15 15:00', action: '装配完成', operator: '张工程师', detail: '装配 Checklist 全部通过' }, + { date: '2025-01-15 16:00', action: '授权项写入', operator: '系统', detail: '写入全模块授权项' }, + { date: '2025-01-15 16:05', action: '配置写入', operator: '系统', detail: '写入配置文件 CFG-GD30-v1.3.0' }, + { date: '2025-01-15 16:10', action: '固件校验', operator: '系统', detail: '固件版本 v2.3.5 校验通过' }, + { date: '2025-01-16 09:00', action: '出厂检测', operator: '李工程师', detail: '出厂检测通过,设备状态变更为已出厂' }, +] + +/** Mock: 装配 Checklist */ +const checklistData = [ + { name: '主板SN扫码绑定', passed: true }, + { name: '采集板SN录入(×6)', passed: true }, + { name: '发射板安装检查', passed: true }, + { name: '升压板安装检查', passed: true }, + { name: '线缆连接检查', passed: true }, + { name: '整机通电测试', passed: true }, + { name: 'GPS/北斗模块检测', passed: true }, + { name: 'WiFi通信测试', passed: true }, + { name: '蓝牙通信测试', passed: true }, + { name: '采集通道校准验证', passed: true }, + { name: '发射电压测试', passed: true }, + { name: '电池安装与充电测试', passed: true }, + { name: 'IP66防护检测', passed: true }, + { name: '固件版本校验', passed: true }, + { name: '配置文件写入', passed: true }, + { name: '授权文件写入', passed: true }, + { name: '整机功能测试', passed: true }, + { name: '数据采集验证', passed: true }, + { name: '外观检查', passed: true }, + { name: '标签粘贴', passed: true }, + { name: '配件清点', passed: true }, + { name: '包装检查', passed: true }, +] + +function getStatusStyle(status: string) { + switch (status) { + case '已激活': return { backgroundColor: '#F6FFED', color: '#52C41A', border: '1px solid #B7EB8F' } + case '已出厂': return { backgroundColor: '#FFF7E6', color: '#FA8C16', border: '1px solid #FFD591' } + case '装配中': return { backgroundColor: '#eef5f0', color: '#4a7c59', border: '1px solid #a3c4ad' } + default: return { backgroundColor: '#FAFAFA', color: 'rgba(0,0,0,0.45)', border: '1px solid #D9D9D9' } + } +} + +function getStatusIcon(status: string) { + switch (status) { + case '已激活': return + case '已出厂': return + case '装配中': return + default: return null + } +} + +export default function DeviceDetailPage({ params }: { params: Promise<{ sn: string }> }) { + const { sn } = use(params) + const router = useRouter() + const [activeTab, setActiveTab] = useState('overview') + + const device = allDevices.find(d => d.sn === sn) + const bom = bomData[sn] || [] + const license = licenseData[sn] + const config = configData[sn] + + if (!device) { + return ( +
+ +

设备未找到

+

SN号 {sn} 对应的设备不存在

+ +
+ ) + } + + const tabs = [ + { key: 'overview', label: '概览' }, + { key: 'bom', label: '装机清单' }, + { key: 'checklist', label: '装配记录' }, + { key: 'license', label: '授权项信息' }, + { key: 'config', label: '配置文件' }, + { key: 'logs', label: '操作日志' }, + ] + + return ( +
+ {/* Header */} +
+ +
+
+

{device.sn}

+
+ {getStatusIcon(device.status)} + {device.status} +
+
+

{device.model} · {device.type}

+
+
+ + {/* Info Cards Row */} +
+
+
+ + 板卡数量 +
+
{bom.length}
+
+
+
+ + 授权项状态 +
+
{license ? license.status : '未授权'}
+ {license &&
到期:{license.expiry}
} +
+
+
+ + 配置文件 +
+
{config ? config.version : '未配置'}
+ {config &&
{config.name}
} +
+
+ + {/* Tabs */} +
+ {tabs.map(tab => ( + + ))} +
+ + {/* Tab Content */} + {activeTab === 'overview' && ( +
+

基本信息

+
+ {[ + { label: '设备SN', value: device.sn }, + { label: '设备型号', value: device.model }, + { label: '设备类型', value: device.type }, + { label: '固件版本', value: device.firmware }, + { label: '生产日期', value: device.productionDate }, + { label: '登记人', value: device.operator }, + { label: '客户', value: device.customer }, + { label: '设备状态', value: device.status, isStatus: true }, + ].map(item => ( +
+
{item.label}
+ {item.isStatus ? ( +
+ {getStatusIcon(item.value)} + {item.value} +
+ ) : ( +
{item.value}
+ )} +
+ ))} +
+
+ )} + + {activeTab === 'bom' && ( +
+
+

装机清单 BOM

+ 共 {bom.length} 项 +
+ {bom.length > 0 ? ( + + + + {['序号', '物料名称', '板卡SN', '型号', '校准状态'].map(h => ( + + ))} + + + + {bom.map((item, i) => ( + + + + + + + + ))} + +
{h}
{i + 1}{item.name}{item.sn}{item.model} + {item.calibration === '-' ? ( + 无需校准 + ) : ( + {item.calibration} + )} +
+ ) : ( +
暂无装机清单数据
+ )} +
+ )} + + {activeTab === 'checklist' && ( +
+
+

装配 Checklist

+ + 通过 {checklistData.filter(c => c.passed).length}/{checklistData.length} + +
+
+
c.passed).length / checklistData.length) * 100}%`, backgroundColor: '#52C41A', borderRadius: 3 }} /> +
+
+ {checklistData.map((item, i) => ( +
+
+ +
+
{i + 1}
+ {item.name} + {item.passed ? '通过' : '未通过'} +
+ ))} +
+
+ )} + + {activeTab === 'license' && ( +
+

授权项信息

+ {license ? ( +
+
+
+
授权项状态
+ {license.status} +
+
+
到期时间
+
{license.expiry}
+
+
+
+
授权项模块
+
+ {license.modules.split(', ').map(m => ( + {m} + ))} +
+
+
+ ) : ( +
+ +
该设备暂未配置授权项
+
+ )} +
+ )} + + {activeTab === 'config' && ( +
+

配置文件

+ {config ? ( +
+
+
配置名称
+
{config.name}
+
+
+
版本
+
{config.version}
+
+
+
写入日期
+
{config.uploadDate}
+
+
+ ) : ( +
+ +
该设备暂未写入配置文件
+
+ )} +
+ )} + + {activeTab === 'logs' && ( +
+

操作日志

+
+ {/* Timeline line */} +
+ {operationLogs.map((log, i) => ( +
+ {/* Timeline dot */} +
+
+
+ {log.action} + {log.operator} +
+
{log.detail}
+
+ {log.date} +
+
+
+ ))} +
+
+ )} +
+ ) +} diff --git a/src/app/devices/page.tsx b/src/app/devices/page.tsx index 72d7aee..e427444 100644 --- a/src/app/devices/page.tsx +++ b/src/app/devices/page.tsx @@ -3,21 +3,59 @@ import { useState, useMemo } from 'react' import Link from 'next/link' import { Download, Plus, Search, ChevronLeft, ChevronRight, Monitor, Cpu, Wifi, Power, Tag } from 'lucide-react' -const devicesData = [ - { id: 1, sn: 'GD30-2025-000001', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-15 14:30', customer: '北京地质研究院', batch: 'B2025-01' }, - { id: 2, sn: 'GD30-2025-000002', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-18 09:15', customer: '中国地质大学', batch: 'B2025-01' }, - { id: 3, sn: 'GD30-2024-000056', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-12-20 16:00', customer: '成都理工大学', batch: 'B2024-12' }, - { id: 4, sn: 'GT20-2025-000045', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-02-10 11:20', customer: '武汉地质调查中心', batch: 'B2025-02' }, - { id: 5, sn: 'GT20-2025-000046', model: 'GD-20', type: '二维电法仪', status: '装配中', firmware: 'v1.8.5', productionDate: '2025-03-01 08:45', customer: '-', batch: 'B2025-03' }, - { id: 6, sn: 'GD30-2024-000078', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-11-05 13:30', customer: '长安大学', batch: 'B2024-11' }, - { id: 7, sn: 'GD10-2024-000033', model: 'GD-10 Supreme', type: '入门级电法仪', status: '已激活', firmware: 'v1.5.2', productionDate: '2024-09-12 10:00', customer: '河海大学', batch: 'B2024-09' }, - { id: 8, sn: 'GD30-2024-000089', model: 'GD-30 Supreme', type: '高密度电法仪', status: '装配中', firmware: 'v2.3.5', productionDate: '2025-03-05 15:10', customer: '-', batch: 'B2025-03' }, - { id: 9, sn: 'GT20-2025-000012', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-01-22 09:30', customer: '中南大学', batch: 'B2025-01' }, - { id: 10, sn: 'GD30-2024-000102', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-10-18 14:00', customer: '吉林大学', batch: 'B2024-10' }, - { id: 11, sn: 'GD10-2024-000034', model: 'GD-10 Supreme', type: '入门级电法仪', status: '装配中', firmware: 'v1.5.2', productionDate: '2025-03-08 11:45', customer: '-', batch: 'B2025-03' }, - { id: 12, sn: 'GD30-2024-000145', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2024-08-25 16:20', customer: '同济大学', batch: 'B2024-08' }, +/** + * 根据日期计算 ISO 周数,返回 "YYYY-WXX" 格式 + * ISO 8601:每周从周一开始,包含该年第一个周四的那周为第1周 + */ +function getYearWeek(dateStr: string): string { + const date = new Date(dateStr) + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())) + // 调整到最近的周四(ISO 周定义) + d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)) + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)) + const weekNo = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7) + return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}` +} + +/** + * 根据年周字符串计算该周的起止日期范围(周一~周日),用于侧边栏展示 + */ +function getWeekRange(yearWeek: string): string { + const [yearStr, weekStr] = yearWeek.split('-W') + const year = parseInt(yearStr, 10) + const week = parseInt(weekStr, 10) + // ISO 周:找到该年1月4日所在周的周一,再偏移到目标周 + const jan4 = new Date(Date.UTC(year, 0, 4)) + const dayOfWeek = jan4.getUTCDay() || 7 + const monday = new Date(jan4.getTime()) + monday.setUTCDate(jan4.getUTCDate() - dayOfWeek + 1 + (week - 1) * 7) + const sunday = new Date(monday.getTime()) + sunday.setUTCDate(monday.getUTCDate() + 6) + const fmt = (d: Date) => `${String(d.getUTCMonth() + 1).padStart(2, '0')}.${String(d.getUTCDate()).padStart(2, '0')}` + return `${fmt(monday)}-${fmt(sunday)}` +} + +const rawDevices = [ + { id: 1, sn: 'GD30-2025-000001', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-15 14:30', customer: '北京地质研究院' }, + { id: 2, sn: 'GD30-2025-000002', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-18 09:15', customer: '中国地质大学' }, + { id: 3, sn: 'GD30-2024-000056', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-12-20 16:00', customer: '成都理工大学' }, + { id: 4, sn: 'GT20-2025-000045', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-02-10 11:20', customer: '武汉地质调查中心' }, + { id: 5, sn: 'GT20-2025-000046', model: 'GD-20', type: '二维电法仪', status: '装配中', firmware: 'v1.8.5', productionDate: '2025-03-01 08:45', customer: '-' }, + { id: 6, sn: 'GD30-2024-000078', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-11-05 13:30', customer: '长安大学' }, + { id: 7, sn: 'GD10-2024-000033', model: 'GD-10 Supreme', type: '入门级电法仪', status: '已激活', firmware: 'v1.5.2', productionDate: '2024-09-12 10:00', customer: '河海大学' }, + { id: 8, sn: 'GD30-2024-000089', model: 'GD-30 Supreme', type: '高密度电法仪', status: '装配中', firmware: 'v2.3.5', productionDate: '2025-03-05 15:10', customer: '-' }, + { id: 9, sn: 'GT20-2025-000012', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-01-22 09:30', customer: '中南大学' }, + { id: 10, sn: 'GD30-2024-000102', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-10-18 14:00', customer: '吉林大学' }, + { id: 11, sn: 'GD10-2024-000034', model: 'GD-10 Supreme', type: '入门级电法仪', status: '装配中', firmware: 'v1.5.2', productionDate: '2025-03-08 11:45', customer: '-' }, + { id: 12, sn: 'GD30-2024-000145', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2024-08-25 16:20', customer: '同济大学' }, ] +/** 根据生产日期动态计算年周批次 */ +const devicesData = rawDevices.map(d => ({ + ...d, + batch: getYearWeek(d.productionDate), +})) + const modelOptions = ['全部', 'GD-30 Supreme', 'GD-20', 'GD-10 Supreme'] const statusOptions = ['全部', '已激活', '已出厂', '装配中'] @@ -48,15 +86,24 @@ export default function DevicesPage() { const [currentPage, setCurrentPage] = useState(1) const pageSize = 8 - // 从数据中提取所有批次并按时间倒序排列,统计每个批次的设备数量 - const batchList = useMemo(() => { + // 从数据中提取所有年周批次,按时间倒序排列,统计每个批次的设备数量,按年分组 + const batchGroups = useMemo(() => { const batchMap = new Map() devicesData.forEach(d => { batchMap.set(d.batch, (batchMap.get(d.batch) || 0) + 1) }) - return Array.from(batchMap.entries()) + const sorted = Array.from(batchMap.entries()) .sort((a, b) => b[0].localeCompare(a[0])) - .map(([batch, count]) => ({ batch, count })) + .map(([batch, count]) => ({ batch, count, year: batch.split('-W')[0] })) + + // 按年分组 + const groups = new Map() + sorted.forEach(item => { + const list = groups.get(item.year) || [] + list.push({ batch: item.batch, count: item.count }) + groups.set(item.year, list) + }) + return groups }, []) const filtered = devicesData.filter(d => { @@ -116,9 +163,6 @@ export default function DevicesPage() { { setSearchText(e.target.value); setCurrentPage(1) }} placeholder="输入设备SN号" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
-
@@ -148,25 +192,33 @@ export default function DevicesPage() { color: selectedBatch === '全部' ? '#fff' : 'rgba(0,0,0,0.45)', }}>{devicesData.length} - {batchList.map(({ batch, count }) => ( - + {Array.from(batchGroups.entries()).map(([year, batches]) => ( +
+
{year} 年
+ {batches.map(({ batch, count }) => ( + + ))} +
))} diff --git a/src/app/firmware/page.tsx b/src/app/firmware/page.tsx index 344674f..fccf06d 100644 --- a/src/app/firmware/page.tsx +++ b/src/app/firmware/page.tsx @@ -6,8 +6,8 @@ import { ArrowLeft, Upload, Download, ChevronDown, ChevronUp, X, Package, Shield const firmwareTypes = ['全部', '主协板', '采集板', '发射板', '升压板', '主机固件', '计算单元固件'] const firmwareData = [ - { id: 1, version: 'v2.1.0', boardModel: 'MCB-3000', type: '主协板', date: '2024-03-10', status: '已发布', size: '12.5MB', downloads: 1234, hwRange: 'MCB-3000 Rev.A~C', upgradeType: '可选', signed: true, md5: 'a1b2c3d4e5f6...', sha256: '9f8e7d6c5b4a...', notes: ['修复通信协议兼容性问题', '优化低功耗模式切换', '新增看门狗超时配置'] }, - { id: 2, version: 'v2.0.0', boardModel: 'MCB-3000', type: '主协板', date: '2024-01-15', status: '已发布', size: '11.8MB', downloads: 2456, hwRange: 'MCB-3000 Rev.A~C', upgradeType: '强制', signed: true, md5: 'b2c3d4e5f6a1...', sha256: '8e7d6c5b4a9f...', notes: ['新增多通道采集支持', '重构通信协议栈'] }, + { id: 1, version: 'v2.1.0', boardModel: 'MB25130025', type: '主协板', date: '2024-03-10', status: '已发布', size: '12.5MB', downloads: 1234, hwRange: 'MB25130025 Rev.A~C', upgradeType: '可选', signed: true, md5: 'a1b2c3d4e5f6...', sha256: '9f8e7d6c5b4a...', notes: ['修复通信协议兼容性问题', '优化低功耗模式切换', '新增看门狗超时配置'] }, + { id: 2, version: 'v2.0.0', boardModel: 'MB25130025', type: '主协板', date: '2024-01-15', status: '已发布', size: '11.8MB', downloads: 2456, hwRange: 'MB25130025 Rev.A~C', upgradeType: '强制', signed: true, md5: 'b2c3d4e5f6a1...', sha256: '8e7d6c5b4a9f...', notes: ['新增多通道采集支持', '重构通信协议栈'] }, { id: 3, version: 'v1.8.5', boardModel: 'MCB-2000', type: '主协板', date: '2023-11-01', status: '已发布', size: '9.2MB', downloads: 3120, hwRange: 'MCB-2000 Rev.B~D', upgradeType: '可选', signed: true, md5: 'c3d4e5f6a1b2...', sha256: '7d6c5b4a9f8e...', notes: ['优化功耗管理', '修复偶发重启问题'] }, { id: 4, version: 'v3.0.2', boardModel: 'ACB-6000', type: '采集板', date: '2024-02-20', status: '已发布', size: '8.7MB', downloads: 890, hwRange: 'ACB-6000 Rev.A~B', upgradeType: '可选', signed: true, md5: 'd4e5f6a1b2c3...', sha256: '6c5b4a9f8e7d...', notes: ['提升采样精度', '修复通道串扰问题', '新增自校准功能'] }, { id: 5, version: 'v2.5.1', boardModel: 'ACB-5000', type: '采集板', date: '2023-09-15', status: '已发布', size: '7.1MB', downloads: 1567, hwRange: 'ACB-5000 Rev.A~C', upgradeType: '可选', signed: true, md5: 'e5f6a1b2c3d4...', sha256: '5b4a9f8e7d6c...', notes: ['优化ADC驱动', '修复温漂补偿算法'] }, @@ -217,7 +217,7 @@ function FirmwareContent() {
- setUploadForm({ ...uploadForm, hwRange: e.target.value })} placeholder="如 MCB-3000 Rev.A~C" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> + setUploadForm({ ...uploadForm, hwRange: e.target.value })} placeholder="如 MB25130025 Rev.A~C" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
diff --git a/src/app/models/page.tsx b/src/app/models/page.tsx index ad5d967..a096d25 100644 --- a/src/app/models/page.tsx +++ b/src/app/models/page.tsx @@ -1,19 +1,18 @@ 'use client' import { useState } from 'react' -import Link from 'next/link' import { useRouter } from 'next/navigation' -import { Plus, X, Info, GripVertical, Trash2, Edit } from 'lucide-react' +import { Plus, X, Info, GripVertical, Trash2, Edit, Key, FileCode, ChevronDown } from 'lucide-react' -const modelsData = [ - { id: 1, name: 'GD-30 Supreme', code: 'GD30', status: '在产', description: '高端三维电法仪', createDate: '2023-06-01' }, - { id: 2, name: 'GD-20 Supreme', code: 'GD20', status: '在产', description: '中端二维电法仪', createDate: '2023-08-15' }, - { id: 3, name: 'GD-10 Supreme', code: 'GD10', status: '停产', description: '入门级电法仪', createDate: '2022-03-10' }, +const initialModelsData = [ + { id: 1, name: 'GD-30 Supreme', code: 'GD30', status: '在产', description: '高端高密度电法仪', createDate: '2023-06-01' }, + { id: 2, name: 'GD-20 Supreme', code: 'GD20', status: '在产', description: '中端高密度电法仪', createDate: '2023-08-15' }, + { id: 3, name: 'GD-10 Supreme', code: 'GD10', status: '停产', description: '入门级高密度电法仪', createDate: '2022-03-10' }, ] const checklistTemplates: Record = { GD30: [ { id: 1, name: '主协板安装检查', required: true }, - { id: 2, name: '采集板安装检查(×6)', required: true }, + { id: 2, name: '采集板安装检查', required: true }, { id: 3, name: '发射板安装检查', required: true }, { id: 4, name: '升压板安装检查', required: true }, { id: 5, name: '线缆连接检查', required: true }, @@ -25,7 +24,7 @@ const checklistTemplates: Record(null) + const [editStatus, setEditStatus] = useState('') + const [actionMenuId, setActionMenuId] = useState(null) + + const handleEdit = (model: typeof initialModelsData[0]) => { + setEditingModel(model) + setEditStatus(model.status) + setEditDrawer(true) + setActionMenuId(null) + } + + const handleSaveEdit = () => { + if (editingModel) { + setModelsData(prev => prev.map(m => m.id === editingModel.id ? { ...m, status: editStatus } : m)) + setEditDrawer(false) + setEditingModel(null) + } + } const addChecklistItem = () => { setChecklistForm({ ...checklistForm, items: [...checklistForm.items, { name: '', required: true }] }) @@ -84,7 +103,7 @@ export default function ModelsPage() {
- 设备型号管理是生产管理的核心枢纽。每个型号定义了设备的板卡组成、装配流程和检测标准。新增型号后,请及时配置对应的装配清单模板。 + 设备型号管理是生产管理的核心枢纽。每个型号定义了设备的装配流程和检测标准。新增型号后,请及时配置对应的装配清单模板。
@@ -99,7 +118,7 @@ export default function ModelsPage() { - {['型号名称', '型号代码', '描述', '状态', '创建日期'].map(h => ( + {['型号名称', '型号代码', '描述', '状态', '创建日期', '操作'].map(h => ( ))} @@ -114,6 +133,44 @@ export default function ModelsPage() { {model.status} + ))} @@ -164,6 +221,46 @@ export default function ModelsPage() {
{h}
{model.createDate} +
+ + {actionMenuId === model.id && ( +
+ + + +
+ )} +
+
+ {/* Edit Status Drawer */} + {editDrawer && editingModel && ( +
+
{ setEditDrawer(false); setEditingModel(null) }} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} /> +
+
+

编辑设备型号

+ +
+
+
+ +
{editingModel.name}
+
+
+ +
{editingModel.code}
+
+
+ +
+ {['在产', '停产'].map(s => ( + + ))} +
+
+
+
+ + +
+
+
+ )} + + {/* Click outside to close action menu */} + {actionMenuId !== null && ( +
setActionMenuId(null)} style={{ position: 'fixed', inset: 0, zIndex: 5 }} /> + )} + {/* New Model Drawer */} {modelDrawer && (
@@ -182,6 +279,11 @@ export default function ModelsPage() { setModelForm({ ...modelForm, code: e.target.value })} placeholder="如 GD30" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
+ +
+ + setModelForm({ ...modelForm, description: e.target.value })} placeholder="如 高端高密度电法仪" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} /> +
diff --git a/src/app/registration/page.tsx b/src/app/registration/page.tsx index 19aaad6..5e6ce87 100644 --- a/src/app/registration/page.tsx +++ b/src/app/registration/page.tsx @@ -106,6 +106,7 @@ export default function RegistrationPage() {
@@ -116,7 +117,7 @@ export default function RegistrationPage() {
- +
@@ -127,7 +128,7 @@ export default function RegistrationPage() {
已匹配型号 {deviceModel} 的关联信息:
-
授权文件:{matchInfo.license} · 配置文件:{matchInfo.config}
+
授权项:{matchInfo.license} · 配置文件:{matchInfo.config}
) : (