From 95259a701ff17a4761afd517903425c1715b7e0b 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 17:34:50 +0800 Subject: [PATCH] =?UTF-8?q?2=E3=80=81=E8=B0=83=E6=95=B4=E4=BA=86=E6=9D=BF?= =?UTF-8?q?=E5=8D=A1=E7=AE=A1=E7=90=86=E7=9A=84=E9=A1=B5=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E6=A0=A1=E5=87=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend-backend-separation/design.md | 885 ++++++++++++++++++ .../requirements.md | 219 +++-- .next/dev/build-manifest.json | 26 +- .next/dev/fallback-build-manifest.json | 26 +- .next/dev/logs/next-development.log | 280 +----- .next/dev/prerender-manifest.json | 6 +- .next/dev/server/app-paths-manifest.json | 8 +- .../page_client-reference-manifest.js | 2 +- .next/dev/server/middleware-build-manifest.js | 26 +- .next/dev/server/pages-manifest.json | 6 +- .../dev/static/development/_buildManifest.js | 3 - .next/dev/trace | 186 ++++ .next/dev/types/routes.d.ts | 6 +- .next/dev/types/validator.ts | 36 +- src/app/{calibration => board-cards}/page.tsx | 165 +++- .../register/page.tsx | 130 ++- src/app/boards/page.tsx | 150 +-- src/app/components/sidebar.tsx | 4 +- src/app/config-files/page.tsx | 41 +- src/app/devices/[sn]/page.tsx | 452 +++++++-- src/app/firmware/page.tsx | 126 ++- src/app/licenses/page.tsx | 41 +- src/app/models/page.tsx | 62 +- src/app/page.tsx | 8 - 24 files changed, 2108 insertions(+), 786 deletions(-) create mode 100644 .kiro/specs/frontend-backend-separation/design.md rename src/app/{calibration => board-cards}/page.tsx (55%) rename src/app/{calibration => board-cards}/register/page.tsx (65%) diff --git a/.kiro/specs/frontend-backend-separation/design.md b/.kiro/specs/frontend-backend-separation/design.md new file mode 100644 index 0000000..2449166 --- /dev/null +++ b/.kiro/specs/frontend-backend-separation/design.md @@ -0,0 +1,885 @@ +# 设计文档:前后端分离 — Java API 后端与前端集成 + +## 概述 + +本设计文档描述将现有 Next.js 16 前端生产管理子系统从硬编码模拟数据迁移到真实 RESTful API 的完整技术方案。涵盖 Java Spring Boot 3.3.6 后端多模块项目架构、PostgreSQL 数据库设计、DDD 分层实现,以及前端 API 集成层的构建。 + +系统为地球物理仪器(高密度电法仪等)的全生命周期管理平台,包含设备管理、板卡管理、固件库、校准管理、校准文件管理、授权管理、配置文件管理、维修工单、报废回收等核心业务模块。 + +### 设计目标 + +- 搭建符合 DDD 分层架构的 Spring Boot 多模块 Maven 项目 +- 设计 PostgreSQL `dev` schema 下的完整数据库表结构 +- 为所有前端页面提供对应的 RESTful API 端点 +- 在前端建立统一的 API 调用层,替换所有硬编码模拟数据 +- 前后端采用统一的分页、筛选、错误响应规范 + +## 架构 + +### 整体架构 + +```mermaid +graph TB + subgraph Frontend["前端 Next.js 16"] + Pages["页面组件
devices / boards / firmware / ..."] + ApiLayer["API 集成层
src/lib/api/"] + Pages --> ApiLayer + end + + subgraph Backend["后端 Spring Boot 3.3.6"] + subgraph ApiAdmin["api-admin 模块"] + Controllers["REST Controllers
/api/admin/*"] + end + subgraph ApiPortal["api-portal 模块"] + PortalControllers["REST Controllers
/api/portal/*
(无需鉴权)"] + end + subgraph Business["business 模块"] + subgraph Device["device 子模块"] + Interfaces["interfaces 层
Controller / VO / Assembler"] + Application["application 层
AppService / Command / Query"] + Domain["domain 层
Entity / Repository接口"] + Infrastructure["infrastructure 层
Mapper / DO / Repository实现"] + end + end + subgraph Common["common 模块"] + Response["统一响应体 R"] + Exception["全局异常处理"] + Audit["审计字段自动填充"] + end + end + + subgraph DB["PostgreSQL 12.14"] + DevSchema["dev schema
devices / device_models / board_types / board_cards / calibration_files / ..."] + end + + ApiLayer -->|HTTP REST| Controllers + ApiLayer -->|HTTP REST| PortalControllers + Controllers --> Interfaces + PortalControllers --> Interfaces + Interfaces --> Application + Application --> Domain + Domain --> Infrastructure + Infrastructure -->|MyBatis-Plus| DevSchema +``` + +### 后端项目结构 + +``` +apps/geo-bps-api/ +├── pom.xml # 父 POM +├── api-admin/ +│ ├── pom.xml +│ └── src/main/java/com/geomative/bps/admin/ +│ ├── ApiAdminApplication.java +│ └── config/ +│ ├── CorsConfig.java # CORS 配置 +│ └── WebMvcConfig.java +├── api-portal/ +│ ├── pom.xml +│ └── src/main/java/com/geomative/bps/portal/ +│ ├── ApiPortalApplication.java +│ └── config/ +│ ├── CorsConfig.java # CORS 配置 +│ └── WebMvcConfig.java # 文件上传大小限制等 +├── business/ +│ ├── pom.xml # pom packaging +│ └── device/ +│ ├── pom.xml +│ └── src/main/java/com/geomative/bps/device/ +│ ├── interfaces/ # Controller + VO +│ ├── application/ # AppService + Command + Query +│ ├── domain/ # Entity + Repository接口 +│ └── infrastructure/ # Mapper + DO + Repository实现 +└── common/ + ├── pom.xml + └── src/main/java/com/geomative/bps/common/ + ├── response/ + │ └── R.java # 统一响应体 + ├── exception/ + │ ├── BizException.java + │ └── GlobalExceptionHandler.java + ├── mybatis/ + │ └── AuditMetaObjectHandler.java + └── page/ + └── PageResult.java # 分页响应 +``` + +### 前端 API 集成层结构 + +``` +src/ +├── lib/ +│ └── api/ +│ ├── client.ts # Axios 实例,统一配置 +│ ├── types.ts # 通用响应/分页类型 +│ ├── deviceApi.ts # 设备管理 API +│ ├── modelApi.ts # 设备型号 API +│ ├── boardApi.ts # 板卡管理 API +│ ├── firmwareApi.ts # 固件库 API +│ ├── calibrationApi.ts # 校准管理 API +│ ├── calibrationFileApi.ts # 校准文件 API +│ ├── configFileApi.ts # 配置文件 API +│ ├── licenseApi.ts # 授权管理 API +│ ├── repairApi.ts # 维修工单 API +│ ├── scrapApi.ts # 报废管理 API +│ └── dashboardApi.ts # 首页统计 API +└── hooks/ + └── useApi.ts # 通用数据获取 Hook +``` + +## 组件与接口 + +### 统一响应体 + +```java +public class R { + private int code; // 业务状态码,0=成功 + private String message; // 提示信息 + private T data; // 响应数据 + + public static R ok(T data) { ... } + public static R fail(int code, String message) { ... } +} +``` + +### 分页响应体 + +```java +public class PageResult { + private long total; // 总记录数 + private int page; // 当前页码 + private int pageSize; // 每页条数 + private List records; // 数据列表 +} +``` + +### 统一分页请求参数 + +所有列表接口统一使用: +- `page`:页码,从 1 开始,默认 1 +- `pageSize`:每页条数,默认 10 + +### API 端点清单 + +| 模块 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 设备 | GET | `/api/admin/devices` | 分页设备列表(筛选:型号/状态/日期/SN/批次) | +| 设备 | GET | `/api/admin/devices/{sn}` | 设备详情(含授权、子设备、固件) | +| 设备 | POST | `/api/admin/devices` | 创建设备(设备登记) | +| 设备 | GET | `/api/admin/devices/batches` | 生产批次列表 | +| 型号 | GET | `/api/admin/device-models` | 设备型号列表 | +| 型号 | POST | `/api/admin/device-models` | 创建设备型号 | +| 型号 | GET | `/api/admin/checklist-templates` | Checklist 模板列表 | +| 型号 | POST | `/api/admin/checklist-templates` | 创建 Checklist 模板 | +| 板卡 | GET | `/api/admin/board-types` | 板卡型号列表(筛选:类型) | +| 板卡 | GET | `/api/admin/board-types/{id}` | 板卡详情 | +| 板卡 | POST | `/api/admin/board-types` | 创建板卡型号 | +| 固件 | GET | `/api/admin/firmware` | 固件版本列表(筛选:类型/板卡型号) | +| 固件 | POST | `/api/admin/firmware` | 创建固件版本 | +| 固件 | GET | `/api/admin/firmware/{id}/download` | 下载固件文件 | +| 校准 | GET | `/api/admin/calibrations` | 校准记录列表(筛选:SN/状态/人员) | +| 校准 | GET | `/api/admin/calibrations/{id}` | 校准详情 | +| 校准 | POST | `/api/admin/calibrations/import` | 批量导入校准记录 | +| 校准文件 | POST | `/api/portal/calibration-files/upload` | 上传校准文件(无需鉴权,上位机调用) | +| 校准文件 | GET | `/api/admin/board-cards/{id}/calibration-files` | 指定采集板的校准文件列表(按上传时间倒序) | +| 校准文件 | GET | `/api/admin/calibration-files/{id}/download` | 下载校准文件 | +| 配置 | GET | `/api/admin/config-files` | 配置文件列表(筛选:型号/版本/关键字) | +| 配置 | GET | `/api/admin/config-files/{id}` | 配置文件详情 | +| 配置 | POST | `/api/admin/config-files` | 创建配置文件 | +| 配置 | PUT | `/api/admin/config-files/{id}` | 更新配置文件 | +| 配置 | DELETE | `/api/admin/config-files/{id}` | 逻辑删除配置文件 | +| 授权 | GET | `/api/admin/licenses` | 授权列表(筛选:型号/状态) | +| 授权 | POST | `/api/admin/licenses` | 创建授权记录 | +| 授权 | PUT | `/api/admin/licenses/{id}` | 更新授权记录 | +| 授权 | PUT | `/api/admin/licenses/{id}/disable` | 停用授权 | +| 维修 | GET | `/api/admin/repair-orders` | 工单列表(筛选:状态/优先级/人员/日期/SN) | +| 维修 | GET | `/api/admin/repair-orders/{id}` | 工单详情 | +| 维修 | POST | `/api/admin/repair-orders` | 创建工单 | +| 维修 | PUT | `/api/admin/repair-orders/{id}/process` | 处理工单 | +| 维修 | PUT | `/api/admin/repair-orders/{id}/close` | 关闭工单 | +| 报废 | GET | `/api/admin/scrap-records` | 报废记录列表(筛选:SN/状态/日期) | +| 报废 | GET | `/api/admin/scrap-records/{id}` | 报废详情 | +| 报废 | PUT | `/api/admin/scrap-records/{id}/approve` | 审批通过 | +| 报废 | PUT | `/api/admin/scrap-records/{id}/reject` | 驳回 | +| 报废 | PUT | `/api/admin/scrap-records/{id}/recover` | 物料回收入库 | +| 报废 | GET | `/api/admin/scrap-records/stats` | 报废统计 | +| 首页 | GET | `/api/admin/dashboard/metrics` | 统计指标 | +| 首页 | GET | `/api/admin/dashboard/device-status` | 设备状态分布 | +| 首页 | GET | `/api/admin/dashboard/tasks` | 待处理任务 | + +### 前端 API 客户端 + +```typescript +// src/lib/api/client.ts +import axios from 'axios'; + +const apiClient = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080', + timeout: 5000, + headers: { 'Content-Type': 'application/json' }, +}); + +// 响应拦截器:统一错误处理 +apiClient.interceptors.response.use( + (res) => res.data, + (error) => { + const message = error.response?.data?.message || '请求失败,请稍后重试'; + return Promise.reject(new Error(message)); + } +); + +export default apiClient; +``` + +### 前端通用类型 + +```typescript +// src/lib/api/types.ts +export interface ApiResponse { + code: number; + message: string; + data: T; +} + +export interface PageResult { + total: number; + page: number; + pageSize: number; + records: T[]; +} + +export interface PageParams { + page?: number; + pageSize?: number; +} +``` + + +## 数据模型 + +### ER 关系图 + +```mermaid +erDiagram + device_models ||--o{ devices : "型号关联" + device_models ||--o{ checklist_templates : "模板关联" + device_models ||--o{ config_files : "配置关联" + device_models ||--o{ licenses : "授权关联" + checklist_templates ||--o{ checklist_items : "包含检查项" + devices ||--o{ device_boards : "装配板卡" + devices ||--o{ repair_orders : "维修工单" + devices ||--o{ scrap_records : "报废记录" + board_types ||--o{ device_boards : "板卡实例" + board_types ||--o{ firmware_versions : "固件版本" + board_types ||--o{ calibration_records : "校准记录" + board_cards ||--o{ calibration_files : "校准文件" + + device_models { + varchar(64) id PK + varchar(100) name + varchar(50) code UK + varchar(20) status + varchar(500) description + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + devices { + varchar(64) id PK + varchar(100) sn UK + varchar(64) model_id FK + varchar(100) model_name + varchar(20) status + varchar(20) firmware_version + timestamp production_date + varchar(200) customer + varchar(20) batch + varchar(64) operator + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + board_types { + varchar(64) id PK + varchar(50) type + varchar(50) version UK + varchar(20) latest_firmware + date production_date + varchar(20) status + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + device_boards { + varchar(64) id PK + varchar(64) device_id FK + varchar(100) device_sn + varchar(64) board_type_id FK + varchar(100) board_sn + varchar(50) board_name + varchar(50) board_model + varchar(20) calibration_status + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + firmware_versions { + varchar(64) id PK + varchar(20) version + varchar(64) board_type_id FK + varchar(50) board_version + varchar(50) firmware_type + date release_date + varchar(20) status + varchar(20) file_size + int download_count + varchar(200) hw_range + varchar(20) upgrade_type + boolean signed + varchar(64) md5 + varchar(128) sha256 + text release_notes + varchar(500) file_path + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + calibration_records { + varchar(64) id PK + varchar(100) board_sn + varchar(64) board_type_id FK + varchar(50) board_version + date calibration_date + date expiry_date + varchar(100) calibrator + varchar(20) status + int channel_count + decimal overall_deviation + jsonb channel_results + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + config_files { + varchar(64) id PK + varchar(100) name + varchar(64) model_id FK + varchar(100) model_name + varchar(20) version + varchar(20) status + jsonb transmission_params + jsonb acquisition_params + jsonb protection_params + jsonb network_params + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + licenses { + varchar(64) id PK + varchar(64) model_id FK + varchar(50) model_code + jsonb modules + varchar(20) validity_type + date expiry_date + varchar(20) status + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + repair_orders { + varchar(64) id PK + varchar(50) order_no UK + varchar(100) device_sn + varchar(50) fault_type + varchar(20) status + varchar(10) priority + varchar(100) assignee + varchar(500) description + varchar(500) phenomenon + date expected_fix_date + text note + jsonb process_records + jsonb board_replacements + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + scrap_records { + varchar(64) id PK + varchar(100) device_sn + varchar(100) model_name + varchar(500) reason + varchar(100) applicant + varchar(20) status + varchar(50) order_id + decimal residual_value + jsonb materials + jsonb approval_timeline + text approval_note + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + checklist_templates { + varchar(64) id PK + varchar(64) model_id FK + varchar(50) model_code + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + checklist_items { + varchar(64) id PK + varchar(64) template_id FK + varchar(200) name + boolean required + int sort_order + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + board_cards { + varchar(64) id PK + varchar(100) sn UK + varchar(50) type + varchar(50) version + varchar(20) firmware_version + varchar(20) status + varchar(100) device_sn + date production_date + varchar(20) calib_status + date calib_date + varchar(500) remark + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } + + calibration_files { + varchar(64) id PK + varchar(100) board_sn + varchar(200) original_name + varchar(500) file_path + bigint file_size + varchar(64) md5 + timestamp upload_time + timestamp created_at + varchar(64) created_by + timestamp updated_at + varchar(64) updated_by + smallint deleted + } +``` + +### 表结构详细说明 + +#### 公共审计字段(所有表必须包含) + +| 字段 | 类型 | 约束 | 说明 | +|------|------|------|------| +| id | VARCHAR(64) | PK | 应用层生成(雪花算法/UUID) | +| created_at | TIMESTAMP | NOT NULL | 创建时间,INSERT 自动填充 | +| created_by | VARCHAR(64) | NULL | 创建人 | +| updated_at | TIMESTAMP | NOT NULL | 修改时间,INSERT/UPDATE 自动填充 | +| updated_by | VARCHAR(64) | NULL | 修改人 | +| deleted | SMALLINT | NOT NULL DEFAULT 0 | 逻辑删除标记 | + +#### dev.devices — 设备表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| sn | VARCHAR(100) | 设备SN号,唯一索引 | +| model_id | VARCHAR(64) | 关联设备型号 | +| model_name | VARCHAR(100) | 型号名称(冗余) | +| status | VARCHAR(20) | 装配中/已出厂/已激活/报废 | +| firmware_version | VARCHAR(20) | 当前固件版本 | +| production_date | TIMESTAMP | 生产日期 | +| customer | VARCHAR(200) | 客户名称 | +| batch | VARCHAR(20) | 生产批次(YYYY-WXX) | +| operator | VARCHAR(64) | 登记人 | + +#### dev.device_models — 设备型号表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| name | VARCHAR(100) | 型号名称(如 GD-30 Supreme) | +| code | VARCHAR(50) | 型号代码(如 GD30),唯一索引 | +| status | VARCHAR(20) | 在产/停产 | +| description | VARCHAR(500) | 描述 | + +#### dev.board_types — 板卡型号表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| type | VARCHAR(50) | 板卡类型(主协板/采集板/发射板/升压板) | +| version | VARCHAR(50) | 版本号(如 MB-V1.8),唯一索引 | +| latest_firmware | VARCHAR(20) | 最新固件版本 | +| production_date | DATE | 生产日期 | +| status | VARCHAR(20) | 在产/停产 | + +#### dev.device_boards — 设备板卡关联表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| device_id | VARCHAR(64) | 关联设备 | +| device_sn | VARCHAR(100) | 设备SN(冗余) | +| board_type_id | VARCHAR(64) | 关联板卡型号 | +| board_sn | VARCHAR(100) | 板卡SN号 | +| board_name | VARCHAR(50) | 板卡名称(主协板/采集板等) | +| board_model | VARCHAR(50) | 板卡型号 | +| calibration_status | VARCHAR(20) | 校准状态 | + +#### dev.firmware_versions — 固件版本表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| version | VARCHAR(20) | 版本号 | +| board_type_id | VARCHAR(64) | 关联板卡型号 | +| board_version | VARCHAR(50) | 板卡版本(冗余) | +| firmware_type | VARCHAR(50) | 固件类型(主协板/采集板/发射板/升压板/主机固件/计算单元固件) | +| release_date | DATE | 发布日期 | +| status | VARCHAR(20) | 已发布/草稿 | +| file_size | VARCHAR(20) | 文件大小 | +| download_count | INT | 下载次数,默认 0 | +| hw_range | VARCHAR(200) | 硬件版本范围 | +| upgrade_type | VARCHAR(20) | 可选/强制 | +| signed | BOOLEAN | 是否数字签名 | +| md5 | VARCHAR(64) | MD5 校验值 | +| sha256 | VARCHAR(128) | SHA256 校验值 | +| release_notes | TEXT | 发布说明(JSON 数组) | +| file_path | VARCHAR(500) | 文件存储路径 | + +#### dev.calibration_records — 校准记录表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| board_sn | VARCHAR(100) | 采集板SN号 | +| board_type_id | VARCHAR(64) | 关联板卡型号 | +| board_version | VARCHAR(50) | 板卡版本(冗余) | +| calibration_date | DATE | 校准日期 | +| expiry_date | DATE | 到期日期 | +| calibrator | VARCHAR(100) | 校准人员 | +| status | VARCHAR(20) | 合格/不合格/待校准 | +| channel_count | INT | 通道数 | +| overall_deviation | DECIMAL(10,4) | 综合偏差 | +| channel_results | JSONB | 各通道校准结果 | + +#### dev.config_files — 配置文件表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| name | VARCHAR(100) | 配置文件名(如 CFG-GD30-v1.3.0) | +| model_id | VARCHAR(64) | 关联设备型号 | +| model_name | VARCHAR(100) | 型号名称(冗余) | +| version | VARCHAR(20) | 配置版本 | +| status | VARCHAR(20) | 生效/已停用 | +| transmission_params | JSONB | 发射参数 | +| acquisition_params | JSONB | 采集参数 | +| protection_params | JSONB | 保护参数 | +| network_params | JSONB | 网络参数 | + +#### dev.licenses — 授权表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| model_id | VARCHAR(64) | 关联设备型号 | +| model_code | VARCHAR(50) | 型号代码(冗余) | +| modules | JSONB | 授权模块列表 | +| validity_type | VARCHAR(20) | 永久/1年/2年/自定义 | +| expiry_date | DATE | 到期日期 | +| status | VARCHAR(20) | 生效/草稿/已停用 | + +#### dev.repair_orders — 维修工单表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| order_no | VARCHAR(50) | 工单号(如 WO-2024-0001),唯一索引 | +| device_sn | VARCHAR(100) | 设备SN号 | +| fault_type | VARCHAR(50) | 故障类型 | +| status | VARCHAR(20) | 待处理/处理中/已处理 | +| priority | VARCHAR(10) | 高/中/低 | +| assignee | VARCHAR(100) | 负责人 | +| description | VARCHAR(500) | 故障描述 | +| phenomenon | VARCHAR(500) | 故障现象 | +| expected_fix_date | DATE | 预计修复日期 | +| note | TEXT | 备注 | +| process_records | JSONB | 处理记录时间线 | +| board_replacements | JSONB | 板卡更换记录 | + +#### dev.scrap_records — 报废记录表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| device_sn | VARCHAR(100) | 设备SN号 | +| model_name | VARCHAR(100) | 设备型号 | +| reason | VARCHAR(500) | 报废原因 | +| applicant | VARCHAR(100) | 申请人 | +| status | VARCHAR(20) | 待审批/审批中/已审批/已驳回/回收中/已回收 | +| order_id | VARCHAR(50) | 来源工单号 | +| residual_value | DECIMAL(12,2) | 残值评估 | +| materials | JSONB | 可回收物料列表 | +| approval_timeline | JSONB | 审批记录时间线 | +| approval_note | TEXT | 审批意见 | + +#### dev.checklist_templates — Checklist 模板表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| model_id | VARCHAR(64) | 关联设备型号 | +| model_code | VARCHAR(50) | 型号代码(冗余) | + +#### dev.checklist_items — Checklist 检查项表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| template_id | VARCHAR(64) | 关联模板 | +| name | VARCHAR(200) | 检查项名称 | +| required | BOOLEAN | 是否必填 | +| sort_order | INT | 排序序号 | + +#### dev.calibration_files — 校准文件表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| board_sn | VARCHAR(100) | 采集板SN号,关联 board_cards.sn | +| original_name | VARCHAR(200) | 原始文件名 | +| file_path | VARCHAR(500) | 服务器存储路径 | +| file_size | BIGINT | 文件大小(字节) | +| md5 | VARCHAR(64) | 文件MD5校验值 | +| upload_time | TIMESTAMP | 上传时间 | + + +## 正确性属性 + +*正确性属性是在系统所有有效执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。* + +### Property 1: 统一响应格式一致性 + +*For any* API 请求(无论成功或失败),响应 JSON 都应包含且仅包含 `code`(整数)、`message`(字符串)、`data`(泛型)三个顶层字段。成功时 code=0,业务异常和参数校验异常均应返回 code≠0 的统一格式响应。 + +**Validates: Requirements 1.3, 1.4** + +### Property 2: 审计字段自动填充 + +*For any* 通过 MyBatis-Plus 插入的实体记录,`created_at` 和 `updated_at` 字段应非空且为合理的时间戳;*For any* 更新操作,`updated_at` 应大于等于更新前的值。*For any* `dev` schema 下的业务表,都应包含 `id`、`created_at`、`created_by`、`updated_at`、`updated_by`、`deleted` 六个审计字段。 + +**Validates: Requirements 1.5, 2.12** + +### Property 3: CRUD 数据往返一致性 + +*For any* 有效的创建命令(设备、型号、板卡、固件、配置文件、授权、工单、Checklist 模板),通过 POST 创建后再通过 GET 查询,返回的数据应与创建时提交的数据一致(在服务端生成的字段如 id、审计字段除外)。 + +**Validates: Requirements 3.2, 3.3, 4.2, 4.4, 5.3, 6.2, 8.3, 8.4, 9.2, 9.3, 10.3** + +### Property 4: 唯一性约束与冲突检测 + +*For any* 已存在的唯一标识(设备SN号、型号代码、同一板卡型号下的固件版本号),尝试创建具有相同唯一标识的记录时,API 应返回 HTTP 409 冲突错误码和描述性错误消息,且数据库中不应产生重复记录。 + +**Validates: Requirements 3.5, 4.5, 6.4** + +### Property 5: 筛选条件正确性 + +*For any* 列表查询接口和任意有效的筛选参数组合,返回的所有记录都应满足指定的筛选条件。例如:按型号筛选时,所有返回的设备型号应与筛选值匹配;按状态筛选时,所有返回记录的状态应与筛选值匹配。 + +**Validates: Requirements 3.1, 5.1, 6.1, 7.1, 8.1, 9.1, 10.1, 11.1** + +### Property 6: 状态转换正确性 + +*For any* 支持状态转换的实体(授权停用、工单关闭、报废审批/驳回/回收),执行状态转换操作后,实体的状态应更新为目标状态,且相关附加信息(如驳回意见、回收物料清单)应被正确记录。 + +**Validates: Requirements 9.4, 10.5, 11.3, 11.4, 11.5** + +### Property 7: 逻辑删除排除性 + +*For any* 被逻辑删除的记录(deleted=1),该记录不应出现在任何列表查询的结果中。 + +**Validates: Requirements 8.5** + +### Property 8: 分页一致性 + +*For any* 列表查询接口,响应应包含 `total`、`page`、`pageSize`、`records` 四个字段;`records` 的长度应不超过 `pageSize`;当不传筛选条件或筛选条件为空时,应返回不带过滤的完整数据集(受分页限制)。 + +**Validates: Requirements 14.1, 14.2, 14.4** + +### Property 9: 聚合统计一致性 + +*For any* 统计接口返回的数据,各分类计数之和应等于总数。例如:设备状态分布中各状态数量之和应等于设备总数;报废统计中各状态数量之和应等于报废总数。 + +**Validates: Requirements 11.6, 12.2** + +### Property 10: 参数校验防御性 + +*For any* 无效的筛选参数(如非法的状态值、格式错误的日期),API 应返回 HTTP 400 错误码和描述性错误消息,而非返回错误数据或抛出未处理异常。 + +**Validates: Requirements 14.3** + +### Property 11: 校准文件上传-下载往返一致性 + +*For any* 有效的校准文件和已存在的采集板SN号,通过 POST `/api/portal/calibration-files/upload` 上传后,再通过 GET `/api/admin/calibration-files/{id}/download` 下载,下载的文件内容应与上传的原始文件完全一致,且 `dev.calibration_files` 表中的记录应正确关联该采集板SN号,文件大小和MD5校验值应与原始文件匹配。 + +**Validates: Requirements 17.2, 17.3, 17.7** + +### Property 12: 校准文件列表完整性与排序 + +*For any* 采集板,上传 N 个校准文件后,通过 GET `/api/admin/board-cards/{id}/calibration-files` 查询应返回恰好 N 条记录,每条记录包含 id、fileName、fileSize、md5、uploadTime 字段,且结果按 uploadTime 倒序排列。 + +**Validates: Requirements 17.6, 17.10** + +## 错误处理 + +### 后端错误处理策略 + +| 错误类型 | HTTP 状态码 | 业务码 | 处理方式 | +|----------|------------|--------|----------| +| 参数校验失败 | 400 | 40001 | `GlobalExceptionHandler` 捕获 `MethodArgumentNotValidException`,返回字段级错误信息 | +| 资源不存在 | 404 | 40401 | Service 层抛出 `BizException`,Handler 统一处理 | +| 文件不存在 | 404 | 40402 | 校准文件在存储目录中不存在时,返回描述性错误消息 | +| 唯一性冲突 | 409 | 40901 | Service 层检测重复后抛出 `BizException` | +| 文件上传失败 | 500 | 50002 | 文件写入磁盘失败时,记录日志并返回错误消息 | +| 服务器内部错误 | 500 | 50001 | `GlobalExceptionHandler` 兜底捕获,记录完整堆栈日志 | + +### 自定义异常体系 + +```java +public class BizException extends RuntimeException { + private final int code; + private final String message; + + public BizException(int code, String message) { + super(message); + this.code = code; + this.message = message; + } +} +``` + +### 全局异常处理器 + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BizException.class) + public R handleBizException(BizException e) { + return R.fail(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleValidation(MethodArgumentNotValidException e) { + String msg = e.getBindingResult().getFieldErrors().stream() + .map(f -> f.getField() + ": " + f.getDefaultMessage()) + .collect(Collectors.joining("; ")); + return R.fail(40001, msg); + } + + @ExceptionHandler(Exception.class) + public R handleException(Exception e) { + log.error("Unhandled exception", e); + return R.fail(50001, "服务器内部错误"); + } +} +``` + +### 前端错误处理策略 + +| 场景 | 处理方式 | +|------|----------| +| 网络超时(5秒) | 显示"网络请求超时,请检查网络连接" | +| HTTP 400 | 显示后端返回的具体校验错误信息 | +| HTTP 404 | 显示"请求的资源不存在" | +| HTTP 409 | 显示后端返回的冲突描述(如"SN号已存在") | +| HTTP 500 | 显示"服务器异常,请稍后重试" | +| 请求进行中 | 页面显示 loading 骨架屏或 spinner | + +## 测试策略 + +### 后端测试 + +#### 单元测试 +- 框架:JUnit 5 + Mockito +- 覆盖范围: + - Application Service 层的业务逻辑 + - Domain Entity 的状态转换逻辑 + - 参数校验逻辑 + - 异常处理逻辑 + +#### 集成测试 +- 框架:Spring Boot Test + TestContainers(PostgreSQL) +- 覆盖范围: + - Controller 层 API 端点的请求/响应格式 + - MyBatis-Plus Mapper 的 CRUD 操作 + - 审计字段自动填充 + - 逻辑删除行为 + - 分页查询 + +#### 属性测试 +- 框架:jqwik(Java 属性测试库) +- 最少 100 次迭代 +- 每个属性测试需引用设计文档中的属性编号 +- 标签格式:`Feature: frontend-backend-separation, Property {number}: {property_text}` +- 覆盖范围: + - Property 1: 统一响应格式(生成随机数据类型,验证序列化格式) + - Property 3: CRUD 往返一致性(生成随机实体数据,验证创建-查询一致性) + - Property 4: 唯一性约束(生成随机唯一标识,验证重复创建返回 409) + - Property 5: 筛选正确性(生成随机筛选参数,验证返回结果匹配) + - Property 8: 分页一致性(生成随机 page/pageSize,验证响应格式) + - Property 10: 参数校验(生成随机无效参数,验证返回 400) + - Property 11: 校准文件上传-下载往返(生成随机文件内容和SN号,验证上传后下载内容一致、元数据正确) + - Property 12: 校准文件列表完整性(生成随机数量的校准文件上传,验证列表返回数量和排序正确) + +### 前端测试 + +#### 单元测试 +- 框架:Vitest + React Testing Library +- 覆盖范围: + - API 客户端的请求拦截和错误处理 + - 各 API 服务文件的请求参数构造 + - 页面组件的加载状态和错误状态渲染 + - 板卡详情抽屉中校准文件列表的渲染和下载按钮交互 + +#### 集成测试 +- 框架:Vitest + MSW(Mock Service Worker) +- 覆盖范围: + - 页面组件与 API 的完整交互流程 + - 筛选、分页操作的数据刷新 + - 表单提交与 API 调用 + diff --git a/.kiro/specs/frontend-backend-separation/requirements.md b/.kiro/specs/frontend-backend-separation/requirements.md index 717a37b..08a0d22 100644 --- a/.kiro/specs/frontend-backend-separation/requirements.md +++ b/.kiro/specs/frontend-backend-separation/requirements.md @@ -2,21 +2,41 @@ ## 简介 -本项目旨在将现有的 Next.js 前端生产管理子系统进行前后端分离改造。当前前端所有页面(设备列表、板卡管理、校准管理、固件库、授权管理、配置文件管理、维修工单、报废管理、设备登记、设备型号管理)均使用硬编码的模拟数据。需要搭建 Java Spring Boot 3.3.6 后端 API 服务和 PostgreSQL 数据库,用真实的 RESTful API 接口和持久化数据替换前端模拟数据。 +本项目旨在将现有的 Next.js 16 前端生产管理子系统进行前后端分离改造。当前前端所有页面均使用硬编码的模拟数据,需要搭建 Java Spring Boot 3.3.6 后端 API 服务和 PostgreSQL 数据库,用真实的 RESTful API 接口和持久化数据替换前端模拟数据。 + +系统为地球物理仪器(高密度电法仪等)的全生命周期管理平台,包含以下核心页面模块: +- 首页 Dashboard(`/`)— 8项统计指标、设备状态分布、4组待处理任务 +- 设备列表(`/devices`)— 卡片式设备列表,按批次侧边栏分组 +- 设备详情(`/devices/[sn]`)— 6个Tab页(概览、装机清单、装配记录、授权项信息、配置文件、操作日志) +- 设备登记(`/registration`)— 装机信息表单、BOM清单、装配Checklist +- 设备型号管理(`/models`)— 型号列表、装配Checklist模板 +- 板卡列表(`/calibration`)— 板卡实例管理,含统计卡片和筛选 +- 板卡登记(`/calibration/register`)— 单个/批量板卡登记 +- 板卡版本管理(`/boards`)— 板卡型号版本管理,Tab筛选 +- 固件库(`/firmware`)— 固件版本卡片列表,支持从板卡版本页跳转筛选 +- 授权管理(`/licenses`)— 授权列表,11项授权模块勾选 +- 配置文件管理(`/config-files`)— 配置文件列表,含详细参数 +- 维修工单(`/repair`)— 卡片式工单列表,新建/处理/详情抽屉 +- 报废管理(`/scrap`)— 审批流程、物料回收 ## 术语表 - **API_Server**:基于 Spring Boot 3.3.6 的 Java 后端 API 服务,项目路径 `apps/geo-bps-api/` -- **Database**:PostgreSQL 12.14 数据库实例,按模块使用不同 schema 隔离 -- **Frontend**:现有 Next.js 前端应用,位于 `src/app/` 目录 +- **Database**:PostgreSQL 12.14 数据库实例,设备管理模块使用 `dev` schema +- **Frontend**:现有 Next.js 16 前端应用,位于 `src/app/` 目录 - **Device_Module**:设备管理业务模块,包名 `com.geomative.bps.device`,数据库 schema `dev` - **Common_Module**:公共模块,包名 `com.geomative.bps.common`,提供统一响应体、全局异常处理、工具类 - **API_Admin**:后台管理入口模块,提供需登录鉴权的 API 端点 - **DDD**:领域驱动设计分层架构(interfaces / application / domain / infrastructure) +- **Board_Card**:板卡实例,指具体的单个板卡物理实体,拥有唯一SN号、状态(在库/已装配/故障/报废)、校准状态 +- **Board_Type**:板卡版本(型号),指板卡的设计版本(如 MB-V1.8、RX-V2.3),管理版本的在产/停产状态 - **DO**:数据库映射对象(Data Object) - **VO**:视图对象(View Object),用于 API 响应 - **Query**:查询参数对象 - **Command**:命令对象,用于创建/更新操作 +- **Calibration_File**:校准文件,采集板校准时由上位机生成并上传的文件,通过采集板SN号关联 +- **Upper_Computer**:上位机,校准设备的PC端软件,负责执行采集板校准并上传校准文件 +- **API_Portal**:前端门户入口模块,提供无需登录鉴权的公开 API 端点 ## 需求 @@ -41,144 +61,186 @@ #### 验收标准 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 审计字段 +2. THE Database SHALL 包含 `dev.devices` 表,存储设备信息(SN号唯一索引、型号ID、型号名称、状态[装配中/已出厂/已激活/报废]、固件版本、生产日期、客户名称、批次号[YYYY-WXX格式]、登记人) +3. THE Database SHALL 包含 `dev.device_models` 表,存储设备型号信息(型号名称如GD-30 Supreme、型号代码如GD30唯一索引、状态[在产/停产]、描述、创建日期) +4. THE Database SHALL 包含 `dev.board_types` 表,存储板卡版本信息(板卡类型[主协板/采集板/发射板/升压板]、版本号如MB-V1.8唯一索引、生产日期、状态[在产/停产]) +5. THE Database SHALL 包含 `dev.board_cards` 表,存储板卡实例信息(板卡SN号唯一索引、板卡类型[主协板/采集板/发射板/升压板]、版本号、固件版本、状态[在库/已装配/故障/报废]、所属设备SN、生产日期、校准状态[合格/不合格/待校准/无需校准]、校准日期、备注) +6. THE Database SHALL 包含 `dev.device_boards` 表,存储设备与板卡的装配关联关系(设备ID、设备SN、板卡实例ID、板卡SN、板卡名称、板卡型号、校准状态) +7. THE Database SHALL 包含 `dev.firmware_versions` 表,存储固件版本信息(版本号、关联板卡版本ID、板卡版本号、固件类型[主协板/采集板/发射板/升压板/主机固件/计算单元固件]、发布日期、状态[已发布/草稿]、文件大小、下载次数、硬件版本范围、升级类型[可选/强制]、是否签名、MD5、SHA256、发布说明JSON数组、文件存储路径) +8. THE Database SHALL 包含 `dev.calibration_records` 表,存储校准记录(采集板SN号、板卡版本ID、板卡版本号、校准日期、到期日期、校准人员、状态[合格/不合格/待校准]、通道数、综合偏差、各通道校准结果JSONB) +9. THE Database SHALL 包含 `dev.config_files` 表,存储配置文件信息(配置名称、适配型号ID、型号名称、版本、状态[生效/已停用]、发射参数JSONB含电压/电流/占空比/脉宽/迭代次数/波形、采集参数JSONB含通道数/采样率/电压量程/全波形、保护参数JSONB含过压/过流/短路/高温、网络参数JSONB含WiFi SSID前缀) +10. THE Database SHALL 包含 `dev.licenses` 表,存储授权信息(设备型号ID、型号代码、授权模块列表JSONB含11项模块[1D SP/2D SP/3D SP/1D VES/2D ERT/3D ERT/1D IP/2D IP/3D IP/跨孔/水上]、有效期类型[永久/1年/2年/3年/自定义]、到期日期、状态[生效/草稿/已停用]) +11. THE Database SHALL 包含 `dev.repair_orders` 表,存储维修工单信息(工单号唯一索引、设备SN、故障类型[板卡故障/固件异常/通信故障/电源故障/传感器故障/其他]、状态[待处理/处理中/已处理]、优先级[高/中/低]、负责人、故障描述、故障现象、预计修复日期、备注、处理记录JSONB时间线、板卡更换记录JSONB) +12. THE Database SHALL 包含 `dev.scrap_records` 表,存储报废记录(设备SN、设备型号名称、报废原因、申请人、状态[待审批/审批中/已审批/已驳回/回收中/已回收]、来源工单号、残值评估金额、可回收物料列表JSONB、审批记录时间线JSONB、审批意见) +13. THE Database SHALL 包含 `dev.checklist_templates` 表和 `dev.checklist_items` 表,存储装配 Checklist 模板及其检查项(检查项含名称、是否必填、排序序号) +14. WHEN 创建任何业务表时,THE Database SHALL 包含 id(VARCHAR(64) 主键)、created_at、created_by、updated_at、updated_by、deleted 审计字段 ### 需求 3:设备管理 API -**用户故事:** 作为前端开发者,我希望通过 API 获取和管理设备数据,以便替换设备列表页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 获取和管理设备数据,以便替换设备列表页面(`/devices`)和设备详情页面(`/devices/[sn]`)的模拟数据。 #### 验收标准 -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 返回所有生产批次列表及每个批次的设备数量 +1. WHEN 前端请求 GET `/api/admin/devices` 时,THE API_Server SHALL 返回分页的设备列表,支持按型号、状态、生产日期、SN号、批次号筛选,每条记录包含 sn、model、type、status、firmware、productionDate、customer、batch 字段 +2. WHEN 前端请求 GET `/api/admin/devices/{sn}` 时,THE API_Server SHALL 返回指定设备的完整详情,包含6个Tab页所需的全部数据: + - 概览Tab:基本信息(SN、型号、类型、固件版本、生产日期、登记人、状态) + - 装机清单Tab:BOM列表(物料名称、板卡SN、型号、校准状态) + - 装配记录Tab:Checklist检查项列表(项目名称、通过状态、照片URL列表、装配记录备注),含通过数/总数统计 + - 授权项信息Tab:授权模块列表、到期时间、授权状态 + - 配置文件Tab:配置文件名称、版本、发射参数(电压/电流/占空比/脉宽/波形/全波形)、采集参数(通道数/采样率/电压量程/迭代次数)、保护参数(过压/过流/短路/高温阈值)、网络参数(WiFi SSID前缀) + - 操作日志Tab:操作记录时间线(日期、操作类型、操作人、详情描述) +3. WHEN 前端请求 POST `/api/admin/devices` 时,THE API_Server SHALL 创建新设备记录(设备登记),包含装机信息(型号、主机SN、主板SN、测试状态、生产日期、登记人)、BOM清单列表和装配Checklist状态 +4. WHEN 前端请求 GET `/api/admin/devices/batches` 时,THE API_Server SHALL 返回所有生产批次列表,每个批次包含批次号(YYYY-WXX格式)、设备数量,按年份分组并按时间倒序排列 5. IF 请求参数中的 SN 号已存在,THEN THE API_Server SHALL 返回 409 冲突错误码和描述性错误消息 ### 需求 4:设备型号管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理设备型号和装配 Checklist 模板,以便替换型号管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理设备型号和装配 Checklist 模板,以便替换型号管理页面(`/models`)的模拟数据。 #### 验收标准 -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 冲突错误码 +1. WHEN 前端请求 GET `/api/admin/device-models` 时,THE API_Server SHALL 返回所有设备型号列表,每条记录包含 name、code、status、description、createDate 字段 +2. WHEN 前端请求 POST `/api/admin/device-models` 时,THE API_Server SHALL 创建新设备型号记录,包含型号名称、型号编码、描述、状态[在产/停产] +3. WHEN 前端请求 PUT `/api/admin/device-models/{id}` 时,THE API_Server SHALL 更新指定型号的状态(在产/停产切换) +4. WHEN 前端请求 GET `/api/admin/checklist-templates?modelCode={code}` 时,THE API_Server SHALL 返回指定型号的装配 Checklist 模板及其检查项列表(含名称、是否必填、排序序号) +5. WHEN 前端请求 POST `/api/admin/checklist-templates` 时,THE API_Server SHALL 创建新的 Checklist 模板,包含关联型号和检查项列表 +6. IF 请求的型号代码已存在,THEN THE API_Server SHALL 返回 409 冲突错误码 -### 需求 5:板卡型号管理 API +### 需求 5:板卡版本管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理板卡型号数据,以便替换板卡管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理板卡版本(型号)数据,以便替换板卡版本管理页面(`/boards`)的模拟数据。该页面管理的是板卡的设计版本(如 MB-V1.8),而非单个板卡实例。 #### 验收标准 -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 创建新板卡型号记录 +1. WHEN 前端请求 GET `/api/admin/board-types` 时,THE API_Server SHALL 返回板卡版本列表,支持按板卡类型(全部/主协板/采集板/发射板/升压板)Tab筛选,每条记录包含 type、version、productionDate、status 字段 +2. WHEN 前端请求 GET `/api/admin/board-types/{id}` 时,THE API_Server SHALL 返回板卡版本详情,包含基本信息(类型、版本、生产日期、状态) +3. WHEN 前端请求 POST `/api/admin/board-types` 时,THE API_Server SHALL 创建新板卡版本记录,包含板卡类型、版本号、生产日期、状态 +4. WHEN 前端请求 PUT `/api/admin/board-types/{id}/toggle-status` 时,THE API_Server SHALL 切换板卡版本的状态(在产↔停产) -### 需求 6:固件库管理 API +### 需求 6:板卡实例管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理固件版本数据,以便替换固件库页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理板卡实例数据,以便替换板卡列表页面(`/calibration`)的模拟数据。该页面管理的是具体的单个板卡物理实体,每个板卡有唯一SN号、库存状态和校准状态。 #### 验收标准 -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 冲突错误码 +1. WHEN 前端请求 GET `/api/admin/board-cards` 时,THE API_Server SHALL 返回分页的板卡实例列表,支持按板卡类型(全部/主协板/采集板/发射板/升压板)、板卡状态(全部/在库/已装配/故障/报废)、校准状态(全部/合格/不合格/待校准)筛选,支持按板卡SN或设备SN搜索,每条记录包含 sn、type、version、firmware、status、deviceSn、productionDate、calibStatus、calibDate 字段 +2. WHEN 前端请求 GET `/api/admin/board-cards/{id}` 时,THE API_Server SHALL 返回板卡实例详情,包含基本信息(SN、类型、版本、固件版本、生产日期、状态)、装配信息(所属设备SN)、校准信息(校准状态、校准日期,仅采集板显示) +3. WHEN 前端请求 GET `/api/admin/board-cards/stats` 时,THE API_Server SHALL 返回板卡统计数据,包含板卡总数、在库数量、已装配数量、故障数量、待校准数量 +4. IF 请求参数中的板卡SN号已存在,THEN THE API_Server SHALL 返回 409 冲突错误码和描述性错误消息 -### 需求 7:校准管理 API +### 需求 7:板卡登记 API -**用户故事:** 作为前端开发者,我希望通过 API 管理采集板校准数据,以便替换校准管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 登记新板卡,以便替换板卡登记页面(`/calibration/register`)的模拟数据。该页面支持单个和批量登记板卡。 #### 验收标准 -1. WHEN 前端请求 GET `/api/admin/calibrations` 时,THE API_Server SHALL 返回分页的校准记录列表,支持按采集板SN号、校准状态、校准人员筛选 +1. WHEN 前端请求 POST `/api/admin/board-cards` 时,THE API_Server SHALL 创建单个板卡实例记录,包含板卡类型、版本号、固件版本(根据版本自动填充)、板卡SN号、生产日期、备注 +2. WHEN 前端请求 POST `/api/admin/board-cards/batch` 时,THE API_Server SHALL 支持批量创建多个板卡实例记录,请求体为板卡信息数组 +3. WHEN 创建采集板类型的板卡时,THE API_Server SHALL 自动将校准状态设置为"待校准" +4. WHEN 创建非采集板类型的板卡时,THE API_Server SHALL 自动将板卡状态设置为"在库",校准状态设置为无需校准 +5. THE API_Server SHALL 提供 GET `/api/admin/board-types/{type}/versions` 接口,返回指定板卡类型的可选版本列表及对应固件版本,用于板卡登记表单的版本下拉选择 + +### 需求 8:固件库管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理固件版本数据,以便替换固件库页面(`/firmware`)的模拟数据。 + +#### 验收标准 + +1. WHEN 前端请求 GET `/api/admin/firmware` 时,THE API_Server SHALL 返回固件版本列表,支持按固件类型(全部/主协板/采集板/发射板/升压板/主机固件/计算单元固件)Tab筛选 +2. WHEN 前端请求 GET `/api/admin/firmware?boardVersion={version}` 时,THE API_Server SHALL 返回指定板卡版本的固件列表,用于从板卡版本管理页面(`/boards`)通过 `?board=` 参数跳转后的筛选展示 +3. WHEN 前端请求 POST `/api/admin/firmware` 时,THE API_Server SHALL 创建新固件版本记录,包含版本号、硬件版本范围、升级类型[可选/强制]、签名状态、发布说明(多行文本,每行一条) +4. WHEN 前端请求 GET `/api/admin/firmware/{id}/download` 时,THE API_Server SHALL 返回固件文件的下载流,并将该固件的下载次数加 1 +5. IF 上传的固件版本号与同一板卡版本的已有版本重复,THEN THE API_Server SHALL 返回 409 冲突错误码 + +### 需求 9:校准记录管理 API + +**用户故事:** 作为前端开发者,我希望通过 API 管理采集板校准数据。校准功能是板卡实例管理的一部分,校准状态直接关联到板卡实例(`dev.board_cards`)的 calibStatus 和 calibDate 字段。 + +#### 验收标准 + +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 支持批量导入校准记录数据 +3. WHEN 前端请求 POST `/api/admin/calibrations/import` 时,THE API_Server SHALL 支持批量导入校准记录数据,并同步更新对应板卡实例(`dev.board_cards`)的校准状态和校准日期 -### 需求 8:配置文件管理 API +### 需求 10:配置文件管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理设备配置文件,以便替换配置文件管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理设备配置文件,以便替换配置文件管理页面(`/config-files`)的模拟数据。 #### 验收标准 -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 创建新配置文件记录 +1. WHEN 前端请求 GET `/api/admin/config-files` 时,THE API_Server SHALL 返回分页的配置文件列表,支持按适配型号、版本、关键字筛选,每条记录包含 name、model、version、createTime、status 字段 +2. WHEN 前端请求 GET `/api/admin/config-files/{id}` 时,THE API_Server SHALL 返回配置文件详情,包含基本信息(名称、型号、版本、状态、创建时间)、发射参数(电压、电流、占空比、脉宽范围、迭代次数)、采集参数(通道数、采样率、电压量程、全波形采集)、网络参数(WiFi SSID前缀) +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 +### 需求 11:授权管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理设备授权数据,以便替换授权管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理设备授权数据,以便替换授权管理页面(`/licenses`)的模拟数据。 #### 验收标准 -1. WHEN 前端请求 GET `/api/admin/licenses` 时,THE API_Server SHALL 返回分页的授权列表,支持按设备型号和状态筛选 -2. WHEN 前端请求 POST `/api/admin/licenses` 时,THE API_Server SHALL 创建新授权记录,包含设备型号、授权模块列表、授权期限 +1. WHEN 前端请求 GET `/api/admin/licenses` 时,THE API_Server SHALL 返回分页的授权列表,支持按设备型号(GD-10/GD-20/GD-30)和状态(生效/草稿/已停用)筛选,每条记录包含 model、modules、expiry、date、status 字段 +2. WHEN 前端请求 POST `/api/admin/licenses` 时,THE API_Server SHALL 创建新授权记录,包含设备型号、授权模块列表(从11项模块中勾选)、授权期限[1年/2年/3年/永久/自定义]、自定义到期日期 3. WHEN 前端请求 PUT `/api/admin/licenses/{id}` 时,THE API_Server SHALL 更新指定授权记录 4. WHEN 前端请求 PUT `/api/admin/licenses/{id}/disable` 时,THE API_Server SHALL 将指定授权记录状态设置为已停用 +5. THE API_Server SHALL 提供 GET `/api/admin/licenses/auth-items` 接口,返回全部11项授权模块定义(ID、名称、描述) +6. THE API_Server SHALL 提供 GET `/api/admin/licenses/model-presets` 接口,返回各型号的默认授权模块预选配置(GD-10: 6项, GD-20: 9项, GD-30: 全部11项) -### 需求 10:维修工单管理 API +### 需求 12:维修工单管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理维修工单数据,以便替换维修工单页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理维修工单数据,以便替换维修工单页面(`/repair`)的模拟数据。 #### 验收标准 -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 更新工单处理信息(处理操作、板卡更换、授权处理、处理备注) +1. WHEN 前端请求 GET `/api/admin/repair-orders` 时,THE API_Server SHALL 返回分页的维修工单列表,支持按状态[全部/待处理/处理中/已处理]、优先级[全部/高/中/低]、负责人、日期范围(开始~结束)、设备SN筛选,每条记录包含 id、sn、faultType、status、priority、assignee、createDate、description 字段 +2. WHEN 前端请求 GET `/api/admin/repair-orders/{id}` 时,THE API_Server SHALL 返回工单详情,包含: + - 工单信息(工单号、状态、优先级、创建时间、负责人、预计修复时间) + - 设备信息(设备SN、型号、类型、固件版本、位置,通过 deviceInfoMap 查询) + - 故障信息(故障类型、描述、现象) + - 处理记录时间线(日期、操作人、操作类型、备注) + - 板卡更换记录(原板卡SN、新板卡SN、板卡类型、板卡型号、更换日期、操作人) +3. WHEN 前端请求 POST `/api/admin/repair-orders` 时,THE API_Server SHALL 创建新维修工单,包含设备SN(从设备列表选择)、故障类型(单选6种)、故障描述、故障现象、优先级(低/中/高)、维修人员、预计修复时间、备注 +4. WHEN 前端请求 PUT `/api/admin/repair-orders/{id}/process` 时,THE API_Server SHALL 更新工单处理信息,包含处理操作类型[更换板卡/固件修复/参数重置/其他处理]、板卡更换信息(板卡类型、板卡型号、原SN、新SN)、授权处理(重新生成授权文件/推送适配固件)、处理备注 5. WHEN 前端请求 PUT `/api/admin/repair-orders/{id}/close` 时,THE API_Server SHALL 关闭工单并将状态设置为已处理 +6. THE API_Server SHALL 提供 GET `/api/admin/repair-orders/device-info/{sn}` 接口,返回指定设备SN的设备信息(型号、类型、固件版本、位置),用于新建工单时选择设备后展示设备信息 -### 需求 11:报废管理 API +### 需求 13:报废管理 API -**用户故事:** 作为前端开发者,我希望通过 API 管理报废审批和物料回收数据,以便替换报废管理页面的模拟数据。 +**用户故事:** 作为前端开发者,我希望通过 API 管理报废审批和物料回收数据,以便替换报废管理页面(`/scrap`)的模拟数据。 #### 验收标准 -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 审批通过报废申请,更新状态为已审批 +1. WHEN 前端请求 GET `/api/admin/scrap-records` 时,THE API_Server SHALL 返回分页的报废记录列表,支持按设备SN、状态[全部/待审批/审批中/已审批/已驳回/回收中/已回收]、日期筛选,每条记录包含 sn、model、reason、applicant、status、orderId、date、value、materials 字段 +2. WHEN 前端请求 GET `/api/admin/scrap-records/{id}` 时,THE API_Server SHALL 返回报废详情,包含设备信息(SN、型号、报废日期、报废原因、残值评估)、审批信息(申请人、状态、来源工单号)、可回收物料标签列表、审批记录时间线(日期、操作类型、操作人、备注) +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 完成物料回收入库,更新状态为已回收,记录回收的物料清单 +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 +### 需求 14:首页 Dashboard 统计 API -**用户故事:** 作为前端开发者,我希望通过 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 返回待处理任务列表,包含校准即将到期、维修工单、固件升级通知、授权即将到期四个分组 +1. WHEN 前端请求 GET `/api/admin/dashboard/metrics` 时,THE API_Server SHALL 返回8项统计指标:设备总数、装配中数量、已激活数量、有新版本数量、维修中数量、报废数量、授权即将到期数量、可升级数量,每项指标包含当前值和趋势变化百分比 +2. WHEN 前端请求 GET `/api/admin/dashboard/device-status` 时,THE API_Server SHALL 返回设备状态分布数据,包含已装配、已出厂、已激活、报废四种状态的数量,用于横向条形图展示 +3. WHEN 前端请求 GET `/api/admin/dashboard/tasks` 时,THE API_Server SHALL 返回待处理任务列表,包含4个分组(校准即将到期、维修工单、固件升级通知、授权即将到期),每组包含总数和最近2条任务详情(设备SN/名称、描述、时间、跳转链接) -### 需求 13:前端 API 集成层 +### 需求 15:前端 API 集成层 **用户故事:** 作为前端开发者,我希望在前端建立统一的 API 调用层,以便各页面组件可以方便地调用后端接口替换模拟数据。 #### 验收标准 -1. THE Frontend SHALL 创建统一的 API 客户端模块,配置后端 API 基础 URL、请求超时时间(5秒)、统一错误处理 -2. THE Frontend SHALL 为每个业务模块创建独立的 API 服务文件(如 deviceApi.ts、boardApi.ts、firmwareApi.ts 等) +1. THE Frontend SHALL 创建统一的 API 客户端模块(`src/lib/api/client.ts`),配置后端 API 基础 URL(通过 NEXT_PUBLIC_API_URL 环境变量)、请求超时时间(5秒)、统一错误处理 +2. THE Frontend SHALL 为每个业务模块创建独立的 API 服务文件:deviceApi.ts、modelApi.ts、boardTypeApi.ts、boardCardApi.ts、firmwareApi.ts、calibrationApi.ts、configFileApi.ts、licenseApi.ts、repairApi.ts、scrapApi.ts、dashboardApi.ts 3. WHEN API 请求失败时,THE Frontend SHALL 在页面上展示友好的错误提示信息 4. WHEN API 请求正在进行时,THE Frontend SHALL 展示加载状态指示器 5. THE Frontend SHALL 将各页面组件中的硬编码模拟数据替换为 API 调用,使用 React 的 useState 和 useEffect 管理数据获取状态 -### 需求 14:分页与筛选标准化 +### 需求 16:分页与筛选标准化 **用户故事:** 作为前端开发者,我希望前后端采用统一的分页和筛选参数规范,以便各列表页面的数据交互保持一致。 @@ -188,3 +250,20 @@ 2. THE API_Server SHALL 对所有列表接口返回统一的分页响应格式,包含 total(总记录数)、page(当前页码)、pageSize(每页条数)、records(数据列表) 3. THE API_Server SHALL 对所有筛选参数进行服务端校验,无效参数返回 400 错误码和描述性错误消息 4. WHEN 筛选条件为空或为"全部"时,THE API_Server SHALL 返回不带该条件过滤的完整数据集 + +### 需求 17:采集板校准文件管理 + +**用户故事:** 作为上位机(校准设备PC端软件)操作员,我希望在完成采集板校准后将校准文件上传到设备管理平台,并通过采集板SN号自动匹配关联,以便前端板卡详情中可以查看和下载校准文件。 + +#### 验收标准 + +1. THE Database SHALL 包含 `dev.calibration_files` 表,存储校准文件信息(采集板SN号、原始文件名、存储路径、文件大小(字节)、文件MD5校验值、上传时间),并包含标准审计字段 +2. WHEN 上位机请求 POST `/api/portal/calibration-files/upload` 并携带采集板SN号和校准文件时,THE API_Server SHALL 保存校准文件到服务器存储目录,并在 `dev.calibration_files` 表中创建关联记录 +3. WHEN 上传校准文件时,THE API_Server SHALL 通过采集板SN号匹配 `dev.board_cards` 表中的板卡实例记录,将校准文件与对应的采集板关联 +4. IF 上传请求中的采集板SN号在 `dev.board_cards` 表中不存在,THEN THE API_Server SHALL 返回 404 错误码和描述性错误消息 +5. THE API_Server SHALL 将校准文件上传接口注册在 `api-portal` 模块中,该接口无需登录鉴权 +6. WHEN 前端请求 GET `/api/admin/board-cards/{id}/calibration-files` 时,THE API_Server SHALL 返回指定采集板关联的校准文件列表,每条记录包含 id、fileName(原始文件名)、fileSize(文件大小)、md5(校验值)、uploadTime(上传时间)字段,按上传时间倒序排列 +7. WHEN 前端请求 GET `/api/admin/calibration-files/{id}/download` 时,THE API_Server SHALL 返回校准文件的下载流,响应头包含正确的文件名和 Content-Type +8. IF 请求下载的校准文件在存储目录中不存在,THEN THE API_Server SHALL 返回 404 错误码和描述性错误消息 +9. THE Frontend SHALL 在板卡详情抽屉的校准信息区域中展示关联的校准文件列表,每条记录显示文件名、文件大小、上传时间,并提供下载按钮 +10. WHEN 同一采集板SN号多次上传校准文件时,THE API_Server SHALL 保留所有历史校准文件记录,支持查看完整的校准文件历史 diff --git a/.next/dev/build-manifest.json b/.next/dev/build-manifest.json index 546e56f..7d41cf9 100644 --- a/.next/dev/build-manifest.json +++ b/.next/dev/build-manifest.json @@ -1,30 +1,6 @@ { "pages": { - "/_app": [ - "static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js", - "static/chunks/node_modules_next_dist_shared_lib_0~pg0mt._.js", - "static/chunks/node_modules_next_dist_client_0pe1dg-._.js", - "static/chunks/node_modules_next_dist_0u_w_5s._.js", - "static/chunks/node_modules_next_app_0jt-zj..js", - "static/chunks/[next]_entry_page-loader_ts_0j~flwh._.js", - "static/chunks/node_modules_react-dom_0bruynb._.js", - "static/chunks/node_modules_0lx093h._.js", - "static/chunks/[root-of-the-server]__0c0okpg._.js", - "static/chunks/pages__app_07xvfw~._.js", - "static/chunks/turbopack-pages__app_0_wu8vy._.js" - ], - "/_error": [ - "static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js", - "static/chunks/node_modules_next_dist_shared_lib_12bi_n7._.js", - "static/chunks/node_modules_next_dist_client_0pe1dg-._.js", - "static/chunks/node_modules_next_dist_0rt-2cr._.js", - "static/chunks/[next]_entry_page-loader_ts_0rqw6yo._.js", - "static/chunks/node_modules_react-dom_0bruynb._.js", - "static/chunks/node_modules_0lx093h._.js", - "static/chunks/[root-of-the-server]__01mw43t._.js", - "static/chunks/pages__error_07xvfw~._.js", - "static/chunks/turbopack-pages__error_016chbq._.js" - ] + "/_app": [] }, "devFiles": [], "polyfillFiles": [ diff --git a/.next/dev/fallback-build-manifest.json b/.next/dev/fallback-build-manifest.json index 41883af..087bbcf 100644 --- a/.next/dev/fallback-build-manifest.json +++ b/.next/dev/fallback-build-manifest.json @@ -1,30 +1,6 @@ { "pages": { - "/_app": [ - "static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js", - "static/chunks/node_modules_next_dist_shared_lib_0~pg0mt._.js", - "static/chunks/node_modules_next_dist_client_0pe1dg-._.js", - "static/chunks/node_modules_next_dist_0u_w_5s._.js", - "static/chunks/node_modules_next_app_0jt-zj..js", - "static/chunks/[next]_entry_page-loader_ts_0j~flwh._.js", - "static/chunks/node_modules_react-dom_0bruynb._.js", - "static/chunks/node_modules_0lx093h._.js", - "static/chunks/[root-of-the-server]__0c0okpg._.js", - "static/chunks/pages__app_07xvfw~._.js", - "static/chunks/turbopack-pages__app_0_wu8vy._.js" - ], - "/_error": [ - "static/chunks/node_modules_next_dist_compiled_0o6l_m6._.js", - "static/chunks/node_modules_next_dist_shared_lib_12bi_n7._.js", - "static/chunks/node_modules_next_dist_client_0pe1dg-._.js", - "static/chunks/node_modules_next_dist_0rt-2cr._.js", - "static/chunks/[next]_entry_page-loader_ts_0rqw6yo._.js", - "static/chunks/node_modules_react-dom_0bruynb._.js", - "static/chunks/node_modules_0lx093h._.js", - "static/chunks/[root-of-the-server]__01mw43t._.js", - "static/chunks/pages__error_07xvfw~._.js", - "static/chunks/turbopack-pages__error_016chbq._.js" - ] + "/_app": [] }, "devFiles": [], "polyfillFiles": [], diff --git a/.next/dev/logs/next-development.log b/.next/dev/logs/next-development.log index a1364c5..e7f4e0f 100644 --- a/.next/dev/logs/next-development.log +++ b/.next/dev/logs/next-development.log @@ -1,233 +1,47 @@ -{"timestamp":"00:00:01.061","source":"Server","level":"LOG","message":""} -{"timestamp":"00:00:03.305","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} -{"timestamp":"19:08:22.325","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} -{"timestamp":"19:08:25.916","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"} -{"timestamp":"117:32:37.131","source":"Server","level":"LOG","message":"✓ Compiled in 153ms"} -{"timestamp":"117:32:38.097","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:38.544","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:38.547","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload when ./src/app/devices/page.tsx changed. Read more: https://nextjs.org/docs/messages/fast-refresh-reload"} -{"timestamp":"117:32:38.940","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:39.220","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:40.255","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:40.535","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:40.837","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:41.104","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:41.590","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:41.994","source":"Server","level":"ERROR","message":"⨯ Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:42.214","source":"Browser","level":"ERROR","message":"uncaughtError: Error: The default export is not a React Component in \"/devices/page\""} -{"timestamp":"117:32:42.258","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught Error: The default export is not a React Component in \\\"/devices/page\\\"\\u001b[39m\""} -{"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 | - + 登记板卡
@@ -139,7 +177,7 @@ export default function BoardCardsPage() { - {['板卡SN', '类型', '型号', '固件', '状态', '所属设备', '校准状态', '操作'].map(h => ( + {['板卡SN', '类型', '版本', '固件', '状态', '所属设备', '校准状态', '操作'].map(h => ( ))} @@ -149,7 +187,7 @@ export default function BoardCardsPage() { - + ))} @@ -203,7 +248,7 @@ export default function BoardCardsPage() {
板卡SN:{detailDrawer.sn}
板卡类型:{detailDrawer.type}
-
型号:{detailDrawer.model}
+
版本:{detailDrawer.version}
固件版本:{detailDrawer.firmware}
生产日期:{detailDrawer.productionDate}
@@ -250,6 +295,82 @@ export default function BoardCardsPage() {
)} + + {/* Calibration File Drawer */} + {calibFileDrawer && (() => { + const files = calibrationFilesMap[calibFileDrawer.sn] || [] + return ( +
+
setCalibFileDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} /> +
+
+

校准文件 - {calibFileDrawer.sn}

+ +
+
+ {files.length === 0 ? ( +
+ +
暂无校准文件
+
+ ) : ( + files.map(file => ( +
+ {/* 文件信息卡片 */} +
+
+
+ + {file.fileName} +
+ +
+
+
文件大小:{formatFileSize(file.fileSize)}
+
MD5:{file.md5}
+
上传时间:{file.uploadTime}
+
+
+ + {/* 校准数据内容 */} +
+
校准数据
+
{h}
{row.sn} {row.type}{row.model}{row.version} {row.firmware} {row.status} @@ -163,9 +201,16 @@ export default function BoardCardsPage() { )} - +
+ + {row.type === '采集板' && ( + + )} +
+ + + {['通道', '参考值', '测量值', '偏差(%)', '结果'].map(h => ( + + ))} + + + + {file.channels.map(ch => ( + + + + + + + + ))} + +
{h}
{ch.channel}{ch.refValue.toFixed(1)}{ch.measuredValue.toFixed(1)} 1 ? '#FF4D4F' : 'rgba(0,0,0,0.65)' }}>{ch.deviation > 0 ? '+' : ''}{ch.deviation.toFixed(2)} + {ch.result} +
+ + + )) + )} + +
+ +
+ + + ) + })()} ) } diff --git a/src/app/calibration/register/page.tsx b/src/app/board-cards/register/page.tsx similarity index 65% rename from src/app/calibration/register/page.tsx rename to src/app/board-cards/register/page.tsx index ef75f34..d8f5371 100644 --- a/src/app/calibration/register/page.tsx +++ b/src/app/board-cards/register/page.tsx @@ -1,38 +1,40 @@ 'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' -import { ArrowLeft, Plus, Trash2, Upload, Info, CheckCircle } from 'lucide-react' +import { useRef } from 'react' +import { ArrowLeft, Plus, Trash2, Upload, Info, CheckCircle, FileText, X } from 'lucide-react' -/** 板卡类型 -> 可选型号 */ -const modelsByType: Record = { +/** 板卡类型 -> 可选版本 */ +const versionsByType: Record = { '主协板': [ - { model: 'MB-V1.2', firmware: 'v2.1' }, - { model: 'MB-V2.1', firmware: 'v1.8' }, + { version: 'MB-V1.2', firmware: 'v2.1' }, + { version: 'MB-V2.1', firmware: 'v1.8' }, ], '采集板': [ - { model: 'RX-V1.3', firmware: 'v3.0' }, - { model: 'RX-V2.1', firmware: 'v2.5' }, + { version: 'RX-V1.3', firmware: 'v3.0' }, + { version: 'RX-V2.1', firmware: 'v2.5' }, ], '发射板': [ - { model: 'TX-V1.5', firmware: 'v1.2' }, - { model: 'TX-V2.1', firmware: 'v1.0' }, + { version: 'TX-V1.5', firmware: 'v1.2' }, + { version: 'TX-V2.1', firmware: 'v1.0' }, ], '升压板': [ - { model: 'BO-V2.1', firmware: 'v1.1' }, - { model: 'BO-V2.2', firmware: 'v0.9' }, + { version: 'BO-V2.1', firmware: 'v1.1' }, + { version: 'BO-V2.2', firmware: 'v0.9' }, ], } -const typeOptions = Object.keys(modelsByType) +const typeOptions = Object.keys(versionsByType) interface BoardEntry { id: number type: string - model: string + version: string firmware: string sn: string productionDate: string remark: string + calibFile: File | null } let nextId = 1 @@ -43,7 +45,7 @@ export default function BoardRegisterPage() { const [batchMode, setBatchMode] = useState(false) function createEntry(): BoardEntry { - return { id: nextId++, type: '采集板', model: 'RX-V2.1', firmware: 'v2.1', sn: '', productionDate: '', remark: '' } + return { id: nextId++, type: '采集板', version: 'RX-V2.1', firmware: 'v2.1', sn: '', productionDate: '', remark: '', calibFile: null } } const addEntry = () => { @@ -59,24 +61,38 @@ export default function BoardRegisterPage() { 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 + const versions = versionsByType[value] + if (versions && versions.length > 0) { + updated.version = versions[0].version + updated.firmware = versions[0].firmware } + // 切换到非采集板时清除校准文件 + if (value !== '采集板') updated.calibFile = null } - // 切换型号时自动填充固件 - if (field === 'model') { - const models = modelsByType[updated.type] - const match = models?.find(m => m.model === value) + // 切换版本时自动填充固件 + if (field === 'version') { + const versions = versionsByType[updated.type] + const match = versions?.find(m => m.version === value) if (match) updated.firmware = match.firmware } return updated })) } + const fileInputRefs = useRef>({}) + + const handleCalibFileChange = (entryId: number, file: File | null) => { + setEntries(prev => prev.map(e => e.id === entryId ? { ...e, calibFile: file } : e)) + } + + const formatFileSize = (bytes: number): string => { + if (bytes < 1024) return bytes + ' B' + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB' + return (bytes / (1024 * 1024)).toFixed(1) + ' MB' + } + const isValid = entries.every(e => e.sn.trim() && e.productionDate) return ( @@ -84,7 +100,7 @@ export default function BoardRegisterPage() {
{/* Header */}
-
@@ -97,7 +113,7 @@ export default function BoardRegisterPage() {
- 板卡SN号为唯一标识,请确保录入正确。采集板登记后需要进行校准才能用于装配。选择型号后固件版本会自动填充。 + 板卡SN号为唯一标识,请确保录入正确。采集板登记后需要进行校准才能用于装配。选择版本后固件版本会自动填充。
@@ -136,11 +152,11 @@ export default function BoardRegisterPage() {
- {/* 板卡型号 */} + {/* 板卡版本 */}
- - updateEntry(entry.id, 'version', e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}> + {(versionsByType[entry.type] || []).map(m => )}
@@ -153,7 +169,7 @@ export default function BoardRegisterPage() { {/* 板卡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, 'sn', e.target.value)} placeholder={`如 ${entry.version}-20250401001`} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
{/* 生产日期 */} @@ -176,6 +192,38 @@ export default function BoardRegisterPage() { 采集板登记后状态为"待校准",需完成校准后才能用于设备装配。
)} + + {/* 采集板校准文件导入 */} + {entry.type === '采集板' && ( +
+ + { fileInputRefs.current[entry.id] = el }} + onChange={e => handleCalibFileChange(entry.id, e.target.files?.[0] || null)} + style={{ display: 'none' }} + /> + {entry.calibFile ? ( +
+
+ +
+
{entry.calibFile.name}
+
{formatFileSize(entry.calibFile.size)}
+
+
+ +
+ ) : ( + + )} +
+ )}
))} @@ -193,7 +241,7 @@ export default function BoardRegisterPage() { - {['序号', '类型', '型号', '固件', 'SN号', '生产日期', '状态'].map(h => ( + {['序号', '类型', '版本', '固件', 'SN号', '生产日期', '校准文件', '状态'].map(h => ( ))} @@ -203,10 +251,23 @@ export default function BoardRegisterPage() { - + +
{h}
{i + 1} {entry.type}{entry.model}{entry.version} {entry.firmware} {entry.sn || '未填写'} {entry.productionDate || '未填写'} + {entry.type === '采集板' ? ( + entry.calibFile ? ( + + {entry.calibFile.name} + + ) : ( + 未导入 + ) + ) : ( + - + )} + {entry.type === '采集板' ? '待校准' : '在库'} @@ -225,11 +286,12 @@ export default function BoardRegisterPage() { 共 {entries.length} 条板卡待登记 {entries.some(e => e.type === '采集板') && · 含采集板需后续校准} + {entries.some(e => e.type === '采集板' && e.calibFile) && · {entries.filter(e => e.calibFile).length} 个已附校准文件}
- +