feat: 国际进港舱单编辑支持交接图片新增与删除
编辑/新增表单页增加交接图片区(选图、上传、长按删除), 保存时随舱单提交 pic/originalPic/picNumber 三字段, 编辑模式回填已有图片且不重复上传。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import com.lukouguoji.gjj.viewModel.GjjManifestAddViewModel
|
||||
import com.lukouguoji.module_base.base.BaseBindingActivity
|
||||
import com.lukouguoji.module_base.common.Constant
|
||||
import com.lukouguoji.module_base.common.DetailsPageType
|
||||
import com.lukouguoji.module_base.ktx.addOnItemClickListener
|
||||
import com.lukouguoji.module_base.ktx.noNull
|
||||
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
|
||||
|
||||
@@ -31,6 +32,10 @@ class GjjManifestAddActivity :
|
||||
binding.actualWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
binding.billingWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
|
||||
// 交接图片列表
|
||||
viewModel.rv = binding.rvPic
|
||||
binding.rvPic.addOnItemClickListener(viewModel)
|
||||
|
||||
viewModel.initOnCreated(intent)
|
||||
|
||||
// 动态设置标题(必须在 initOnCreated 之后,pageType 已从 Intent 解析)
|
||||
|
||||
@@ -5,26 +5,45 @@ import android.view.View
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lukouguoji.gjj.R
|
||||
import com.lukouguoji.module_base.base.BaseViewModel
|
||||
import com.lukouguoji.module_base.bean.FileBean
|
||||
import com.lukouguoji.module_base.common.Constant
|
||||
import com.lukouguoji.module_base.common.ConstantEvent
|
||||
import com.lukouguoji.module_base.common.DetailsPageType
|
||||
import com.lukouguoji.module_base.http.net.NetApply
|
||||
import com.lukouguoji.module_base.impl.FlowBus
|
||||
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.finish
|
||||
import com.lukouguoji.module_base.ktx.formatDate
|
||||
import com.lukouguoji.module_base.ktx.launchCollect
|
||||
import com.lukouguoji.module_base.ktx.launchLoadingCollect
|
||||
import com.lukouguoji.module_base.ktx.loadMore
|
||||
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.ktx.verifyNullOrEmpty
|
||||
import com.lukouguoji.module_base.util.DictUtils
|
||||
import com.lukouguoji.module_base.util.MediaUtil
|
||||
import com.lukouguoji.module_base.util.UploadUtil
|
||||
import dev.utils.app.info.KeyValue
|
||||
import dev.utils.common.DateUtils
|
||||
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
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GjjManifestAddViewModel : BaseViewModel() {
|
||||
class GjjManifestAddViewModel : BaseViewModel(), IOnItemClickListener {
|
||||
|
||||
// 页面类型(必须用LiveData)
|
||||
val pageType = MutableLiveData(DetailsPageType.Add)
|
||||
@@ -167,6 +186,11 @@ class GjjManifestAddViewModel : BaseViewModel() {
|
||||
val waybillTypeList = MutableLiveData<List<KeyValue>>()
|
||||
val waybillType = MutableLiveData("")
|
||||
|
||||
// ========== 交接图片 ==========
|
||||
val itemLayoutId = R.layout.item_image_select_new
|
||||
val itemViewHolder = ImageSelectNewViewHolder::class.java
|
||||
var rv: RecyclerView? = null
|
||||
|
||||
/**
|
||||
* 初始化(从Intent获取参数)
|
||||
*/
|
||||
@@ -205,6 +229,9 @@ class GjjManifestAddViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
// 交接图片:末尾追加空白占位项作为添加按钮
|
||||
rv?.post { rv?.commonAdapter()?.addItem(FileBean()) }
|
||||
|
||||
// 加载下拉列表(在编辑数据加载之后,以便使用 checkedValue 将选中项置顶)
|
||||
loadDictLists()
|
||||
}
|
||||
@@ -302,6 +329,21 @@ class GjjManifestAddViewModel : BaseViewModel() {
|
||||
businessType.value = manifest.businessType
|
||||
goodsType.value = manifest.cargoType
|
||||
waybillType.value = manifest.awbType
|
||||
|
||||
// 回填交接图片:path 取原图 URL 确保预览清晰,url 取缩略图用于提交
|
||||
val picList = manifest.pic.noNull().split(",").filter { it.isNotEmpty() }
|
||||
val originalList = manifest.originalPic.noNull().split(",").filter { it.isNotEmpty() }
|
||||
if (picList.isNotEmpty()) {
|
||||
val images = picList.mapIndexed { index, picUrl ->
|
||||
val originalUrl = originalList.getOrElse(index) { picUrl }
|
||||
FileBean(
|
||||
path = MediaUtil.fillUrl(originalUrl),
|
||||
url = picUrl,
|
||||
originalPic = originalUrl
|
||||
)
|
||||
}
|
||||
rv?.loadMore(images)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,30 +453,82 @@ class GjjManifestAddViewModel : BaseViewModel() {
|
||||
paramsMap["prefix"] = prefix
|
||||
}
|
||||
|
||||
val params = paramsMap.toRequestBody()
|
||||
|
||||
launchLoadingCollect({
|
||||
if (isModify) {
|
||||
NetApply.api.gjjManifestUpdate(params)
|
||||
} else {
|
||||
NetApply.api.gjjManifestInsert(params)
|
||||
// 先上传本地新增的交接图片,全部完成后再组装三字段提交表单
|
||||
var uploadFailed = false
|
||||
(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=缩略图(较小)
|
||||
// FileBean.url 用作缩略图标识,FileBean.originalPic 用作原图标识
|
||||
it.url = data?.zipFileName ?: ""
|
||||
it.originalPic = data?.newName ?: ""
|
||||
}
|
||||
}) {
|
||||
onSuccess = {
|
||||
if (it.verifySuccess()) {
|
||||
val successMsg = if (isModify) "修改成功" else "保存成功"
|
||||
showToast(successMsg)
|
||||
.flowOn(Dispatchers.IO)
|
||||
.onStart { showLoading() }
|
||||
.catch {
|
||||
uploadFailed = true
|
||||
showToast(it.message.noNull("上传图片失败"))
|
||||
dismissLoading()
|
||||
}
|
||||
.onCompletion {
|
||||
if (uploadFailed) return@onCompletion
|
||||
|
||||
// 发送刷新事件
|
||||
viewModelScope.launch {
|
||||
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
|
||||
// 交接图片三字段:pic=缩略图、originalPic=原图、picNumber=数量
|
||||
val picBeans = (rv?.commonAdapter()?.items ?: emptyList())
|
||||
.filterIsInstance<FileBean>()
|
||||
.filter { it.path.isNotEmpty() }
|
||||
paramsMap["picNumber"] = picBeans.size.toString()
|
||||
paramsMap["pic"] = picBeans.joinToString(",") { MediaUtil.removeUrl(it.url) }
|
||||
paramsMap["originalPic"] =
|
||||
picBeans.joinToString(",") { MediaUtil.removeUrl(it.originalPic) }
|
||||
|
||||
val params = paramsMap.toRequestBody()
|
||||
|
||||
launchLoadingCollect({
|
||||
if (isModify) {
|
||||
NetApply.api.gjjManifestUpdate(params)
|
||||
} else {
|
||||
NetApply.api.gjjManifestInsert(params)
|
||||
}
|
||||
}) {
|
||||
onSuccess = {
|
||||
if (it.verifySuccess()) {
|
||||
val successMsg = if (isModify) "修改成功" else "保存成功"
|
||||
showToast(successMsg)
|
||||
|
||||
view.context.finish()
|
||||
} else {
|
||||
showToast(it.msg.noNull("保存失败"))
|
||||
// 发送刷新事件
|
||||
viewModelScope.launch {
|
||||
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
|
||||
}
|
||||
|
||||
view.context.finish()
|
||||
} else {
|
||||
showToast(it.msg.noNull("保存失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* 交接图片列表点击回调
|
||||
* 长按图片(R.id.rl)切换删除按钮显隐,点击删除按钮(R.id.iv_delete)移除该图片
|
||||
*/
|
||||
override fun onItemClick(position: Int, type: Int) {
|
||||
val adapter = rv?.commonAdapter() ?: return
|
||||
val bean = adapter.getItem(position) as? FileBean ?: return
|
||||
when (type) {
|
||||
R.id.rl -> {
|
||||
bean.canDelete.set(!bean.canDelete.get())
|
||||
}
|
||||
R.id.iv_delete -> {
|
||||
adapter.removeItem(position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
@@ -356,7 +357,37 @@
|
||||
android:layout_weight="0.605" />
|
||||
|
||||
</LinearLayout>
|
||||
<!-- -->
|
||||
|
||||
<!-- 第8行:交接图片 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
completeSpace="@{5}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="交接图片"
|
||||
android:textColor="@color/text_gray" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_pic"
|
||||
itemLayoutId="@{viewModel.itemLayoutId}"
|
||||
viewHolder="@{viewModel.itemViewHolder}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="9"
|
||||
tools:listitem="@layout/item_image_select_new" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user