feat(form): 重构净菜包装表单实现动态字段和提交功能
- 移除固定 DTO 类型,改用 Map<String, Any> 接收 API 参数 - 实现动态表单字段配置,支持净菜和餐品两种包装类型 - 添加下拉选项动态加载和自动填充溯源码功能 - 实现表单验证和通用提交逻辑 - 添加回调版本的数据加载方法提高性能 - 优化字段绑定和值映射机制
This commit is contained in:
@@ -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 而非 value;DATE_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 提交参数 Map,key 为接口参数名,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 |
Reference in New Issue
Block a user