提交
This commit is contained in:
1 parent
6dc8cfb2c1
commit
d26fb23fcf
181 files changed
+25727
-24557
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,190 @@
|
||||
# 字典下拉手输即入库(DictSelect + ensure)— 设计文档
|
||||
|
||||
> **日期**: 2026-08-21
|
||||
> **状态**: 已实现
|
||||
> **仓库**: shanxia2.0(`backend/` + `web-admin/`)
|
||||
> **方案**: 方案 1 — 通用 `DictSelect` + 通用 `dict-data/ensure` API
|
||||
> **影响范围**: web-admin 报价/合同/运输计划/客户/线索等字典可创建下拉;系统字典 `sys_dict_data`
|
||||
|
||||
## 背景
|
||||
|
||||
客户希望:字典表驱动的下拉在支持手动输入新值时,能把新值写入字典,后续可直接选择,不必每次手输。
|
||||
|
||||
现状:
|
||||
|
||||
- 包装规格、单位、园区等已开 `allow-create`,注释写「写入字典」,但 `handlePackageSpecChange` 等**仅改行内字段,未落库**。
|
||||
- 送货方式(`crm_shfs`)为主路径字典下拉,**未**开 `allow-create`。
|
||||
- 值班排班已有 `POST /web/admin/duty/dict/ensure` 可复用思路,但是值班专用白名单。
|
||||
|
||||
## 已确认决策
|
||||
|
||||
| # | 决策 |
|
||||
|---|------|
|
||||
| D1 | 范围 **C**:所有「已开 `allow-create` 且选项来自字典」的字段,统一改造 |
|
||||
| D2 | **送货方式**与包装规格同一套:补 `allow-create` + ensure |
|
||||
| D3 | 写入时机:**选中/确认后立刻写入**(非等业务单据保存) |
|
||||
| D4 | 新值须 **二次确认** 后再 ensure(A') |
|
||||
| D5 | 权限:能打开该业务页的登录用户均可 ensure;后端 **字典类型白名单** 限权 |
|
||||
| D6 | 删除:**仅本会话刚新增**的项在下拉旁显示删除;预置项不可删;后端仅允许软删当前用户创建的项 |
|
||||
| D7 | 实现路径:通用 `DictSelect` + 通用 ensure/ensure-remove API(方案 1) |
|
||||
| D8 | 合同单位字段现无 `allow-create`,与报价对齐时**一并加上** DictSelect |
|
||||
|
||||
## 白名单(首批)
|
||||
|
||||
| dict_type | 用途 |
|
||||
|-----------|------|
|
||||
| `crm_product_gg` | 包装规格 |
|
||||
| `crm_shfs` | 送货方式 |
|
||||
| `crm_product_unit` | 单位 |
|
||||
| `crm_customer_park` | 所在园区 |
|
||||
|
||||
后续扩容:改前后端同一白名单常量即可。值班三类(`duty_*`)可后续迁入,**本次不强迁值班页**。
|
||||
|
||||
## 方案选择
|
||||
|
||||
**采用方案 1**:`DictSelect` + `POST /web/admin/dict-data/ensure`(及 remove)。
|
||||
|
||||
未采用:
|
||||
|
||||
- 各页手写 `el-select` + 工具函数(易行为不一致)
|
||||
- 直接调字典管理「创建」接口(权限与白名单不匹配)
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
业务页
|
||||
└── <DictSelect dict-type="crm_shfs" v-model="..." />
|
||||
│
|
||||
├─ options ← dictStore.getOptions(dictType)
|
||||
├─ 新值 → ElMessageBox 确认 → dictStore.ensure
|
||||
│ → POST /web/admin/dict-data/ensure
|
||||
│ → upsert 本地 dictMap + sessionCreated
|
||||
└─ 本会话新增 → option 旁删除 → dictStore.ensureRemove
|
||||
→ 软删 + 移出 dictMap / sessionCreated
|
||||
```
|
||||
|
||||
| 层 | 职责 |
|
||||
|----|------|
|
||||
| **DictSelect** | 唯一 UI 入口;确认、loading、会话删除按钮 |
|
||||
| **dictStore** | options、ensure、ensureRemove、sessionCreated、本地 upsert/remove |
|
||||
| **backend** | 白名单校验、按 label 复用或插入、软删且仅允许当前用户创建的项 |
|
||||
|
||||
## 后端接口
|
||||
|
||||
### `POST /web/admin/dict-data/ensure`
|
||||
|
||||
鉴权:管理端已登录用户(与业务页一致),**不**要求字典管理菜单权限。
|
||||
|
||||
| 入参 | 说明 |
|
||||
|------|------|
|
||||
| `dictType` | 必须在白名单 |
|
||||
| `label` | trim 后非空 |
|
||||
| `preferredValue?` | 可选;缺省则 `dict_value = dict_label`(对齐现有 `crm_shfs` 等) |
|
||||
|
||||
行为:
|
||||
|
||||
1. `dictType` 不在白名单 → 400
|
||||
2. 同租户、未删除、同 `dict_type` 下已有相同 `dict_label` → 返回已有项,`created: false`
|
||||
3. 否则插入 `sys_dict_data`(`tenant_no` 取当前用户),返回 `created: true`
|
||||
|
||||
响应:`{ label, value, created: boolean }`
|
||||
|
||||
可抽取与 `duty_schedule.ensure_dict_data` 同类的通用服务(白名单入参化);值班接口可继续薄封装或后续切换。
|
||||
|
||||
### `POST /web/admin/dict-data/ensure-remove`(或等价 DELETE)
|
||||
|
||||
| 入参 | 说明 |
|
||||
|------|------|
|
||||
| `dictType` | 白名单 |
|
||||
| `value` | `dict_value` |
|
||||
|
||||
行为:
|
||||
|
||||
1. 白名单校验
|
||||
2. 仅当记录存在、未删除、且 `create_by == 当前用户` 时允许软删(`del_flag=1`)
|
||||
3. 否则 400(防止删预置项或他人创建项)
|
||||
|
||||
前端「仅本会话显示删除」是体验约束;后端以 `create_by` 兜底。
|
||||
|
||||
## 前端设计
|
||||
|
||||
### `dictStore` 增补
|
||||
|
||||
- `sessionCreated: Record<string, Set<string>>`(dictType → values)
|
||||
- `ensure(dictType, label): Promise<{ label, value, created }>`
|
||||
- `ensureRemove(dictType, value): Promise<void>`
|
||||
- `upsertLocal` / `removeLocal`:更新 `dictMap`,避免每次全量 `loadAllDict`
|
||||
- 仅当 `created === true` 时写入 `sessionCreated`
|
||||
- 刷新页面 / `clearDict` 时清空 `sessionCreated`
|
||||
|
||||
### 组件 `web-admin/src/components/DictSelect/index.vue`
|
||||
|
||||
| Prop | 说明 |
|
||||
|------|------|
|
||||
| `dictType` | 必填 |
|
||||
| `modelValue` | v-model |
|
||||
| `allowCreate` | 默认 true |
|
||||
| `clearable` / `filterable` / `placeholder` / `size` / `disabled` | 透传 |
|
||||
|
||||
行为:
|
||||
|
||||
1. 选项来自 `dictStore.getOptions(dictType)`
|
||||
2. change 时若值不在已有 options → 确认框;取消则不更新 v-model
|
||||
3. 确认后 ensure;成功则 v-model = 返回 value,并刷新 options
|
||||
4. `sessionCreated` 中的项在 option 槽显示删除图标;确认后 ensureRemove
|
||||
5. ensure/删除期间 loading,防重复提交
|
||||
6. 首期不做 multiple
|
||||
|
||||
业务页**禁止**直接调 ensure API;一律用 `DictSelect`。
|
||||
|
||||
### 改造清单
|
||||
|
||||
| 界面 | 文件 | 字段 |
|
||||
|------|------|------|
|
||||
| 报价单 | `QuoteFormPanel.vue` | 单位、包装规格、送货方式 |
|
||||
| 合同弹窗 | `ContractFormDialog.vue` | 单位、包装规格、送货方式 |
|
||||
| 合同独立页 | `contract/form.vue` | 同上 |
|
||||
| 运输计划 | `PlPlanFormProductTable.vue` | 单位、包装规格、送货方式 |
|
||||
| 客户 | `CustomerFormDialog.vue` | 所在园区 |
|
||||
| 线索 | `clue/form.vue` | 所在园区 |
|
||||
|
||||
删除空壳 `handlePackageSpecChange` / `handleUnitChange` 中「仅赋值」逻辑(若无其它职责)。
|
||||
|
||||
### 不在范围
|
||||
|
||||
- 非字典 `allow-create`:维保类型、尿素单位、加油站、证件名、`DbCrmCustomerSelect`
|
||||
- 旧版 `pl/plan/form.vue` 写死送货方式
|
||||
- 强制迁移值班 ensure UI
|
||||
- 字典管理页大改(仍可手动维护预置项)
|
||||
|
||||
## 错误与边界
|
||||
|
||||
- 空串 / 纯空格:不调接口
|
||||
- 用户取消确认:不写入、不选中
|
||||
- ensure / 删除失败:`ElMessage.error`,状态回滚
|
||||
- 历史业务数据保留原文案;软删后下拉不再出现,展示回退为原文案
|
||||
- 合同「自提」条款仍按存库文案匹配现有逻辑,不因本功能改变匹配规则
|
||||
|
||||
## 如何保证 web-admin 改造一致
|
||||
|
||||
1. **单一入口**:仅 `DictSelect`
|
||||
2. **白名单双检**:前端常量 + 后端拒绝
|
||||
3. **对照表验收**:上表每个字段走一遍手输 → 确认 → 可选 → 会话可删 → 刷新无删按钮
|
||||
4. **Code review 门禁**:新增字典可创建下拉必须用 `DictSelect`,禁止再抄 `allow-create` 空壳 handler
|
||||
|
||||
## 测试要点
|
||||
|
||||
1. 手输新包装规格 → 确认 → 立刻在下拉与字典中可见
|
||||
2. 取消确认 → 不入库
|
||||
3. 报价/合同/运输计划互开 → 能选到同一新值
|
||||
4. 本会话删除 → 软删且下拉消失
|
||||
5. 预置「自提」「一票制送到」无删除按钮;接口无法删预置
|
||||
6. 刷新后会话删除按钮消失
|
||||
7. 非白名单 dictType → 400
|
||||
8. 客户/线索园区字段行为一致
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不做跨用户删除治理 UI
|
||||
- 不做 DictSelect 多选首期
|
||||
- 不清理历史已手输但未入字典的脏业务数据(可另开任务)
|
||||
Reference in new issue
Block a user