feat: 完成进港查询与进港移库页面5.5寸手机端适配(设计稿11页全部完成)

进港查询(10,06出港查询的进港镜像):
- IntImpQueryActivity 手机布局:搜索行 + 三格统计(总票数/已入库/已出库客户端计数)
  + 状态标签卡片(dlvTime/inDate 前端推断)+ 筛选弹层9项(入参全部经api-doc核对存在)
- 详情页与运单追踪复用08/06已适配页零新增;平板侧滑筛选面板判空守卫,Pad端零变化
- 修复代理人空值兜底:转换器将JSON null归一为空串致DataBinding `??` 失效,
  改用Bean计算属性 agentDisplay(06出港查询同步修正)

进港移库(11,首个后端整模块缺失的设计稿页):
- 全新手机专属3页:列表(双Tab+角标/筛选4项/多选批量移库/卡片长按拍照浮层)
  + 运单详情(三卡片,Intent传bean零请求)+ 拍照上传(复用ImageSelectNewViewHolder,
  pic/originalPic/picNumber三字段规范)
- 后端无IntImpMove模块(api-doc核对),按IntExpMove同名约定预接四接口
  + 预定义权限串AppIntImpMove,后端交付后前端零改动生效(缺口#31)

其他:手机首页国际Tab新增进港查询/进港移库入口;新增资源bg_phone_btn_green/
bg_phone_card_header_orange/bg_phone_photo_add/phone_amber;问题清单登记#30~#31;
双端模拟器验证零崩溃(手机Map Local mock全交互链路,平板回归零变化)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 22:35:56 +08:00
parent b2a2cce585
commit a33776d959
37 changed files with 2884 additions and 476 deletions

View File

@@ -0,0 +1,147 @@
package com.lukouguoji.gjj.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.databinding.ObservableBoolean
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpMoveBinding
import com.lukouguoji.gjj.viewModel.IntImpMoveViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.observe
import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.model.ConfirmDialogModel
import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.router.ARouterConstants
/**
* 国际进港-进港移库(5.5 寸手持端专属页面,无平板版本)
*
* 结构镜像 IntExpMoveActivity(国际出港移库),另有卡片长按「拍照」浮层(设计稿新增交互)。
* ⚠️ 后端目前没有 IntImpMove 模块,接口按同名约定预接(问题清单已登记)。
*/
@Route(path = ARouterConstants.ACTIVITY_URL_INT_IMP_MOVE)
class IntImpMoveActivity : BaseBindingActivity<ActivityIntImpMoveBinding, IntImpMoveViewModel>() {
override fun layoutId() = R.layout.activity_int_imp_move
override fun viewModelClass() = IntImpMoveViewModel::class.java
/** 筛选弹层显隐(仅 UI 状态,不涉及业务逻辑) */
val filterPanelVisible = ObservableBoolean(false)
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("进港移库")
binding.viewModel = viewModel
binding.activity = this
viewModel.initDictLists()
viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)
binding.rv.addOnItemClickListener(viewModel)
// 监听item选择变化事件,更新全选状态
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).observe(this) {
viewModel.onItemCheckChanged()
}
// 监听刷新事件
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
viewModel.refresh()
}
/**
* 扫码运单号
*/
fun scanWaybill() {
ScanModel.startScan(this, Constant.RequestCode.WAYBILL)
}
// ==================== 筛选弹层 ====================
fun toggleFilterPanel() {
filterPanelVisible.set(!filterPanelVisible.get())
}
/**
* 筛选:重置(只清空弹层内条件,不影响 Tab 与顶部搜索框)
*/
fun resetFilter() {
viewModel.flightDate.value = ""
viewModel.flightNo.value = ""
viewModel.spCode.value = ""
viewModel.awbType.value = ""
}
fun confirmFilter() {
filterPanelVisible.set(false)
viewModel.searchClick()
}
// ==================== 卡片长按「拍照」浮层 ====================
/** 卡片长按:展开操作浮层(View.OnLongClickListener 要求返回 Boolean) */
fun showCardActionPhone(bean: IntImpMoveBean): Boolean {
bean.actionMenuVisible.set(true)
return true
}
/** 点浮层空白处关闭 */
fun hideCardActionPhone(bean: IntImpMoveBean) {
bean.actionMenuVisible.set(false)
}
/** 浮层「拍照」→ 拍照上传页 */
fun openPhoto(bean: IntImpMoveBean) {
bean.actionMenuVisible.set(false)
IntImpMovePhotoActivity.start(this, bean)
}
/**
* 显示移库确认对话框(底部操作条 setOnActionClick 调用)
*/
fun showMoveConfirmDialog() {
val selectedItems = viewModel.getSelectedItems()
if (selectedItems.isEmpty()) {
showToast("请至少选择一条运单")
return
}
ConfirmDialogModel(
message = "确定要移库选中的 ${selectedItems.size} 条运单吗?",
title = "移库确认"
) {
viewModel.submitMove()
}.show()
}
/**
* 扫码回调
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) {
val code = data?.getStringExtra(Constant.Result.CODED_CONTENT)
viewModel.waybillNo.value = code
viewModel.searchClick()
}
}
companion object {
@JvmStatic
fun start(context: Context) {
context.startActivity(Intent(context, IntImpMoveActivity::class.java))
}
}
}

View File

@@ -0,0 +1,46 @@
package com.lukouguoji.gjj.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.google.gson.Gson
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpMoveDetailBinding
import com.lukouguoji.gjj.viewModel.IntImpMoveDetailViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.Constant
/**
* 国际进港移库「运单详情」(5.5 寸手持端专属页面)
*
* 由列表卡片的「查看详情」进入;「异常照片」卡片的加号槽位跳拍照上传页。
*/
class IntImpMoveDetailActivity :
BaseBindingActivity<ActivityIntImpMoveDetailBinding, IntImpMoveDetailViewModel>() {
override fun layoutId() = R.layout.activity_int_imp_move_detail
override fun viewModelClass() = IntImpMoveDetailViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("运单详情")
binding.viewModel = viewModel
binding.activity = this
viewModel.initOnCreated(intent)
}
/** 「异常照片」加号槽位 → 拍照上传页 */
fun openPhoto() {
viewModel.dataBean.value?.let { IntImpMovePhotoActivity.start(this, it) }
}
companion object {
@JvmStatic
fun start(context: Context, bean: IntImpMoveBean) {
context.startActivity(
Intent(context, IntImpMoveDetailActivity::class.java)
.putExtra(Constant.Key.DATA, Gson().toJson(bean))
)
}
}
}

