feat: 国际进港
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
package com.lukouguoji.module_base.ui.weight.data.layout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动查询配置类
|
||||||
|
* 用于在 XML 中配置 PadDataLayoutNew 的自动查询功能
|
||||||
|
*
|
||||||
|
* 使用示例:
|
||||||
|
* ```xml
|
||||||
|
* <PadDataLayoutNew
|
||||||
|
* autoQueryEnabled="@{true}"
|
||||||
|
* autoQueryUrl="@{`/IntExpCheckIn/checked/queryWbNoList`}"
|
||||||
|
* autoQueryParamKey="@{`wbNo`}"
|
||||||
|
* autoQueryMinLength="@{4}"
|
||||||
|
* autoQueryMaxLength="@{8}"
|
||||||
|
* autoQueryTitle="@{`选择运单号`}"
|
||||||
|
* ... />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
data class AutoQueryConfig(
|
||||||
|
/** 是否启用自动查询 */
|
||||||
|
var enabled: Boolean = false,
|
||||||
|
|
||||||
|
/** 查询接口地址(必需) */
|
||||||
|
var url: String = "",
|
||||||
|
|
||||||
|
/** 查询参数的 key 名称(默认 "value") */
|
||||||
|
var paramKey: String = "value",
|
||||||
|
|
||||||
|
/** 触发查询的最小长度(默认 4) */
|
||||||
|
var minLength: Int = 4,
|
||||||
|
|
||||||
|
/** 触发查询的最大长度(默认 8) */
|
||||||
|
var maxLength: Int = 8,
|
||||||
|
|
||||||
|
/** 弹框标题(默认 "请选择") */
|
||||||
|
var title: String = "请选择",
|
||||||
|
|
||||||
|
/** 防抖延迟(毫秒,默认 300ms) */
|
||||||
|
var debounceMillis: Long = 300L
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 验证配置是否有效
|
||||||
|
* @return true 如果配置有效,false 否则
|
||||||
|
*/
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
return enabled && url.isNotBlank() && minLength > 0 && maxLength >= minLength
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package com.lukouguoji.module_base.ui.weight.data.layout
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.alibaba.fastjson.JSONArray
|
||||||
|
import com.lukouguoji.module_base.http.net.NetApply
|
||||||
|
import com.lukouguoji.module_base.ktx.getActivity
|
||||||
|
import com.lukouguoji.module_base.ktx.launchCollect
|
||||||
|
import com.lukouguoji.module_base.ktx.toRequestBody
|
||||||
|
import com.lukouguoji.module_base.ktx.toJson
|
||||||
|
import com.lukouguoji.module_base.util.Common
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动查询管理器
|
||||||
|
* 负责处理输入监听、防抖、查询请求、结果处理
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* 1. 监听 EditText 输入变化
|
||||||
|
* 2. 防抖延迟(避免频繁请求)
|
||||||
|
* 3. 防重复查询(相同值不重复请求)
|
||||||
|
* 4. 调用接口查询数据
|
||||||
|
* 5. 处理查询结果(单条填充、多条弹框)
|
||||||
|
* 6. 自动管理协程生命周期
|
||||||
|
*/
|
||||||
|
class AutoQueryManager(
|
||||||
|
private val layout: PadDataLayoutNew,
|
||||||
|
private val config: AutoQueryConfig
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** 协程作用域(从 ViewTree 获取) */
|
||||||
|
private var scope: CoroutineScope? = null
|
||||||
|
|
||||||
|
/** 上次查询的值(防重复查询) */
|
||||||
|
private var lastQueriedValue: String = ""
|
||||||
|
|
||||||
|
/** 防抖任务 */
|
||||||
|
private var debounceJob: Job? = null
|
||||||
|
|
||||||
|
/** 文本监听器 */
|
||||||
|
private var textWatcher: TextWatcher? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定到视图(添加文本监听)
|
||||||
|
*/
|
||||||
|
fun attach() {
|
||||||
|
// 获取协程作用域(从 ViewTree 获取 LifecycleOwner)
|
||||||
|
val lifecycleOwner = ViewTreeLifecycleOwner.get(layout)
|
||||||
|
if (lifecycleOwner == null) {
|
||||||
|
// 延迟绑定(等待 ViewTree 附加)
|
||||||
|
layout.post { attach() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope = lifecycleOwner.lifecycleScope
|
||||||
|
|
||||||
|
// 添加文本监听
|
||||||
|
textWatcher = object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
val text = s?.toString() ?: ""
|
||||||
|
handleTextChanged(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.et.addTextChangedListener(textWatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑(移除监听、取消协程)
|
||||||
|
*/
|
||||||
|
fun detach() {
|
||||||
|
textWatcher?.let { layout.et.removeTextChangedListener(it) }
|
||||||
|
textWatcher = null
|
||||||
|
debounceJob?.cancel()
|
||||||
|
debounceJob = null
|
||||||
|
scope = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理文本变化
|
||||||
|
*/
|
||||||
|
private fun handleTextChanged(text: String) {
|
||||||
|
val trimmedText = text.trim()
|
||||||
|
val length = trimmedText.length
|
||||||
|
|
||||||
|
// 取消之前的防抖任务
|
||||||
|
debounceJob?.cancel()
|
||||||
|
|
||||||
|
// 判断是否需要触发查询
|
||||||
|
if (length in config.minLength..config.maxLength) {
|
||||||
|
// 防抖延迟
|
||||||
|
debounceJob = scope?.launch {
|
||||||
|
delay(config.debounceMillis)
|
||||||
|
performQuery(trimmedText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 长度不符合,清空上次查询记录
|
||||||
|
lastQueriedValue = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查询
|
||||||
|
*/
|
||||||
|
private fun performQuery(value: String) {
|
||||||
|
// 防重复查询
|
||||||
|
if (value == lastQueriedValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastQueriedValue = value
|
||||||
|
|
||||||
|
// 构建查询参数
|
||||||
|
val params = mapOf(config.paramKey to value).toRequestBody()
|
||||||
|
|
||||||
|
// 发起网络请求
|
||||||
|
scope?.launchCollect({ NetApply.api.getWbNoList(config.url, params) }) {
|
||||||
|
onSuccess = { result ->
|
||||||
|
val results = result.data ?: emptyList()
|
||||||
|
handleQueryResults(results)
|
||||||
|
}
|
||||||
|
onFailed = { code, msg ->
|
||||||
|
// 查询失败,清空记录(允许重试)
|
||||||
|
lastQueriedValue = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理查询结果
|
||||||
|
*/
|
||||||
|
private fun handleQueryResults(results: List<String>) {
|
||||||
|
when {
|
||||||
|
// 1 条结果:直接填充
|
||||||
|
results.size == 1 -> {
|
||||||
|
layout.value = results[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多条结果:显示弹框选择
|
||||||
|
results.size > 1 -> {
|
||||||
|
showSelectionDialog(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 条结果:不做处理
|
||||||
|
else -> {
|
||||||
|
// 可选:showToast("未找到匹配数据")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示选择弹框
|
||||||
|
*/
|
||||||
|
private fun showSelectionDialog(results: List<String>) {
|
||||||
|
val activity = layout.context.getActivity()
|
||||||
|
|
||||||
|
// 转换为 Common.singleSelect 需要的格式
|
||||||
|
val jsonArray = JSONArray.parseArray(
|
||||||
|
results.map { mapOf("name" to it, "code" to it) }.toJson(false)
|
||||||
|
)
|
||||||
|
|
||||||
|
Common.singleSelect(
|
||||||
|
activity,
|
||||||
|
config.title,
|
||||||
|
jsonArray,
|
||||||
|
null
|
||||||
|
) { position, _ ->
|
||||||
|
// 用户选择后更新值
|
||||||
|
layout.value = results[position]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -293,4 +293,108 @@ fun setTextAllCapsNew(layout: PadDataLayoutNew, textAllCaps: Boolean) {
|
|||||||
} else {
|
} else {
|
||||||
layout.et.filters = emptyArray<InputFilter>()
|
layout.et.filters = emptyArray<InputFilter>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ========== 自动查询功能 BindingAdapter(新增) ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用自动查询功能
|
||||||
|
* @param enabled 是否启用
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryEnabled")
|
||||||
|
fun setAutoQueryEnabled(layout: PadDataLayoutNew, enabled: Boolean) {
|
||||||
|
layout.autoQueryConfig.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置查询接口地址
|
||||||
|
* @param url 接口地址(如:/IntExpCheckIn/checked/queryWbNoList)
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryUrl")
|
||||||
|
fun setAutoQueryUrl(layout: PadDataLayoutNew, url: String?) {
|
||||||
|
layout.autoQueryConfig.url = url ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置查询参数的 key 名称
|
||||||
|
* @param paramKey 参数名(默认 "value")
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryParamKey")
|
||||||
|
fun setAutoQueryParamKey(layout: PadDataLayoutNew, paramKey: String?) {
|
||||||
|
layout.autoQueryConfig.paramKey = paramKey ?: "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置触发查询的最小长度
|
||||||
|
* @param minLength 最小长度(默认 4)
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryMinLength")
|
||||||
|
fun setAutoQueryMinLength(layout: PadDataLayoutNew, minLength: Int?) {
|
||||||
|
layout.autoQueryConfig.minLength = minLength ?: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置触发查询的最大长度
|
||||||
|
* @param maxLength 最大长度(默认 8)
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryMaxLength")
|
||||||
|
fun setAutoQueryMaxLength(layout: PadDataLayoutNew, maxLength: Int?) {
|
||||||
|
layout.autoQueryConfig.maxLength = maxLength ?: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置弹框标题
|
||||||
|
* @param title 标题(默认 "请选择")
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryTitle")
|
||||||
|
fun setAutoQueryTitle(layout: PadDataLayoutNew, title: String?) {
|
||||||
|
layout.autoQueryConfig.title = title ?: "请选择"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置防抖延迟
|
||||||
|
* @param debounceMillis 延迟毫秒数(默认 300ms)
|
||||||
|
*/
|
||||||
|
@BindingAdapter("autoQueryDebounce")
|
||||||
|
fun setAutoQueryDebounce(layout: PadDataLayoutNew, debounceMillis: Long?) {
|
||||||
|
layout.autoQueryConfig.debounceMillis = debounceMillis ?: 300L
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一配置自动查询(所有属性设置完成后调用)
|
||||||
|
*
|
||||||
|
* ⚠️ 重要:必须在所有 autoQuery* 属性之后绑定,使用 requireAll = false
|
||||||
|
*/
|
||||||
|
@BindingAdapter(
|
||||||
|
"autoQueryEnabled",
|
||||||
|
"autoQueryUrl",
|
||||||
|
"autoQueryParamKey",
|
||||||
|
"autoQueryMinLength",
|
||||||
|
"autoQueryMaxLength",
|
||||||
|
"autoQueryTitle",
|
||||||
|
"autoQueryDebounce",
|
||||||
|
requireAll = false
|
||||||
|
)
|
||||||
|
fun configureAutoQuery(
|
||||||
|
layout: PadDataLayoutNew,
|
||||||
|
enabled: Boolean?,
|
||||||
|
url: String?,
|
||||||
|
paramKey: String?,
|
||||||
|
minLength: Int?,
|
||||||
|
maxLength: Int?,
|
||||||
|
title: String?,
|
||||||
|
debounceMillis: Long?
|
||||||
|
) {
|
||||||
|
// 应用所有配置
|
||||||
|
enabled?.let { layout.autoQueryConfig.enabled = it }
|
||||||
|
url?.let { layout.autoQueryConfig.url = it }
|
||||||
|
paramKey?.let { layout.autoQueryConfig.paramKey = it }
|
||||||
|
minLength?.let { layout.autoQueryConfig.minLength = it }
|
||||||
|
maxLength?.let { layout.autoQueryConfig.maxLength = it }
|
||||||
|
title?.let { layout.autoQueryConfig.title = it }
|
||||||
|
debounceMillis?.let { layout.autoQueryConfig.debounceMillis = it }
|
||||||
|
|
||||||
|
// 验证并启用自动查询
|
||||||
|
if (layout.autoQueryConfig.isValid()) {
|
||||||
|
layout.enableAutoQuery(layout.autoQueryConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,6 +139,18 @@ class PadDataLayoutNew : FrameLayout {
|
|||||||
*/
|
*/
|
||||||
var refreshCallBack: (() -> Unit)? = {}
|
var refreshCallBack: (() -> Unit)? = {}
|
||||||
|
|
||||||
|
// ========== 自动查询相关属性(新增) ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动查询配置
|
||||||
|
*/
|
||||||
|
var autoQueryConfig: AutoQueryConfig = AutoQueryConfig()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动查询管理器(延迟初始化)
|
||||||
|
*/
|
||||||
|
private var autoQueryManager: AutoQueryManager? = null
|
||||||
|
|
||||||
// 选择日期
|
// 选择日期
|
||||||
private val dateClick: (v: View) -> Unit = {
|
private val dateClick: (v: View) -> Unit = {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
@@ -258,4 +270,26 @@ class PadDataLayoutNew : FrameLayout {
|
|||||||
this.onChangeListener = listener
|
this.onChangeListener = listener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用自动查询功能
|
||||||
|
* @param config 查询配置
|
||||||
|
*/
|
||||||
|
fun enableAutoQuery(config: AutoQueryConfig) {
|
||||||
|
this.autoQueryConfig = config
|
||||||
|
if (config.isValid()) {
|
||||||
|
// 初始化查询管理器
|
||||||
|
autoQueryManager = AutoQueryManager(this, config)
|
||||||
|
autoQueryManager?.attach()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁时清理资源
|
||||||
|
*/
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
autoQueryManager?.detach()
|
||||||
|
autoQueryManager = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,12 @@
|
|||||||
titleLength="@{5}"
|
titleLength="@{5}"
|
||||||
type="@{DataLayoutType.INPUT}"
|
type="@{DataLayoutType.INPUT}"
|
||||||
value='@={viewModel.maWbBean.wbNo}'
|
value='@={viewModel.maWbBean.wbNo}'
|
||||||
|
autoQueryEnabled="@{true}"
|
||||||
|
autoQueryUrl="@{`/IntExpCheckIn/checked/queryWbNoList`}"
|
||||||
|
autoQueryParamKey="@{`wbNo`}"
|
||||||
|
autoQueryMinLength="@{4}"
|
||||||
|
autoQueryMaxLength="@{8}"
|
||||||
|
autoQueryTitle="@{`选择运单号`}"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|||||||
Reference in New Issue
Block a user