Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app
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 {
|
||||
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 autoQueryConfig: AutoQueryConfig = AutoQueryConfig()
|
||||
|
||||
/**
|
||||
* 自动查询管理器(延迟初始化)
|
||||
*/
|
||||
private var autoQueryManager: AutoQueryManager? = null
|
||||
|
||||
// 选择日期
|
||||
private val dateClick: (v: View) -> Unit = {
|
||||
if (enable) {
|
||||
@@ -258,4 +270,26 @@ class PadDataLayoutNew : FrameLayout {
|
||||
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}"
|
||||
type="@{DataLayoutType.INPUT}"
|
||||
value='@={viewModel.maWbBean.wbNo}'
|
||||
autoQueryEnabled="@{true}"
|
||||
autoQueryUrl="@{`/IntExpCheckIn/checked/queryWbNoList`}"
|
||||
autoQueryParamKey="@{`wbNo`}"
|
||||
autoQueryMinLength="@{4}"
|
||||
autoQueryMaxLength="@{8}"
|
||||
autoQueryTitle="@{`选择运单号`}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
Reference in New Issue
Block a user