View File

@@ -0,0 +1,45 @@
package com.lukouguoji.gjj.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.google.gson.Gson
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpMovePhotoBinding
import com.lukouguoji.gjj.viewModel.IntImpMovePhotoViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.ktx.addOnItemClickListener
/**
* 国际进港移库「拍照上传」(5.5 寸手持端专属页面)
*
* 由列表卡片长按浮层「拍照」或运单详情「异常照片」加号槽位进入。
*/
class IntImpMovePhotoActivity :
BaseBindingActivity<ActivityIntImpMovePhotoBinding, IntImpMovePhotoViewModel>() {
override fun layoutId() = R.layout.activity_int_imp_move_photo
override fun viewModelClass() = IntImpMovePhotoViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("拍照上传")
binding.viewModel = viewModel
viewModel.rv = binding.rv
binding.rv.addOnItemClickListener(viewModel)
viewModel.initOnCreated(intent)
}
companion object {
@JvmStatic
fun start(context: Context, bean: IntImpMoveBean) {
context.startActivity(
Intent(context, IntImpMovePhotoActivity::class.java)
.putExtra(Constant.Key.DATA, Gson().toJson(bean))
)
}
}
}

View File

@@ -19,6 +19,7 @@ import com.lukouguoji.module_base.ktx.getLifecycleOwner
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
import com.lukouguoji.module_base.ktx.setUpperCaseLetterFilter
import com.lukouguoji.module_base.router.ARouterConstants
import com.lukouguoji.module_base.util.DeviceUtil
/**
* 国际进港查询列表页
@@ -50,22 +51,35 @@ class IntImpQueryActivity :
viewModel.initSpecialCodeList()
viewModel.initFilterLists()
// 观察筛选面板显示状态
viewModel.filterVisible.observe(this) { visible ->
if (visible) showFilterPanel() else hideFilterPanel()
// 平板变体才有侧滑筛选面板(filter_panel/filter_overlay 手机变体不存在,Binding 字段为 null)
val padFilterPanel = binding.filterPanel
if (padFilterPanel != null) {
// 观察筛选面板显示状态(滑入/滑出动画)
viewModel.filterVisible.observe(this) { visible ->
if (visible) showFilterPanel() else hideFilterPanel()
}
// 航班号:大写字母+数字
padFilterPanel.filterFlightNo.et.setUpperCaseAlphanumericFilter()
// 始发港:仅大写字母
padFilterPanel.filterOrigin.et.setUpperCaseLetterFilter()
} else {
// 手机变体:PhoneFilterPanel 的显隐由 XML 直接绑定 viewModel.filterVisible,无需动画代码
binding.phoneFilterFlightNo?.et?.setUpperCaseAlphanumericFilter()
binding.phoneFilterOrigin?.et?.setUpperCaseLetterFilter()
}
// 航班号:大写字母+数字
binding.filterPanel.filterFlightNo.et.setUpperCaseAlphanumericFilter()
// 始发港:仅大写字母
binding.filterPanel.filterOrigin.et.setUpperCaseLetterFilter()
// 手机形态专属:已入库/已出库统计计数
if (DeviceUtil.isPhone()) {
viewModel.initPhoneExtras()
}
viewModel.refresh()
}
private fun showFilterPanel() {
val panel = binding.filterPanel.root
val overlay = binding.filterOverlay
val panel = binding.filterPanel?.root ?: return
val overlay = binding.filterOverlay ?: return
val panelWidth = window.decorView.width / 3
panel.layoutParams = (panel.layoutParams as FrameLayout.LayoutParams).apply {
@@ -87,8 +101,8 @@ class IntImpQueryActivity :
}
private fun hideFilterPanel() {
val panel = binding.filterPanel.root
val overlay = binding.filterOverlay
val panel = binding.filterPanel?.root ?: return
val overlay = binding.filterOverlay ?: return
if (panel.visibility != View.VISIBLE) return

View File

@@ -0,0 +1,48 @@
package com.lukouguoji.gjj.holder
import android.view.View
import com.lukouguoji.gjj.activity.IntImpMoveActivity
import com.lukouguoji.gjj.activity.IntImpMoveDetailActivity
import com.lukouguoji.gjj.databinding.ItemIntImpMoveBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
/**
* 国际进港移库列表项 ViewHolder(5.5 寸手持端专属页面)
*
* 卡片交互:
* · 勾选框点击 → 切换选中并通知 Activity 更新全选/已选计数
* · 「查看详情」→ 运单详情页
* · 卡片长按 → 「拍照」操作浮层(XML 绑定 activity 回调)
*/
class IntImpMoveViewHolder(view: View) :
BaseViewHolder<IntImpMoveBean, ItemIntImpMoveBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
binding.activity = itemView.context as? IntImpMoveActivity
// 「查看详情」→ 运单详情页
binding.llDetail.setOnClickListener {
IntImpMoveDetailActivity.start(itemView.context, bean)
}
// 勾选框点击 - 切换选择状态
binding.ivIcon.setOnClickListener {
bean.checked.set(!bean.checked.get())
binding.executePendingBindings()
GlobalScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_CHECK_CHANGED).emit("check_changed")
}
}
binding.executePendingBindings()
}
}

