feat: opt 开始组装 tree

This commit is contained in:
2025-12-18 11:34:06 +08:00
parent 1906e7ce65
commit 222ae8a0f5
6 changed files with 476 additions and 58 deletions

View File

@@ -1,12 +1,50 @@
package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean
/**
* 组装信息Bean(左侧列表)
* 组装信息Bean(左侧折叠列表)
* 支持两种行类型:
* 1. 一级ULD行: 显示ULD编号、总件数、总重量
* 2. 二级运单行: 显示运单号、件数、重量
*/
class AssembleInfoBean {
// ========== 层级类型 ==========
enum class ItemType {
ULD_HEADER, // 一级ULD行
WAYBILL_DETAIL // 二级运单行
}
var itemType: ItemType = ItemType.ULD_HEADER // 行类型
// ========== 一级ULD行字段 ==========
var uldNo: String = "" // ULD编号
var weightInfo: String = "" // 重量信息(如100/290kg)
var hasArrow: Boolean = false // 是否显示箭头(可点击)
var isOrange: Boolean = false // 是否橙色文字
var showIndex: Boolean = true // 是否显示序号圆圈
var uldIndex: Int = 0 // ULD序号用于显示1、2、3...
var totalPieces: Int = 0 // 总件数(二级运单件数求和)
var totalWeight: Double = 0.0 // 总重量(二级运单重量求和)
// 展开/折叠状态使用ObservableBoolean支持DataBinding
val isExpanded: ObservableBoolean = ObservableBoolean(false)
// 子运单列表(用于数据管理,不直接显示)
var waybillChildren: MutableList<AssembleInfoBean> = mutableListOf()
// ========== 二级运单行字段 ==========
var parentUldNo: String = "" // 父级ULD编号用于关联
var wbNo: String = "" // 运单号(直接使用后端字段名)
var waybillPieces: Int = 0 // 运单件数
var waybillWeight: Double = 0.0 // 运单重量
// 原始运单数据(用于同步更新和填充表单)
var waybillData: AssembleWaybillBean? = null
// ========== 视觉样式字段 ==========
var hasArrow: Boolean = false // 是否显示箭头一级ULD行为true
var isOrange: Boolean = false // 是否橙色文字(暂保留)
var showIndex: Boolean = false // 是否显示序号圆圈改为false不再显示序号
var showIndent: Boolean = false // 是否显示缩进二级运单行为true
// 保留原有的weightInfo字段兼容性但不再使用
var weightInfo: String = ""
}

View File

@@ -7,6 +7,9 @@ import com.lukouguoji.module_base.bean.AssembleInfoBean
/**
* 组装信息ViewHolder
* 支持两种行类型:
* 1. 一级ULD行: 显示ULD编号、总件数、总重量、展开/折叠箭头
* 2. 二级运单行: 显示缩进、运单号、件数、重量
*/
class AssembleInfoViewHolder(view: View) :
BaseViewHolder<AssembleInfoBean, ItemAssembleInfoBinding>(view) {
@@ -15,6 +18,12 @@ class AssembleInfoViewHolder(view: View) :
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
// 设置点击事件type=2表示组装信息列表点击
itemView.setOnClickListener {
clickListener?.onItemClick(position, 2)
}
binding.executePendingBindings()
}
}

View File

