feat(form): 重构净菜包装表单实现动态字段和提交功能
- 移除固定 DTO 类型,改用 Map<String, Any> 接收 API 参数 - 实现动态表单字段配置,支持净菜和餐品两种包装类型 - 添加下拉选项动态加载和自动填充溯源码功能 - 实现表单验证和通用提交逻辑 - 添加回调版本的数据加载方法提高性能 - 优化字段绑定和值映射机制
This commit is contained in:
@@ -137,18 +137,11 @@ class FormFieldAdapter(
|
|||||||
popHeight = popHeight
|
popHeight = popHeight
|
||||||
) { dictType ->
|
) { dictType ->
|
||||||
item.value = dictType.value ?: ""
|
item.value = dictType.value ?: ""
|
||||||
item.valueId = dictType.id ?: ""
|
|
||||||
b.tvDropdownValue.text = item.value
|
b.tvDropdownValue.text = item.value
|
||||||
// 将 extraApiKeys 映射的 DictType 字段值写入 extraValues
|
// 将 extraApiKeys 映射的 DictType 字段值写入 extraValues
|
||||||
item.extraValues.clear()
|
item.extraValues.clear()
|
||||||
item.extraApiKeys.forEach { (apiKey, dictField) ->
|
item.extraApiKeys.forEach { (apiKey, dictField) ->
|
||||||
val fieldValue = when (dictField) {
|
item.extraValues[apiKey] = dictType[dictField]
|
||||||
"id" -> dictType.id ?: ""
|
|
||||||
"value" -> dictType.value ?: ""
|
|
||||||
"type" -> dictType.type
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
item.extraValues[apiKey] = fieldValue
|
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
it.bgLayout = b.flDropdown
|
it.bgLayout = b.flDropdown
|
||||||
@@ -175,7 +168,6 @@ class FormFieldAdapter(
|
|||||||
}
|
}
|
||||||
// value 存显示格式,valueId 存 ISO 格式供提交使用
|
// value 存显示格式,valueId 存 ISO 格式供提交使用
|
||||||
item.value = displayDateFormat.format(selected.time)
|
item.value = displayDateFormat.format(selected.time)
|
||||||
item.valueId = isoFormat.format(selected.time)
|
|
||||||
b.tvDateValue.text = item.value
|
b.tvDateValue.text = item.value
|
||||||
},
|
},
|
||||||
cal.get(Calendar.YEAR),
|
cal.get(Calendar.YEAR),
|
||||||
@@ -224,7 +216,6 @@ class FormFieldAdapter(
|
|||||||
required = field.required,
|
required = field.required,
|
||||||
hint = field.hint,
|
hint = field.hint,
|
||||||
options = field.options,
|
options = field.options,
|
||||||
submitValueId = field.submitValueId
|
|
||||||
)
|
)
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
item.childrenRows.add(newRow)
|
item.childrenRows.add(newRow)
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ class IngredientRowAdapter(
|
|||||||
popHeight = kotlin.math.min(field.options.size.coerceAtLeast(1), 4) * 50.dp
|
popHeight = kotlin.math.min(field.options.size.coerceAtLeast(1), 4) * 50.dp
|
||||||
) { dictType ->
|
) { dictType ->
|
||||||
field.value = dictType.value ?: ""
|
field.value = dictType.value ?: ""
|
||||||
field.valueId = dictType.id ?: ""
|
|
||||||
valueView.text = field.value
|
valueView.text = field.value
|
||||||
// 回调通知外部更新
|
// 回调通知外部更新
|
||||||
onChildClick(rowPosition, childIndex, field)
|
onChildClick(rowPosition, childIndex, field)
|
||||||
|
|||||||
@@ -11,4 +11,14 @@ 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 = ""
|
||||||
) : 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 hidden 是否隐藏,hidden=true 时不渲染该字段
|
||||||
* @param hint 校验失败提示文案,同时作为输入框 hint
|
* @param hint 校验失败提示文案,同时作为输入框 hint
|
||||||
* @param options 下拉框选项列表(DROPDOWN 类型使用)
|
* @param options 下拉框选项列表(DROPDOWN 类型使用)
|
||||||
* @param submitValueId 提交时使用 valueId 而非 value;DATE_PICKER 选 ISO 格式时设为 true
|
|
||||||
* @param value 当前显示值/输入值;DATE_PICKER 存 yyyy-MM-dd 显示格式
|
* @param value 当前显示值/输入值;DATE_PICKER 存 yyyy-MM-dd 显示格式
|
||||||
* @param valueId DATE_PICKER 存 ISO 格式 yyyy-MM-dd'T'HH:mm:ss
|
|
||||||
* @param extraValues 下拉选中后按 extraApiKeys 映射存储的提交值,key=接口参数名
|
* @param extraValues 下拉选中后按 extraApiKeys 映射存储的提交值,key=接口参数名
|
||||||
* @param children 子字段模板(CHILDREN 类型使用),每行按此模板创建一组字段
|
* @param children 子字段模板(CHILDREN 类型使用),每行按此模板创建一组字段
|
||||||
* @param childAddLabel 添加按钮文案(CHILDREN 类型使用),如 "+ 添加食材"
|
* @param childAddLabel 添加按钮文案(CHILDREN 类型使用),如 "+ 添加食材"
|
||||||
@@ -44,10 +42,8 @@ data class FormField(
|
|||||||
val required: Boolean = false,
|
val required: Boolean = false,
|
||||||
val hidden: Boolean = false,
|
val hidden: Boolean = false,
|
||||||
val hint: String = "",
|
val hint: String = "",
|
||||||
val options: List<DictType> = emptyList(),
|
var options: MutableList<DictType> = mutableListOf(),
|
||||||
val submitValueId: Boolean = false,
|
|
||||||
var value: String = "",
|
var value: String = "",
|
||||||
var valueId: String = "",
|
|
||||||
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,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.shuwei.dish.match.net
|
package com.shuwei.dish.match.net
|
||||||
|
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
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.NutSupPkgDeviceVO
|
||||||
import com.shuwei.dish.match.model.NutCanteenVO
|
import com.shuwei.dish.match.model.NutCanteenVO
|
||||||
import com.shuwei.dish.match.model.NutDictItemVO
|
import com.shuwei.dish.match.model.NutDictItemVO
|
||||||
@@ -123,7 +121,7 @@ interface ApiServiceV2 {
|
|||||||
@POST
|
@POST
|
||||||
suspend fun addCleanPackage(
|
suspend fun addCleanPackage(
|
||||||
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/clean-package/add",
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/clean-package/add",
|
||||||
@Body param: NutSupCleanPackageDTO
|
@Body param: Map<String, @JvmSuppressWildcards Any>
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
// ========== 四、餐品净菜包装接口 ==========
|
// ========== 四、餐品净菜包装接口 ==========
|
||||||
@@ -134,7 +132,7 @@ interface ApiServiceV2 {
|
|||||||
@POST
|
@POST
|
||||||
suspend fun addMealPackage(
|
suspend fun addMealPackage(
|
||||||
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/meal-package/add",
|
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/meal-package/add",
|
||||||
@Body param: NutSupMealPackageDTO
|
@Body param: Map<String, @JvmSuppressWildcards Any>
|
||||||
): ApiResponse<Unit>
|
): 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.NutMaterOptionVO
|
||||||
import com.shuwei.dish.match.model.NutProdComboTaskDTO
|
import com.shuwei.dish.match.model.NutProdComboTaskDTO
|
||||||
import com.shuwei.dish.match.model.NutProdComboVideoVO
|
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.NutSupHygieneIngredientOptionVO
|
||||||
import com.shuwei.dish.match.model.NutSupMealPackageDTO
|
|
||||||
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
|
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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)
|
private val _ingredientByTraceState = MutableStateFlow<UiState<List<NutSupHygieneIngredientOptionVO>?>>(UiState.Idle)
|
||||||
val ingredientByTraceState: StateFlow<UiState<List<NutSupHygieneIngredientOptionVO>?>> = _ingredientByTraceState.asStateFlow()
|
val ingredientByTraceState: StateFlow<UiState<List<NutSupHygieneIngredientOptionVO>?>> = _ingredientByTraceState.asStateFlow()
|
||||||
@@ -143,7 +184,7 @@ class NetViewModelV2(
|
|||||||
private val _addCleanPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
|
private val _addCleanPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
|
||||||
val addCleanPackageState: StateFlow<UiState<Unit?>> = _addCleanPackageState.asStateFlow()
|
val addCleanPackageState: StateFlow<UiState<Unit?>> = _addCleanPackageState.asStateFlow()
|
||||||
|
|
||||||
fun addCleanPackage(param: NutSupCleanPackageDTO) {
|
fun addCleanPackage(param: Map<String, Any>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_addCleanPackageState.value = UiState.Loading
|
_addCleanPackageState.value = UiState.Loading
|
||||||
_addCleanPackageState.value = repository.addCleanPackage(param)
|
_addCleanPackageState.value = repository.addCleanPackage(param)
|
||||||
@@ -155,7 +196,7 @@ class NetViewModelV2(
|
|||||||
private val _addMealPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
|
private val _addMealPackageState = MutableStateFlow<UiState<Unit?>>(UiState.Idle)
|
||||||
val addMealPackageState: StateFlow<UiState<Unit?>> = _addMealPackageState.asStateFlow()
|
val addMealPackageState: StateFlow<UiState<Unit?>> = _addMealPackageState.asStateFlow()
|
||||||
|
|
||||||
fun addMealPackage(param: NutSupMealPackageDTO) {
|
fun addMealPackage(param: Map<String, Any>) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_addMealPackageState.value = UiState.Loading
|
_addMealPackageState.value = UiState.Loading
|
||||||
_addMealPackageState.value = repository.addMealPackage(param)
|
_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.NutMaterOptionVO
|
||||||
import com.shuwei.dish.match.model.NutProdComboTaskDTO
|
import com.shuwei.dish.match.model.NutProdComboTaskDTO
|
||||||
import com.shuwei.dish.match.model.NutProdComboVideoVO
|
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.NutSupHygieneIngredientOptionVO
|
||||||
import com.shuwei.dish.match.model.NutSupMealPackageDTO
|
|
||||||
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
|
import com.shuwei.dish.match.model.NutSupPkgDeviceVO
|
||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import okhttp3.OkHttpClient
|
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) }
|
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) }
|
safeApiCall { apiService.addMealPackage(param = param) }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package com.shuwei.dish.match.ui
|
package com.shuwei.dish.match.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
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.R
|
||||||
import com.shuwei.dish.match.adapter.FormFieldAdapter
|
import com.shuwei.dish.match.adapter.FormFieldAdapter
|
||||||
import com.shuwei.dish.match.base.BaseActivity
|
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.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.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.gone
|
||||||
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
import com.shuwei.dish.match.utils.ext.visible
|
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 lateinit var binding: ActivityCleanPackBinding
|
||||||
private val fields = mutableListOf<FormField>()
|
private val fields = mutableListOf<FormField>()
|
||||||
private val formAdapter by lazy { FormFieldAdapter(this, fields) }
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -42,7 +56,7 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
binding = ActivityCleanPackBinding.inflate(layoutInflater)
|
binding = ActivityCleanPackBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
setHeaderBackground()
|
setHeaderBackground()
|
||||||
val packType = intent.getIntExtra(EXTRA_TYPE, TYPE_CLEAN)
|
packType = intent.getIntExtra(EXTRA_TYPE, TYPE_CLEAN)
|
||||||
|
|
||||||
// 根据类型设置标题
|
// 根据类型设置标题
|
||||||
val title = when (packType) {
|
val title = when (packType) {
|
||||||
@@ -84,7 +98,13 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnSubmit.setOnClickListener {
|
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() {
|
private fun initCleanPackFields() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
fields.add(
|
|
||||||
FormField(
|
// 溯源码
|
||||||
|
traceCodeField = FormField(
|
||||||
label = "溯源码",
|
label = "溯源码",
|
||||||
type = FieldType.TEXT,
|
type = FieldType.TEXT,
|
||||||
apiKey = "traceCode",
|
apiKey = "traceCode",
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
)
|
fields.add(traceCodeField!!)
|
||||||
fields.add(
|
|
||||||
FormField(
|
// 净菜名称
|
||||||
|
cleanNameField = FormField(
|
||||||
label = "净菜名称",
|
label = "净菜名称",
|
||||||
type = FieldType.TEXT,
|
type = FieldType.TEXT,
|
||||||
apiKey = "cleanName",
|
apiKey = "cleanName",
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
)
|
fields.add(cleanNameField!!)
|
||||||
fields.add(
|
|
||||||
FormField(
|
// 包装方式(下拉)
|
||||||
|
packageMethodField = FormField(
|
||||||
label = "包装方式",
|
label = "包装方式",
|
||||||
type = FieldType.TEXT,
|
type = FieldType.DROPDOWN,
|
||||||
apiKey = "packageMethod",
|
extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"),
|
||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
)
|
fields.add(packageMethodField!!)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(label = "包装规格", type = FieldType.TEXT, apiKey = "spec", required = true)
|
FormField(label = "包装规格", type = FieldType.TEXT, apiKey = "spec", required = true)
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "包装份数",
|
label = "包装份数",
|
||||||
@@ -130,6 +155,7 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
hint = "请输入整数"
|
hint = "请输入整数"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "包装日期",
|
label = "包装日期",
|
||||||
@@ -138,6 +164,7 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "保质期至",
|
label = "保质期至",
|
||||||
@@ -146,6 +173,7 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
required = true
|
required = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "存放温度(℃)",
|
label = "存放温度(℃)",
|
||||||
@@ -155,6 +183,7 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
hint = "请输入小数"
|
hint = "请输入小数"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "包装人",
|
label = "包装人",
|
||||||
@@ -163,10 +192,23 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
required = false
|
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 })
|
formAdapter.submitList(fields.filter { !it.hidden })
|
||||||
|
|
||||||
|
// 监听溯源码输入,自动填充净菜名称
|
||||||
|
setupTraceCodeQuery()
|
||||||
|
// 加载下拉选项
|
||||||
|
loadDictItems("sup_package_method", packageMethodField!!)
|
||||||
|
loadDeviceOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,37 +218,29 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
private fun initMealPackFields() {
|
private fun initMealPackFields() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
|
|
||||||
// 包装餐品
|
// 包装餐品(下拉)
|
||||||
fields.add(
|
foodOptionsField = FormField(
|
||||||
FormField(
|
|
||||||
label = "包装餐品", type = FieldType.DROPDOWN,
|
label = "包装餐品", type = FieldType.DROPDOWN,
|
||||||
apiKey = "foodId", required = true,
|
extraApiKeys = mapOf("foodId" to "id"),
|
||||||
options = listOf(
|
required = true
|
||||||
DictType(id = "1", value = "1", type = "宫保鸡丁"),
|
|
||||||
DictType(id = "2", value = "2", type = "鱼香肉丝"),
|
|
||||||
DictType(id = "3", value = "3", type = "红烧茄子"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
fields.add(foodOptionsField!!)
|
||||||
|
|
||||||
// 包装规格
|
// 包装规格(下拉)
|
||||||
fields.add(
|
packagingSpecField = FormField(
|
||||||
FormField(
|
|
||||||
label = "包装规格", type = FieldType.DROPDOWN,
|
label = "包装规格", type = FieldType.DROPDOWN,
|
||||||
apiKey = "spec", required = true,
|
extraApiKeys = mapOf("spec" to "value"),
|
||||||
options = listOf(
|
required = true
|
||||||
DictType(id = "1", value = "500g/盒", type = "500g/盒"),
|
|
||||||
DictType(id = "2", value = "1kg/盒", type = "1kg/盒"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
fields.add(packagingSpecField!!)
|
||||||
|
|
||||||
// 包装方式
|
// 包装方式(下拉)
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "包装方式", type = FieldType.DROPDOWN,
|
label = "包装方式", type = FieldType.DROPDOWN,
|
||||||
apiKey = "packageMethod", required = true,
|
extraApiKeys = mapOf("packageMethodId" to "id", "packageMethod" to "value"),
|
||||||
options = listOf(
|
required = true,
|
||||||
|
options = mutableListOf(
|
||||||
DictType(id = "1", value = "真空包装", type = "真空包装"),
|
DictType(id = "1", value = "真空包装", type = "真空包装"),
|
||||||
DictType(id = "2", value = "气调包装", type = "气调包装"),
|
DictType(id = "2", value = "气调包装", type = "气调包装"),
|
||||||
)
|
)
|
||||||
@@ -223,24 +257,28 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
|
|
||||||
// 包含食材(子列表容器)
|
// 包含食材(子列表容器)
|
||||||
val ingredientTemplate = listOf(
|
val ingredientTemplate = listOf(
|
||||||
FormField(label = "食材溯源码", type = FieldType.TEXT, required = true, hint = "溯源码"),
|
FormField(label = "食材溯源码", type = FieldType.TEXT, apiKey = "traceCode", required = true, hint = "溯源码"),
|
||||||
FormField(
|
FormField(
|
||||||
label = "食材名称", type = FieldType.DROPDOWN, required = true,
|
label = "食材名称", type = FieldType.DROPDOWN,
|
||||||
options = listOf(
|
extraApiKeys = mapOf("materId" to "id", "ingredientName" to "value"),
|
||||||
|
required = true,
|
||||||
|
options = mutableListOf(
|
||||||
DictType(id = "1", value = "土豆", type = "土豆"),
|
DictType(id = "1", value = "土豆", type = "土豆"),
|
||||||
DictType(id = "2", value = "鸡肉", type = "鸡肉"),
|
DictType(id = "2", value = "鸡肉", type = "鸡肉"),
|
||||||
DictType(id = "3", value = "青椒", type = "青椒"),
|
DictType(id = "3", value = "青椒", type = "青椒"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
label = "分类", type = FieldType.DROPDOWN, required = true,
|
label = "分类", type = FieldType.DROPDOWN,
|
||||||
options = listOf(
|
extraApiKeys = mapOf("classify" to "value"),
|
||||||
|
required = true,
|
||||||
|
options = mutableListOf(
|
||||||
DictType(id = "1", value = "蔬菜类", type = "蔬菜类"),
|
DictType(id = "1", value = "蔬菜类", type = "蔬菜类"),
|
||||||
DictType(id = "2", value = "肉禽类", type = "肉禽类"),
|
DictType(id = "2", value = "肉禽类", type = "肉禽类"),
|
||||||
DictType(id = "3", 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(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
@@ -283,24 +321,21 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 终端设备
|
// 终端设备(下拉)
|
||||||
fields.add(
|
deviceField = FormField(
|
||||||
FormField(
|
|
||||||
label = "终端设备", type = FieldType.DROPDOWN,
|
label = "终端设备", type = FieldType.DROPDOWN,
|
||||||
apiKey = "deviceId", required = false,
|
extraApiKeys = mapOf("deviceId" to "id"),
|
||||||
options = listOf(
|
required = false
|
||||||
DictType(id = "1", value = "1", type = "包装机-01"),
|
|
||||||
DictType(id = "2", value = "2", type = "包装机-02"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
fields.add(deviceField!!)
|
||||||
|
|
||||||
// 操作类型
|
// 操作类型(下拉)
|
||||||
fields.add(
|
fields.add(
|
||||||
FormField(
|
FormField(
|
||||||
label = "操作类型", type = FieldType.DROPDOWN,
|
label = "操作类型", type = FieldType.DROPDOWN,
|
||||||
apiKey = "opType", required = true,
|
extraApiKeys = mapOf("opType" to "value"),
|
||||||
options = listOf(
|
required = true,
|
||||||
|
options = mutableListOf(
|
||||||
DictType(id = "1", value = "1", type = "设备自动"),
|
DictType(id = "1", value = "1", type = "设备自动"),
|
||||||
DictType(id = "2", value = "2", type = "人工录入"),
|
DictType(id = "2", value = "2", type = "人工录入"),
|
||||||
)
|
)
|
||||||
@@ -308,6 +343,287 @@ class CleanPackActivity : BaseActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
formAdapter.submitList(fields.filter { !it.hidden })
|
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