View File

@@ -19,9 +19,9 @@ class IntImpQueryViewHolder(view: View) :
// 注册整行点击事件
notifyItemClick(position, binding.ll)
// 侧滑菜单 - 修改按钮
binding.btnEdit.setOnClickListener {
binding.swipeMenu.quickClose()
// 侧滑菜单 - 修改按钮(平板变体独有;手机变体无侧滑编辑入口,字段为 null)
binding.btnEdit?.setOnClickListener {
binding.swipeMenu?.quickClose()
clickListener?.onItemClick(position, 2000) // type=2000表示修改操作
}
}

View File

@@ -0,0 +1,26 @@
package com.lukouguoji.gjj.viewModel
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.Constant
/**
* 国际进港移库「运单详情」ViewModel(5.5 寸手持端专属页面)
*
* 列表项数据已包含详情页所需全部字段,故直接由列表带入,不额外请求接口
* (后端暂无 IntImpMove 模块,也没有 detail 接口可查,问题清单已登记)。
*/
class IntImpMoveDetailViewModel : BaseViewModel() {
val dataBean = MutableLiveData(IntImpMoveBean())
fun initOnCreated(intent: Intent) {
val json = intent.getStringExtra(Constant.Key.DATA).orEmpty()
if (json.isNotEmpty()) {
dataBean.value = Gson().fromJson(json, IntImpMoveBean::class.java)
}
}
}

