优化体验,修复 bug

This commit is contained in:
eafonyang
2026-07-09 10:33:19 +08:00
parent e2577ff661
commit b4926ba148
21 changed files with 1196 additions and 260 deletions
@@ -3,19 +3,34 @@
<cite>
**本文档引用的文件**
- [frontend/src/views/model/index.vue](file://frontend/src/views/model/index.vue)
- [frontend/src/views/model/composables/useModelList.js](file://frontend/src/views/model/composables/useModelList.js)
- [frontend/src/views/model/components/ModelFormDialog.vue](file://frontend/src/views/model/components/ModelFormDialog.vue)
- [frontend/src/views/model/components/CsvViewerDialog.vue](file://frontend/src/views/model/components/CsvViewerDialog.vue)
- [frontend/src/views/model/components/EqCacheDialog.vue](file://frontend/src/views/model/components/EqCacheDialog.vue)
- [frontend/src/views/model/components/PushProgressDialog.vue](file://frontend/src/views/model/components/PushProgressDialog.vue)
- [frontend/src/views/model/components/PushViewDialog.vue](file://frontend/src/views/model/components/PushViewDialog.vue)
- [frontend/src/api/model.js](file://frontend/src/api/model.js)
- [backend/src/routes/models.js](file://backend/src/routes/models.js)
- [backend/src/models/Model.js](file://backend/src/models/Model.js)
- [backend/src/validators/model.js](file://backend/src/validators/model.js)
- [frontend/src/api/model.js](file://frontend/src/api/model.js)
- [frontend/src/api/brand.js](file://frontend/src/api/brand.js)
- [backend/src/models/Brand.js](file://backend/src/models/Brand.js)
- [backend/src/routes/brands.js](file://backend/src/routes/brands.js)
- [backend/src/services/measurementStorage.js](file://backend/src/services/measurementStorage.js)
- [backend/src/services/eqCacheStorage.js](file://backend/src/services/eqCacheStorage.js)
- [backend/src/services/squiglink.js](file://backend/src/services/squiglink.js)
- [frontend/src/utils/request.js](file://frontend/src/utils/request.js)
- [frontend/src/router/index.js](file://frontend/src/router/index.js)
</cite>
## 更新摘要
**变更内容**
- 新增useModelList组合式函数,提供统一的数据管理与状态控制
- 新增5个专用对话框组件,增强用户体验和功能完整性
- 新增squig.link外部数据导入功能,支持从第三方网站抓取频响数据
- 增强型号表单的复杂处理能力,支持文件上传、品牌自动创建等功能
- 完善批量操作的进度跟踪与错误处理机制
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
@@ -29,25 +44,35 @@
10. [附录](#附录)
## 简介
本文件面向“型号管理页面”的技术实现与使用说明,覆盖以下关键能力:
本文件面向"型号管理页面"的技术实现与使用说明,覆盖以下关键能力:
- 型号列表展示、搜索过滤、排序与分页
- 型号详情查看与参数配置
- 型号表单的数据结构、字段验证与复杂表单处理(含频响文件上传)
- 型号与品牌的关联关系、级联数据管理与外键约束处理
- 型号参数的动态配置、数据格式验证与存储优化
- 批量操作(复制、推送搜索)的实现方案
- **新增**:squig.link外部数据导入功能
- **新增**:组合式函数useModelList的状态管理
- **新增**:5个专用对话框组件的完整功能
## 项目结构
型号管理页面位于前端单页应用中,后端提供 REST 接口与数据模型支撑,前端通过 API 模块与后端交互。
型号管理页面位于前端单页应用中,后端提供 REST 接口与数据模型支撑,前端通过 API 模块与后端交互。系统现已包含完整的对话框组件体系和组合式函数支持。
```mermaid
graph TB
subgraph "前端"
FE_Model["型号视图<br/>frontend/src/views/model/index.vue"]
FE_useModelList["组合式函数<br/>useModelList.js"]
FE_API_Model["型号 API<br/>frontend/src/api/model.js"]
FE_API_Brand["品牌 API<br/>frontend/src/api/brand.js"]
FE_Request["请求封装<br/>frontend/src/utils/request.js"]
FE_Router["路由<br/>frontend/src/router/index.js"]
FE_Components["对话框组件<br/>components/"]
FE_ModelFormDialog["ModelFormDialog.vue"]
FE_CsvViewerDialog["CsvViewerDialog.vue"]
FE_EqCacheDialog["EqCacheDialog.vue"]
FE_PushProgressDialog["PushProgressDialog.vue"]
FE_PushViewDialog["PushViewDialog.vue"]
end
subgraph "后端"
BE_Routes_Models["型号路由<br/>backend/src/routes/models.js"]
@@ -57,107 +82,213 @@ BE_Routes_Brands["品牌路由<br/>backend/src/routes/brands.js"]
BE_Models_Brand["品牌模型<br/>backend/src/models/Brand.js"]
BE_Services_Measure["频响存储服务<br/>backend/src/services/measurementStorage.js"]
BE_Services_EQ["EQ 缓存服务<br/>backend/src/services/eqCacheStorage.js"]
BE_Services_Squiglink["squig.link 服务<br/>backend/src/services/squiglink.js"]
end
FE_Model --> FE_useModelList
FE_Model --> FE_API_Model
FE_Model --> FE_API_Brand
FE_useModelList --> FE_API_Model
FE_useModelList --> FE_API_Brand
FE_API_Model --> FE_Request
FE_API_Brand --> FE_Request
FE_Router --> FE_Model
FE_Model --> FE_Components
FE_Components --> FE_ModelFormDialog
FE_Components --> FE_CsvViewerDialog
FE_Components --> FE_EqCacheDialog
FE_Components --> FE_PushProgressDialog
FE_Components --> FE_PushViewDialog
FE_API_Model --> BE_Routes_Models
FE_API_Brand --> BE_Routes_Brands
BE_Routes_Models --> BE_Models_Model
BE_Routes_Models --> BE_Validators_Model
BE_Routes_Models --> BE_Services_Measure
BE_Routes_Models --> BE_Services_EQ
BE_Routes_Models --> BE_Services_Squiglink
BE_Routes_Brands --> BE_Models_Brand
```
图表来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/brand.js:1-66](file://frontend/src/api/brand.js#L1-L66)
- [frontend/src/utils/request.js:1-72](file://frontend/src/utils/request.js#L1-L72)
- [frontend/src/router/index.js:1-91](file://frontend/src/router/index.js#L1-L91)
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/validators/model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [backend/src/routes/brands.js:1-147](file://backend/src/routes/brands.js#L1-L147)
- [backend/src/models/Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
**图表来源**
- [frontend/src/views/model/index.vue:1-533](file://frontend/src/views/model/index.vue#L1-L533)
- [frontend/src/views/model/composables/useModelList.js:1-191](file://frontend/src/views/model/composables/useModelList.js#L1-L191)
- [frontend/src/views/model/components/ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
- [frontend/src/views/model/components/CsvViewerDialog.vue:1-50](file://frontend/src/views/model/components/CsvViewerDialog.vue#L1-L50)
- [frontend/src/views/model/components/EqCacheDialog.vue:1-306](file://frontend/src/views/model/components/EqCacheDialog.vue#L1-L306)
- [frontend/src/views/model/components/PushProgressDialog.vue:1-303](file://frontend/src/views/model/components/PushProgressDialog.vue#L1-L303)
- [frontend/src/views/model/components/PushViewDialog.vue:1-60](file://frontend/src/views/model/components/PushViewDialog.vue#L1-L60)
章节来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
**章节来源**
- [frontend/src/views/model/index.vue:1-533](file://frontend/src/views/model/index.vue#L1-L533)
- [frontend/src/views/model/composables/useModelList.js:1-191](file://frontend/src/views/model/composables/useModelList.js#L1-L191)
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
## 核心组件
- 前端视图组件:负责渲染列表、表单、对话框、分页与批量操作,并通过 API 模块与后端交互。
- 前端 API 模块:封装型号与品牌相关的 HTTP 请求,统一处理 Content-Type 与超时策略。
- 后端路由:提供型号与品牌的 CRUD、搜索、排序、分页、推送搜索校验与推送、频响文件与 EQ 缓存查询等接口。
- 数据模型:定义型号与品牌的数据结构、字段类型与约束。
- 服务层:频响文件上传至 S3、EQ 缓存读取 Redis。
- **组合式函数**:useModelList提供统一的数据加载、搜索、排序、分页和状态管理功能
- **主视图组件**:型号管理页面的核心容器,协调各对话框组件和业务逻辑
- **对话框组件族**:
- ModelFormDialog:完整的型号表单对话框,支持文件上传和squig.link导入
- CsvViewerDialog:CSV频响数据查看对话框
- EqCacheDialog:EQ缓存查看对话框,支持JSON树形展示
- PushProgressDialog:推送进度跟踪对话框
- PushViewDialog:推送状态查看对话框
- **API模块**:封装所有型号相关的HTTP请求,包括新增的squig.link导入功能
- **后端路由**:提供完整的REST接口,包括squig.link数据抓取功能
- **服务层**:频响文件上传、EQ缓存读取、squig.link数据抓取等服务
章节来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/brand.js:1-66](file://frontend/src/api/brand.js#L1-L66)
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/models/Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
**章节来源**
- [frontend/src/views/model/composables/useModelList.js:1-191](file://frontend/src/views/model/composables/useModelList.js#L1-L191)
- [frontend/src/views/model/components/ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
- [frontend/src/views/model/components/CsvViewerDialog.vue:1-50](file://frontend/src/views/model/components/CsvViewerDialog.vue#L1-L50)
- [frontend/src/views/model/components/EqCacheDialog.vue:1-306](file://frontend/src/views/model/components/EqCacheDialog.vue#L1-L306)
- [frontend/src/views/model/components/PushProgressDialog.vue:1-303](file://frontend/src/views/model/components/PushProgressDialog.vue#L1-L303)
- [frontend/src/views/model/components/PushViewDialog.vue:1-60](file://frontend/src/views/model/components/PushViewDialog.vue#L1-L60)
- [frontend/src/api/model.js:1-165](file://frontend/src/api/model.js#L1-L165)
## 架构总览
型号管理页面采用前后端分离架构,前端通过 Element Plus 组件与 Axios 进行交互,后端基于 Express 提供 REST 接口,数据持久化使用 Sequelize ORM,外部集成 S3 与 Redis。
型号管理页面采用现代化的Vue 3 Composition API架构,结合Element Plus组件库和Axios进行数据交互。系统支持完整的数据流:从组合式函数的状态管理到对话框组件的用户交互,再到后端服务的业务处理。
```mermaid
sequenceDiagram
participant U as "用户"
participant V as "型号视图<br/>index.vue"
participant A as "API 模块<br/>model.js/brand.js"
participant R as "后端路由<br/>models.js/brands.js"
participant M as "模型<br/>Model.js/Brand.js"
participant S3 as "S3 存储"
participant EQ as "Redis 缓存"
participant CM as "组合式函数<br/>useModelList"
participant MD as "ModelFormDialog"
participant API as "API 模块<br/>model.js"
participant R as "后端路由<br/>models.js"
participant S as "服务层<br/>squiglink.js"
U->>V : 打开型号管理页面
V->>A : 加载品牌列表
A->>R : GET /api/brands/
R->>M : 查询品牌
M-->>R : 品牌数据
R-->>A : 品牌列表
A-->>V : 渲染品牌下拉
U->>V : 搜索/排序/分页
V->>A : 获取型号列表
A->>R : GET /api/models/
R->>M : 分页查询型号
M-->>R : 型号数据
R-->>A : 列表与总数
A-->>V : 渲染表格
U->>V : 新增/编辑型号
V->>A : 提交表单含文件上传
A->>R : POST/PUT /api/models/
R->>S3 : 上传频响文件CSV/TXT/JSON
S3-->>R : S3 Key
R->>M : 保存型号记录
M-->>R : 新建/更新结果
R-->>A : 成功响应
A-->>V : 关闭对话框并刷新列表
V->>CM : 初始化状态管理
CM->>API : 加载品牌列表
API->>R : GET /api/models/
R->>S : 调用squig.link服务
S-->>R : 返回数据
R-->>API : 品牌数据
API-->>CM : 品牌选项
CM-->>V : 渲染界面
U->>MD : 点击"新型号"
MD->>API : 上传文件或导入squig.link
API->>R : POST /api/models/squiglink-fetch
R->>S : 抓取外部数据
S-->>R : 返回CSV内容
R-->>API : 处理结果
API-->>MD : 成功响应
MD-->>V : 关闭对话框并刷新
```
图表来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/brand.js:1-66](file://frontend/src/api/brand.js#L1-L66)
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [backend/src/routes/brands.js:1-147](file://backend/src/routes/brands.js#L1-L147)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/models/Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
**图表来源**
- [frontend/src/views/model/index.vue:227-434](file://frontend/src/views/model/index.vue#L227-L434)
- [frontend/src/views/model/composables/useModelList.js:31-191](file://frontend/src/views/model/composables/useModelList.js#L31-L191)
- [frontend/src/views/model/components/ModelFormDialog.vue:158-464](file://frontend/src/views/model/components/ModelFormDialog.vue#L158-L464)
- [frontend/src/api/model.js:157-165](file://frontend/src/api/model.js#L157-L165)
- [backend/src/routes/models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [backend/src/services/squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
**章节来源**
- [frontend/src/views/model/index.vue:227-434](file://frontend/src/views/model/index.vue#L227-L434)
- [frontend/src/views/model/composables/useModelList.js:31-191](file://frontend/src/views/model/composables/useModelList.js#L31-L191)
## 详细组件分析
### useModelList组合式函数
useModelList是型号管理页面的核心状态管理函数,提供统一的数据操作接口。
**核心功能**
- **数据加载**:统一处理型号列表的加载、搜索、排序和分页
- **状态管理**:维护表格数据、选中行、品牌选项等状态
- **搜索过滤**:支持品牌名称和型号名称的模糊搜索
- **排序控制**:支持按ID和创建时间的升序/降序排序
- **分页处理**:支持不同页面大小的分页控制
- **批量操作**:提供复制功能和格式化工具方法
**状态属性**
- `loading`: 数据加载状态
- `tableData`: 当前表格数据
- `selectedRows`: 选中的行数据
- `selectedIds`: 选中行的ID数组
- `brandOptions`: 品牌下拉选项
- `sortBy`: 排序字段
- `sortOrder`: 排序顺序
- `searchForm`: 搜索表单数据
- `pagination`: 分页配置
**方法功能**
- `loadData()`: 加载型号列表数据
- `loadBrands()`: 加载品牌选项
- `handleSearch()`: 执行搜索
- `handleReset()`: 重置搜索条件
- `handleSortChange()`: 处理排序变更
- `handleSizeChange()`: 处理页面大小变更
- `handlePageChange()`: 处理页码变更
- `handleSelectionChange()`: 处理选择变更
- `handleCopyName()`: 复制单个型号
- `handleCopyBrand()`: 复制品牌名称
- `handleCopySelectedNames()`: 复制选中型号
- `formatDateTime()`: 格式化日期时间
- `formatFormLabel()`: 格式化佩戴方式
**章节来源**
- [frontend/src/views/model/composables/useModelList.js:1-191](file://frontend/src/views/model/composables/useModelList.js#L1-L191)
### ModelFormDialog型号表单对话框
ModelFormDialog是完整的型号创建和编辑对话框,支持复杂的表单处理和外部数据导入。
**核心特性**
- **squig.link导入**:支持从第三方网站抓取频响数据
- **文件上传**:支持CSV、TXT、JSON格式的频响文件上传
- **品牌自动创建**:当品牌不存在时自动创建新品牌
- **文件名智能解析**:根据文件名自动解析品牌和型号
- **表单验证**:完整的字段验证和错误处理
- **多步骤提交**:支持单次提交和连续提交模式
**squig.link导入流程**
1. 用户输入squig.link分享链接
2. 系统抓取phone_book.json获取可用文件列表
3. 自动匹配或让用户选择目标文件
4. 下载TXT频响文件并转换为CSV格式
5. 自动填充表单字段(品牌、型号、佩戴方式等)
**文件上传处理**
- 支持multipart/form-data格式
- 自动检测文件扩展名并验证格式
- TXT文件自动转换为CSV格式
- 上传成功后与型号记录关联
**章节来源**
- [frontend/src/views/model/components/ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
- [backend/src/routes/models.js:307-499](file://backend/src/routes/models.js#L307-L499)
- [backend/src/services/squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
### 对话框组件体系
系统包含5个专用对话框组件,每个组件都有明确的功能边界和使用场景。
**CsvViewerDialog**
- 功能:查看CSV频响数据内容
- 特点:支持S3 Key显示和只读文本区域
- 适用:仅限特定来源的型号查看
**EqCacheDialog**
- 功能:查看EQ缓存的Redis数据
- 特点:支持JSON树形结构展示,可展开查看嵌套数据
- 适用:调试和分析EQ参数配置
**PushProgressDialog**
- 功能:跟踪批量推送的进度
- 特点:显示校验和推送两个阶段的状态,支持错误详情
- 适用:批量型号推送操作
**PushViewDialog**
- 功能:查看推送状态和文档详情
- 特点:简洁的信息展示,支持加载状态
- 适用:查看单个型号的推送结果
**章节来源**
- [frontend/src/views/model/components/CsvViewerDialog.vue:1-50](file://frontend/src/views/model/components/CsvViewerDialog.vue#L1-L50)
- [frontend/src/views/model/components/EqCacheDialog.vue:1-306](file://frontend/src/views/model/components/EqCacheDialog.vue#L1-L306)
- [frontend/src/views/model/components/PushProgressDialog.vue:1-303](file://frontend/src/views/model/components/PushProgressDialog.vue#L1-L303)
- [frontend/src/views/model/components/PushViewDialog.vue:1-60](file://frontend/src/views/model/components/PushViewDialog.vue#L1-L60)
### 型号数据模型与表单结构
- 数据模型字段
- **数据模型字段**
- 主键:自增整型 ID
- 品牌名称:字符串,最大 100 字符,必填
- 型号名称:字符串,最大 100 字符,必填
@@ -166,66 +297,64 @@ A-->>V : 关闭对话框并刷新列表
- 来源:字符串,最大 100 字符,可空
- EQ 键:字符串,最大 255 字符,可空
- 创建时间:日期,默认当前时间
- 表单字段与验证
- **表单字段与验证**
- 品牌名称:必填
- 型号名称:必填,长度 1-100
- 形式:可选枚举值
- 阻抗:可选
- 来源:可选
- 频响文件:可选上传,支持 CSV/TXT/JSON,TXT 自动转换为 CSV
- 复杂表单处理
- **复杂表单处理**
- 文件上传采用 multipart/form-data
- 新增时若品牌不存在,先尝试创建品牌再保存型号
- 文件名解析:优先按品牌接口返回的品牌名进行最长前缀匹配,否则按首个空格拆分
章节来源
**章节来源**
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/validators/model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [frontend/src/views/model/index.vue:578-777](file://frontend/src/views/model/index.vue#L578-L777)
- [backend/src/routes/models.js:306-436](file://backend/src/routes/models.js#L306-L436)
- [frontend/src/views/model/components/ModelFormDialog.vue:196-214](file://frontend/src/views/model/components/ModelFormDialog.vue#L196-L214)
### 品牌关联与外键约束
- 品牌模型
- **品牌模型**
- 主键:自增整型 ID
- 名称:唯一、非空、最大 100 字符
- 型号与品牌的关系
- **型号与品牌的关系**
- 前端通过品牌下拉选择型号所属品牌
- 后端在新增/编辑时检查品牌是否存在,不存在则创建
- 当前模型字段为字符串类型的“品牌名称”,未建立显式的外键约束
- 级联与一致性
- 当前模型字段为字符串类型的"品牌名称",未建立显式的外键约束
- **级联与一致性**
- 若后续迁移为外键关联,建议在数据库层面添加约束,并在删除品牌时处理级联或阻止删除
章节来源
**章节来源**
- [backend/src/models/Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [backend/src/routes/brands.js:1-147](file://backend/src/routes/brands.js#L1-L147)
- [frontend/src/views/model/index.vue:664-693](file://frontend/src/views/model/index.vue#L664-L693)
### 搜索过滤、排序与分页
- 搜索条件
- **搜索条件**
- 品牌名称(模糊匹配)
- 型号名称(模糊匹配)
- 排序
- **排序**
- 支持按 ID 与创建时间排序,升序/降序切换
- 分页
- **分页**
- 页面大小:10/20/50/100
- 当前页变更时重置为第一页并重新加载
- 后端实现
- **后端实现**
- 使用 Sequelize 查询构建 where 条件与 order 排序
- 限制每页最大 1000 条,总数统计用于分页控件
章节来源
- [frontend/src/views/model/index.vue:541-550](file://frontend/src/views/model/index.vue#L541-L550)
- [frontend/src/views/model/index.vue:695-711](file://frontend/src/views/model/index.vue#L695-L711)
**章节来源**
- [frontend/src/views/model/composables/useModelList.js:18-27](file://frontend/src/views/model/composables/useModelList.js#L18-L27)
- [frontend/src/views/model/composables/useModelList.js:87-111](file://frontend/src/views/model/composables/useModelList.js#L87-L111)
- [backend/src/routes/models.js:133-181](file://backend/src/routes/models.js#L133-L181)
### 批量操作与推送搜索
- 批量复制
- 支持复制所选项的“品牌+型号”文本
- 推送搜索
- **批量复制**
- 支持复制所选项的"品牌+型号"文本
- **推送搜索**
- 校验:对选中型号调用曲线校验服务,返回校验失败项
- 推送:将型号数据批量写入 Meilisearch 索引
- 进度弹窗:显示校验与推送步骤状态、当前处理项与错误明细
- 多媒体资源
- **多媒体资源**
- 查看 EQ 缓存:读取 Redis Hash 字段,支持展开查看 JSON 结构
- 查看 CSV:仅支持来源为特定值的型号,按佩戴方式拼接 S3 Key 并读取
@@ -233,157 +362,197 @@ A-->>V : 关闭对话框并刷新列表
sequenceDiagram
participant U as "用户"
participant V as "型号视图"
participant A as "API 模块"
participant PP as "PushProgressDialog"
participant API as "API 模块"
participant R as "后端路由"
participant C as "曲线校验服务"
participant MS as "Meilisearch"
U->>V : 选择多个型号并点击“推送搜索”
V->>A : validatePushToMeilisearch(ids)
A->>R : POST /api/models/push-to-search/validate
U->>V : 选择多个型号并点击"推送搜索"
V->>PP : 打开进度对话框
PP->>API : validatePushToMeilisearch(ids)
API->>R : POST /api/models/push-to-search/validate
R->>C : 校验曲线数据
C-->>R : 校验结果
R-->>A : 校验错误列表或通过
A-->>V : 显示校验结果
U->>V : 确认推送
V->>A : pushToMeilisearch(ids)
A->>R : POST /api/models/push-to-search
R-->>API : 校验错误列表或通过
API-->>PP : 显示校验结果
U->>PP : 确认推送
PP->>API : pushToMeilisearch(ids)
API->>R : POST /api/models/push-to-search
R->>MS : 写入索引
MS-->>R : 任务 UID
R-->>A : 推送结果
A-->>V : 关闭进度弹窗并刷新
R-->>API : 推送结果
API-->>PP : 关闭进度弹窗并刷新
```
图表来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
**图表来源**
- [frontend/src/views/model/index.vue:402-428](file://frontend/src/views/model/index.vue#L402-L428)
- [frontend/src/views/model/components/PushProgressDialog.vue:117-195](file://frontend/src/views/model/components/PushProgressDialog.vue#L117-L195)
- [frontend/src/api/model.js:128-150](file://frontend/src/api/model.js#L128-L150)
- [backend/src/routes/models.js:466-566](file://backend/src/routes/models.js#L466-L566)
章节来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
**章节来源**
- [frontend/src/views/model/index.vue:402-428](file://frontend/src/views/model/index.vue#L402-L428)
- [frontend/src/views/model/components/PushProgressDialog.vue:117-195](file://frontend/src/views/model/components/PushProgressDialog.vue#L117-L195)
- [frontend/src/api/model.js:128-150](file://frontend/src/api/model.js#L128-L150)
- [backend/src/routes/models.js:466-566](file://backend/src/routes/models.js#L466-L566)
### 频响文件上传与存储优化
- 支持格式
- **支持格式**
- CSV、TXT、JSON
- TXT 自动转换为 CSV(保留频率与幅度列)
- 存储路径
- **存储路径**
- S3 Key 规则:autoeq/measurements/{source}/data/{form}/{brandFirstChar}/{brand model}.csv
- 上传成功后将 S3 Key 与型号记录关联
- 读取与展示
- **读取与展示**
- 仅对特定来源与完整字段的型号允许读取 S3 中的 CSV 内容
- 提供弹窗展示 CSV 文本内容
章节来源
**章节来源**
- [backend/src/routes/models.js:59-68](file://backend/src/routes/models.js#L59-L68)
- [backend/src/services/measurementStorage.js:48-108](file://backend/src/services/measurementStorage.js#L48-L108)
- [backend/src/routes/models.js:246-278](file://backend/src/routes/models.js#L246-L278)
- [frontend/src/views/model/components/CsvViewerDialog.vue:1-50](file://frontend/src/views/model/components/CsvViewerDialog.vue#L1-L50)
### EQ 缓存查看与动态配置
- Redis Key 规则:{品牌名称} {型号名称}
- 功能
### EQ缓存查看与动态配置
- **Redis Key 规则**:{品牌名称} {型号名称}
- **功能**
- 列出可用的 Hash Key
- 展示指定 Key 的 JSON 值(带层级与图标)
- 支持加载状态指示与空状态提示
- 适用场景
- **适用场景**
- 动态配置 EQ 参数、调试频响数据结构
章节来源
**章节来源**
- [backend/src/services/eqCacheStorage.js:24-66](file://backend/src/services/eqCacheStorage.js#L24-L66)
- [backend/src/routes/models.js:183-223](file://backend/src/routes/models.js#L183-L223)
- [frontend/src/views/model/index.vue:303-383](file://frontend/src/views/model/index.vue#L303-L383)
- [frontend/src/views/model/components/EqCacheDialog.vue:1-306](file://frontend/src/views/model/components/EqCacheDialog.vue#L1-L306)
### 表单字段解析与文件名智能填充
- 文件名预处理:去除扩展名、合并多余空格
- 品牌解析策略
- **文件名预处理**:去除扩展名、合并多余空格
- **品牌解析策略**
- 优先使用品牌接口返回的品牌名进行最长前缀匹配(支持多词品牌)
- 回退为“第一个空格”左侧为品牌、右侧为型号
- 默认阻抗:新增时若未填写,按规则自动填充
- 回退为"第一个空格"左侧为品牌、右侧为型号
- **默认阻抗**:新增时若未填写,按规则自动填充
章节来源
- [frontend/src/views/model/index.vue:588-637](file://frontend/src/views/model/index.vue#L588-L637)
- [frontend/src/views/model/index.vue:640-655](file://frontend/src/views/model/index.vue#L640-L655)
**章节来源**
- [frontend/src/views/model/components/ModelFormDialog.vue:233-265](file://frontend/src/views/model/components/ModelFormDialog.vue#L233-L265)
### Squig.link外部数据导入
**新增功能**:系统现在支持从squig.link网站导入频响数据。
**工作原理**
- **URL解析**:解析squig.link分享URL,提取基础URL和分享参数
- **清单获取**:获取phone_book.json文件,列出所有可用的测量数据
- **文件匹配**:根据分享参数精确匹配或模糊匹配目标文件
- **数据抓取**:下载TXT频响文件并转换为CSV格式
- **信息提取**:从文件名解析品牌和型号信息
- **自动填充**:自动填充表单字段(品牌、型号、佩戴方式等)
**支持的URL格式**
- `https://theaudiostore.squig.link/?share=Nostalgia_Audio_Camelot`
- `https://squig.link/?share=Moondrop_Chu_2`
**章节来源**
- [frontend/src/api/model.js:157-165](file://frontend/src/api/model.js#L157-L165)
- [backend/src/routes/models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [backend/src/services/squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
## 依赖关系分析
- 前端依赖
- **前端依赖**
- Element Plus UI 组件库
- Axios 请求封装
- Vue Router 路由守卫
- 后端依赖
- Vue 3 Composition API(组合式函数)
- vue-json-pretty JSON展示组件
- **后端依赖**
- Express 路由
- Sequelize ORM
- AWS SDK(S3)
- Redis 客户端
- Meilisearch 客户端
- axios(外部数据抓取)
```mermaid
graph LR
FE_View["前端视图<br/>index.vue"] --> FE_API["前端 API<br/>model.js/brand.js"]
FE_View["前端视图<br/>index.vue"] --> FE_useModelList["组合式函数<br/>useModelList.js"]
FE_useModelList --> FE_API["前端 API<br/>model.js/brand.js"]
FE_API --> FE_Request["请求封装<br/>request.js"]
FE_API --> BE_Routes["后端路由<br/>models.js/brands.js"]
BE_Routes --> BE_Models["数据模型<br/>Model.js/Brand.js"]
BE_Routes --> BE_Services["服务层<br/>measurementStorage.js/eqCacheStorage.js"]
BE_Routes --> BE_Services["服务层<br/>measurementStorage.js/eqCacheStorage.js/squiglink.js"]
BE_Routes --> S3["S3 存储"]
BE_Routes --> Redis["Redis 缓存"]
BE_Routes --> MS["Meilisearch"]
```
图表来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/brand.js:1-66](file://frontend/src/api/brand.js#L1-L66)
- [frontend/src/utils/request.js:1-72](file://frontend/src/utils/request.js#L1-L72)
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [backend/src/routes/brands.js:1-147](file://backend/src/routes/brands.js#L1-L147)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/models/Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
**图表来源**
- [frontend/src/views/model/index.vue:227-434](file://frontend/src/views/model/index.vue#L227-L434)
- [frontend/src/views/model/composables/useModelList.js:1-191](file://frontend/src/views/model/composables/useModelList.js#L1-L191)
- [frontend/src/api/model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
章节来源
**章节来源**
- [frontend/src/router/index.js:1-91](file://frontend/src/router/index.js#L1-L91)
- [frontend/src/utils/request.js:1-72](file://frontend/src/utils/request.js#L1-L72)
## 性能考虑
- 分页与排序
- **分页与排序**
- 后端限制每页最大 1000 条,避免一次性传输大量数据
- 排序字段限定为 ID 与创建时间,减少复杂索引需求
- 文件上传
- **文件上传**
- 仅允许特定格式,TXT 自动转换降低客户端处理成本
- S3 上传采用内存存储,注意控制文件大小与并发
- 缓存与索引
- **缓存与索引**
- EQ 缓存仅列出 Hash Key,避免一次性返回大体积 JSON
- 推送搜索采用异步任务与进度弹窗,提升用户体验
- **squig.link抓取**
- 使用内存缓存squigsites.json,减少外部请求频率
- 并行处理多项操作,提高响应速度
**章节来源**
- [backend/src/services/squiglink.js:33-48](file://backend/src/services/squiglink.js#L33-L48)
## 故障排查指南
- 登录与鉴权
- **登录与鉴权**
- 401:令牌失效或缺失,自动跳转登录页
- 403:无权限执行操作
- 网络与超时
- **网络与超时**
- 请求超时:检查后端服务与网络连通性
- 响应错误:查看控制台日志与消息提示
- 品牌与型号
- **品牌与型号**
- 品牌名称重复:确保品牌唯一性
- 型号重复:同一品牌下型号名称唯一
- 文件上传
- **文件上传**
- 不支持的文件格式:确认扩展名为 CSV/TXT/JSON
- TXT 转换失败:检查文件内容格式是否符合预期
- S3 与 Redis
- **S3 与 Redis**
- 读取失败:确认 S3 Key 是否正确、对象是否存在
- 缓存为空:确认型号 EQ 缓存是否已生成
- **squig.link导入**
- URL格式错误:确认使用标准的squig.link分享链接
- 数据抓取失败:检查网络连接和目标网站可用性
- 文件匹配失败:确认分享参数与目标文件名匹配
章节来源
**章节来源**
- [frontend/src/utils/request.js:27-69](file://frontend/src/utils/request.js#L27-L69)
- [backend/src/routes/brands.js:59-81](file://backend/src/routes/brands.js#L59-L81)
- [backend/src/routes/models.js:306-361](file://backend/src/routes/models.js#L306-L361)
- [backend/src/routes/models.js:371-499](file://backend/src/routes/models.js#L371-L499)
- [backend/src/services/measurementStorage.js:87-108](file://backend/src/services/measurementStorage.js#L87-L108)
- [backend/src/services/eqCacheStorage.js:47-66](file://backend/src/services/eqCacheStorage.js#L47-L66)
- [backend/src/services/squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
## 结论
型号管理页面提供了完整的型号生命周期管理能力:从列表检索、表单录入与校验,到文件上传与外部系统集成(S3、Redis、Meilisearch),再到批量操作与可视化展示。当前实现以字符串关联品牌,具备良好的扩展性;建议在后续版本中引入外键约束与更严格的级联策略,进一步提升数据一致性与安全性。
型号管理页面经过重构后,现已成为一个功能完整、架构清晰的现代化管理系统。通过useModelList组合式函数实现了状态管理的统一化,通过5个专用对话框组件提供了丰富的用户交互体验,通过squig.link导入功能扩展了数据来源的多样性。
系统的主要优势包括:
- **模块化设计**:组合式函数和对话框组件的分离提高了代码的可维护性
- **用户体验优化**:完整的进度跟踪和错误处理提升了用户满意度
- **数据来源多样化**:支持本地文件上传和外部网站数据导入
- **扩展性强**:基于Vue 3 Composition API的设计便于功能扩展
建议在后续版本中继续完善:
- 添加更多外部数据源的支持
- 增强数据验证和错误恢复机制
- 优化大数据量场景下的性能表现
## 附录
- API 一览
- **API 一览**
- 获取型号列表:GET /api/models/
- 获取单个型号:GET /api/models/:id
- 创建型号:POST /api/models/
@@ -400,9 +569,10 @@ BE_Routes --> MS["Meilisearch"]
- 创建品牌:POST /api/brands/
- 更新品牌:PUT /api/brands/:id
- 删除品牌:DELETE /api/brands/:id
- **新增**:squig.link数据抓取:POST /api/models/squiglink-fetch
章节来源
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
**章节来源**
- [frontend/src/api/model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [frontend/src/api/brand.js:1-66](file://frontend/src/api/brand.js#L1-L66)
- [backend/src/routes/models.js:133-566](file://backend/src/routes/models.js#L133-L566)
- [backend/src/routes/models.js:133-659](file://backend/src/routes/models.js#L133-L659)
- [backend/src/routes/brands.js:14-147](file://backend/src/routes/brands.js#L14-L147)
@@ -10,8 +10,18 @@
- [frontend/src/api/model.js](file://frontend/src/api/model.js)
- [backend/src/models/Ota.js](file://backend/src/models/Ota.js)
- [backend/src/routes/ota.js](file://backend/src/routes/ota.js)
- [backend/src/services/squiglink.js](file://backend/src/services/squiglink.js)
- [frontend/src/views/model/components/ModelFormDialog.vue](file://frontend/src/views/model/components/ModelFormDialog.vue)
</cite>
## 更新摘要
**变更内容**
- 新增squig.link外部URL数据源抓取功能
- 增强模型创建/更新流程支持外部频响数据源
- 添加squiglink_csv字段支持直接上传CSV内容
- 新增squiglink-fetch接口用于从外部URL获取频响数据
- 前端ModelFormDialog组件集成squig.link导入功能
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
@@ -24,63 +34,74 @@
9. [结论](#结论)
## 简介
本文件为型号管理API的详细RESTful API文档,覆盖型号的CRUD操作、查询筛选与排序、数据验证规则,以及与OTA固件的一对多关联关系与级联操作。文档同时提供前端调用示例与后端实现细节,帮助开发者快速集成与维护。
本文件为型号管理API的详细RESTful API文档,覆盖型号的CRUD操作、查询筛选与排序、数据验证规则,以及与OTA固件的一对多关联关系与级联操作。文档同时提供前端调用示例与后端实现细节,帮助开发者快速集成与维护。**最新更新**:新增squig.link外部数据源支持,增强模型创建/更新流程以支持外部URL数据源。
## 项目结构
型号管理API位于后端Express应用中,采用模块化设计:
- 路由层:集中于 models.js,提供型号的增删改查、搜索推送、频响文件处理等接口
- 路由层:集中于 models.js,提供型号的增删改查、搜索推送、频响文件处理、**squig.link数据抓取**等接口
- 模型层:Model.js 定义数据库表结构
- 验证层:model.js 使用Zod进行请求体验证
- 工具层:response.js 统一响应格式
- 中间件:auth.js 提供鉴权保护
- **服务层**:squiglink.js 提供squig.link外部数据源抓取服务
- 前端封装:frontend/src/api/model.js 提供HTTP调用封装
- **前端组件**:ModelFormDialog.vue 集成squig.link导入功能
```mermaid
graph TB
FE["前端应用<br/>frontend/src/api/model.js"] --> R["路由层<br/>backend/src/routes/models.js"]
FE["前端应用<br/>frontend/src/api/model.js<br/>frontend/src/views/model/components/ModelFormDialog.vue"] --> R["路由层<br/>backend/src/routes/models.js"]
R --> M["模型层<br/>backend/src/models/Model.js"]
R --> V["验证层<br/>backend/src/validators/model.js"]
R --> U["工具层<br/>backend/src/utils/response.js"]
R --> A["中间件<br/>backend/src/middleware/auth.js"]
R --> S["服务层<br/>backend/src/services/squiglink.js"]
R -. 关联 .-> OTA["OTA模型<br/>backend/src/models/Ota.js"]
```
图表来源
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
**图表来源**
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/validators/model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
- [backend/src/services/squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [frontend/src/views/model/components/ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
章节来源
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
**章节来源**
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/validators/model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
- [frontend/src/api/model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [frontend/src/api/model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
- [backend/src/services/squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [frontend/src/views/model/components/ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
## 核心组件
- 路由控制器:models.js 提供型号的列表查询、详情获取、创建、更新、删除、搜索推送、频响文件上传与查询等接口
- 路由控制器:models.js 提供型号的列表查询、详情获取、创建、更新、删除、搜索推送、频响文件上传与查询、**squig.link数据抓取**等接口
- 数据模型:Model.js 定义型号字段及约束
- 请求验证:model.js 使用Zod Schema进行创建/更新的输入校验
- 统一响应:response.js 提供统一的响应结构
- 鉴权中间件:auth.js 实现Bearer Token鉴权
- **squig.link服务**:squiglink.js 提供外部数据源抓取功能
- 前端封装:frontend/src/api/model.js 提供HTTP调用封装
- **前端组件**:ModelFormDialog.vue 集成squig.link导入功能
章节来源
**章节来源**
- [backend/src/routes/models.js:133-181](file://backend/src/routes/models.js#L133-L181)
- [backend/src/models/Model.js:4-50](file://backend/src/models/Model.js#L4-L50)
- [backend/src/validators/model.js:3-19](file://backend/src/validators/model.js#L3-L19)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
- [frontend/src/api/model.js:13-150](file://frontend/src/api/model.js#L13-L150)
- [backend/src/services/squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [frontend/src/api/model.js:13-165](file://frontend/src/api/model.js#L13-L165)
- [frontend/src/views/model/components/ModelFormDialog.vue:158-464](file://frontend/src/views/model/components/ModelFormDialog.vue#L158-L464)
## 架构概览
型号管理API采用分层架构,路由层负责HTTP协议与参数解析,模型层负责数据持久化,验证层确保输入合法性,工具层统一输出格式,中间件提供安全控制。
型号管理API采用分层架构,路由层负责HTTP协议与参数解析,模型层负责数据持久化,验证层确保输入合法性,工具层统一输出格式,中间件提供安全控制,**服务层提供外部数据源集成**。
```mermaid
sequenceDiagram
@@ -88,6 +109,7 @@ participant C as "客户端"
participant R as "路由(models.js)"
participant V as "验证(model.js)"
participant M as "模型(Model.js)"
participant S as "服务(squiglink.js)"
participant U as "响应(response.js)"
participant A as "鉴权(auth.js)"
C->>R : "HTTP请求"
@@ -95,16 +117,19 @@ R->>A : "鉴权检查"
A-->>R : "通过/拒绝"
R->>V : "请求体验证"
V-->>R : "验证结果"
R->>S : "外部数据源抓取可选"
S-->>R : "抓取结果"
R->>M : "数据库操作"
M-->>R : "结果"
R->>U : "封装响应"
U-->>C : "统一响应"
```
图表来源
- [backend/src/routes/models.js:307-361](file://backend/src/routes/models.js#L307-L361)
**图表来源**
- [backend/src/routes/models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [backend/src/validators/model.js:3-19](file://backend/src/validators/model.js#L3-L19)
- [backend/src/models/Model.js:4-50](file://backend/src/models/Model.js#L4-L50)
- [backend/src/services/squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -135,10 +160,10 @@ datetime create_at
}
```
图表来源
**图表来源**
- [backend/src/models/Model.js:4-50](file://backend/src/models/Model.js#L4-L50)
章节来源
**章节来源**
- [backend/src/models/Model.js:4-50](file://backend/src/models/Model.js#L4-L50)
### 型号参数验证规则
@@ -149,7 +174,7 @@ datetime create_at
- 更新请求体验证:
- 字段同上,但允许部分字段为空(表示不更新)
章节来源
**章节来源**
- [backend/src/validators/model.js:3-19](file://backend/src/validators/model.js#L3-L19)
### 型号CRUD接口
@@ -167,7 +192,7 @@ datetime create_at
- 成功响应:包含items、total、skip、limit的分页数据
- 异常响应:统一错误码与消息
章节来源
**章节来源**
- [backend/src/routes/models.js:133-181](file://backend/src/routes/models.js#L133-L181)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -179,7 +204,7 @@ datetime create_at
- 成功响应:型号基础字段
- 异常响应:未找到或通用错误
章节来源
**章节来源**
- [backend/src/routes/models.js:280-304](file://backend/src/routes/models.js#L280-L304)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -190,16 +215,20 @@ datetime create_at
- 内容类型:multipart/form-data
- 表单字段:
- measurement_file:频响文件(CSV/TXT/JSON),可选
- **squiglink_csv**:squig.link抓取的CSV内容,可选
- brand_name、name、form、rig、source、eq_key
- 文件处理:
- **优先级**:squiglink_csv > measurement_file > 无文件
- TXT文件自动转换为CSV
- 仅允许.csv、.txt、.json
- 上传至S3,键值包含source、form、brand_name、name
- 成功响应:创建后的型号信息
- 异常响应:重复、格式不支持、通用错误
章节来源
- [backend/src/routes/models.js:306-361](file://backend/src/routes/models.js#L306-L361)
**更新** 新增squiglink_csv字段支持直接上传CSV内容
**章节来源**
- [backend/src/routes/models.js:371-437](file://backend/src/routes/models.js#L371-L437)
- [backend/src/validators/model.js:3-10](file://backend/src/validators/model.js#L3-L10)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -214,8 +243,8 @@ datetime create_at
- 成功响应:更新后的型号信息
- 异常响应:未找到、重复、格式不支持、通用错误
章节来源
- [backend/src/routes/models.js:363-436](file://backend/src/routes/models.js#L363-L436)
**章节来源**
- [backend/src/routes/models.js:439-526](file://backend/src/routes/models.js#L439-L526)
- [backend/src/validators/model.js:12-19](file://backend/src/validators/model.js#L12-L19)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -230,8 +259,8 @@ datetime create_at
- 成功响应:删除成功
- 异常响应:未找到、Meilisearch删除失败、通用错误
章节来源
- [backend/src/routes/models.js:438-464](file://backend/src/routes/models.js#L438-L464)
**章节来源**
- [backend/src/routes/models.js:528-554](file://backend/src/routes/models.js#L528-L554)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
@@ -240,7 +269,7 @@ datetime create_at
- 支持按id或create_at排序,升序/降序
- 分页参数:skip、limit(上限1000)
章节来源
**章节来源**
- [backend/src/routes/models.js:136-157](file://backend/src/routes/models.js#L136-L157)
### 型号与OTA固件的一对多关联
@@ -264,11 +293,11 @@ string model
}
```
图表来源
**图表来源**
- [backend/src/models/Model.js:4-50](file://backend/src/models/Model.js#L4-L50)
- [backend/src/models/Ota.js:44-48](file://backend/src/models/Ota.js#L44-L48)
章节来源
**章节来源**
- [backend/src/models/Ota.js:44-48](file://backend/src/models/Ota.js#L44-L48)
### Meilisearch搜索推送与校验
@@ -279,15 +308,15 @@ string model
- 输入:model_ids数组
- 输出:推送成功数量、任务ID与推送数据
章节来源
- [backend/src/routes/models.js:466-566](file://backend/src/routes/models.js#L466-L566)
**章节来源**
- [backend/src/routes/models.js:556-656](file://backend/src/routes/models.js#L556-L656)
### 频响文件上传与查询
- 上传:POST /api/models/(multipart/form-data),支持TXT自动转换为CSV
- 上传:POST /api/models/(multipart/form-data),支持TXT自动转换为CSV,**新增squiglink_csv字段**
- 查询:GET /api/models/:model_id/measurement(仅Eafonyoung来源且存在form时可用)
章节来源
- [backend/src/routes/models.js:306-361](file://backend/src/routes/models.js#L306-L361)
**章节来源**
- [backend/src/routes/models.js:371-437](file://backend/src/routes/models.js#L371-L437)
- [backend/src/routes/models.js:246-278](file://backend/src/routes/models.js#L246-L278)
### EQ缓存与Meilisearch文档查询
@@ -295,13 +324,53 @@ string model
- 获取指定Hash Field:GET /api/models/:model_id/eq-cache/field?key=...
- 查询Meilisearch推送状态:GET /api/models/:model_id/meilisearch
章节来源
**章节来源**
- [backend/src/routes/models.js:183-244](file://backend/src/routes/models.js#L183-L244)
### Squig.link外部数据源集成
#### Squig.link数据抓取接口
- 方法与路径:POST /api/models/squiglink-fetch
- 认证:需要登录
- 请求体参数:
- share_url:squig.link分享链接(必填)
- selected_file:用户选择的特定文件名(可选)
- 功能特性:
- **自动抓取**:根据share_url自动解析并抓取频响数据
- **候选选择**:当存在多个匹配文件时,返回候选列表供用户选择
- **直接下载**:当提供selected_file时,直接下载指定文件
- **佩戴方式检测**:从squigsites.json自动检测佩戴方式
- 响应格式:
- 单个匹配:返回brand_name、model_name、form、csv_content、data_url
- 多个匹配:返回matches候选列表
- 错误:返回具体的抓取失败原因
#### Squig.link服务功能
- **URL解析**:解析squig.link分享URL,提取baseURL和share参数
- **站点发现**:从https://squig.link/squigsites.json获取站点配置
- **文件匹配**:在phone_book.json中查找匹配的测量文件
- **数据下载**:支持多种文件后缀的频响数据下载
- **格式转换**:将TXT文件转换为CSV格式
- **缓存机制**:squigsites.json内存缓存(1小时TTL)
#### 前端集成
- **ModelFormDialog组件**:集成squig.link导入功能
- **自动填充**:抓取成功后自动填充品牌、型号、佩戴方式等字段
- **候选选择**:多文件匹配时提供选择对话框
- **实时预览**:显示数据URL便于验证
**新增** 完整的squig.link外部数据源支持
**章节来源**
- [backend/src/routes/models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [backend/src/services/squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [frontend/src/views/model/components/ModelFormDialog.vue:158-464](file://frontend/src/views/model/components/ModelFormDialog.vue#L158-L464)
## 依赖分析
- 路由依赖:models.js 依赖Model模型、Zod验证器、响应工具、鉴权中间件,并与Meilisearch、S3服务交互
- 路由依赖:models.js 依赖Model模型、Zod验证器、响应工具、鉴权中间件,并与Meilisearch、S3服务交互,**新增squiglink服务依赖**
- 模型依赖:Model.js 依赖Sequelize ORM
- 前端依赖:frontend/src/api/model.js 依赖通用请求封装
- **服务依赖**:squiglink.js 依赖axios、logger,提供外部数据源抓取功能
```mermaid
graph LR
@@ -309,25 +378,30 @@ R["models.js"] --> M["Model.js"]
R --> V["model.js(Zod)"]
R --> U["response.js"]
R --> A["auth.js"]
R --> S["squiglink.js"]
R -. 外部 .-> MS["Meilisearch"]
R -. 外部 .-> S3["S3存储"]
S -. 外部 .-> SL["squig.link"]
```
图表来源
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
**图表来源**
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [backend/src/validators/model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
- [backend/src/services/squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
章节来源
- [backend/src/routes/models.js:1-569](file://backend/src/routes/models.js#L1-L569)
**章节来源**
- [backend/src/routes/models.js:1-659](file://backend/src/routes/models.js#L1-L659)
## 性能考虑
- 分页限制:列表查询limit上限为1000,避免一次性返回过多数据
- Meilisearch超时:查询与删除文档设置超时,防止阻塞
- 文件上传:内存存储multer,建议在高并发场景下优化为流式处理或外部对象存储直传
- 验证前置:使用Zod在进入数据库操作前完成字段校验,减少无效请求
- **squig.link抓取超时**:设置15秒超时,避免外部服务影响系统性能
- **squigsites.json缓存**:1小时TTL减少对外部服务的频繁请求
## 故障排除指南
- 401 未登录/无效凭证:检查Authorization头是否为Bearer Token且有效
@@ -336,12 +410,15 @@ R -. 外部 .-> S3["S3存储"]
- 重复型号:创建/更新时如提示品牌+型号已存在,请调整参数
- 文件格式不支持:仅允许.csv、.txt、.json,TXT会自动转换为CSV
- Meilisearch异常:检查服务连通性与API密钥
- **squig.link抓取失败**:检查share_url格式、网络连通性、目标站点可用性
- **squig.link文件下载失败**:确认文件存在、权限正确、支持的文件后缀
章节来源
**章节来源**
- [backend/src/middleware/auth.js:3-26](file://backend/src/middleware/auth.js#L3-L26)
- [backend/src/routes/models.js:314-317](file://backend/src/routes/models.js#L314-L317)
- [backend/src/routes/models.js:322-325](file://backend/src/routes/models.js#L322-L325)
- [backend/src/routes/models.js:450-455](file://backend/src/routes/models.js#L450-L455)
- [backend/src/services/squiglink.js:33-47](file://backend/src/services/squiglink.js#L33-L47)
## 结论
型号管理API提供了完善的CRUD能力、灵活的查询过滤与排序、严格的输入验证、以及与搜索与存储系统的集成。结合OTA模型的关联关系,可支撑从型号到固件升级的完整业务闭环。建议在生产环境中关注分页与超时配置、文件上传性能与安全策略,并在删除型号前做好OTA关联清理。
型号管理API提供了完善的CRUD能力、灵活的查询过滤与排序、严格的输入验证、以及与搜索与存储系统的集成。**最新更新**:新增squig.link外部数据源支持,显著提升了型号数据的获取效率和准确性。结合OTA模型的关联关系,可支撑从型号到固件升级的完整业务闭环。建议在生产环境中关注分页与超时配置、文件上传性能与安全策略、**squig.link抓取的超时与缓存策略**,并在删除型号前做好OTA关联清理。
@@ -6,13 +6,22 @@
- [Brand.js](file://backend/src/models/Brand.js)
- [model.js](file://backend/src/validators/model.js)
- [models.js](file://backend/src/routes/models.js)
- [squiglink.js](file://backend/src/services/squiglink.js)
- [measurementStorage.js](file://backend/src/services/measurementStorage.js)
- [eqCacheStorage.js](file://backend/src/services/eqCacheStorage.js)
- [curveClient.js](file://backend/src/services/curveClient.js)
- [index.vue](file://frontend/src/views/model/index.vue)
- [ModelFormDialog.vue](file://frontend/src/views/model/components/ModelFormDialog.vue)
- [model.js](file://frontend/src/api/model.js)
</cite>
## 更新摘要
**变更内容**
- 新增squig.link集成服务,支持从外部频率响应数据源自动获取数据
- 增强前端导入功能,支持从squig.link分享链接直接抓取频响数据
- 新增自动佩戴方式检测和品牌/型号解析功能
- 扩展型号创建和更新流程,支持squiglink_csv参数
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
@@ -26,10 +35,10 @@
10. [附录](#附录)
## 简介
本文件围绕“型号管理”功能进行系统性说明,涵盖型号的 CRUD 操作、技术规格管理、测量数据存储与检索、以及与品牌模型的一对一关联关系。文档基于实际代码库实现,提供接口调用流程、数据验证规则、存储策略与常见问题的解决方案,并给出面向初学者与高级开发者的分层解读。
本文件围绕"型号管理"功能进行系统性说明,涵盖型号的 CRUD 操作、技术规格管理、测量数据存储与检索、与品牌模型的一对一关联关系,以及**新增的squig.link集成服务**。文档基于实际代码库实现,提供接口调用流程、数据验证规则、存储策略与常见问题的解决方案,并给出面向初学者与高级开发者的分层解读。
## 项目结构
型号管理功能由前端视图、API 客户端与后端路由/模型/服务共同组成,形成“前端交互—HTTP 请求—后端路由—数据模型—外部存储”的完整链路。
型号管理功能由前端视图、API 客户端与后端路由/模型/服务共同组成,形成"前端交互—HTTP 请求—后端路由—数据模型—外部存储—squig.link集成"的完整链路。
```mermaid
graph TB
@@ -40,45 +49,50 @@ ROUTER --> VALIDATOR["型号验证器<br/>backend/src/validators/model.js"]
ROUTER --> MSVC["频响存储服务<br/>backend/src/services/measurementStorage.js"]
ROUTER --> ESVC["EQ 缓存服务<br/>backend/src/services/eqCacheStorage.js"]
ROUTER --> CSVC["曲线校验服务<br/>backend/src/services/curveClient.js"]
ROUTER --> SSVC["squig.link集成服务<br/>backend/src/services/squiglink.js"]
ROUTER --> BRAND["品牌模型<br/>backend/src/models/Brand.js"]
```
图表来源
**图表来源**
- [index.vue:1-1226](file://frontend/src/views/model/index.vue#L1-L1226)
- [model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
- [curveClient.js:1-147](file://backend/src/services/curveClient.js#L1-L147)
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
章节来源
**章节来源**
- [index.vue:1-1226](file://frontend/src/views/model/index.vue#L1-L1226)
- [model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
- [curveClient.js:1-147](file://backend/src/services/curveClient.js#L1-L147)
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
## 核心组件
- 型号模型:定义型号的数据结构与约束,包括品牌名称、型号名称、形式、阻抗、来源、EQ 键等字段。
- 品牌模型:与型号存在一对一关联,型号通过品牌名称进行关联。
- 型号验证器:使用 Zod 对创建/更新请求体进行严格校验。
- 型号路由:提供列表查询、详情查看、新增、更新、删除、搜索推送、频响 CSV 查看、EQ 缓存查看等接口。
- 型号路由:提供列表查询、详情查看、新增、更新、删除、搜索推送、频响 CSV 查看、EQ 缓存查看、**squig.link数据抓取**等接口。
- **squig.link集成服务**:支持从外部频率响应数据源自动获取数据,包括URL解析、数据抓取、格式转换、品牌/型号解析等功能。
- 测量存储服务:负责将频响 CSV/文本文件上传至 S3,并支持按路径键读取。
- EQ 缓存服务:通过 Redis 提供型号 EQ 数据的哈希字段列表与具体值读取。
- 曲线校验服务:对接外部曲线 API,校验型号的 parametric_eq 数据有效性。
章节来源
**章节来源**
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [models.js:133-566](file://backend/src/routes/models.js#L133-L566)
- [models.js:133-659](file://backend/src/routes/models.js#L133-L659)
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
- [curveClient.js:1-147](file://backend/src/services/curveClient.js#L1-L147)
@@ -86,8 +100,8 @@ ROUTER --> BRAND["品牌模型<br/>backend/src/models/Brand.js"]
## 架构总览
型号管理的端到端流程如下:
- 前端通过 API 客户端发起请求,后端路由接收并进行鉴权与参数校验。
- 路由根据业务逻辑调用模型层与服务层,完成数据库读写、外部存储交互与第三方校验。
- 前端渲染结果,支持批量推送至搜索引擎、查看频响 CSV、查看 EQ 缓存等。
- 路由根据业务逻辑调用模型层与服务层,完成数据库读写、外部存储交互、第三方校验以及**squig.link数据抓取**。
- 前端渲染结果,支持批量推送至搜索引擎、查看频响 CSV、查看 EQ 缓存等,**以及从squig.link直接导入数据**。
```mermaid
sequenceDiagram
@@ -96,11 +110,12 @@ participant FE as "前端视图"
participant API as "API 客户端"
participant RT as "后端路由(models.js)"
participant MD as "型号模型(Model.js)"
participant SS as "squig.link服务(squiglink.js)"
participant MS as "频响存储(measurementStorage.js)"
participant ES as "EQ 缓存(eqCacheStorage.js)"
participant CS as "曲线校验(curveClient.js)"
U->>FE : 打开型号页面
FE->>API : 发起列表/详情/新增/更新/删除请求
FE->>API : 发起列表/详情/新增/更新/删除/抓取请求
API->>RT : HTTP 请求
alt 查询列表/详情
RT->>MD : 读取数据库
@@ -108,7 +123,9 @@ MD-->>RT : 返回数据
else 新增/更新
RT->>MD : 写入数据库
MD-->>RT : 返回新记录
opt 上传频响文件
opt 上传频响文件或squig.link数据
RT->>SS : 抓取squig.link数据可选
SS-->>RT : 返回CSV内容和元数据
RT->>MS : 上传CSV/文本至S3
MS-->>RT : 返回S3 Key
end
@@ -120,14 +137,15 @@ RT-->>API : 返回响应
API-->>FE : 渲染结果
```
图表来源
- [models.js:133-566](file://backend/src/routes/models.js#L133-L566)
**图表来源**
- [models.js:133-659](file://backend/src/routes/models.js#L133-L659)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
- [measurementStorage.js:61-108](file://backend/src/services/measurementStorage.js#L61-L108)
- [eqCacheStorage.js:24-66](file://backend/src/services/eqCacheStorage.js#L24-L66)
- [curveClient.js:81-138](file://backend/src/services/curveClient.js#L81-L138)
- [index.vue:714-1063](file://frontend/src/views/model/index.vue#L714-L1063)
- [model.js:13-151](file://frontend/src/api/model.js#L13-L151)
- [model.js:13-165](file://frontend/src/api/model.js#L13-L165)
## 详细组件分析
@@ -155,11 +173,11 @@ datetime create_at
BRAND ||--o{ MODEL : "拥有多个型号"
```
图表来源
**图表来源**
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
章节来源
**章节来源**
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
@@ -168,18 +186,18 @@ BRAND ||--o{ MODEL : "拥有多个型号"
- 更新验证器:允许部分字段可选更新,其余规则与创建一致。
- 返回值:验证通过后进入业务流程,失败则返回错误信息。
章节来源
**章节来源**
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
### 型号 CRUD 与查询接口
- 列表查询:支持按品牌名称、型号名称模糊过滤,支持按 id 或创建时间排序,支持分页与跳过。
- 详情查看:按 id 获取单条型号信息。
- 新增:支持上传频响文件(CSV/TXT/JSON),自动转换 TXT 为 CSV;若存在同品牌同型号则拒绝重复创建。
- 新增:支持上传频响文件(CSV/TXT/JSON)或**squiglink_csv参数**,自动转换 TXT 为 CSV;若存在同品牌同型号则拒绝重复创建。
- 更新:支持部分字段更新;若上传新文件,需提供来源与形式字段;更新后写入数据库并可重新上传频响文件。
- 删除:删除前尝试从搜索引擎删除文档,再删除数据库记录。
章节来源
- [models.js:133-566](file://backend/src/routes/models.js#L133-L566)
**章节来源**
- [models.js:133-659](file://backend/src/routes/models.js#L133-L659)
### 技术规格管理
- 形式字段:支持入耳式、头戴式、耳塞式等枚举值映射。
@@ -187,7 +205,7 @@ BRAND ||--o{ MODEL : "拥有多个型号"
- 来源字段:标识型号数据来源(如 Eafonyoung)。
- EQ 键字段:用于关联 EQ 缓存的键名。
章节来源
**章节来源**
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [index.vue:226-250](file://frontend/src/views/model/index.vue#L226-L250)
@@ -209,12 +227,12 @@ Put --> Done(["完成"])
ErrExt --> Done
```
图表来源
**图表来源**
- [models.js:59-68](file://backend/src/routes/models.js#L59-L68)
- [models.js:307-335](file://backend/src/routes/models.js#L307-L335)
- [measurementStorage.js:48-79](file://backend/src/services/measurementStorage.js#L48-L79)
章节来源
**章节来源**
- [models.js:59-68](file://backend/src/routes/models.js#L59-L68)
- [models.js:307-335](file://backend/src/routes/models.js#L307-L335)
- [measurementStorage.js:48-79](file://backend/src/services/measurementStorage.js#L48-L79)
@@ -223,7 +241,7 @@ ErrExt --> Done
- EQ 缓存:通过 Redis 哈希存储型号的 EQ 数据,提供字段列表与单字段值读取。
- 频响 CSV:仅对特定来源开放,按来源、形式、品牌、型号拼接 S3 Key 并读取内容。
章节来源
**章节来源**
- [models.js:183-278](file://backend/src/routes/models.js#L183-L278)
- [eqCacheStorage.js:24-66](file://backend/src/services/eqCacheStorage.js#L24-L66)
- [measurementStorage.js:87-108](file://backend/src/services/measurementStorage.js#L87-L108)
@@ -252,32 +270,94 @@ RT-->>API : 返回错误列表
end
```
图表来源
**图表来源**
- [models.js:466-514](file://backend/src/routes/models.js#L466-L514)
- [models.js:516-566](file://backend/src/routes/models.js#L516-L566)
- [curveClient.js:81-138](file://backend/src/services/curveClient.js#L81-L138)
- [index.vue:1117-1220](file://frontend/src/views/model/index.vue#L1117-L1220)
章节来源
**章节来源**
- [models.js:466-514](file://backend/src/routes/models.js#L466-L514)
- [models.js:516-566](file://backend/src/routes/models.js#L516-L566)
- [curveClient.js:81-138](file://backend/src/services/curveClient.js#L81-L138)
- [index.vue:1117-1220](file://frontend/src/views/model/index.vue#L1117-L1220)
### Squig.link集成服务与自动数据抓取
**新增功能**:型号管理现已集成squig.link外部数据源,支持从分享链接自动抓取频响数据。
#### 核心特性
- **URL解析**:支持标准squig.link分享URL格式,自动提取baseURL和share参数
- **数据抓取**:自动获取phone_book.json清单,匹配目标文件
- **格式转换**:将TXT格式频响数据转换为CSV格式
- **品牌/型号解析**:从文件名自动解析品牌和型号信息
- **佩戴方式检测**:通过squigsites.json配置自动识别入耳式/头戴式/耳塞式
- **多候选处理**:当存在多个匹配文件时,提供候选列表供用户选择
#### 技术实现
- **squigsites.json缓存**:1小时内存缓存,减少外部请求
- **并行处理**:同时获取phone_book和检测佩戴方式,提升响应速度
- **智能匹配**:精确匹配失败时进行模糊搜索,提高成功率
- **容错机制**:多种TXT文件后缀尝试,确保数据完整性
```mermaid
flowchart TD
Start(["squig.link抓取开始"]) --> ParseURL["解析分享URL"]
ParseURL --> GetPhoneBook["获取phone_book.json"]
GetPhoneBook --> MatchFile["匹配目标文件"]
MatchFile --> ExactMatch{"精确匹配?"}
ExactMatch --> |是| DownloadTXT["下载TXT文件"]
ExactMatch --> |否| FuzzySearch["模糊搜索候选"]
FuzzySearch --> Multiple{"多个候选?"}
Multiple --> |是| ShowCandidates["显示候选列表"]
Multiple --> |否| DownloadTXT
ShowCandidates --> UserSelect["用户选择文件"]
UserSelect --> DownloadTXT
DownloadTXT --> ConvertCSV["转换为CSV"]
ConvertCSV --> DetectForm["检测佩戴方式"]
DetectForm --> ParseBrand["解析品牌/型号"]
ParseBrand --> ReturnData["返回抓取结果"]
```
**图表来源**
- [squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
- [squiglink.js:148-207](file://backend/src/services/squiglink.js#L148-L207)
- [squiglink.js:216-247](file://backend/src/services/squiglink.js#L216-L247)
#### 前端集成
- **导入界面**:在型号表单中新增squig.link导入区域
- **实时抓取**:输入URL后自动抓取数据,无需手动提交
- **候选选择**:当存在多个匹配文件时,弹出选择对话框
- **自动填充**:成功抓取后自动填充品牌、型号、佩戴方式等字段
#### 后端API
- **POST /api/models/squiglink-fetch**:处理squig.link数据抓取请求
- **支持参数**:share_url(必需)、selected_file(可选)
- **返回数据**:CSV内容、品牌/型号、佩戴方式、数据URL、候选列表
**章节来源**
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [ModelFormDialog.vue:16-46](file://frontend/src/views/model/components/ModelFormDialog.vue#L16-L46)
- [ModelFormDialog.vue:289-332](file://frontend/src/views/model/components/ModelFormDialog.vue#L289-L332)
- [model.js:152-165](file://frontend/src/api/model.js#L152-L165)
### 前端交互与使用示例
- 列表查询:支持品牌名称与型号名称模糊搜索、排序与分页。
- 详情查看:点击更多操作可查看推送状态、EQ 缓存、CSV 内容。
- 参数配置:新增/编辑对话框支持品牌选择、型号输入、形式、阻抗、来源与频响文件上传。
- 参数配置:新增/编辑对话框支持品牌选择、型号输入、形式、阻抗、来源与频响文件上传,**以及squig.link分享链接导入**。
- 批量推送:勾选多条后执行推送流程,包含校验与进度提示。
- **squig.link导入**:在表单中输入squig.link分享链接,自动抓取频响数据并填充表单字段。
章节来源
**章节来源**
- [index.vue:1-1226](file://frontend/src/views/model/index.vue#L1-L1226)
- [model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [ModelFormDialog.vue:1-555](file://frontend/src/views/model/components/ModelFormDialog.vue#L1-L555)
- [model.js:1-165](file://frontend/src/api/model.js#L1-L165)
## 依赖关系分析
- 前端依赖:Element Plus 组件库、Vue 3、axios 请求封装。
- 后端依赖:Express 路由、Sequelize ORM、Zod 验证、Multer 文件上传、Axios 第三方调用、AWS SDK S3 访问、Redis 客户端。
- 外部服务:Meilisearch(搜索引擎)、Luxsin 曲线 API、S3 存储。
- 外部服务:Meilisearch(搜索引擎)、Luxsin 曲线 API、S3 存储、**squig.link外部数据源**。
```mermaid
graph LR
@@ -288,29 +368,32 @@ RT --> VAL["验证器(model.js)"]
RT --> MS["S3(measurementStorage.js)"]
RT --> ES["Redis(eqCacheStorage.js)"]
RT --> CS["曲线(curveClient.js)"]
RT --> SS["squig.link(squiglink.js)"]
RT --> BR["品牌(Brand.js)"]
```
图表来源
**图表来源**
- [index.vue:1-1226](file://frontend/src/views/model/index.vue#L1-L1226)
- [model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
- [curveClient.js:1-147](file://backend/src/services/curveClient.js#L1-L147)
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
章节来源
**章节来源**
- [index.vue:1-1226](file://frontend/src/views/model/index.vue#L1-L1226)
- [model.js:1-151](file://frontend/src/api/model.js#L1-L151)
- [models.js:1-569](file://backend/src/routes/models.js#L1-L569)
- [model.js:1-165](file://frontend/src/api/model.js#L1-L165)
- [models.js:1-659](file://backend/src/routes/models.js#L1-L659)
- [Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
- [model.js:1-22](file://backend/src/validators/model.js#L1-L22)
- [measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
- [eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
- [curveClient.js:1-147](file://backend/src/services/curveClient.js#L1-L147)
- [squiglink.js:1-322](file://backend/src/services/squiglink.js#L1-L322)
- [Brand.js:1-23](file://backend/src/models/Brand.js#L1-L23)
## 性能考虑
@@ -318,6 +401,7 @@ RT --> BR["品牌(Brand.js)"]
- 文件上传:上传 TXT 时会进行转换,建议控制文件大小与格式,减少不必要的转换开销。
- 搜索推送:批量推送包含校验步骤,建议分批处理并监控进度,避免超时。
- 缓存与存储:EQ 缓存通过 Redis 快速读取字段列表,避免大对象传输;S3 读取采用流式读取,注意网络抖动与超时配置。
- **squig.link抓取**:squigsites.json采用1小时缓存,减少外部请求;并行处理phone_book获取和佩戴方式检测,提升响应速度。
## 故障排查指南
- 参数验证失败
@@ -327,19 +411,19 @@ RT --> BR["品牌(Brand.js)"]
- [model.js:3-19](file://backend/src/validators/model.js#L3-L19)
- [models.js:307-361](file://backend/src/routes/models.js#L307-L361)
- 重复型号
- 现象:新增时报“该品牌下型号名称已存在”。
- 现象:新增时报"该品牌下型号名称已存在"。
- 排查:确认数据库中是否已存在相同品牌+型号组合;更新时注意品牌与型号变更后的冲突检测。
- 参考
- [models.js:312-317](file://backend/src/routes/models.js#L312-L317)
- [models.js:380-388](file://backend/src/routes/models.js#L380-L388)
- 不支持的文件格式
- 现象:上传文件返回“不支持的文件格式”。
- 现象:上传文件返回"不支持的文件格式"。
- 排查:确认扩展名为 CSV/TXT/JSON;TXT 会被转换为 CSV。
- 参考
- [models.js:320-325](file://backend/src/routes/models.js#L320-L325)
- [models.js:59-68](file://backend/src/routes/models.js#L59-L68)
- S3 读取失败
- 现象:查看 CSV 时提示“S3 上未找到该型号的频响文件”或读取失败。
- 现象:查看 CSV 时提示"S3 上未找到该型号的频响文件"或读取失败。
- 排查:确认来源为 Eafonyoung、具备形式字段、S3 Key 构造正确;检查桶权限与区域配置。
- 参考
- [models.js:255-258](file://backend/src/routes/models.js#L255-L258)
@@ -350,8 +434,15 @@ RT --> BR["品牌(Brand.js)"]
- 参考
- [models.js:466-514](file://backend/src/routes/models.js#L466-L514)
- [curveClient.js:81-138](file://backend/src/services/curveClient.js#L81-L138)
- **squig.link抓取失败**
- 现象:输入squig.link分享链接后无法抓取数据。
- 排查:确认URL格式正确、网络连接正常、目标文件存在于phone_book中;检查squigsites.json缓存是否过期;查看后端日志获取详细错误信息。
- 参考
- [models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
- [ModelFormDialog.vue:289-332](file://frontend/src/views/model/components/ModelFormDialog.vue#L289-L332)
章节来源
**章节来源**
- [model.js:3-19](file://backend/src/validators/model.js#L3-L19)
- [models.js:312-317](file://backend/src/routes/models.js#L312-L317)
- [models.js:320-325](file://backend/src/routes/models.js#L320-L325)
@@ -359,9 +450,12 @@ RT --> BR["品牌(Brand.js)"]
- [measurementStorage.js:87-108](file://backend/src/services/measurementStorage.js#L87-L108)
- [models.js:466-514](file://backend/src/routes/models.js#L466-L514)
- [curveClient.js:81-138](file://backend/src/services/curveClient.js#L81-L138)
- [models.js:307-369](file://backend/src/routes/models.js#L307-L369)
- [squiglink.js:261-310](file://backend/src/services/squiglink.js#L261-L310)
- [ModelFormDialog.vue:289-332](file://frontend/src/views/model/components/ModelFormDialog.vue#L289-L332)
## 结论
型号管理功能通过清晰的前后端职责划分与严格的参数验证,实现了从基础 CRUD 到技术规格管理、测量数据存储与搜索引擎推送的完整闭环。借助 Redis 与 S3 的高效读写能力,以及第三方曲线校验服务,系统在保证数据一致性的同时提升了用户体验与可维护性。建议在生产环境中进一步完善日志与监控,优化批量推送的并发与重试策略,并持续关注外部依赖的稳定性。
型号管理功能通过清晰的前后端职责划分与严格的参数验证,实现了从基础 CRUD 到技术规格管理、测量数据存储与搜索引擎推送的完整闭环。**新增的squig.link集成服务进一步增强了数据获取能力,支持从外部频率响应数据源自动抓取数据,显著提升了用户体验**。借助 Redis 与 S3 的高效读写能力,以及第三方曲线校验服务,系统在保证数据一致性的同时提升了用户体验与可维护性。建议在生产环境中进一步完善日志与监控,优化批量推送的并发与重试策略,并持续关注外部依赖的稳定性。
## 附录
- 常用接口清单
@@ -376,7 +470,8 @@ RT --> BR["品牌(Brand.js)"]
- 查看频响 CSV:GET /api/models/:id/measurement
- 校验推送:POST /api/models/push-to-search/validate
- 推送:POST /api/models/push-to-search
- **squig.link数据抓取**:POST /api/models/squiglink-fetch
章节来源
- [models.js:133-566](file://backend/src/routes/models.js#L133-L566)
- [model.js:13-151](file://frontend/src/api/model.js#L13-L151)
**章节来源**
- [models.js:133-659](file://backend/src/routes/models.js#L133-L659)
- [model.js:13-165](file://frontend/src/api/model.js#L13-L165)