docs: 材料列表重构设计稿
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b2d518e736
commit
e927826ee0
|
|
@ -0,0 +1,299 @@
|
|||
# 材料管理列表页重构设计
|
||||
|
||||
**日期**:2026-04-24
|
||||
**范围**:`frontend/src/views/MaterialManage.vue` 列表页与 `frontend/src/views/material/MaterialForm.vue` 详情视图;`backend/apps/material/` 列表序列化器与筛选集
|
||||
**目标**:列表展示完整信息、分级分组呈现、可配置列显隐(持久化)、更多筛选、操作体验更紧凑
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机
|
||||
|
||||
当前 `MaterialManage.vue` 列表页存在以下问题:
|
||||
|
||||
- 14 列平铺展示,缺少语义分组,用户难以快速定位字段
|
||||
- 重要字段(成本、优势、主要参数、星级)未在列表展示,必须点入详情
|
||||
- 供应商/品牌仅展示 `factory_short_name` 与 `brand_name`,其余属性(合作模式、省市)不可见
|
||||
- 筛选条件仅 4 项,无法按大类、阶段、供应商、成本区间等过滤
|
||||
- 工具栏按钮松散,需手动点"查询"按钮,效率低
|
||||
- 详情页为 `MaterialForm` 的 view 模式复用,字段扁平铺陈无分块
|
||||
|
||||
## 2. 设计原则
|
||||
|
||||
- **信息分组**:列表与详情使用同一套三分组语义——"材料信息 / 品牌与供应商 / 案例信息"
|
||||
- **列表完整、可配置**:一次性加载完整字段,由前端决定显示哪几列;用户偏好持久化到 `localStorage`
|
||||
- **即时响应**:下拉筛选 `@change` 立即触发查询、输入框 `@keyup.enter` 触发查询,删除显式"查询"按钮
|
||||
- **YAGNI**:仅做列显隐(不做列拖拽排序);仅持久化列偏好(不持久化筛选条件)
|
||||
|
||||
## 3. 分组与列归属
|
||||
|
||||
### A. 材料信息
|
||||
|
||||
- 材料名称、材料大类、细分种类、材料子类
|
||||
- 阶段、重要等级、状态
|
||||
- 规格、执行标准
|
||||
- 应用场景(JSON 数组 → tag)、应用说明、替代类型
|
||||
- 连接方式、施工方式、使用限制
|
||||
- 优势(JSON 数组 → tag)、优势说明
|
||||
- 成本比较(%)、成本说明
|
||||
- 质量/耐久/环保/碳/综合评分(星级 1-3)
|
||||
|
||||
### B. 品牌与供应商
|
||||
|
||||
- 品牌名称
|
||||
- 供应商简称、供应商全称、合作模式、省-市、对接人、对接电话
|
||||
|
||||
### C. 案例信息
|
||||
|
||||
- 落地项目、案例(cases)、经办人、备注
|
||||
|
||||
操作列独立固定右侧,不属于三组。
|
||||
|
||||
## 4. 后端改动
|
||||
|
||||
### 4.1 `MaterialListSerializer`(`backend/apps/material/serializers.py`)
|
||||
|
||||
新增字段(所有字段均为只读,源自 `Material` 实例或其关系):
|
||||
|
||||
**A 组补充**(直接暴露 model 字段):
|
||||
`spec`, `standard`, `application_scene`, `application_desc`, `replace_type`, `advantage`, `advantage_desc`, `connection_method`, `construction_method`, `limit_condition`, `cost_compare`, `cost_desc`, `quality_level`, `durability_level`, `eco_level`, `carbon_level`, `score_level`
|
||||
|
||||
**B 组补充**:
|
||||
- `factory_full_name` — `source='factory.factory_name'`
|
||||
- `factory_cooperation_mode` — `source='factory.cooperation_mode'`
|
||||
- `factory_cooperation_mode_display` — `SerializerMethodField`,返回 `get_cooperation_mode_display()`
|
||||
- `factory_province` — `source='factory.province'`
|
||||
- `factory_city` — `source='factory.city'`
|
||||
|
||||
**C 组**:`cases` 已在,无需改动。
|
||||
|
||||
**性能**:视图层确认对列表查询加上 `select_related('factory', 'brand')`(若已有则保留)。
|
||||
|
||||
### 4.2 筛选集(ViewSet `filterset_fields` 或 FilterSet 类)
|
||||
|
||||
追加:
|
||||
- `major_category`(exact)
|
||||
- `material_category`(icontains)
|
||||
- `stage`(exact)
|
||||
- `importance_level`(exact)
|
||||
- `factory`(exact,id)
|
||||
- `factory__cooperation_mode`(exact)
|
||||
- `landing_project`(icontains)
|
||||
- `cost_compare__gte`, `cost_compare__lte`
|
||||
- `score_level__gte`
|
||||
- `contact_person`(icontains)
|
||||
- `handler`(icontains)
|
||||
|
||||
保留原有:`name`(icontains)、`status`、`material_subcategory`、`brand`。
|
||||
|
||||
### 4.3 不改动
|
||||
|
||||
- `MaterialDetailSerializer`:字段已全
|
||||
- `Brand` / `Factory` 模型
|
||||
- 导入/导出/审批相关接口
|
||||
|
||||
## 5. 前端改动
|
||||
|
||||
### 5.1 新增文件
|
||||
|
||||
#### `frontend/src/views/material/materialColumns.js`
|
||||
|
||||
导出两个常量:
|
||||
|
||||
- `COLUMN_GROUPS`:`[{ key: 'material', label: '材料信息' }, { key: 'supplier', label: '品牌与供应商' }, { key: 'case', label: '案例信息' }]`
|
||||
- `MATERIAL_COLUMNS`:有序数组,每项形如
|
||||
```js
|
||||
{
|
||||
group: 'material',
|
||||
key: 'name',
|
||||
label: '材料名称',
|
||||
minWidth: 180, // 或 width
|
||||
showOverflowTooltip: true,
|
||||
slot: null, // 特殊渲染时填 slot 名:'advantage' | 'appScene' | 'stars'
|
||||
formatter: (row) => ..., // 普通文本格式化
|
||||
}
|
||||
```
|
||||
|
||||
列顺序(按用户"靠前展示"要求):
|
||||
1. A 组:材料名称、材料大类、细分种类、材料子类、阶段、重要等级、状态、成本比较、成本说明、优势、优势说明、应用场景、应用说明、替代类型、连接方式、施工方式、使用限制、规格、执行标准、质量/耐久/环保/碳/综合评分
|
||||
2. B 组:品牌、供应商简称、供应商全称、合作模式、省-市、对接人、对接电话
|
||||
3. C 组:落地项目、案例、经办人、备注
|
||||
|
||||
#### `frontend/src/composables/useColumnPreferences.js`
|
||||
|
||||
通用 composable:
|
||||
|
||||
```js
|
||||
export function useColumnPreferences(storageKey, allColumnKeys) {
|
||||
const hidden = ref([]) // 从 localStorage 初始化
|
||||
|
||||
const load = () => { /* try parse, fallback [] */ }
|
||||
const save = () => localStorage.setItem(storageKey, JSON.stringify(hidden.value))
|
||||
|
||||
const isVisible = (key) => !hidden.value.includes(key)
|
||||
const toggle = (key) => { /* add or remove, then save */ }
|
||||
const setGroupVisible = (groupKeys, visible) => { /* batch, then save */ }
|
||||
const reset = () => { hidden.value = []; save() }
|
||||
|
||||
load()
|
||||
return { hidden, isVisible, toggle, setGroupVisible, reset }
|
||||
}
|
||||
```
|
||||
|
||||
**存储约定**:
|
||||
- 键:`mat3:material-list:columns:v1`
|
||||
- 值:仅存"隐藏的列 key 数组"(新增列默认显示,不受旧偏好影响)
|
||||
- 操作列 key `__actions__`(或类似)不纳入可切换列表
|
||||
|
||||
### 5.2 `MaterialManage.vue` 改造
|
||||
|
||||
#### 工具栏(两行,紧凑)
|
||||
|
||||
**第一行**(常用筛选 + 主操作,`gap: 8px`):
|
||||
- 材料名称 input(width 180)`@keyup.enter="loadMaterials"`
|
||||
- 状态 select(140)`@change="loadMaterials"`
|
||||
- 材料子类 select(160)`@change`
|
||||
- 品牌远程 select(160)`@change`
|
||||
- "高级筛选"切换按钮(text 按钮 + 箭头图标;若存在任意高级筛选值则右上角红点徽标)
|
||||
- `.toolbar-spacer`(flex: 1)
|
||||
- 导入 / 导出 / 新增材料 / **列设置**(齿轮图标按钮)
|
||||
|
||||
删除原"查询"按钮(自动触发)。
|
||||
|
||||
**第二行**(`v-show="advancedOpen"`,`gap: 8px`,`flex-wrap: wrap`):
|
||||
- 材料大类 select、阶段 select、重要等级 select、合作模式 select(`@change`)
|
||||
- 细分种类 input、落地项目 input、对接人 input、经办人 input(`@keyup.enter`)
|
||||
- 供应商 select(远程搜索,`@change`)
|
||||
- 成本比较 min / max 双 InputNumber(`@change`)
|
||||
- 综合评分 ≥ N(select 1-3 或 slider,`@change`)
|
||||
- "重置筛选"按钮(清空全部筛选并查询)
|
||||
|
||||
#### 触发逻辑
|
||||
|
||||
所有筛选值变更:`pagination.page = 1` → `loadMaterials()`。
|
||||
折叠"高级筛选"面板**不**清空字段值;仅控制显隐。"重置筛选"清空全部(含常用与高级)。
|
||||
|
||||
#### 表格(数据驱动 + 分级表头)
|
||||
|
||||
```html
|
||||
<el-table :data="materials" border height="100%">
|
||||
<template v-for="group in COLUMN_GROUPS" :key="group.key">
|
||||
<el-table-column
|
||||
v-if="hasVisibleInGroup(group.key)"
|
||||
:label="group.label"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="col in visibleColumnsOfGroup(group.key)"
|
||||
:key="col.key"
|
||||
:prop="col.key"
|
||||
:label="col.label"
|
||||
:min-width="col.minWidth"
|
||||
:width="col.width"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip"
|
||||
>
|
||||
<template v-if="col.slot" #default="scope">
|
||||
<AdvantageCell v-if="col.slot === 'advantage'" :value="scope.row[col.key]" />
|
||||
<AppSceneCell v-else-if="col.slot === 'appScene'" :value="scope.row[col.key]" />
|
||||
<StarsCell v-else-if="col.slot === 'stars'" :value="scope.row[col.key]" />
|
||||
</template>
|
||||
<template v-else-if="col.formatter" #default="scope">
|
||||
{{ col.formatter(scope.row) ?? '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<!-- 原有操作按钮,不动 -->
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
```
|
||||
|
||||
特殊单元格组件(内嵌或单独 SFC 均可):
|
||||
- **AdvantageCell / AppSceneCell**:接收 JSON 数组,渲染 `el-tag` 列表;空则 "-"
|
||||
- **StarsCell**:接收 1-3 数字,渲染对应数量的黄色 ⭐,空显示 "-"
|
||||
|
||||
整组隐藏(`hasVisibleInGroup = false`)时整个分组表头消失。
|
||||
|
||||
#### 列设置 popover
|
||||
|
||||
齿轮图标按钮触发 `el-popover`(宽 ~420px,最大高度带滚动):
|
||||
|
||||
- 按 `COLUMN_GROUPS` 分节,每节标题 + "全选 / 全不选" 小按钮
|
||||
- 每列渲染 `el-checkbox`,绑定到 `isVisible(col.key)`,`@change` 调用 `toggle(col.key)`
|
||||
- 底部一个"恢复默认"按钮调用 `reset()`
|
||||
|
||||
### 5.3 详情页分块(`MaterialForm.vue` view 模式)
|
||||
|
||||
将当前单个 `el-descriptions` 拆为三个,顺序与列表分组一致:
|
||||
|
||||
```html
|
||||
<template v-if="mode === 'view'">
|
||||
<el-descriptions title="材料信息" :column="2" border>
|
||||
<!-- A 组字段 -->
|
||||
</el-descriptions>
|
||||
<el-descriptions title="品牌与供应商" :column="2" border class="mt-4">
|
||||
<!-- B 组字段 -->
|
||||
</el-descriptions>
|
||||
<el-descriptions title="案例信息" :column="1" border class="mt-4">
|
||||
<!-- C 组字段,cases 单独一行 white-space: pre-wrap -->
|
||||
</el-descriptions>
|
||||
</template>
|
||||
```
|
||||
|
||||
星级字段使用 ⭐ 渲染;JSON 数组用 `el-tag` 列表;宣传册图片独占一行。
|
||||
|
||||
edit 模式不变。
|
||||
|
||||
## 6. 数据流
|
||||
|
||||
```
|
||||
用户动作(change / enter / checkbox)
|
||||
└─→ filters reactive 更新 / hidden 数组更新
|
||||
├─→ loadMaterials() → GET /api/materials/?filters → 更新 materials
|
||||
└─→ localStorage.setItem('mat3:material-list:columns:v1', JSON)
|
||||
```
|
||||
|
||||
## 7. 错误与边界
|
||||
|
||||
- **localStorage 损坏**(JSON.parse 失败):捕获异常,回退到 `hidden = []`,覆盖写回合法值
|
||||
- **列结构演进**:后续新增列时,因只存"隐藏列",新列对老用户默认可见;如需强制重置偏好,bump 存储键版本号为 `v2`
|
||||
- **空值**:所有 formatter 与 slot 遇 `null/undefined/[]` 显示 `-`
|
||||
- **整组隐藏**:通过 `hasVisibleInGroup()` 计算属性剔除整个分组表头
|
||||
|
||||
## 8. 测试 / 验证清单
|
||||
|
||||
- [ ] 后端 `/api/materials/` 返回新增字段;新增筛选参数工作
|
||||
- [ ] 前端列表三组表头正确渲染,列顺序符合设计
|
||||
- [ ] `advantage / application_scene / stars` 特殊渲染正确
|
||||
- [ ] 列设置 popover 勾选立即生效,刷新后偏好恢复
|
||||
- [ ] 全部隐藏某组时整组表头消失;"恢复默认"还原全显
|
||||
- [ ] 所有下拉 `@change` 自动触发查询;输入框 Enter 触发查询
|
||||
- [ ] 高级筛选收起后值保留;有值时红点标记显示;"重置筛选"清全部
|
||||
- [ ] 详情页三块分节展示
|
||||
- [ ] 现有导入/导出/新增/编辑/审批/分页流程不受影响
|
||||
- [ ] 工具栏按钮紧凑、无换行
|
||||
|
||||
## 9. 非目标(YAGNI)
|
||||
|
||||
- 列拖拽排序
|
||||
- 筛选条件持久化
|
||||
- 分页默认值调整
|
||||
- 品牌字段扩展
|
||||
- 列宽持久化
|
||||
- 表格导出仅可见列(导出使用现有后端接口,所有字段)
|
||||
|
||||
## 10. 文件清单
|
||||
|
||||
**后端**
|
||||
- `backend/apps/material/serializers.py`(改 `MaterialListSerializer`)
|
||||
- `backend/apps/material/views.py` 或 `filters.py`(追加筛选字段)
|
||||
|
||||
**前端新增**
|
||||
- `frontend/src/views/material/materialColumns.js`
|
||||
- `frontend/src/composables/useColumnPreferences.js`
|
||||
- (可选)`frontend/src/views/material/cells/AdvantageCell.vue` 等,亦可内嵌
|
||||
|
||||
**前端改动**
|
||||
- `frontend/src/views/MaterialManage.vue`
|
||||
- `frontend/src/views/material/MaterialForm.vue`(view 模式)
|
||||
Loading…
Reference in New Issue