Files
dashboard/.qoder/repowiki/zh/content/前端组件/页面组件/型号管理页面.md
T
2026-06-30 14:46:52 +08:00

408 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 型号管理页面
<cite>
**本文档引用的文件**
- [frontend/src/views/model/index.vue](file://frontend/src/views/model/index.vue)
- [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)
- [frontend/src/utils/request.js](file://frontend/src/utils/request.js)
- [frontend/src/router/index.js](file://frontend/src/router/index.js)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向“型号管理页面”的技术实现与使用说明,覆盖以下关键能力:
- 型号列表展示、搜索过滤、排序与分页
- 型号详情查看与参数配置
- 型号表单的数据结构、字段验证与复杂表单处理(含频响文件上传)
- 型号与品牌的关联关系、级联数据管理与外键约束处理
- 型号参数的动态配置、数据格式验证与存储优化
- 批量操作(复制、推送搜索)的实现方案
## 项目结构
型号管理页面位于前端单页应用中,后端提供 REST 接口与数据模型支撑,前端通过 API 模块与后端交互。
```mermaid
graph TB
subgraph "前端"
FE_Model["型号视图<br/>frontend/src/views/model/index.vue"]
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"]
end
subgraph "后端"
BE_Routes_Models["型号路由<br/>backend/src/routes/models.js"]
BE_Models_Model["型号模型<br/>backend/src/models/Model.js"]
BE_Validators_Model["型号校验器<br/>backend/src/validators/model.js"]
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"]
end
FE_Model --> FE_API_Model
FE_Model --> FE_API_Brand
FE_API_Model --> FE_Request
FE_API_Brand --> FE_Request
FE_Router --> FE_Model
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_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-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)
## 核心组件
- 前端视图组件:负责渲染列表、表单、对话框、分页与批量操作,并通过 API 模块与后端交互。
- 前端 API 模块:封装型号与品牌相关的 HTTP 请求,统一处理 Content-Type 与超时策略。
- 后端路由:提供型号与品牌的 CRUD、搜索、排序、分页、推送搜索校验与推送、频响文件与 EQ 缓存查询等接口。
- 数据模型:定义型号与品牌的数据结构、字段类型与约束。
- 服务层:频响文件上传至 S3、EQ 缓存读取 Redis。
章节来源
- [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)
## 架构总览
型号管理页面采用前后端分离架构,前端通过 Element Plus 组件与 Axios 进行交互,后端基于 Express 提供 REST 接口,数据持久化使用 Sequelize ORM,外部集成 S3 与 Redis。
```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 缓存"
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 : 关闭对话框并刷新列表
```
图表来源
- [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)
## 详细组件分析
### 型号数据模型与表单结构
- 数据模型字段
- 主键:自增整型 ID
- 品牌名称:字符串,最大 100 字符,必填
- 型号名称:字符串,最大 100 字符,必填
- 形式:字符串,最大 100 字符,可空(入耳式/头戴式/耳塞式)
- 阻抗:字符串,最大 100 字符,可空
- 来源:字符串,最大 100 字符,可空
- EQ 键:字符串,最大 255 字符,可空
- 创建时间:日期,默认当前时间
- 表单字段与验证
- 品牌名称:必填
- 型号名称:必填,长度 1-100
- 形式:可选枚举值
- 阻抗:可选
- 来源:可选
- 频响文件:可选上传,支持 CSV/TXT/JSONTXT 自动转换为 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)
### 品牌关联与外键约束
- 品牌模型
- 主键:自增整型 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)
- [backend/src/routes/models.js:133-181](file://backend/src/routes/models.js#L133-L181)
### 批量操作与推送搜索
- 批量复制
- 支持复制所选项的“品牌+型号”文本
- 推送搜索
- 校验:对选中型号调用曲线校验服务,返回校验失败项
- 推送:将型号数据批量写入 Meilisearch 索引
- 进度弹窗:显示校验与推送步骤状态、当前处理项与错误明细
- 多媒体资源
- 查看 EQ 缓存:读取 Redis Hash 字段,支持展开查看 JSON 结构
- 查看 CSV:仅支持来源为特定值的型号,按佩戴方式拼接 S3 Key 并读取
```mermaid
sequenceDiagram
participant U as "用户"
participant V as "型号视图"
participant A 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
R->>C : 校验曲线数据
C-->>R : 校验结果
R-->>A : 校验错误列表或通过
A-->>V : 显示校验结果
U->>V : 确认推送
V->>A : pushToMeilisearch(ids)
A->>R : POST /api/models/push-to-search
R->>MS : 写入索引
MS-->>R : 任务 UID
R-->>A : 推送结果
A-->>V : 关闭进度弹窗并刷新
```
图表来源
- [frontend/src/views/model/index.vue:1-1575](file://frontend/src/views/model/index.vue#L1-L1575)
- [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/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)
### 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/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)
## 依赖关系分析
- 前端依赖
- Element Plus UI 组件库
- Axios 请求封装
- Vue Router 路由守卫
- 后端依赖
- Express 路由
- Sequelize ORM
- AWS SDKS3
- Redis 客户端
- Meilisearch 客户端
```mermaid
graph LR
FE_View["前端视图<br/>index.vue"] --> 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 --> 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/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
- 推送搜索采用异步任务与进度弹窗,提升用户体验
## 故障排查指南
- 登录与鉴权
- 401:令牌失效或缺失,自动跳转登录页
- 403:无权限执行操作
- 网络与超时
- 请求超时:检查后端服务与网络连通性
- 响应错误:查看控制台日志与消息提示
- 品牌与型号
- 品牌名称重复:确保品牌唯一性
- 型号重复:同一品牌下型号名称唯一
- 文件上传
- 不支持的文件格式:确认扩展名为 CSV/TXT/JSON
- TXT 转换失败:检查文件内容格式是否符合预期
- S3 与 Redis
- 读取失败:确认 S3 Key 是否正确、对象是否存在
- 缓存为空:确认型号 EQ 缓存是否已生成
章节来源
- [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/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)
## 结论
型号管理页面提供了完整的型号生命周期管理能力:从列表检索、表单录入与校验,到文件上传与外部系统集成(S3、Redis、Meilisearch),再到批量操作与可视化展示。当前实现以字符串关联品牌,具备良好的扩展性;建议在后续版本中引入外键约束与更严格的级联策略,进一步提升数据一致性与安全性。
## 附录
- API 一览
- 获取型号列表:GET /api/models/
- 获取单个型号:GET /api/models/:id
- 创建型号:POST /api/models/
- 更新型号:PUT /api/models/:id
- 删除型号:DELETE /api/models/:id
- 推送搜索校验:POST /api/models/push-to-search/validate
- 推送搜索:POST /api/models/push-to-search
- 查看 Meilisearch 推送:GET /api/models/:id/meilisearch
- 查看频响 CSVGET /api/models/:id/measurement
- 查看 EQ 缓存:GET /api/models/:id/eq-cache
- 查看 EQ 缓存字段:GET /api/models/:id/eq-cache/field?key=...
- 获取品牌列表:GET /api/brands/
- 获取单个品牌:GET /api/brands/:id
- 创建品牌:POST /api/brands/
- 更新品牌:PUT /api/brands/:id
- 删除品牌:DELETE /api/brands/:id
章节来源
- [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:133-566](file://backend/src/routes/models.js#L133-L566)
- [backend/src/routes/brands.js:14-147](file://backend/src/routes/brands.js#L14-L147)