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:
@@ -143,6 +143,8 @@ class FormFieldAdapter(
|
|||||||
item.extraApiKeys.forEach { (apiKey, dictField) ->
|
item.extraApiKeys.forEach { (apiKey, dictField) ->
|
||||||
item.extraValues[apiKey] = dictType[dictField]
|
item.extraValues[apiKey] = dictType[dictField]
|
||||||
}
|
}
|
||||||
|
// 触发选中回调
|
||||||
|
item.onDropdownSelected?.invoke(dictType)
|
||||||
}.also {
|
}.also {
|
||||||
it.bgLayout = b.flDropdown
|
it.bgLayout = b.flDropdown
|
||||||
it.showAsDropDown(v)
|
it.showAsDropDown(v)
|
||||||
@@ -166,7 +168,7 @@ class FormFieldAdapter(
|
|||||||
set(year, month, dayOfMonth, 0, 0, 0)
|
set(year, month, dayOfMonth, 0, 0, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}
|
}
|
||||||
// value 存显示格式,valueId 存 ISO 格式供提交使用
|
// value 存日期显示格式,供提交使用
|
||||||
item.value = displayDateFormat.format(selected.time)
|
item.value = displayDateFormat.format(selected.time)
|
||||||
b.tvDateValue.text = item.value
|
b.tvDateValue.text = item.value
|
||||||
},
|
},
|
||||||
@@ -215,7 +217,7 @@ class FormFieldAdapter(
|
|||||||
extraApiKeys = field.extraApiKeys,
|
extraApiKeys = field.extraApiKeys,
|
||||||
required = field.required,
|
required = field.required,
|
||||||
hint = field.hint,
|
hint = field.hint,
|
||||||
options = field.options,
|
options = field.options
|
||||||
)
|
)
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
item.childrenRows.add(newRow)
|
item.childrenRows.add(newRow)
|
||||||
|
|||||||
@@ -62,13 +62,22 @@ class IngredientRowAdapter(
|
|||||||
b.etTraceCode.tag = watcher
|
b.etTraceCode.tag = watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// 食材名称 - DROPDOWN
|
// 食材名称
|
||||||
materNameField?.let { field ->
|
materNameField?.let { field ->
|
||||||
b.tvMaterName.hint = field.hint.ifBlank { "请选择${field.label}" }
|
when (field.type) {
|
||||||
b.tvMaterName.text = field.value.ifBlank { null }
|
com.shuwei.dish.match.model.FieldType.FIXED -> {
|
||||||
b.flMaterName.setOnClickListener { v ->
|
b.flMaterName.setOnClickListener { v -> v.hideKeyboard() }
|
||||||
v.hideKeyboard()
|
b.flMaterName.isClickable = false
|
||||||
showDropdown(b.flMaterName, field, b.tvMaterName, holder.getAdapterPosition(), 1)
|
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 ->
|
) { dictType ->
|
||||||
field.value = dictType.value ?: ""
|
field.value = dictType.value ?: ""
|
||||||
valueView.text = field.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)
|
onChildClick(rowPosition, childIndex, field)
|
||||||
}.also {
|
}.also {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import kotlinx.parcelize.Parcelize
|
|||||||
data class DictType(
|
data class DictType(
|
||||||
val id: String? = null,
|
val id: String? = null,
|
||||||
val value: String? = null,
|
val value: String? = null,
|
||||||
val type: String = ""
|
val type: String = "",
|
||||||
|
var position: Int = 0
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,5 +47,6 @@ data class FormField(
|
|||||||
val extraValues: MutableMap<String, String> = mutableMapOf(),
|
val extraValues: MutableMap<String, String> = mutableMapOf(),
|
||||||
val children: List<FormField>? = null,
|
val children: List<FormField>? = null,
|
||||||
val childAddLabel: String? = 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)
|
private val _foodOptionsState = MutableStateFlow<UiState<Page<NutFoodOptionVO>?>>(UiState.Idle)
|
||||||
val foodOptionsState: StateFlow<UiState<Page<NutFoodOptionVO>?>> = _foodOptionsState.asStateFlow()
|
val foodOptionsState: StateFlow<UiState<Page<NutFoodOptionVO>?>> = _foodOptionsState.asStateFlow()
|
||||||
|
|
||||||
fun getFoodOptions(keyword: String? = null, pageNum: Int = 1, pageSize: Int = 10) {
|
// fun getFoodOptions(keyword: String? = null, pageNum: Int = 1, pageSize: Int = 10) {
|
||||||
viewModelScope.launch {
|
// viewModelScope.launch {
|
||||||
_foodOptionsState.value = UiState.Loading
|
// _foodOptionsState.value = UiState.Loading
|
||||||
_foodOptionsState.value = repository.getFoodOptions(keyword, pageNum, pageSize)
|
// _foodOptionsState.value = repository.getFoodOptions(keyword, pageNum, pageSize)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典项下拉(回调版本),不依赖 StateFlow,适合多次不同 dictType 的场景
|
* 字典项下拉(回调版本),不依赖 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.DictType
|
||||||
import com.shuwei.dish.match.model.FieldType
|
import com.shuwei.dish.match.model.FieldType
|
||||||
import com.shuwei.dish.match.model.FormField
|
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.NetViewModelV2
|
||||||
import com.shuwei.dish.match.net.UiState
|
import com.shuwei.dish.match.net.UiState
|
||||||
import com.shuwei.dish.match.utils.ext.gone
|
import com.shuwei.dish.match.utils.ext.gone
|
||||||
@@ -47,8 +48,12 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
private var deviceField: FormField? = null
|
private var deviceField: FormField? = null
|
||||||
private var foodOptionsField: FormField? = null
|
private var foodOptionsField: FormField? = null
|
||||||
private var packagingSpecField: FormField? = null
|
private var packagingSpecField: FormField? = null
|
||||||
|
private var ingredientsField: FormField? = null
|
||||||
private var traceCodeJob: Job? = null
|
private var traceCodeJob: Job? = null
|
||||||
|
|
||||||
|
// 缓存餐品选项完整数据,用于选择后获取 compositions
|
||||||
|
private val foodOptionsData = mutableListOf<NutFoodOptionVO>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
statusBarDarkFont(enable = true)
|
statusBarDarkFont(enable = true)
|
||||||
@@ -266,14 +271,10 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
hint = "溯源码"
|
hint = "溯源码"
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
label = "食材名称", type = FieldType.DROPDOWN,
|
label = "食材名称",
|
||||||
|
type = FieldType.FIXED,
|
||||||
extraApiKeys = mapOf("materId" to "id", "ingredientName" to "value"),
|
extraApiKeys = mapOf("materId" to "id", "ingredientName" to "value"),
|
||||||
required = true,
|
required = true
|
||||||
options = mutableListOf(
|
|
||||||
DictType(id = "1", value = "土豆", type = "土豆"),
|
|
||||||
DictType(id = "2", value = "鸡肉", type = "鸡肉"),
|
|
||||||
DictType(id = "3", value = "青椒", type = "青椒"),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
label = "分类", type = FieldType.DROPDOWN,
|
label = "分类", type = FieldType.DROPDOWN,
|
||||||
@@ -293,14 +294,54 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
hint = "如 120g"
|
hint = "如 120g"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
fields.add(
|
ingredientsField = FormField(
|
||||||
FormField(
|
label = "包含食材", type = FieldType.CHILDREN,
|
||||||
label = "包含食材", type = FieldType.CHILDREN,
|
required = true,
|
||||||
required = true,
|
children = ingredientTemplate,
|
||||||
children = ingredientTemplate,
|
childAddLabel = "+ 添加食材"
|
||||||
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(
|
fields.add(
|
||||||
@@ -422,12 +463,17 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
onResult = { state ->
|
onResult = { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is UiState.Success -> {
|
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(
|
DictType(
|
||||||
id = vo.foodId?.toString() ?: "",
|
id = vo.foodId?.toString() ?: "",
|
||||||
value = vo.foodName ?: ""
|
value = vo.foodName ?: "",
|
||||||
|
type = vo.foodCode ?: "",
|
||||||
|
position = index
|
||||||
)
|
)
|
||||||
} ?: emptyList()
|
}
|
||||||
field.options.clear()
|
field.options.clear()
|
||||||
field.options.addAll(options)
|
field.options.addAll(options)
|
||||||
val index = fields.indexOf(field)
|
val index = fields.indexOf(field)
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
android:id="@+id/tvMaterName"
|
android:id="@+id/tvMaterName"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
@@ -44,14 +43,15 @@
|
|||||||
android:textColor="#FF141428"
|
android:textColor="#FF141428"
|
||||||
android:textColorHint="#FF96A0AA"
|
android:textColorHint="#FF96A0AA"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
<!-- android:layout_marginEnd="24dp"-->
|
||||||
|
|
||||||
<ImageView
|
<!-- <ImageView-->
|
||||||
android:layout_width="18dp"
|
<!-- android:layout_width="18dp"-->
|
||||||
android:layout_height="wrap_content"
|
<!-- android:layout_height="wrap_content"-->
|
||||||
android:layout_gravity="end|center_vertical"
|
<!-- android:layout_gravity="end|center_vertical"-->
|
||||||
android:layout_marginEnd="6dp"
|
<!-- android:layout_marginEnd="6dp"-->
|
||||||
android:src="@mipmap/ic_triangle_down"
|
<!-- android:src="@mipmap/ic_triangle_down"-->
|
||||||
tools:ignore="ContentDescription" />
|
<!-- tools:ignore="ContentDescription" />-->
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- 分类 -->
|
<!-- 分类 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user