fix: 国际进港舱单编辑页下拉框回填及移除404接口

- 使用 DictUtils checkedValue 机制回填编辑模式下拉框选中项
- 移除已404的 searchCargoType 接口调用
- PadDataLayoutNew 增加 updateSpinnerSilently 防止 adapter 重建覆盖值
- CLAUDE.md 补充编辑表单 SPINNER 回填规范

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 17:55:19 +08:00
parent 67b2dc6d71
commit c9625f6bfd
3 changed files with 141 additions and 48 deletions

View File

@@ -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` 置顶排序)
---
## 开发检查清单
### 新页面开发必做

View File

@@ -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<KeyValue>()
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 {

View File

@@ -153,51 +153,13 @@ class GjjManifestAddViewModel : BaseViewModel() {
val specialCodeList = MutableLiveData<List<KeyValue>>()
val specialCode = MutableLiveData("")
// 货物类型
val goodsTypeList = MutableLiveData<List<KeyValue>>()
// 货物类型(无下拉列表,仅用于编辑模式回传)
val goodsType = MutableLiveData("")
// 运单类型
val waybillTypeList = MutableLiveData<List<KeyValue>>()
val waybillType = MutableLiveData("")
init {
DictUtils.getIntImpAgentList(addAll = false) {
agentList.postValue(listOf(KeyValue("", "")) + it)
}
DictUtils.getSpecialCodeList(addAll = false, flag = 1, ieFlag = "") {
val list = arrayListOf<KeyValue>()
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<KeyValue>()
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()
}
/**
* 加载下拉列表数据
* 编辑模式下传入 checkedValuehandleCallBack 会将匹配项置于列表首位,
* 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<KeyValue>()
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<KeyValue>()
it.find { b -> b.key.contains("干线") }?.let { b -> list.add(b) }
list.addAll(it.filter { b -> !b.key.contains("干线") })
waybillTypeList.postValue(list)
}
}
}
/**