From c9625f6bfd992e4a8ea2c43570c593b40bb87c3c Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Wed, 1 Apr 2026 17:55:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9B=BD=E9=99=85=E8=BF=9B=E6=B8=AF?= =?UTF-8?q?=E8=88=B1=E5=8D=95=E7=BC=96=E8=BE=91=E9=A1=B5=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E6=A1=86=E5=9B=9E=E5=A1=AB=E5=8F=8A=E7=A7=BB=E9=99=A4404?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 DictUtils checkedValue 机制回填编辑模式下拉框选中项 - 移除已404的 searchCargoType 接口调用 - PadDataLayoutNew 增加 updateSpinnerSilently 防止 adapter 重建覆盖值 - CLAUDE.md 补充编辑表单 SPINNER 回填规范 Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 53 +++++++++ .../ui/weight/data/layout/PadDataLayoutNew.kt | 31 ++++-- .../gjj/viewModel/GjjManifestAddViewModel.kt | 105 +++++++++++------- 3 files changed, 141 insertions(+), 48 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a1cec22..8106b8a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -861,6 +861,59 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志 --- +## 编辑表单下拉框(SPINNER)回填规范 + +编辑页面(DetailsPageType.Modify)中,下拉框需要根据已有数据自动选中对应项。**必须使用 `DictUtils` 的 `checkedValue` 参数**,禁止依赖组件自动匹配 value。 + +### 原理 + +`DictUtils` 的 `handleCallBack` 会将 `checkedValue` 匹配的 `KeyValue` 置于列表首位。`PadDataLayoutNew` 的 SPINNER 默认显示列表第 0 项,因此匹配项自动成为选中项,无需额外设置 selectedIndex。 + +### 标准做法(参考 `GjjManifestDetailsViewModel`、`GjjManifestAddViewModel`) + +1. **字典加载必须在编辑数据加载之后**(不能放在 `init` 中),确保 `checkedValue` 可用 +2. **编辑模式传入 `checkedValue`**,新增模式传 `null` +3. **编辑模式不预置空 `KeyValue("", "")`**(否则空项会占据首位,覆盖 checkedValue 排序) + +```kotlin +fun initOnCreated(intent: Intent) { + // 1. 先解析页面类型和编辑数据 + if (pageType.value == DetailsPageType.Modify) { + loadManifestFromBean(bean) // 设置 agent.value、specialCode.value 等 + } + // 2. 再加载字典列表(此时 checkedValue 已可用) + loadDictLists() +} + +private fun loadDictLists() { + val isModify = pageType.value == DetailsPageType.Modify + + DictUtils.getXxxList( + addAll = false, + checkedValue = if (isModify) field.value else null // 编辑模式传值,新增传 null + ) { + xxxList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it) + } +} +``` + +### checkedValue 取值规则 + +提交时用的哪个字段值,`checkedValue` 就传哪个。对照 `toKeyValue()` 的 `value` 字段确认匹配: + +| DictUtils 方法 | KeyValue.value 来源 | checkedValue 示例 | +|---|---|---| +| 通用(`handleCallBack`) | `DictBean.code` | `manifest.agentCode`(如 "SFINT") | +| `getShouYunPackageTypeList` | `PackageBean.name` | `manifest.packageType`(如 "木框") | + +### 禁止做法 + +- ❌ 在 `init` 中加载字典(编辑数据尚未可用,无法传 `checkedValue`) +- ❌ 依赖 `PadDataLayoutNew` 的 `value` 属性自动匹配列表(Spinner adapter 重建时 `onItemSelected` 回调会覆盖已有值) +- ❌ 编辑模式下在列表前添加 `KeyValue("", "")`(会干扰 `checkedValue` 置顶排序) + +--- + ## 开发检查清单 ### 新页面开发必做 diff --git a/module_base/src/main/java/com/lukouguoji/module_base/ui/weight/data/layout/PadDataLayoutNew.kt b/module_base/src/main/java/com/lukouguoji/module_base/ui/weight/data/layout/PadDataLayoutNew.kt index 93dbe7d..eecc6fc 100644 --- a/module_base/src/main/java/com/lukouguoji/module_base/ui/weight/data/layout/PadDataLayoutNew.kt +++ b/module_base/src/main/java/com/lukouguoji/module_base/ui/weight/data/layout/PadDataLayoutNew.kt @@ -97,7 +97,7 @@ class PadDataLayoutNew : FrameLayout { et.hint = value tv.hint = value - bindAdapter(spinner, list, hint) + updateSpinnerSilently { bindAdapter(spinner, list, hint) } } var required = false @@ -106,11 +106,29 @@ class PadDataLayoutNew : FrameLayout { tvM.visibility = if (value) VISIBLE else INVISIBLE } + private val spinnerCallback = object : IOnSpinnerSelected { + override fun onSelected(position: Int) { + value = list.getOrNull(position)?.value ?: "" + refreshCallBack?.invoke() + } + } + + private val restoreListenerRunnable = Runnable { + bindOnSelected(spinner, spinnerCallback) + } + + private fun updateSpinnerSilently(block: () -> Unit) { + spinner.onItemSelectedListener = null + spinner.removeCallbacks(restoreListenerRunnable) + block() + onValueSet() + spinner.post(restoreListenerRunnable) + } + var list = emptyList() set(value) { field = value - bindAdapter(spinner, value, hint) - onValueSet() + updateSpinnerSilently { bindAdapter(spinner, value, hint) } } var icon: Any? = null @@ -183,12 +201,7 @@ class PadDataLayoutNew : FrameLayout { et.doOnTextChanged { text, _, _, _ -> value = text.toString() } - bindOnSelected(spinner, object : IOnSpinnerSelected { - override fun onSelected(position: Int) { - value = list.getOrNull(position)?.value ?: "" - refreshCallBack?.invoke() - } - }) + bindOnSelected(spinner, spinnerCallback) // 监听输入框焦点变化 com.lukouguoji.module_base.adapter.setOnFocusChangeListener( et, object : IOnFocusChangeListener { diff --git a/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/GjjManifestAddViewModel.kt b/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/GjjManifestAddViewModel.kt index 7421eec..9aa608a 100644 --- a/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/GjjManifestAddViewModel.kt +++ b/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/GjjManifestAddViewModel.kt @@ -153,51 +153,13 @@ class GjjManifestAddViewModel : BaseViewModel() { val specialCodeList = MutableLiveData>() val specialCode = MutableLiveData("") - // 货物类型 - val goodsTypeList = MutableLiveData>() + // 货物类型(无下拉列表,仅用于编辑模式回传) val goodsType = MutableLiveData("") // 运单类型 val waybillTypeList = MutableLiveData>() val waybillType = MutableLiveData("") - init { - DictUtils.getIntImpAgentList(addAll = false) { - agentList.postValue(listOf(KeyValue("", "")) + it) - } - DictUtils.getSpecialCodeList(addAll = false, flag = 1, ieFlag = "") { - val list = arrayListOf() - it.find { b -> b.key.contains("普通货物") }?.let { b -> - list.add(b) - } - list.addAll(it.filter { b -> !b.key.contains("普通货物") }) - specialCodeList.postValue(list) - } - DictUtils.getBusinessTypeList(addAll = false) { - businessTypeList.postValue(it) - // 新增模式下默认选中"普通货物运输" - if (pageType.value == DetailsPageType.Add && businessType.value.isNullOrEmpty()) { - it.find { b -> b.key.contains("普通货物运输") }?.let { b -> - businessType.postValue(b.value) - } - } - } - DictUtils.getShouYunPackageTypeList { - packageTypeList.postValue(listOf(KeyValue("", "")) + it) - } - DictUtils.getGjjGoodsTypeList(addAll = false) { - goodsTypeList.postValue(it) - } - DictUtils.getWaybillTypeList(type = "II", addAll = false) { - val list = arrayListOf() - it.find { b -> b.key.contains("干线") }?.let { b -> - list.add(b) - } - list.addAll(it.filter { b -> !b.key.contains("干线") }) - waybillTypeList.postValue(list) - } - } - /** * 初始化(从Intent获取参数) */ @@ -235,6 +197,71 @@ class GjjManifestAddViewModel : BaseViewModel() { loadManifestFromImportBean(bean) } } + + // 加载下拉列表(在编辑数据加载之后,以便使用 checkedValue 将选中项置顶) + loadDictLists() + } + + /** + * 加载下拉列表数据 + * 编辑模式下传入 checkedValue,handleCallBack 会将匹配项置于列表首位, + * Spinner 默认显示首项即完成回填 + */ + private fun loadDictLists() { + val isModify = pageType.value == DetailsPageType.Modify + + DictUtils.getIntImpAgentList( + addAll = false, + checkedValue = if (isModify) agent.value else null + ) { + agentList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it) + } + + DictUtils.getSpecialCodeList( + addAll = false, flag = 1, ieFlag = "", + checkedValue = if (isModify) specialCode.value else null + ) { + if (isModify) { + specialCodeList.postValue(it) + } else { + val list = arrayListOf() + it.find { b -> b.key.contains("普通货物") }?.let { b -> list.add(b) } + list.addAll(it.filter { b -> !b.key.contains("普通货物") }) + specialCodeList.postValue(list) + } + } + + DictUtils.getBusinessTypeList( + addAll = false, + checkedValue = if (isModify) businessType.value else null + ) { + businessTypeList.postValue(it) + if (!isModify && businessType.value.isNullOrEmpty()) { + it.find { b -> b.key.contains("普通货物运输") }?.let { b -> + businessType.postValue(b.value) + } + } + } + + DictUtils.getShouYunPackageTypeList( + checkedValue = if (isModify) packageType.value else null + ) { + packageTypeList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it) + } + + DictUtils.getWaybillTypeList( + type = "II", addAll = false, + checkedValue = if (isModify) waybillType.value else null + ) { + if (isModify) { + waybillTypeList.postValue(it) + } else { + val list = arrayListOf() + it.find { b -> b.key.contains("干线") }?.let { b -> list.add(b) } + list.addAll(it.filter { b -> !b.key.contains("干线") }) + waybillTypeList.postValue(list) + } + } } /**