@@ -76,6 +76,8 @@ class IntExpAssembleStartActivity :
)
binding.rvAssembleInfo.layoutManager = LinearLayoutManager(this)
binding.rvAssembleInfo.adapter = assembleInfoAdapter
// 添加点击监听器
binding.rvAssembleInfo.addOnItemClickListener(this)
// 左侧组装位置列表
assemblePositionAdapter = CommonAdapter(
@@ -122,8 +124,9 @@ class IntExpAssembleStartActivity :
*/
override fun onItemClick(position: Int, type: Int) {
when (type) {
0 -> viewModel.onPositionItemClick(position) // 组装位置点击
1 -> viewModel.onWaybillItemClick(position) // 运单列表点击
0 -> viewModel.onPositionItemClick(position) // 组装位置点击
1 -> viewModel.onWaybillItemClick(position) // 运单列表点击
2 -> viewModel.onAssembleInfoItemClick(position) // 组装信息列表点击
else -> {}
}
}

View File

@@ -55,12 +55,32 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
// ========== 标记位,避免重复查询 ==========
private var lastQueriedUldNo = ""
// ========== 本地存储(页面生命周期内)==========
/**
* 初始化模拟数据(已废弃)
* 已组装货物的本地存储
* Key: ULD编号
* Value: 该ULD下的运单列表
*/
fun initMockData() {
// 不再加载mock数据
}
private val assembledCargoMap = mutableMapOf<String, MutableList<AssembleWaybillBean>>()
/**
* 扁平化的组装信息列表(包含一级和二级行)
* 用于RecyclerView显示
*/
private val flatAssembleInfoList = mutableListOf<AssembleInfoBean>()
/**
* 编辑模式标记
* null: 新增模式
* 非null: 编辑模式存储原始ULD编号和运单号
*/
private var editMode: Pair<String, String>? = null // Pair<ULD编号, 运单号>
/**
* ULD编号锁定状态
*/
val isUldNoLocked = MutableLiveData(false)
/**
* 加载组装位置列表
@@ -118,15 +138,20 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
val selectedWaybill = list[position]
selectedWaybill.isSelected.set(true)
// 保存当前的组装件数、组装重量、组装人(在创建新对象前)
val previousAssembleCount = waybillInfo.value?.assembleCount ?: ""
val previousAssembleWeight = waybillInfo.value?.assembleWeight ?: ""
val previousOperator = waybillInfo.value?.operator ?: ""
// 同步运单信息到表单
waybillInfo.value = WaybillInfoBean().apply {
waybillNo = selectedWaybill.waybillNo
waybillPieces = selectedWaybill.pieces
waybillWeight = selectedWaybill.weight
// 保留当前的组装件数、组装重量、组装人
assembleCount = waybillInfo.value?.assembleCount ?: ""
assembleWeight = waybillInfo.value?.assembleWeight ?: ""
operator = waybillInfo.value?.operator ?: ""
assembleCount = previousAssembleCount
assembleWeight = previousAssembleWeight
operator = previousOperator
}
}
@@ -391,9 +416,12 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
"userId" to SharedPreferenceUtil.getString(Constant.Share.account)
).toRequestBody()
// 6. 调用接口
// 6. 立即同步到组装信息列表(不等接口成功)
val operationName = if (isLoad) "装货" else "卸货"
launchLoadingCollect({
handleOperationSuccess(operationName, isLoad, uldNo, selectedWaybill, assembleCount, assembleWeight)
// 7. 调用接口后台执行不阻塞UI
launchCollect({
if (isLoad) {
NetApply.api.assembleLoadCargo(params)
} else {
@@ -401,15 +429,308 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
}
}) {
onSuccess = { result ->
showToast("${operationName}成功")
// 刷新运单列表
loadInitialWaitingAssemble()
// 清空运单信息
waybillInfo.value = WaybillInfoBean()
// 接口成功,无需额外操作(已经同步到本地)
}
onFailed = { code, message ->
showToast("${operationName}失败: $message")
showToast("${operationName}接口调用失败: $message(数据已保存到本地)")
}
}
}
/**
* 处理装货/卸货操作成功
*/
private fun handleOperationSuccess(
operationName: String,
isLoad: Boolean,
uldNo: String,
selectedWaybill: AssembleWaybillBean,
assembleCount: String,
assembleWeight: String
) {
showToast("${operationName}成功")
// 创建一个包含组装件数和重量的运单Bean
val assembleWaybill = AssembleWaybillBean()
assembleWaybill.waybillNo = selectedWaybill.waybillNo
assembleWaybill.pieces = assembleCount
assembleWaybill.weight = assembleWeight
assembleWaybill.flight = selectedWaybill.flight
assembleWaybill.fno = selectedWaybill.fno
assembleWaybill.fdate = selectedWaybill.fdate
assembleWaybill.whId = selectedWaybill.whId
assembleWaybill.isMarked = selectedWaybill.isMarked
// 判断是新增还是编辑模式
if (editMode != null) {
// 编辑模式:更新本地存储中的对应运单
updateAssembledCargoInLocal(
uldNo = editMode!!.first,
oldWaybillNo = editMode!!.second,
newWaybillBean = assembleWaybill,
isLoad = isLoad
)
} else {
// 新增模式:添加到本地存储
addAssembledCargoToLocal(uldNo, assembleWaybill, isLoad)
}
// 同步刷新组装信息列表
syncAssembleInfoListFromLocal()
// 刷新运单列表
loadInitialWaitingAssemble()
// 清空表单
clearForm()
}
/**
* 从本地存储同步组装信息列表
* 1. 遍历 assembledCargoMap为每个 ULD 生成一级行
* 2. 计算总件数/总重量(求和子运单)
* 3. 根据展开状态决定是否添加二级运单行
* 4. 更新 LiveData 触发 UI 刷新
*/
private fun syncAssembleInfoListFromLocal() {
// 记录当前展开状态(避免刷新后展开状态丢失)
val expandedUlds = flatAssembleInfoList
.filter { it.itemType == AssembleInfoBean.ItemType.ULD_HEADER && it.isExpanded.get() }
.map { it.uldNo }
.toSet()
flatAssembleInfoList.clear()
// 遍历每个 ULD带序号
var uldIndex = 1
assembledCargoMap.forEach { (uldNo, waybillList) ->
// 计算总件数和总重量
var totalPieces = 0
var totalWeight = 0.0
waybillList.forEach { waybill ->
totalPieces += waybill.pieces.toIntOrNull() ?: 0
totalWeight += waybill.weight.toDoubleOrNull() ?: 0.0
}
// 创建一级 ULD 行
val uldHeader = AssembleInfoBean().apply {
itemType = AssembleInfoBean.ItemType.ULD_HEADER
this.uldNo = uldNo
this.uldIndex = uldIndex
this.totalPieces = totalPieces
this.totalWeight = totalWeight
hasArrow = true
showIndex = true // 显示序号
// 恢复展开状态
isExpanded.set(expandedUlds.contains(uldNo))
// 保存子运单(用于展开/折叠)
waybillChildren = waybillList.map { waybill ->
AssembleInfoBean().apply {
itemType = AssembleInfoBean.ItemType.WAYBILL_DETAIL
parentUldNo = uldNo
wbNo = waybill.waybillNo
waybillPieces = waybill.pieces.toIntOrNull() ?: 0
waybillWeight = waybill.weight.toDoubleOrNull() ?: 0.0
waybillData = waybill
showIndent = true
showIndex = false
}
}.toMutableList()
}
// 添加一级行
flatAssembleInfoList.add(uldHeader)
// 如果展开,添加二级运单行
if (uldHeader.isExpanded.get()) {
flatAssembleInfoList.addAll(uldHeader.waybillChildren)
}
// 递增序号
uldIndex++
}
// 更新 LiveData
assembleInfoList.value = flatAssembleInfoList.toMutableList()
}
/**
* 组装信息列表点击事件
* 1. 点击一级 ULD 行:切换展开/折叠状态
* 2. 点击二级运单行:进入编辑模式,填充表单
*/
fun onAssembleInfoItemClick(position: Int) {
val list = flatAssembleInfoList
if (position !in list.indices) return
val clickedItem = list[position]
when (clickedItem.itemType) {
AssembleInfoBean.ItemType.ULD_HEADER -> {
// 一级 ULD 行:切换展开/折叠
val newExpandState = !clickedItem.isExpanded.get()
clickedItem.isExpanded.set(newExpandState)
// 重新生成扁平化列表
syncAssembleInfoListFromLocal()
}
AssembleInfoBean.ItemType.WAYBILL_DETAIL -> {
// 二级运单行:填充表单(编辑模式)
fillFormFromWaybillDetail(clickedItem)
}
}
}
/**
* 从二级运单行填充表单(编辑模式)
* 1. 填充 ULD 信息(并锁定 ULD 编号)
* 2. 填充运单信息(只读)
* 3. 保留组装件数、组装重量、组装人为可编辑
* 4. 标记编辑模式
*/
private fun fillFormFromWaybillDetail(item: AssembleInfoBean) {
val waybill = item.waybillData ?: return
// 1. 填充 ULD 信息
uldInfo.value = uldInfo.value?.apply {
uldNo = item.parentUldNo
// 耗材重量、ULD 状态保持不变
}
// 保存当前的组装人(在创建新对象前)
val previousOperator = waybillInfo.value?.operator ?: ""
// 2. 填充运单信息
waybillInfo.value = WaybillInfoBean().apply {
waybillNo = waybill.waybillNo
waybillPieces = waybill.pieces
waybillWeight = waybill.weight
// 保留可编辑字段为空(需要用户重新输入)
assembleCount = ""
assembleWeight = ""
operator = previousOperator // 保留之前选择的组装人
}
// 3. 锁定 ULD 编号
isUldNoLocked.value = true
// 4. 标记编辑模式(存储原始 ULD 编号和运单号)
editMode = Pair(item.parentUldNo, waybill.waybillNo)
// 5. 查询 ULD 信息(如果需要)
if (item.parentUldNo != lastQueriedUldNo) {
lastQueriedUldNo = item.parentUldNo
queryUldInfo(item.parentUldNo)
}
showToast("已进入编辑模式,请修改组装信息")
}
/**
* 新增模式:添加已组装货物到本地存储
* @param isLoad true-装货false-卸货(减)
*/
private fun addAssembledCargoToLocal(uldNo: String, waybill: AssembleWaybillBean, isLoad: Boolean) {
val list = assembledCargoMap.getOrPut(uldNo) { mutableListOf() }
// 检查是否已存在
val existingIndex = list.indexOfFirst { it.waybillNo == waybill.waybillNo }
if (existingIndex >= 0) {
// 已存在:累加或减去件数和重量
val existing = list[existingIndex]
val existingPieces = existing.pieces.toIntOrNull() ?: 0
val existingWeight = existing.weight.toDoubleOrNull() ?: 0.0
val newPieces = waybill.pieces.toIntOrNull() ?: 0
val newWeight = waybill.weight.toDoubleOrNull() ?: 0.0
if (isLoad) {
// 装货:累加
existing.pieces = (existingPieces + newPieces).toString()
existing.weight = String.format("%.1f", existingWeight + newWeight)
} else {
// 卸货:减去
val resultPieces = existingPieces - newPieces
val resultWeight = existingWeight - newWeight
if (resultPieces <= 0 || resultWeight <= 0) {
// 减到0或负数从列表中移除
list.removeAt(existingIndex)
// 如果该ULD下没有运单了删除整个ULD
if (list.isEmpty()) {
assembledCargoMap.remove(uldNo)
}
} else {
existing.pieces = resultPieces.toString()
existing.weight = String.format("%.1f", resultWeight)
}
}
} else {
// 不存在:直接新增(只有装货时才会走到这里)
if (isLoad) {
list.add(waybill)
}
}
}
/**
* 编辑模式:更新本地存储中的运单数据
* @param isLoad true-装货false-卸货(减)
*/
private fun updateAssembledCargoInLocal(
uldNo: String,
oldWaybillNo: String,
newWaybillBean: AssembleWaybillBean,
isLoad: Boolean
) {
val list = assembledCargoMap[uldNo] ?: return
// 查找并更新
val index = list.indexOfFirst { it.waybillNo == oldWaybillNo }
if (index >= 0) {
val existing = list[index]
val existingPieces = existing.pieces.toIntOrNull() ?: 0
val existingWeight = existing.weight.toDoubleOrNull() ?: 0.0
val newPieces = newWaybillBean.pieces.toIntOrNull() ?: 0
val newWeight = newWaybillBean.weight.toDoubleOrNull() ?: 0.0
if (isLoad) {
// 装货:累加
existing.pieces = (existingPieces + newPieces).toString()
existing.weight = String.format("%.1f", existingWeight + newWeight)
} else {
// 卸货:减去
val resultPieces = existingPieces - newPieces
val resultWeight = existingWeight - newWeight
if (resultPieces <= 0 || resultWeight <= 0) {
// 减到0或负数从列表中移除
list.removeAt(index)
// 如果该ULD下没有运单了删除整个ULD
if (list.isEmpty()) {
assembledCargoMap.remove(uldNo)
}
} else {
existing.pieces = resultPieces.toString()
existing.weight = String.format("%.1f", resultWeight)
}
}
}
}
/**
* 清空表单并退出编辑模式
*/
private fun clearForm() {
// 保留组装人的值(用户期望保留上一次的选择)
val previousOperator = waybillInfo.value?.operator ?: ""
waybillInfo.value = WaybillInfoBean().apply {
operator = previousOperator // 恢复组装人
}
isUldNoLocked.value = false
editMode = null
}
}