View File

@@ -0,0 +1,152 @@
package com.lukouguoji.gjj.viewModel
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.lukouguoji.gjj.R
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.ImageSelectNewViewHolder
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.util.MediaUtil
import com.lukouguoji.module_base.util.UploadUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
/**
* 国际进港移库「拍照上传」ViewModel(5.5 寸手持端专属页面)
*
* 图片选择/预览/删除复用 module_base 的 ImageSelectNewViewHolder(末位加号槽位)。
* 提交走三字段规范(pic 缩略图 / originalPic 原图 / picNumber 数量)。
* ⚠️ 后端暂无 IntImpMove 模块,提交接口按约定预接(问题清单已登记)。
*/
class IntImpMovePhotoViewModel : BaseViewModel(), IOnItemClickListener {
val dataBean = MutableLiveData(IntImpMoveBean())
// 图片网格适配器配置(复用公共图片选择组件)
val itemViewHolder = ImageSelectNewViewHolder::class.java
val itemLayoutId = com.lukouguoji.module_base.R.layout.item_image_select_new
var rv: RecyclerView? = null
fun initOnCreated(intent: Intent) {
val json = intent.getStringExtra(Constant.Key.DATA).orEmpty()
if (json.isNotEmpty()) {
dataBean.value = Gson().fromJson(json, IntImpMoveBean::class.java)
}
// 已有照片回填(pic 缩略图 + originalPic 原图),末位补加号槽位
val bean = dataBean.value ?: IntImpMoveBean()
val picList = bean.pic.split(",").filter { it.isNotEmpty() }
val originalList = bean.originalPic.split(",").filter { it.isNotEmpty() }
val images = picList.mapIndexed { index, picUrl ->
FileBean(
path = MediaUtil.fillUrl(picUrl),
url = picUrl,
originalPic = MediaUtil.fillUrl(originalList.getOrElse(index) { picUrl })
)
}
rv?.post {
rv?.commonAdapter()?.refresh(images + FileBean())
}
}
/**
* 确认提交:先补传未上传的本地图片,再按三字段规范提交
*/
fun submitClick() {
(rv?.commonAdapter()?.items ?: emptyList())
.asFlow()
.map { it as FileBean }
.filter { it.path.isNotEmpty() && it.url.isEmpty() }
.onEach {
val data = UploadUtil.upload(it.path).data
// UploadUtil 返回:newName=原图(较大),zipFileName=缩略图(较小)
it.url = data?.zipFileName ?: ""
it.originalPic = data?.newName ?: ""
}
.flowOn(Dispatchers.IO)
.onStart { showLoading() }
.catch {
showToast(it.message.noNull("上传图片失败"))
dismissLoading()
}
.onCompletion {
launchLoadingCollect({
val list = (rv?.commonAdapter()?.items ?: emptyList())
.filterIsInstance<FileBean>()
.filter { it.path.isNotEmpty() }
val bean = dataBean.value!!
bean.picNumber = list.size.toString()
bean.pic = list.joinToString(",") { MediaUtil.removeUrl(it.url) }
bean.originalPic = list.joinToString(",") { MediaUtil.removeUrl(it.originalPic) }
NetApply.api.submitIntImpMovePic(bean.toRequestBody())
}) {
onSuccess = {
showToast("提交成功")
getTopActivity().finish()
}
onFailed = { _, msg ->
showToast(msg.noNull("提交失败"))
}
}
}
.launchIn(viewModelScope)
}
/**
* 「拍照/上传」按钮:直接唤起图片选择(含拍照入口)
*/
fun pickClick() {
MediaUtil.pickImage(getTopActivity(), maxNum = 9) { results ->
val adapter = rv?.commonAdapter() ?: return@pickImage
results.forEach { result ->
// 在加号槽位(最后一项)之前插入
val insertPos = (adapter.items.size - 1).coerceAtLeast(0)
adapter.items.add(insertPos, FileBean(path = result.realPath))
adapter.notifyItemInserted(insertPos)
}
}
}
/**
* 图片网格点击回调(长按出删除角标 / 点删除角标移除)
*/
override fun onItemClick(position: Int, type: Int) {
val adapter = rv?.commonAdapter() ?: return
val bean = adapter.getItem(position) as? FileBean ?: return
when (type) {
com.lukouguoji.module_base.R.id.rl -> {
if (bean.path.isNotEmpty()) {
bean.canDelete.set(!bean.canDelete.get())
}
}
com.lukouguoji.module_base.R.id.iv_delete -> {
adapter.removeItem(position)
}
else -> {}
}
}
}

