feat(form): 实现餐品选择后自动加载食材功能

- 添加 NutFoodOptionVO 模型导入并增加 foodOptionsData 缓存列表
- 新增 ingredientsField 表单字段用于包含食材动态加载
- 将食材名称字段类型从 DROPDOWN 改为 FIXED 类型并移除固定选项
- 实现餐品选中后根据 position 从 compositions 加载包含食材逻辑
- 在 DictType 中新增 position 属性用于索引定位
- 在 FormField 中新增 onDropdownSelected 回调函数属性
- 更新 FormFieldAdapter 中触发选中回调并在日期字段注释中调整说明
- 修改 IngredientRowAdapter 中根据字段类型区分 FIXED 和其他类型处理
- 添加 extraApiKeys 到 extraValues 映射逻辑并更新下拉选中回调
- 调整 list_item_ingredient_row.xml 中食材名称布局去除右侧边距和下拉箭头
- 注释掉 NetViewModelV2 中的 getFoodOptions 方法(暂未使用)
This commit is contained in:
2026-06-01 20:05:00 +08:00
parent a25ee7d987
commit 3884738911
7 changed files with 106 additions and 41 deletions
@@ -143,6 +143,8 @@ class FormFieldAdapter(
item.extraApiKeys.forEach { (apiKey, dictField) ->
item.extraValues[apiKey] = dictType[dictField]
}
// 触发选中回调
item.onDropdownSelected?.invoke(dictType)
}.also {
it.bgLayout = b.flDropdown
it.showAsDropDown(v)
@@ -166,7 +168,7 @@ class FormFieldAdapter(
set(year, month, dayOfMonth, 0, 0, 0)
set(Calendar.MILLISECOND, 0)
}
// value 存显示格式,valueId 存 ISO 格式供提交使用
// value 存日期显示格式,供提交使用
item.value = displayDateFormat.format(selected.time)
b.tvDateValue.text = item.value
},
@@ -215,7 +217,7 @@ class FormFieldAdapter(
extraApiKeys = field.extraApiKeys,
required = field.required,
hint = field.hint,
options = field.options,
options = field.options
)
}.toMutableList()
item.childrenRows.add(newRow)
@@ -62,13 +62,22 @@ class IngredientRowAdapter(
b.etTraceCode.tag = watcher
}
// 食材名称 - DROPDOWN
// 食材名称
materNameField?.let { field ->
b.tvMaterName.hint = field.hint.ifBlank { "请选择${field.label}" }
b.tvMaterName.text = field.value.ifBlank { null }
b.flMaterName.setOnClickListener { v ->
v.hideKeyboard()
showDropdown(b.flMaterName, field, b.tvMaterName, holder.getAdapterPosition(), 1)
when (field.type) {
com.shuwei.dish.match.model.FieldType.FIXED -> {
b.flMaterName.setOnClickListener { v -> v.hideKeyboard() }
b.flMaterName.isClickable = false
b.tvMaterName.text = field.value
}
else -> {
b.tvMaterName.hint = field.hint.ifBlank { "请选择${field.label}" }
b.tvMaterName.text = field.value.ifBlank { null }
b.flMaterName.setOnClickListener { v ->
v.hideKeyboard()
showDropdown(b.flMaterName, field, b.tvMaterName, holder.getAdapterPosition(), 1)
}
}
}
}
@@ -123,6 +132,12 @@ class IngredientRowAdapter(
) { dictType ->
field.value = dictType.value ?: ""
valueView.text = field.value
// 将 extraApiKeys 映射值写入 extraValues
field.extraValues.clear()
field.extraApiKeys.forEach { (apiKey, dictField) ->
field.extraValues[apiKey] = dictType[dictField]
}
field.onDropdownSelected?.invoke(dictType)
// 回调通知外部更新
onChildClick(rowPosition, childIndex, field)
}.also {
@@ -10,7 +10,8 @@ import kotlinx.parcelize.Parcelize
data class DictType(
val id: String? = null,
val value: String? = null,
val type: String = ""
val type: String = "",
var position: Int = 0
) : Parcelable {
/**
@@ -47,5 +47,6 @@ data class FormField(
val extraValues: MutableMap<String, String> = mutableMapOf(),
val children: List<FormField>? = null,
val childAddLabel: String? = null,
val childrenRows: MutableList<MutableList<FormField>> = mutableListOf()
val childrenRows: MutableList<MutableList<FormField>> = mutableListOf(),
var onDropdownSelected: ((DictType) -> Unit)? = null
)
@@ -72,12 +72,12 @@ class NetViewModelV2(
private val _foodOptionsState = MutableStateFlow<UiState<Page<NutFoodOptionVO>?>>(UiState.Idle)
val foodOptionsState: StateFlow<UiState<Page<NutFoodOptionVO>?>> = _foodOptionsState.asStateFlow()
fun getFoodOptions(keyword: String? = null, pageNum: Int = 1, pageSize: Int = 10) {
viewModelScope.launch {
_foodOptionsState.value = UiState.Loading
_foodOptionsState.value = repository.getFoodOptions(keyword, pageNum, pageSize)
}
}
// fun getFoodOptions(keyword: String? = null, pageNum: Int = 1, pageSize: Int = 10) {
// viewModelScope.launch {
// _foodOptionsState.value = UiState.Loading
// _foodOptionsState.value = repository.getFoodOptions(keyword, pageNum, pageSize)
// }
// }
/**
* 字典项下拉(回调版本),不依赖 StateFlow,适合多次不同 dictType 的场景
@@ -10,6 +10,7 @@ import com.shuwei.dish.match.databinding.ActivityCleanPackBinding
import com.shuwei.dish.match.model.DictType
import com.shuwei.dish.match.model.FieldType
import com.shuwei.dish.match.model.FormField
import com.shuwei.dish.match.model.NutFoodOptionVO
import com.shuwei.dish.match.net.NetViewModelV2
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.ext.gone
@@ -47,8 +48,12 @@ class CleanPackActivity : BaseActivity() {
private var deviceField: FormField? = null
private var foodOptionsField: FormField? = null
private var packagingSpecField: FormField? = null
private var ingredientsField: FormField? = null
private var traceCodeJob: Job? = null
// 缓存餐品选项完整数据,用于选择后获取 compositions
private val foodOptionsData = mutableListOf<NutFoodOptionVO>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
statusBarDarkFont(enable = true)
@@ -266,14 +271,10 @@ class CleanPackActivity : BaseActivity() {
hint = "溯源码"
),
FormField(
label = "食材名称", type = FieldType.DROPDOWN,
label = "食材名称",
type = FieldType.FIXED,
extraApiKeys = mapOf("materId" to "id", "ingredientName" to "value"),
required = true,
options = mutableListOf(
DictType(id = "1", value = "土豆", type = "土豆"),
DictType(id = "2", value = "鸡肉", type = "鸡肉"),
DictType(id = "3", value = "青椒", type = "青椒"),
)
required = true
),
FormField(
label = "分类", type = FieldType.DROPDOWN,
@@ -293,14 +294,54 @@ class CleanPackActivity : BaseActivity() {
hint = "如 120g"
),
)
fields.add(
FormField(
label = "包含食材", type = FieldType.CHILDREN,
required = true,
children = ingredientTemplate,
childAddLabel = "+ 添加食材"
)
ingredientsField = FormField(
label = "包含食材", type = FieldType.CHILDREN,
required = true,
children = ingredientTemplate,
childAddLabel = "+ 添加食材"
)
fields.add(ingredientsField!!)
// 餐品选中后,根据 position 从 compositions 加载包含食材
foodOptionsField!!.onDropdownSelected = { dictType ->
val position = dictType.position
if (position >= 0 && position < foodOptionsData.size) {
val compositions = foodOptionsData[position].compositions ?: emptyList()
val ingredients = ingredientsField!!
ingredients.childrenRows.clear()
for (comp in compositions) {
// 每行复制模板的4个字段,将 composition 数据填充进去
val newRow = ingredients.children?.map { template ->
// 根据 extraApiKeys 判断是否为食材名称字段
val hasMaterId = template.extraApiKeys.containsKey("materId")
val hasIngredientName = template.extraApiKeys.containsKey("ingredientName")
val fieldValue = if (hasIngredientName) comp.ingredientName ?: "" else ""
val fieldExtraValues = mutableMapOf<String, String>()
template.extraApiKeys.forEach { (apiKey, dictField) ->
fieldExtraValues[apiKey] = when (dictField) {
"id" -> comp.materId?.toString() ?: ""
"value" -> comp.ingredientName ?: ""
else -> ""
}
}
FormField(
label = template.label,
type = template.type,
apiKey = template.apiKey,
extraApiKeys = template.extraApiKeys,
required = template.required,
hint = template.hint,
options = template.options,
value = fieldValue,
extraValues = fieldExtraValues
)
}?.toMutableList() ?: mutableListOf()
ingredients.childrenRows.add(newRow)
}
val idx = fields.indexOf(ingredients)
if (idx >= 0) formAdapter.notifyItemChanged(idx)
}
}
// 包装日期
fields.add(
@@ -422,12 +463,17 @@ class CleanPackActivity : BaseActivity() {
onResult = { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.records?.map { vo ->
val records = state.data?.records ?: emptyList()
foodOptionsData.clear()
foodOptionsData.addAll(records)
val options = records.mapIndexed { index, vo ->
DictType(
id = vo.foodId?.toString() ?: "",
value = vo.foodName ?: ""
value = vo.foodName ?: "",
type = vo.foodCode ?: "",
position = index
)
} ?: emptyList()
}
field.options.clear()
field.options.addAll(options)
val index = fields.indexOf(field)
@@ -36,7 +36,6 @@
android:id="@+id/tvMaterName"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="24dp"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
@@ -44,14 +43,15 @@
android:textColor="#FF141428"
android:textColorHint="#FF96A0AA"
android:textSize="16sp" />
<!-- android:layout_marginEnd="24dp"-->
<ImageView
android:layout_width="18dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="6dp"
android:src="@mipmap/ic_triangle_down"
tools:ignore="ContentDescription" />
<!-- <ImageView-->
<!-- android:layout_width="18dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="end|center_vertical"-->
<!-- android:layout_marginEnd="6dp"-->
<!-- android:src="@mipmap/ic_triangle_down"-->
<!-- tools:ignore="ContentDescription" />-->
</FrameLayout>
<!-- 分类 -->