View File

@@ -249,6 +249,7 @@
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:id="@+id/uldNoInput"
enable="@{!viewModel.isUldNoLocked}"
required="@{false}"
setRefreshCallBack="@{viewModel::onUldNoInputComplete}"
title='@{"ULD编号"}'

View File

@@ -6,6 +6,8 @@
<import type="android.view.View" />
<import type="com.lukouguoji.module_base.bean.AssembleInfoBean" />
<variable
name="bean"
type="com.lukouguoji.module_base.bean.AssembleInfoBean" />
@@ -23,45 +25,89 @@
android:paddingHorizontal="8dp"
android:paddingVertical="6dp">
<!-- 序号圆圈 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_circle_gray"
android:gravity="center"
android:minWidth="20dp"
android:paddingHorizontal="4dp"
android:text="@{String.valueOf(position + 1)}"
android:textColor="@color/text_normal"
android:textSize="13sp"
android:visibility="@{bean.showIndex ? View.VISIBLE : View.GONE}"
tools:text="1" />
<!-- ULD编号 -->
<TextView
android:layout_width="0dp"
<!-- ========== 一级ULD行 ========== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{bean.uldNo}"
android:textColor="@color/text_normal"
android:textSize="14sp" />
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="@{bean.itemType == AssembleInfoBean.ItemType.ULD_HEADER ? View.VISIBLE : View.GONE}">
<!-- 重量信息 -->
<TextView
android:layout_width="wrap_content"
<!-- 序号圆圈 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_circle_gray"
android:gravity="center"
android:minWidth="20dp"
android:paddingHorizontal="4dp"
android:text="@{String.valueOf(bean.uldIndex)}"
android:textColor="@color/c333333"
android:textSize="13sp"
android:visibility="@{bean.showIndex ? View.VISIBLE : View.GONE}"
tools:text="1" />
<!-- ULD编号 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{bean.uldNo}"
android:textColor="@color/text_normal"
android:textSize="14sp"
android:textStyle="bold" />
<!-- 件数/重量 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text='@{String.valueOf(bean.totalPieces) + "/" + String.format("%.1f", bean.totalWeight) + "kg"}'
android:textColor="@color/colorPrimary"
android:textSize="14sp" />
<!-- 展开/折叠箭头 -->
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:rotation="@{bean.isExpanded.get() ? 90f : 0f}"
android:src="@drawable/img_pda_right" />
</LinearLayout>
<!-- ========== 二级运单行 ========== -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="@{bean.weightInfo}"
android:textColor="@{bean.isOrange ? @color/text_orange : @color/colorPrimary}"
android:textSize="14sp" />
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="@{bean.itemType == AssembleInfoBean.ItemType.WAYBILL_DETAIL ? View.VISIBLE : View.GONE}">
<!-- 缩进占位 -->
<Space
android:layout_width="28dp"
android:layout_height="wrap_content" />
<!-- 运单号 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@{bean.wbNo}"
android:textColor="@color/text_gray"
android:textSize="13sp" />
<!-- 件数/重量 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="28dp"
android:text='@{String.valueOf(bean.waybillPieces) + "/" + String.format("%.1f", bean.waybillWeight) + "kg"}'
android:textColor="#e89e42"
android:textSize="13sp" />
</LinearLayout>
<!-- 箭头图标 -->
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:src="@drawable/img_pda_right"
android:visibility="@{bean.hasArrow ? View.VISIBLE : View.GONE}" />
</LinearLayout>
</layout>