View File

@@ -0,0 +1,187 @@
package com.lukouguoji.gjj.viewModel
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.holder.IntImpMoveViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.IntImpMoveBean
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.util.CheckUtil
import com.lukouguoji.module_base.util.DictUtils
import dev.utils.app.info.KeyValue
/**
* 国际进港移库 ViewModel(5.5 寸手持端专属页面,无平板版本)
*
* 结构镜像 IntExpMoveViewModel(国际出港移库)。
* ⚠️ 后端目前没有 IntImpMove 模块(2026-07-31 api-doc 核对),接口按同名约定预接,
* 实机会请求失败(问题清单已登记),后端交付后自动生效。
*/
class IntImpMoveViewModel : BasePageViewModel(), IOnItemClickListener {
// ========== 搜索字段(设计稿筛选弹层:航班日期 / 航班号 / 特码 / 运单类型) ==========
val flightDate = MutableLiveData("") // 航班日期
val flightNo = MutableLiveData("") // 航班号
val spCode = MutableLiveData("") // 特码
val awbType = MutableLiveData("") // 运单类型
val moveState = MutableLiveData("0") // 移库状态(默认「待移库」Tab)
val waybillNo = MutableLiveData("") // 运单号
// ========== 下拉数据 ==========
val spCodeList = MutableLiveData<List<KeyValue>>(emptyList())
val awbTypeList = MutableLiveData<List<KeyValue>>(emptyList())
/** 状态 Tab 配置(设计稿只有两个 Tab,无「全部」) */
val moveStateTabs = MutableLiveData(
listOf(KeyValue("待移库", "0"), KeyValue("已移库", "1"))
)
/** 状态 Tab 角标(与 [moveStateTabs] 等长) */
val moveStateCounts = MutableLiveData(listOf("", ""))
/** 已选数量(底部操作条「已选 N 项」) */
val selectedCount = MutableLiveData("0")
// ========== 全选状态 ==========
val isAllChecked = MutableLiveData(false)
// ========== 适配器配置 ==========
val itemViewHolder = IntImpMoveViewHolder::class.java
val itemLayoutId = R.layout.item_int_imp_move
/**
* 初始化下拉字典(特码 + 运单类型,type=II 国际进港)
*/
fun initDictLists() {
// flag=1 国际;ieFlag 必须留空——传值后端返回空数组(与既有国际页面口径一致)
DictUtils.getSpecialCodeList(addAll = false, flag = 1, ieFlag = "") { list ->
spCodeList.postValue(listOf(KeyValue("全部", "")) + list)
}
DictUtils.getWaybillTypeList(type = "II", addAll = true) {
awbTypeList.postValue(it)
}
}
fun searchClick() {
refresh()
}
/**
* 全选/取消全选
*/
fun toggleSelectAll() {
val adapter = pageModel.rv?.commonAdapter() ?: return
val list = adapter.items.filterIsInstance<IntImpMoveBean>()
CheckUtil.handleAllCheck(list)
updateCheckAllStatus()
adapter.notifyDataSetChanged()
}
/**
* 更新全选状态与已选计数
*/
fun updateCheckAllStatus() {
val adapter = pageModel.rv?.commonAdapter() ?: return
val list = adapter.items.filterIsInstance<IntImpMoveBean>()
isAllChecked.value = list.isNotEmpty() && list.all { it.checked.get() }
selectedCount.value = list.count { it.checked.get() }.toString()
}
/**
* 单个item选择变化时调用(从Activity接收事件)
*/
fun onItemCheckChanged() {
updateCheckAllStatus()
}
/**
* 获取选中的运单
*/
fun getSelectedItems(): List<IntImpMoveBean> {
val adapter = pageModel.rv?.commonAdapter() ?: return emptyList()
return adapter.items.filterIsInstance<IntImpMoveBean>().filter { it.checked.get() }
}
/**
* 提交移库(在Activity中显示确认对话框后调用)
*/
fun submitMove() {
val selectedItems = getSelectedItems()
if (selectedItems.isEmpty()) {
showToast("请至少选择一条运单")
return
}
launchLoadingCollect({ NetApply.api.submitIntImpMove(selectedItems.toRequestBody()) }) {
onSuccess = {
showToast("移库成功")
refresh()
}
onFailed = { _, msg ->
showToast(msg.noNull("移库失败"))
}
}
}
/**
* 获取列表数据
*/
override fun getData() {
// 构建筛选参数(只传递非空值;参数名按 IntExpMove 同名约定,待后端交付核对)
val filterParams = mutableMapOf<String, Any>()
flightDate.value?.takeIf { it.isNotEmpty() }?.let { filterParams["fdate"] = it }
flightNo.value?.takeIf { it.isNotEmpty() }?.let { filterParams["fno"] = it }
spCode.value?.takeIf { it.isNotEmpty() }?.let { filterParams["spCode"] = it }
awbType.value?.takeIf { it.isNotEmpty() }?.let { filterParams["awbType"] = it }
moveState.value?.takeIf { it.isNotEmpty() }?.let { filterParams["moveState"] = it }
waybillNo.value?.takeIf { it.isNotEmpty() }?.let { filterParams["wbNo"] = it }
val listParams = (filterParams + mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit
)).toRequestBody()
launchLoadingCollect({ NetApply.api.getIntImpMoveList(listParams) }) {
onSuccess = {
pageModel.handleListBean(it) { updateCheckAllStatus() }
}
}
// Tab 角标计数(复用统计接口,moveState=0/1 各查一次)
loadTabCounts(filterParams)
}
private fun loadTabCounts(filterParams: Map<String, Any>) {
listOf("0", "1").forEachIndexed { index, state ->
val params = (filterParams + mapOf("moveState" to state)).toRequestBody()
launchCollect({ NetApply.api.getIntImpMoveTotal(params) }) {
onSuccess = { result ->
val count = (result.data?.wbNumber ?: 0).toString()
val current = moveStateCounts.value?.toMutableList() ?: mutableListOf("", "")
if (index < current.size) {
current[index] = count
moveStateCounts.value = current
}
}
}
}
}
/**
* Item点击事件处理(点击已在ViewHolder中处理)
*/
override fun onItemClick(position: Int, type: Int) {
// 卡片交互均在 ViewHolder / XML 中处理
}
}

View File

@@ -64,6 +64,24 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
val awbTypeList = MutableLiveData<List<KeyValue>>(emptyList())
val businessTypeList = MutableLiveData<List<KeyValue>>(emptyList())
// ==================== 5.5寸手机版专属 ====================
// 三格统计:已入库 / 已出库(总票数复用 totalCount,为服务端全量口径)
// pageQueryTotal 无分项统计出参——已入库/已出库为「已加载数据」客户端计数口径,
// 与卡片状态标签同一判定(dlvTime / inDate,见 IntImpQueryBean)
val storedCount = MutableLiveData("0")
val outboundCount = MutableLiveData("0")
private var phoneExtrasEnabled = false
private var phoneLoadedList: List<IntImpQueryBean> = emptyList()
/**
* 手机形态专属初始化(Activity 在 DeviceUtil.isPhone() 时调用)。
* 平板端不调用,请求次数与行为零变化。
*/
fun initPhoneExtras() {
phoneExtrasEnabled = true
}
// ==================== 筛选条件 ====================
val spCode = MutableLiveData("")
val flightNo = MutableLiveData("")
@@ -165,6 +183,14 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
}) {
onSuccess = { pageInfo ->
pageModel.handleListBean(pageInfo.toBaseListBean())
// 手机三格统计:对已加载数据做客户端计数(平板不触发)
if (phoneExtrasEnabled) {
val pageItems = pageInfo.list ?: emptyList()
phoneLoadedList =
if (pageModel.page <= 1) pageItems else phoneLoadedList + pageItems
updatePhoneCounts()
}
}
}
@@ -180,6 +206,16 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
}
}
/**
* 手机三格统计(已入库/已出库):与卡片状态标签同一判定口径(dlvTime / inDate),
* 统计范围为已加载的列表数据。
*/
private fun updatePhoneCounts() {
outboundCount.value = phoneLoadedList.count { it.isQueryOutbound }.toString()
storedCount.value =
phoneLoadedList.count { !it.isQueryOutbound && it.isQueryStored }.toString()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {