Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app

This commit is contained in:
2026-01-04 10:19:34 +08:00
5 changed files with 368 additions and 1 deletions

View File

@@ -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
}
}

View File

@@ -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]
}
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}