feat(form): 重构净菜包装表单实现动态字段和提交功能

- 移除固定 DTO 类型,改用 Map<String, Any> 接收 API 参数
- 实现动态表单字段配置,支持净菜和餐品两种包装类型
- 添加下拉选项动态加载和自动填充溯源码功能
- 实现表单验证和通用提交逻辑
- 添加回调版本的数据加载方法提高性能
- 优化字段绑定和值映射机制
This commit is contained in:
2026-06-01 16:51:47 +08:00
parent 5d6df58e46
commit 4a2abf6eec
9 changed files with 450 additions and 101 deletions
@@ -137,18 +137,11 @@ class FormFieldAdapter(
popHeight = popHeight
) { dictType ->
item.value = dictType.value ?: ""
item.valueId = dictType.id ?: ""
b.tvDropdownValue.text = item.value
// 将 extraApiKeys 映射的 DictType 字段值写入 extraValues
item.extraValues.clear()
item.extraApiKeys.forEach { (apiKey, dictField) ->
val fieldValue = when (dictField) {
"id" -> dictType.id ?: ""
"value" -> dictType.value ?: ""
"type" -> dictType.type
else -> ""
}
item.extraValues[apiKey] = fieldValue
item.extraValues[apiKey] = dictType[dictField]
}
}.also {
it.bgLayout = b.flDropdown
@@ -175,7 +168,6 @@ class FormFieldAdapter(
}
// value 存显示格式,valueId 存 ISO 格式供提交使用
item.value = displayDateFormat.format(selected.time)
item.valueId = isoFormat.format(selected.time)
b.tvDateValue.text = item.value
},
cal.get(Calendar.YEAR),
@@ -224,7 +216,6 @@ class FormFieldAdapter(
required = field.required,
hint = field.hint,
options = field.options,
submitValueId = field.submitValueId
)
}.toMutableList()
item.childrenRows.add(newRow)
@@ -122,7 +122,6 @@ class IngredientRowAdapter(
popHeight = kotlin.math.min(field.options.size.coerceAtLeast(1), 4) * 50.dp
) { dictType ->
field.value = dictType.value ?: ""
field.valueId = dictType.id ?: ""
valueView.text = field.value
// 回调通知外部更新
onChildClick(rowPosition, childIndex, field)
@@ -11,4 +11,14 @@ data class DictType(
val id: String? = null,
val value: String? = null,
val type: String = ""
) : Parcelable
) : Parcelable {
/**
* 按字段名取值,供 extraApiKeys 映射直接使用
*/
operator fun get(fieldName: String): String = when (fieldName) {
"id" -> id ?: ""
"value" -> value ?: ""
else -> type
}
}
@@ -28,9 +28,7 @@ enum class FieldType {
* @param hidden 是否隐藏,hidden=true 时不渲染该字段
* @param hint 校验失败提示文案,同时作为输入框 hint
* @param options 下拉框选项列表(DROPDOWN 类型使用)
* @param submitValueId 提交时使用 valueId 而非 valueDATE_PICKER 选 ISO 格式时设为 true
* @param value 当前显示值/输入值;DATE_PICKER 存 yyyy-MM-dd 显示格式
* @param valueId DATE_PICKER 存 ISO 格式 yyyy-MM-dd'T'HH:mm:ss
* @param extraValues 下拉选中后按 extraApiKeys 映射存储的提交值,key=接口参数名
* @param children 子字段模板(CHILDREN 类型使用),每行按此模板创建一组字段
* @param childAddLabel 添加按钮文案(CHILDREN 类型使用),如 "+ 添加食材"
@@ -44,10 +42,8 @@ data class FormField(
val required: Boolean = false,
val hidden: Boolean = false,
val hint: String = "",
val options: List<DictType> = emptyList(),
val submitValueId: Boolean = false,
var options: MutableList<DictType> = mutableListOf(),
var value: String = "",
var valueId: String = "",
val extraValues: MutableMap<String, String> = mutableMapOf(),
val children: List<FormField>? = null,
val childAddLabel: String? = null,
@@ -1,8 +1,6 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.model.NutSupCleanPackageDTO
import com.shuwei.dish.match.model.NutSupMealPackageDTO
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
import com.shuwei.dish.match.model.NutCanteenVO
import com.shuwei.dish.match.model.NutDictItemVO
@@ -123,7 +121,7 @@ interface ApiServiceV2 {
@POST
suspend fun addCleanPackage(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/clean-package/add",
@Body param: NutSupCleanPackageDTO
@Body param: Map<String, @JvmSuppressWildcards Any>
): ApiResponse<Unit>
// ========== 四、餐品净菜包装接口 ==========
@@ -134,7 +132,7 @@ interface ApiServiceV2 {
@POST
suspend fun addMealPackage(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/meal-package/add",
@Body param: NutSupMealPackageDTO
@Body param: Map<String, @JvmSuppressWildcards Any>
): ApiResponse<Unit>
}
@@ -8,9 +8,7 @@ import com.shuwei.dish.match.model.NutFoodOptionVO
import com.shuwei.dish.match.model.NutMaterOptionVO
import com.shuwei.dish.match.model.NutProdComboTaskDTO
import com.shuwei.dish.match.model.NutProdComboVideoVO
import com.shuwei.dish.match.model.NutSupCleanPackageDTO
import com.shuwei.dish.match.model.NutSupHygieneIngredientOptionVO
import com.shuwei.dish.match.model.NutSupMealPackageDTO
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -81,6 +79,49 @@ class NetViewModelV2(
}
}
/**
* 字典项下拉(回调版本),不依赖 StateFlow,适合多次不同 dictType 的场景
*/
fun getDictItemsWithCallback(
dictType: String,
onLoading: () -> Unit = {},
onResult: (UiState<List<NutDictItemVO>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getDictItems(dictType))
}
}
/**
* 餐品下拉(回调版本),不依赖 StateFlow,适合多次调用的场景
*/
fun getFoodOptionsWithCallback(
keyword: String? = null,
pageNum: Int = 1,
pageSize: Int = 10,
onLoading: () -> Unit = {},
onResult: (UiState<Page<NutFoodOptionVO>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getFoodOptions(keyword, pageNum, pageSize))
}
}
/**
* 设备下拉(回调版本),不依赖 StateFlow,适合多次调用的场景
*/
fun getDeviceOptionsWithCallback(
onLoading: () -> Unit = {},
onResult: (UiState<List<NutSupPkgDeviceVO>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getDeviceOptions())
}
}
// 按溯源码查食材信息
private val _ingredientByTraceState = MutableStateFlow<UiState<List<NutSupHygieneIngredientOptionVO>?>>(UiState.Idle)
val ingredientByTraceState: StateFlow<UiState<List<NutSupHygieneIngredientOptionVO>?>> = _ingredientByTraceState.asStateFlow()
@@ -143,7 +184,7 @@ class NetViewModelV2(
private val _addCleanPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
val addCleanPackageState: StateFlow<UiState<Unit?>> = _addCleanPackageState.asStateFlow()
fun addCleanPackage(param: NutSupCleanPackageDTO) {
fun addCleanPackage(param: Map<String, Any>) {
viewModelScope.launch {
_addCleanPackageState.value = UiState.Loading
_addCleanPackageState.value = repository.addCleanPackage(param)
@@ -155,7 +196,7 @@ class NetViewModelV2(
private val _addMealPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
val addMealPackageState: StateFlow<UiState<Unit?>> = _addMealPackageState.asStateFlow()
fun addMealPackage(param: NutSupMealPackageDTO) {
fun addMealPackage(param: Map<String, Any>) {
viewModelScope.launch {
_addMealPackageState.value = UiState.Loading
_addMealPackageState.value = repository.addMealPackage(param)
@@ -7,9 +7,7 @@ import com.shuwei.dish.match.model.NutFoodOptionVO
import com.shuwei.dish.match.model.NutMaterOptionVO
import com.shuwei.dish.match.model.NutProdComboTaskDTO
import com.shuwei.dish.match.model.NutProdComboVideoVO
import com.shuwei.dish.match.model.NutSupCleanPackageDTO
import com.shuwei.dish.match.model.NutSupHygieneIngredientOptionVO
import com.shuwei.dish.match.model.NutSupMealPackageDTO
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
import com.shuwei.dish.match.base.GlobalData
import okhttp3.OkHttpClient
@@ -81,12 +79,12 @@ class RemoteRepositoryV2 {
// ========== 单品净菜包装接口 ==========
suspend fun addCleanPackage(param: NutSupCleanPackageDTO): UiState<Unit?> =
suspend fun addCleanPackage(param: Map<String, Any>): UiState<Unit?> =
safeApiCall { apiService.addCleanPackage(param = param) }
// ========== 餐品净菜包装接口 ==========
suspend fun addMealPackage(param: NutSupMealPackageDTO): UiState<Unit?> =
suspend fun addMealPackage(param: Map<String, Any>): UiState<Unit?> =
safeApiCall { apiService.addMealPackage(param = param) }
}
@@ -1,10 +1,8 @@
package com.shuwei.dish.match.ui
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.flexbox.AlignItems
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexWrap
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FormFieldAdapter
import com.shuwei.dish.match.base.BaseActivity
@@ -12,9 +10,14 @@ 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.utils.ext.dp
import com.shuwei.dish.match.net.NetViewModelV2
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* 净菜包装表单页面,根据类型显示不同的表单字段
@@ -34,6 +37,17 @@ class CleanPackActivity : BaseActivity() {
private lateinit var binding: ActivityCleanPackBinding
private val fields = mutableListOf<FormField>()
private val formAdapter by lazy { FormFieldAdapter(this, fields) }
private val viewModelV2 by lazy { NetViewModelV2() }
private var packType: Int = TYPE_CLEAN
// 字段引用,用于跨方法访问
private var traceCodeField: FormField? = null
private var cleanNameField: FormField? = null
private var packageMethodField: FormField? = null
private var deviceField: FormField? = null
private var foodOptionsField: FormField? = null
private var packagingSpecField: FormField? = null
private var traceCodeJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -42,7 +56,7 @@ class CleanPackActivity : BaseActivity() {
binding = ActivityCleanPackBinding.inflate(layoutInflater)
setContentView(binding.root)
setHeaderBackground()
val packType = intent.getIntExtra(EXTRA_TYPE, TYPE_CLEAN)
packType = intent.getIntExtra(EXTRA_TYPE, TYPE_CLEAN)
// 根据类型设置标题
val title = when (packType) {
@@ -84,7 +98,13 @@ class CleanPackActivity : BaseActivity() {
}
binding.btnSubmit.setOnClickListener {
// TODO: 提交表单数据
hideKeyboard()
if (!validateFields()) return@setOnClickListener
when (packType) {
TYPE_CLEAN -> submitCleanPackage()
TYPE_MEAL -> submitMealPackage()
}
}
}
@@ -94,33 +114,38 @@ class CleanPackActivity : BaseActivity() {
*/
private fun initCleanPackFields() {
fields.clear()
fields.add(
FormField(
label = "溯源码",
type = FieldType.TEXT,
apiKey = "traceCode",
required = true
)
// 溯源码
traceCodeField = FormField(
label = "溯源码",
type = FieldType.TEXT,
apiKey = "traceCode",
required = true
)
fields.add(
FormField(
label = "净菜名称",
type = FieldType.TEXT,
apiKey = "cleanName",
required = true
)
fields.add(traceCodeField!!)
// 净菜名称
cleanNameField = FormField(
label = "净菜名称",
type = FieldType.TEXT,
apiKey = "cleanName",
required = true
)
fields.add(
FormField(
label = "包装方式",
type = FieldType.TEXT,
apiKey = "packageMethod",
required = true
)
fields.add(cleanNameField!!)
// 包装方式(下拉)
packageMethodField = FormField(
label = "包装方式",
type = FieldType.DROPDOWN,
extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"),
required = true
)
fields.add(packageMethodField!!)
fields.add(
FormField(label = "包装规格", type = FieldType.TEXT, apiKey = "spec", required = true)
)
fields.add(
FormField(
label = "包装份数",
@@ -130,6 +155,7 @@ class CleanPackActivity : BaseActivity() {
hint = "请输入整数"
)
)
fields.add(
FormField(
label = "包装日期",
@@ -138,6 +164,7 @@ class CleanPackActivity : BaseActivity() {
required = true
)
)
fields.add(
FormField(
label = "保质期至",
@@ -146,6 +173,7 @@ class CleanPackActivity : BaseActivity() {
required = true
)
)
fields.add(
FormField(
label = "存放温度(℃)",
@@ -155,6 +183,7 @@ class CleanPackActivity : BaseActivity() {
hint = "请输入小数"
)
)
fields.add(
FormField(
label = "包装人",
@@ -163,10 +192,23 @@ class CleanPackActivity : BaseActivity() {
required = false
)
)
fields.add(
FormField(label = "终端设备", type = FieldType.TEXT, apiKey = "", required = false)
// 终端设备(下拉)
deviceField = FormField(
label = "终端设备",
type = FieldType.DROPDOWN,
extraApiKeys = mapOf("deviceId" to "id"),
required = false
)
fields.add(deviceField!!)
formAdapter.submitList(fields.filter { !it.hidden })
// 监听溯源码输入,自动填充净菜名称
setupTraceCodeQuery()
// 加载下拉选项
loadDictItems("sup_package_method", packageMethodField!!)
loadDeviceOptions()
}
/**
@@ -176,37 +218,29 @@ class CleanPackActivity : BaseActivity() {
private fun initMealPackFields() {
fields.clear()
// 包装餐品
fields.add(
FormField(
label = "包装餐品", type = FieldType.DROPDOWN,
apiKey = "foodId", required = true,
options = listOf(
DictType(id = "1", value = "1", type = "宫保鸡丁"),
DictType(id = "2", value = "2", type = "鱼香肉丝"),
DictType(id = "3", value = "3", type = "红烧茄子"),
)
)
// 包装餐品(下拉)
foodOptionsField = FormField(
label = "包装餐品", type = FieldType.DROPDOWN,
extraApiKeys = mapOf("foodId" to "id"),
required = true
)
fields.add(foodOptionsField!!)
// 包装规格
fields.add(
FormField(
label = "包装规格", type = FieldType.DROPDOWN,
apiKey = "spec", required = true,
options = listOf(
DictType(id = "1", value = "500g/盒", type = "500g/盒"),
DictType(id = "2", value = "1kg/盒", type = "1kg/盒"),
)
)
// 包装规格(下拉)
packagingSpecField = FormField(
label = "包装规格", type = FieldType.DROPDOWN,
extraApiKeys = mapOf("spec" to "value"),
required = true
)
fields.add(packagingSpecField!!)
// 包装方式
// 包装方式(下拉)
fields.add(
FormField(
label = "包装方式", type = FieldType.DROPDOWN,
apiKey = "packageMethod", required = true,
options = listOf(
extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"),
required = true,
options = mutableListOf(
DictType(id = "1", value = "真空包装", type = "真空包装"),
DictType(id = "2", value = "气调包装", type = "气调包装"),
)
@@ -223,24 +257,28 @@ class CleanPackActivity : BaseActivity() {
// 包含食材(子列表容器)
val ingredientTemplate = listOf(
FormField(label = "食材溯源码", type = FieldType.TEXT, required = true, hint = "溯源码"),
FormField(label = "食材溯源码", type = FieldType.TEXT, apiKey = "traceCode", required = true, hint = "溯源码"),
FormField(
label = "食材名称", type = FieldType.DROPDOWN, required = true,
options = listOf(
label = "食材名称", type = FieldType.DROPDOWN,
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 = "青椒"),
)
),
FormField(
label = "分类", type = FieldType.DROPDOWN, required = true,
options = listOf(
label = "分类", type = FieldType.DROPDOWN,
extraApiKeys = mapOf("classify" to "value"),
required = true,
options = mutableListOf(
DictType(id = "1", value = "蔬菜类", type = "蔬菜类"),
DictType(id = "2", value = "肉禽类", type = "肉禽类"),
DictType(id = "3", value = "水产类", type = "水产类"),
)
),
FormField(label = "每份用量", type = FieldType.TEXT, required = true, hint = "如 120g"),
FormField(label = "每份用量", type = FieldType.TEXT, apiKey = "amount", required = true, hint = "如 120g"),
)
fields.add(
FormField(
@@ -283,24 +321,21 @@ class CleanPackActivity : BaseActivity() {
)
)
// 终端设备
fields.add(
FormField(
label = "终端设备", type = FieldType.DROPDOWN,
apiKey = "deviceId", required = false,
options = listOf(
DictType(id = "1", value = "1", type = "包装机-01"),
DictType(id = "2", value = "2", type = "包装机-02"),
)
)
// 终端设备(下拉)
deviceField = FormField(
label = "终端设备", type = FieldType.DROPDOWN,
extraApiKeys = mapOf("deviceId" to "id"),
required = false
)
fields.add(deviceField!!)
// 操作类型
// 操作类型(下拉)
fields.add(
FormField(
label = "操作类型", type = FieldType.DROPDOWN,
apiKey = "opType", required = true,
options = listOf(
extraApiKeys = mapOf("opType" to "value"),
required = true,
options = mutableListOf(
DictType(id = "1", value = "1", type = "设备自动"),
DictType(id = "2", value = "2", type = "人工录入"),
)
@@ -308,6 +343,287 @@ class CleanPackActivity : BaseActivity() {
)
formAdapter.submitList(fields.filter { !it.hidden })
// 加载下拉选项
loadFoodOptionsWithKeyword()
loadDictItemsWithCallback("sup_package_spec", packagingSpecField!!)
loadDeviceOptionsWithCallbackForMeal()
}
/**
* 监听溯源码字段变化,输入后 800ms 无变化则触发查询
*/
private fun setupTraceCodeQuery() {
val field = traceCodeField ?: return
val cleanField = cleanNameField ?: return
traceCodeJob = lifecycleScope.launch {
var lastValue = ""
while (true) {
val current = field.value.trim()
if (current != lastValue && current.isNotBlank()) {
lastValue = current
viewModelV2.getIngredientByTrace(current)
}
delay(800)
}
}
lifecycleScope.launch {
viewModelV2.ingredientByTraceState.collect { state ->
when (state) {
is UiState.Success -> {
val data = state.data
if (!data.isNullOrEmpty()) {
val info = data[0]
cleanField.value = info.materName ?: ""
// 通知 adapter 刷新净菜名称字段(索引为 1)
val idx = fields.indexOf(cleanField)
if (idx >= 0) formAdapter.notifyItemChanged(idx)
}
}
is UiState.Error -> {
toast(state.msg)
}
else -> {}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
traceCodeJob?.cancel()
}
/**
* 加载餐品下拉选项(回调版本)
*/
private fun loadFoodOptionsWithKeyword() {
val field = foodOptionsField ?: return
viewModelV2.getFoodOptionsWithCallback(
onLoading = {},
onResult = { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.records?.map { vo ->
DictType(
id = vo.foodId?.toString() ?: "",
value = vo.foodId?.toString() ?: "",
type = vo.foodName ?: ""
)
} ?: emptyList()
field.options.clear()
field.options.addAll(options)
val index = fields.indexOf(field)
if (index >= 0) formAdapter.notifyItemChanged(index)
}
is UiState.Error -> {
toast("加载餐品列表失败:${state.msg}")
}
else -> {}
}
}
)
}
/**
* 加载字典项下拉(回调版本),适合多次不同 dictType 的场景
*/
private fun loadDictItemsWithCallback(dictType: String, targetField: FormField) {
viewModelV2.getDictItemsWithCallback(
dictType = dictType,
onLoading = {},
onResult = { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.map { vo ->
DictType(
id = vo.id?.toString() ?: "",
value = vo.dictCode ?: "",
type = vo.dictLabel ?: ""
)
} ?: emptyList()
targetField.options.clear()
targetField.options.addAll(options)
val index = fields.indexOf(targetField)
if (index >= 0) formAdapter.notifyItemChanged(index)
}
is UiState.Error -> {
toast("加载字典项失败:${state.msg}")
}
else -> {}
}
}
)
}
/**
* 加载设备下拉(回调版本),用于餐品净菜包装
*/
private fun loadDeviceOptionsWithCallbackForMeal() {
val field = deviceField ?: return
viewModelV2.getDeviceOptionsWithCallback(
onLoading = {},
onResult = { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.map { vo ->
DictType(
id = vo.id?.toString() ?: "",
value = vo.id?.toString() ?: "",
type = "${vo.deviceName ?: ""}(${vo.deviceNo ?: ""})"
)
} ?: emptyList()
field.options.clear()
field.options.addAll(options)
val index = fields.indexOf(field)
if (index >= 0) formAdapter.notifyItemChanged(index)
}
is UiState.Error -> {
toast("加载设备列表失败:${state.msg}")
}
else -> {}
}
}
)
}
/**
* 加载字典项下拉选项
* @param dictType 字典类型编码
* @param targetField 目标字段
*/
private fun loadDictItems(dictType: String, targetField: FormField) {
viewModelV2.getDictItems(dictType)
lifecycleScope.launch {
viewModelV2.dictItemsState.collect { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.map { vo ->
DictType(
id = vo.id?.toString() ?: "",
value = vo.dictCode ?: "",
type = vo.dictLabel ?: ""
)
} ?: emptyList()
targetField.options.clear()
targetField.options.addAll(options)
val index = fields.indexOf(targetField)
if (index >= 0) formAdapter.notifyItemChanged(index)
}
is UiState.Error -> {
toast("加载字典项失败:${state.msg}")
}
else -> {}
}
}
}
}
/**
* 加载设备下拉选项
*/
private fun loadDeviceOptions() {
viewModelV2.getDeviceOptions()
val field = deviceField ?: return
lifecycleScope.launch {
viewModelV2.deviceOptionsState.collect { state ->
when (state) {
is UiState.Success -> {
val options = state.data?.map { vo ->
DictType(
id = vo.id?.toString() ?: "",
value = vo.id?.toString() ?: "",
type = "${vo.deviceName ?: ""}(${vo.deviceNo ?: ""})"
)
} ?: emptyList()
field.options.clear()
field.options.addAll(options)
val index = fields.indexOf(field)
if (index >= 0) formAdapter.notifyItemChanged(index)
}
is UiState.Error -> {
toast("加载设备列表失败:${state.msg}")
}
else -> {}
}
}
}
}
/**
* 校验必填字段
*/
private fun validateFields(): Boolean {
for (field in fields) {
if (field.hidden) continue
if (field.required) {
if (field.type == FieldType.CHILDREN) {
if (field.childrenRows.isEmpty()) {
toast("请至少添加一条${field.label}")
return false
}
// 校验子行必填
for ((rowIdx, row) in field.childrenRows.withIndex()) {
for (child in row) {
if (child.required && child.value.isBlank()) {
toast("${rowIdx + 1}${child.label}不能为空")
return false
}
}
}
} else if (field.value.isBlank()) {
toast("${field.label}不能为空")
return false
}
}
}
return true
}
/**
* 收集字段提交值,根据 apiKey / extraApiKeys 规则汇总
* @return 提交参数 Mapkey 为接口参数名,value 为提交值
*/
private fun collectSubmitValues(): MutableMap<String, String> {
val params = mutableMapOf<String, String>()
for (field in fields) {
if (field.hidden) continue
// 跳过 CHILDREN 类型,暂不处理
if (field.type == FieldType.CHILDREN) continue
if (field.extraApiKeys.isNotEmpty()) {
// 下拉字段:直接读取 bindDropdown 已解析好的 extraValues
field.extraValues.forEach { (apiKey, value) ->
params[apiKey] = value
}
} else if (field.apiKey.isNotBlank()) {
// 普通字段
params[field.apiKey] = field.value
}
}
return params
}
/**
* 提交单品净菜包装
*/
private fun submitCleanPackage() {
val params = collectSubmitValues()
viewModelV2.addCleanPackage(params)
toast("提交成功")
finish()
}
/**
* 提交餐品净菜包装
*/
private fun submitMealPackage() {
val params = collectSubmitValues()
viewModelV2.addMealPackage(params)
toast("提交成功")
finish()
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB