Compare commits

..

7 Commits

Author SHA1 Message Date
52171c94df fix: 进港舱单详情页业务类型回显取 businessName 字段
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 19:24:07 +08:00
39934df970 feat: 国际进港舱单详情页展示交接图片缩略图并支持点击查看大图
从舱单列表接口新增的 pic/originalPic/picNumber 字段透传至详情页,
缩略图以水平 RecyclerView 展示,点击后通过 PreviewActivity 查看原图。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 19:10:58 +08:00
624331ca68 feat: 国际进港查询筛选按钮有效时显示红点提示
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:39:28 +08:00
3e5f185721 fix: 修复意外签注编辑页运单号校验及字段拼写错误
- 修正 GjjImportManifest 中 dgrContactMame 字段名拼写错误为 dgrContactName
- 同步修复 IntArrSupplementInfoViewModel 和布局中对应字段引用
- 意外签注编辑页运单号输入改为纯数字模式,并新增11位格式+校验位验证
- 修改航班日期/航班号输入完成时清空始发站和目的站,避免旧数据残留
- 保存前新增始发站/目的站非空校验
- 布局微调:图片上传区域边距和对齐优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:35:30 +08:00
936af73ec0 feat: 国际进港舱单主分单独立选择及分离删除接口
主分单选择解除联动,删除操作使用独立接口,先删分单再删主单

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:23:20 +08:00
43acf0a2de fix: 国际进港装机单修改库位弹框仅单选时自动带出库位号
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:01:32 +08:00
c9625f6bfd fix: 国际进港舱单编辑页下拉框回填及移除404接口
- 使用 DictUtils checkedValue 机制回填编辑模式下拉框选中项
- 移除已404的 searchCargoType 接口调用
- PadDataLayoutNew 增加 updateSpinnerSilently 防止 adapter 重建覆盖值
- CLAUDE.md 补充编辑表单 SPINNER 回填规范

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 17:55:19 +08:00
28 changed files with 415 additions and 111 deletions

View File

@@ -116,7 +116,10 @@
"Bash(echo \"exit:$?\")", "Bash(echo \"exit:$?\")",
"mcp__apifox__read_project_oas_j7j64k", "mcp__apifox__read_project_oas_j7j64k",
"mcp__apifox__read_project_oas_ref_resources_j7j64k", "mcp__apifox__read_project_oas_ref_resources_j7j64k",
"mcp__apifox__read_project_oas_ruugy8" "mcp__apifox__read_project_oas_ruugy8",
"mcp__apifox__read_project_oas_ref_resources_ldmedm",
"mcp__apifox__read_project_oas_ldmedm",
"mcp__apifox__refresh_project_oas_ldmedm"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@@ -861,6 +861,59 @@ adb logcat | grep "com.lukouguoji.aerologic" # 日志
--- ---
## 编辑表单下拉框SPINNER回填规范
编辑页面DetailsPageType.Modify下拉框需要根据已有数据自动选中对应项。**必须使用 `DictUtils``checkedValue` 参数**,禁止依赖组件自动匹配 value。
### 原理
`DictUtils``handleCallBack` 会将 `checkedValue` 匹配的 `KeyValue` 置于列表首位。`PadDataLayoutNew` 的 SPINNER 默认显示列表第 0 项,因此匹配项自动成为选中项,无需额外设置 selectedIndex。
### 标准做法(参考 `GjjManifestDetailsViewModel`、`GjjManifestAddViewModel`
1. **字典加载必须在编辑数据加载之后**(不能放在 `init` 中),确保 `checkedValue` 可用
2. **编辑模式传入 `checkedValue`**,新增模式传 `null`
3. **编辑模式不预置空 `KeyValue("", "")`**(否则空项会占据首位,覆盖 checkedValue 排序)
```kotlin
fun initOnCreated(intent: Intent) {
// 1. 先解析页面类型和编辑数据
if (pageType.value == DetailsPageType.Modify) {
loadManifestFromBean(bean) // 设置 agent.value、specialCode.value 等
}
// 2. 再加载字典列表(此时 checkedValue 已可用)
loadDictLists()
}
private fun loadDictLists() {
val isModify = pageType.value == DetailsPageType.Modify
DictUtils.getXxxList(
addAll = false,
checkedValue = if (isModify) field.value else null // 编辑模式传值,新增传 null
) {
xxxList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it)
}
}
```
### checkedValue 取值规则
提交时用的哪个字段值,`checkedValue` 就传哪个。对照 `toKeyValue()``value` 字段确认匹配:
| DictUtils 方法 | KeyValue.value 来源 | checkedValue 示例 |
|---|---|---|
| 通用(`handleCallBack` | `DictBean.code` | `manifest.agentCode`(如 "SFINT" |
| `getShouYunPackageTypeList` | `PackageBean.name` | `manifest.packageType`(如 "木框" |
### 禁止做法
- ❌ 在 `init` 中加载字典(编辑数据尚未可用,无法传 `checkedValue`
- ❌ 依赖 `PadDataLayoutNew``value` 属性自动匹配列表Spinner adapter 重建时 `onItemSelected` 回调会覆盖已有值)
- ❌ 编辑模式下在列表前添加 `KeyValue("", "")`(会干扰 `checkedValue` 置顶排序)
---
## 开发检查清单 ## 开发检查清单
### 新页面开发必做 ### 新页面开发必做

View File

@@ -44,7 +44,7 @@ data class GjjImportManifest(
// 目的地 // 目的地
var dest: String = "", var dest: String = "",
// 危险品收货人通讯方式 // 危险品收货人通讯方式
var dgrContactMame: String = "", var dgrContactName: String = "",
// 危险品收货人通讯方式 // 危险品收货人通讯方式
var dgrContactNumber: String = "", var dgrContactNumber: String = "",
// 航班日期 // 航班日期

View File

@@ -28,6 +28,7 @@ data class GjjManifest(
var awbType: String = "", // 运单类型 var awbType: String = "", // 运单类型
var awbTypeName: String = "", // 运单类型(中) var awbTypeName: String = "", // 运单类型(中)
var businessType: String = "", // 业务类型 var businessType: String = "", // 业务类型
var businessName: String = "", // 业务类型名称
var cargoType: String = "", // 货物类型 var cargoType: String = "", // 货物类型
var spCode: String = "", // 特码 var spCode: String = "", // 特码
var packageType: String = "", // 包装类型 var packageType: String = "", // 包装类型

View File

@@ -10,6 +10,7 @@ class GjjManifestBean(
var awbType: String = "", var awbType: String = "",
var awbpc: Int = 0, var awbpc: Int = 0,
var businessType: String? = "", var businessType: String? = "",
var businessName: String? = "",
var cargoType: String? = "", var cargoType: String? = "",
var cashWeight: String? = "", var cashWeight: String? = "",
var cneeCode: String? = "", var cneeCode: String? = "",
@@ -73,7 +74,10 @@ class GjjManifestBean(
var wbNo: String = "", var wbNo: String = "",
var weight: String = "", var weight: String = "",
var storageTime: String = "", var storageTime: String = "",
var whslocation: String? = "" var whslocation: String? = "",
var pic: String = "",
var originalPic: String = "",
var picNumber: String = ""
) : BaseObservable(), ICheck { ) : BaseObservable(), ICheck {
// 展示逻辑 // 展示逻辑

View File

@@ -401,5 +401,8 @@ interface Constant {
// 运单类型 // 运单类型
const val AWB_TYPE = "awbType" const val AWB_TYPE = "awbType"
const val PIC = "pic"
const val ORIGINAL_PIC = "originalPic"
} }
} }

View File

@@ -1280,6 +1280,18 @@ interface Api {
@POST("IntImpManiFest/deleteFestList") @POST("IntImpManiFest/deleteFestList")
suspend fun gjjManifestDeleteBatch(@Body data: RequestBody): BaseResultBean<Any> suspend fun gjjManifestDeleteBatch(@Body data: RequestBody): BaseResultBean<Any>
/**
* 删除舱单(主单)- 请求体为 GjjManifest 对象数组
*/
@POST("IntImpManifest/deleteManifest")
suspend fun intImpManifestDeleteManifest(@Body data: RequestBody): BaseResultBean<Any>
/**
* 删除分单 - 请求体为 GjjHaWb 对象数组
*/
@POST("IntImpManifest/deleteHawb")
suspend fun intImpManifestDeleteHawb(@Body data: RequestBody): BaseResultBean<Any>
/** /**
* 新增-国际进港舱单 * 新增-国际进港舱单
*/ */

View File

@@ -97,7 +97,7 @@ class PadDataLayoutNew : FrameLayout {
et.hint = value et.hint = value
tv.hint = value tv.hint = value
bindAdapter(spinner, list, hint) updateSpinnerSilently { bindAdapter(spinner, list, hint) }
} }
var required = false var required = false
@@ -106,11 +106,29 @@ class PadDataLayoutNew : FrameLayout {
tvM.visibility = if (value) VISIBLE else INVISIBLE tvM.visibility = if (value) VISIBLE else INVISIBLE
} }
private val spinnerCallback = object : IOnSpinnerSelected {
override fun onSelected(position: Int) {
value = list.getOrNull(position)?.value ?: ""
refreshCallBack?.invoke()
}
}
private val restoreListenerRunnable = Runnable {
bindOnSelected(spinner, spinnerCallback)
}
private fun updateSpinnerSilently(block: () -> Unit) {
spinner.onItemSelectedListener = null
spinner.removeCallbacks(restoreListenerRunnable)
block()
onValueSet()
spinner.post(restoreListenerRunnable)
}
var list = emptyList<KeyValue>() var list = emptyList<KeyValue>()
set(value) { set(value) {
field = value field = value
bindAdapter(spinner, value, hint) updateSpinnerSilently { bindAdapter(spinner, value, hint) }
onValueSet()
} }
var icon: Any? = null var icon: Any? = null
@@ -183,12 +201,7 @@ class PadDataLayoutNew : FrameLayout {
et.doOnTextChanged { text, _, _, _ -> et.doOnTextChanged { text, _, _, _ ->
value = text.toString() value = text.toString()
} }
bindOnSelected(spinner, object : IOnSpinnerSelected { bindOnSelected(spinner, spinnerCallback)
override fun onSelected(position: Int) {
value = list.getOrNull(position)?.value ?: ""
refreshCallBack?.invoke()
}
})
// 监听输入框焦点变化 // 监听输入框焦点变化
com.lukouguoji.module_base.adapter.setOnFocusChangeListener( com.lukouguoji.module_base.adapter.setOnFocusChangeListener(
et, object : IOnFocusChangeListener { et, object : IOnFocusChangeListener {

View File

@@ -2,12 +2,14 @@ package com.lukouguoji.gjj.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.lukouguoji.gjj.R import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityGjjManifestDetailsBinding import com.lukouguoji.gjj.databinding.ActivityGjjManifestDetailsBinding
import com.lukouguoji.gjj.holder.GjjManifestPicViewHolder
import com.lukouguoji.gjj.viewModel.GjjManifestDetailsViewModel import com.lukouguoji.gjj.viewModel.GjjManifestDetailsViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.base.CommonAdapter
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.ktx.noNull import com.lukouguoji.module_base.ktx.noNull
@@ -21,17 +23,33 @@ class GjjManifestDetailsActivity :
override fun initOnCreate(savedInstanceState: Bundle?) { override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("国际进港舱单详情") setBackArrow("国际进港舱单详情")
viewModel.id = intent.getStringExtra(Constant.Key.ID).noNull() viewModel.id = intent.getStringExtra(Constant.Key.ID).noNull()
viewModel.pic = intent.getStringExtra(Constant.Key.PIC).noNull()
viewModel.originalPic = intent.getStringExtra(Constant.Key.ORIGINAL_PIC).noNull()
binding.viewModel = viewModel binding.viewModel = viewModel
val picAdapter = CommonAdapter(
this,
R.layout.item_gjj_manifest_pic,
GjjManifestPicViewHolder::class.java
)
binding.rvPic.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.rvPic.adapter = picAdapter
viewModel.picList.observe(this) { list ->
picAdapter.refresh(list)
}
viewModel.getData() viewModel.getData()
} }
companion object { companion object {
@JvmStatic @JvmStatic
fun start(context: Context, id: String) { fun start(context: Context, id: String, pic: String = "", originalPic: String = "") {
val starter = Intent(context, GjjManifestDetailsActivity::class.java) val starter = Intent(context, GjjManifestDetailsActivity::class.java)
.putExtra(Constant.Key.ID, id) .putExtra(Constant.Key.ID, id)
.putExtra(Constant.Key.PIC, pic)
.putExtra(Constant.Key.ORIGINAL_PIC, originalPic)
context.startActivity(starter) context.startActivity(starter)
} }
} }

View File

@@ -3,6 +3,7 @@ package com.lukouguoji.gjj.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.InputType
import com.lukouguoji.gjj.R import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpAccidentVisaEditBinding import com.lukouguoji.gjj.databinding.ActivityIntImpAccidentVisaEditBinding
import com.lukouguoji.gjj.viewModel.IntImpAccidentVisaEditViewModel import com.lukouguoji.gjj.viewModel.IntImpAccidentVisaEditViewModel
@@ -26,8 +27,8 @@ class IntImpAccidentVisaEditActivity :
// 航班号:大写字母+数字 // 航班号:大写字母+数字
binding.fnoInput.et.setUpperCaseAlphanumericFilter() binding.fnoInput.et.setUpperCaseAlphanumericFilter()
// 运单号:大写字母+数字 // 运单号:纯数字11位
binding.wbNoInput.et.setUpperCaseAlphanumericFilter() binding.wbNoInput.et.inputType = InputType.TYPE_CLASS_NUMBER
viewModel.rv = binding.rv viewModel.rv = binding.rv
binding.rv.addOnItemClickListener(viewModel) binding.rv.addOnItemClickListener(viewModel)

View File

@@ -108,13 +108,11 @@ class IntImpLoadingListActivity :
return return
} }
// 如果所有选中项库位相同,则预选该库位 // 单选时自动带出该项的库位号,多选不带出
val commonLocation = selectedItems.map { it.locationTally }.distinct().let { val presetLocation = if (selectedItems.size == 1) selectedItems.first().locationTally ?: "" else ""
if (it.size == 1) it.first() else ""
}
IntImpModifyStorageDialogModel( IntImpModifyStorageDialogModel(
currentLocationName = commonLocation currentLocationName = presetLocation
) { dialog -> ) { dialog ->
val locationName = dialog.locationName val locationName = dialog.locationName
val locationId = dialog.locationId val locationId = dialog.locationId

View File

@@ -0,0 +1,26 @@
package com.lukouguoji.gjj.holder
import android.view.View
import com.lukouguoji.gjj.databinding.ItemGjjManifestPicBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ui.page.preview.PreviewActivity
class GjjManifestPicViewHolder(view: View) :
BaseViewHolder<FileBean, ItemGjjManifestPicBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)!!
binding.bean = bean
binding.ivThumbnail.setOnClickListener {
val items = getRecyclerView()?.commonAdapter()?.items
?.filterIsInstance<FileBean>() ?: listOf(bean)
val originalList = items.map { fb ->
FileBean(path = if (fb.originalPic.isNotEmpty()) fb.originalPic else fb.path)
}
PreviewActivity.start(itemView.context, originalList, position)
}
}
}

View File

@@ -1,11 +1,9 @@
package com.lukouguoji.gjj.holder package com.lukouguoji.gjj.holder
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.gjj.databinding.ItemIntImpManifestSubBinding import com.lukouguoji.gjj.databinding.ItemIntImpManifestSubBinding
import com.lukouguoji.module_base.base.BaseViewHolder import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjjHaWb import com.lukouguoji.module_base.bean.GjjHaWb
import com.lukouguoji.module_base.bean.GjjManifest
/** /**
* 国际进港舱单 - 分单子列表 ViewHolder * 国际进港舱单 - 分单子列表 ViewHolder
@@ -24,13 +22,6 @@ class IntImpManifestSubViewHolder(view: View) :
val newCheckedState = !bean.checked.get() val newCheckedState = !bean.checked.get()
bean.checked.set(newCheckedState) bean.checked.set(newCheckedState)
binding.executePendingBindings() binding.executePendingBindings()
// 反向联动主列表项(勾选子项时自动勾选父项)
if (newCheckedState) {
val recyclerView = itemView.parent as? RecyclerView ?: return@setOnClickListener
val parentBean = recyclerView.tag as? GjjManifest ?: return@setOnClickListener
parentBean.checked.set(true)
}
} }
} }
} }

View File

@@ -25,10 +25,7 @@ class IntImpManifestViewHolder(view: View) :
binding.ivIcon.setOnClickListener { binding.ivIcon.setOnClickListener {
val newCheckedState = !bean.checked.get() val newCheckedState = !bean.checked.get()
bean.checked.set(newCheckedState) bean.checked.set(newCheckedState)
// 联动子列表选中状态
bean.haWbList?.forEach { sub -> sub.checked.set(newCheckedState) }
binding.executePendingBindings() binding.executePendingBindings()
binding.rvSub.adapter?.notifyDataSetChanged()
} }
// 整卡点击 - 跳转详情页 // 整卡点击 - 跳转详情页

View File

@@ -153,51 +153,13 @@ class GjjManifestAddViewModel : BaseViewModel() {
val specialCodeList = MutableLiveData<List<KeyValue>>() val specialCodeList = MutableLiveData<List<KeyValue>>()
val specialCode = MutableLiveData("") val specialCode = MutableLiveData("")
// 货物类型 // 货物类型(无下拉列表,仅用于编辑模式回传)
val goodsTypeList = MutableLiveData<List<KeyValue>>()
val goodsType = MutableLiveData("") val goodsType = MutableLiveData("")
// 运单类型 // 运单类型
val waybillTypeList = MutableLiveData<List<KeyValue>>() val waybillTypeList = MutableLiveData<List<KeyValue>>()
val waybillType = MutableLiveData("") val waybillType = MutableLiveData("")
init {
DictUtils.getIntImpAgentList(addAll = false) {
agentList.postValue(listOf(KeyValue("", "")) + it)
}
DictUtils.getSpecialCodeList(addAll = false, flag = 1, ieFlag = "") {
val list = arrayListOf<KeyValue>()
it.find { b -> b.key.contains("普通货物") }?.let { b ->
list.add(b)
}
list.addAll(it.filter { b -> !b.key.contains("普通货物") })
specialCodeList.postValue(list)
}
DictUtils.getBusinessTypeList(addAll = false) {
businessTypeList.postValue(it)
// 新增模式下默认选中"普通货物运输"
if (pageType.value == DetailsPageType.Add && businessType.value.isNullOrEmpty()) {
it.find { b -> b.key.contains("普通货物运输") }?.let { b ->
businessType.postValue(b.value)
}
}
}
DictUtils.getShouYunPackageTypeList {
packageTypeList.postValue(listOf(KeyValue("", "")) + it)
}
DictUtils.getGjjGoodsTypeList(addAll = false) {
goodsTypeList.postValue(it)
}
DictUtils.getWaybillTypeList(type = "II", addAll = false) {
val list = arrayListOf<KeyValue>()
it.find { b -> b.key.contains("干线") }?.let { b ->
list.add(b)
}
list.addAll(it.filter { b -> !b.key.contains("干线") })
waybillTypeList.postValue(list)
}
}
/** /**
* 初始化从Intent获取参数 * 初始化从Intent获取参数
*/ */
@@ -235,6 +197,71 @@ class GjjManifestAddViewModel : BaseViewModel() {
loadManifestFromImportBean(bean) loadManifestFromImportBean(bean)
} }
} }
// 加载下拉列表(在编辑数据加载之后,以便使用 checkedValue 将选中项置顶)
loadDictLists()
}
/**
* 加载下拉列表数据
* 编辑模式下传入 checkedValuehandleCallBack 会将匹配项置于列表首位,
* Spinner 默认显示首项即完成回填
*/
private fun loadDictLists() {
val isModify = pageType.value == DetailsPageType.Modify
DictUtils.getIntImpAgentList(
addAll = false,
checkedValue = if (isModify) agent.value else null
) {
agentList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it)
}
DictUtils.getSpecialCodeList(
addAll = false, flag = 1, ieFlag = "",
checkedValue = if (isModify) specialCode.value else null
) {
if (isModify) {
specialCodeList.postValue(it)
} else {
val list = arrayListOf<KeyValue>()
it.find { b -> b.key.contains("普通货物") }?.let { b -> list.add(b) }
list.addAll(it.filter { b -> !b.key.contains("普通货物") })
specialCodeList.postValue(list)
}
}
DictUtils.getBusinessTypeList(
addAll = false,
checkedValue = if (isModify) businessType.value else null
) {
businessTypeList.postValue(it)
if (!isModify && businessType.value.isNullOrEmpty()) {
it.find { b -> b.key.contains("普通货物运输") }?.let { b ->
businessType.postValue(b.value)
}
}
}
DictUtils.getShouYunPackageTypeList(
checkedValue = if (isModify) packageType.value else null
) {
packageTypeList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it)
}
DictUtils.getWaybillTypeList(
type = "II", addAll = false,
checkedValue = if (isModify) waybillType.value else null
) {
if (isModify) {
waybillTypeList.postValue(it)
} else {
val list = arrayListOf<KeyValue>()
it.find { b -> b.key.contains("干线") }?.let { b -> list.add(b) }
list.addAll(it.filter { b -> !b.key.contains("干线") })
waybillTypeList.postValue(list)
}
}
} }
/** /**

View File

@@ -3,6 +3,7 @@ package com.lukouguoji.gjj.viewModel
import android.view.View import android.view.View
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.lukouguoji.module_base.base.BaseViewModel import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.http.net.NetApply import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.interfaces.IGetData import com.lukouguoji.module_base.interfaces.IGetData
import com.lukouguoji.module_base.ktx.finish import com.lukouguoji.module_base.ktx.finish
@@ -13,6 +14,7 @@ import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import com.lukouguoji.module_base.util.DictUtils import com.lukouguoji.module_base.util.DictUtils
import com.lukouguoji.module_base.util.MediaUtil
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils import dev.utils.common.DateUtils
@@ -20,6 +22,11 @@ class GjjManifestDetailsViewModel : BaseViewModel(), IGetData {
var id = "" var id = ""
var fid = "" var fid = ""
var pic = ""
var originalPic = ""
// 交接图片列表
val picList = MutableLiveData<List<FileBean>>(emptyList())
// 是否修改状态 // 是否修改状态
var modifyAble = MutableLiveData(false) var modifyAble = MutableLiveData(false)
@@ -88,6 +95,7 @@ class GjjManifestDetailsViewModel : BaseViewModel(), IGetData {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
override fun getData() { override fun getData() {
parsePicList()
showLoading() showLoading()
launchCollect({ launchCollect({
NetApply.api.getGjjManifestDetail(id) NetApply.api.getGjjManifestDetail(id)
@@ -256,4 +264,21 @@ class GjjManifestDetailsViewModel : BaseViewModel(), IGetData {
fun onCancelClick(view: View) { fun onCancelClick(view: View) {
modifyAble.value = false modifyAble.value = false
} }
/**
* 解析 pic / originalPic 字符串为 FileBean 列表
*/
fun parsePicList() {
val thumbUrls = pic.split(",").filter { it.isNotEmpty() }
val originalUrls = originalPic.split(",").filter { it.isNotEmpty() }
val list = thumbUrls.mapIndexed { index, thumbFile ->
val originalFile = originalUrls.getOrElse(index) { thumbFile }
FileBean(
path = MediaUtil.fillUrl(thumbFile),
url = thumbFile,
originalPic = MediaUtil.fillUrl(originalFile)
)
}
picList.value = list
}
} }

View File

@@ -243,7 +243,7 @@ class GjjManifestListViewModel : BasePageViewModel(), IOnItemClickListener {
} }
R.id.tv_details -> { R.id.tv_details -> {
GjjManifestDetailsActivity.start(DevUtils.getTopActivity(), bean.mfId) GjjManifestDetailsActivity.start(DevUtils.getTopActivity(), bean.mfId, bean.pic, bean.originalPic)
} }
R.id.tv_delete -> { R.id.tv_delete -> {

View File

@@ -129,7 +129,7 @@ class IntArrSupplementInfoViewModel : BaseViewModel() {
consignorPNum = formBean.consignorPNum, consignorPNum = formBean.consignorPNum,
consignorAddress = formBean.consignorAddress, consignorAddress = formBean.consignorAddress,
// 危险品信息 // 危险品信息
dgrContactMame = formBean.dgrContactMame, dgrContactName = formBean.dgrContactName,
dgrContactNumber = formBean.dgrContactNumber, dgrContactNumber = formBean.dgrContactNumber,
unNumber = formBean.unNumber unNumber = formBean.unNumber
) )

View File

@@ -173,13 +173,23 @@ class IntImpAccidentVisaEditViewModel : BaseViewModel(), IOnItemClickListener {
fun onFlightDateInputComplete() { fun onFlightDateInputComplete() {
lastQueriedFlight = "" lastQueriedFlight = ""
clearFlightInfo()
queryFlightIfReady() queryFlightIfReady()
} }
fun onFlightNoInputComplete() { fun onFlightNoInputComplete() {
lastQueriedFlight = ""
clearFlightInfo()
queryFlightIfReady() queryFlightIfReady()
} }
private fun clearFlightInfo() {
val b = dataBean.value ?: GjAccidentVisaEditBean()
b.dep = ""
b.dest = ""
dataBean.value = b
}
private fun queryFlightIfReady() { private fun queryFlightIfReady() {
val bean = dataBean.value ?: return val bean = dataBean.value ?: return
val fdate = bean.fdate val fdate = bean.fdate
@@ -228,11 +238,39 @@ class IntImpAccidentVisaEditViewModel : BaseViewModel(), IOnItemClickListener {
// 保存 // 保存
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
/**
* 校验运单号格式
* 规则纯数字固定11位后8位中前7位 mod 7 == 最后一位
* 返回 true 表示校验失败(有错误)
*/
private fun verifyWaybillNo(wbNo: String?): Boolean {
if (wbNo.isNullOrEmpty()) return false
if (wbNo.length != 11) {
showToast("运单号必须为11位数字")
return true
}
if (!wbNo.all { it.isDigit() }) {
showToast("运单号必须为纯数字")
return true
}
val last8 = wbNo.substring(3)
val first7ofLast8 = last8.substring(0, 7).toLong()
val lastDigit = last8.last().toString().toInt()
if (first7ofLast8 % 7 != lastDigit.toLong()) {
showToast("运单号校验位不正确")
return true
}
return false
}
fun onSaveClick() { fun onSaveClick() {
val bean = dataBean.value ?: return val bean = dataBean.value ?: return
if (bean.fdate.verifyNullOrEmpty("请输入航班日期")) return if (bean.fdate.verifyNullOrEmpty("请输入航班日期")) return
if (bean.fno.verifyNullOrEmpty("请输入航班号")) return if (bean.fno.verifyNullOrEmpty("请输入航班号")) return
if (bean.wbNo.verifyNullOrEmpty("请输入运单号")) return if (bean.wbNo.verifyNullOrEmpty("请输入运单号")) return
if (verifyWaybillNo(bean.wbNo)) return
if (bean.dep.verifyNullOrEmpty("请先填写航班信息(始发站不能为空)")) return
if (bean.dest.verifyNullOrEmpty("请先填写航班信息(目的站不能为空)")) return
(rv?.commonAdapter()?.items ?: emptyList()) (rv?.commonAdapter()?.items ?: emptyList())
.asFlow() .asFlow()

View File

@@ -9,6 +9,7 @@ import com.lukouguoji.gjj.activity.GjjManifestAddActivity
import com.lukouguoji.gjj.activity.IntImpManifestSubEditActivity import com.lukouguoji.gjj.activity.IntImpManifestSubEditActivity
import com.lukouguoji.gjj.holder.IntImpManifestViewHolder import com.lukouguoji.gjj.holder.IntImpManifestViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjjHaWb
import com.lukouguoji.module_base.bean.GjjManifest import com.lukouguoji.module_base.bean.GjjManifest
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent import com.lukouguoji.module_base.common.ConstantEvent
@@ -121,18 +122,6 @@ class IntImpManifestViewModel : BasePageViewModel() {
// ========== 分单管理模式 ========== // ========== 分单管理模式 ==========
val isSubManagementMode = MutableLiveData(false) val isSubManagementMode = MutableLiveData(false)
init {
// 监听全选状态,自动更新所有列表项(联动子列表)
isAllChecked.observeForever { checked ->
val list = pageModel.rv?.commonAdapter()?.items as? List<GjjManifest> ?: return@observeForever
list.forEach {
it.checked.set(checked)
it.haWbList?.forEach { sub -> sub.checked.set(checked) }
}
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
}
// ========== 适配器配置 ========== // ========== 适配器配置 ==========
val itemViewHolder = IntImpManifestViewHolder::class.java val itemViewHolder = IntImpManifestViewHolder::class.java
val itemLayoutId = R.layout.item_int_imp_manifest val itemLayoutId = R.layout.item_int_imp_manifest
@@ -312,7 +301,7 @@ class IntImpManifestViewModel : BasePageViewModel() {
message = "确定要删除运单号 ${bean.getWaybillNo()} 的舱单吗?", message = "确定要删除运单号 ${bean.getWaybillNo()} 的舱单吗?",
title = "提示" title = "提示"
) { ) {
doDeleteByIds(listOf(bean.mfId)) doDelete(listOf(bean), emptyList())
}.show() }.show()
} }
@@ -321,37 +310,67 @@ class IntImpManifestViewModel : BasePageViewModel() {
*/ */
fun onDeleteClick() { fun onDeleteClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjjManifest> ?: return val list = pageModel.rv?.commonAdapter()?.items as? List<GjjManifest> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) { val selectedManifests = list.filter { it.isSelected }
val selectedHawbs = list.flatMap { it.haWbList?.filter { hawb -> hawb.isSelected } ?: emptyList() }
if (selectedManifests.isEmpty() && selectedHawbs.isEmpty()) {
showToast("请选择要删除的记录") showToast("请选择要删除的记录")
return return
} }
// 构建确认提示信息
val msgParts = mutableListOf<String>()
if (selectedManifests.isNotEmpty()) {
msgParts.add("${selectedManifests.size} 条主单")
}
if (selectedHawbs.isNotEmpty()) {
msgParts.add("${selectedHawbs.size} 条分单")
}
ConfirmDialogModel( ConfirmDialogModel(
message = "确定要删除选中的 ${selectedItems.size} 条舱单吗?", message = "确定要删除选中的 ${msgParts.joinToString("和")} 吗?",
title = "批量删除确认" title = "批量删除确认"
) { ) {
doDeleteByIds(selectedItems.map { it.mfId }) doDelete(selectedManifests, selectedHawbs)
}.show() }.show()
} }
/** /**
* 执行批量删除(统一接口,请求体为 mfId 数组 * 执行删除:先删分单,再删主单(串行执行
*/ */
private fun doDeleteByIds(mfIds: List<Long>) { private fun doDelete(manifests: List<GjjManifest>, hawbs: List<GjjHaWb>) {
launchLoadingCollect({ NetApply.api.gjjManifestDeleteBatch(mfIds.toRequestBody()) }) { viewModelScope.launch {
onSuccess = { try {
if (it.verifySuccess()) { showLoading()
// 第一步:删除分单(如果有选中的分单)
if (hawbs.isNotEmpty()) {
val hawbResult = NetApply.api.intImpManifestDeleteHawb(hawbs.toRequestBody())
if (!hawbResult.verifySuccess()) {
showToast(hawbResult.msg.noNull("分单删除失败"))
refresh()
return@launch
}
}
// 第二步:删除主单(分单删除成功后才执行)
if (manifests.isNotEmpty()) {
val manifestResult = NetApply.api.intImpManifestDeleteManifest(manifests.toRequestBody())
if (!manifestResult.verifySuccess()) {
showToast(manifestResult.msg.noNull("分单已删除,但主单删除失败"))
refresh()
return@launch
}
}
showToast("删除成功") showToast("删除成功")
isAllChecked.value = false isAllChecked.value = false
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh") FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
} } catch (e: Exception) {
refresh() showToast("删除失败:${e.message}")
} else { } finally {
showToast(it.msg.noNull("删除失败")) dismissLoading()
}
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package com.lukouguoji.gjj.viewModel
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjj.R import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.activity.IntImpQueryEditActivity import com.lukouguoji.gjj.activity.IntImpQueryEditActivity
@@ -71,6 +72,20 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
val businessType = MutableLiveData("") val businessType = MutableLiveData("")
val goodsCn = MutableLiveData("") val goodsCn = MutableLiveData("")
// 是否有筛选条件(任意一个非空则为 true
val hasFilter: MediatorLiveData<Boolean> = MediatorLiveData<Boolean>().apply {
val update = { _: Any? ->
value = listOf(spCode, flightNo, origin, awbType, businessType, goodsCn)
.any { !it.value.isNullOrEmpty() }
}
addSource(spCode, update)
addSource(flightNo, update)
addSource(origin, update)
addSource(awbType, update)
addSource(businessType, update)
addSource(goodsCn, update)
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// 方法区 // 方法区
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#F44336" />
</shape>

View File

@@ -432,6 +432,25 @@
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginRight="15dp"
android:gravity="center_vertical">
<TextView
style="@style/tv_manifest_details_label_no_mi"
android:text="交接图片:" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"

View File

@@ -260,7 +260,7 @@
title='@{"名称"}' title='@{"名称"}'
titleLength="@{5}" titleLength="@{5}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@={viewModel.dataBean.dgrContactMame}' /> value='@={viewModel.dataBean.dgrContactName}' />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -323,6 +323,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:layout_marginStart="4dp"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@@ -332,13 +333,14 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:text='@{viewModel.isDetailMode ? "图片" : "上传图像"}' android:text='@{viewModel.isDetailMode ? "图片" : "上传图像"}'
android:textColor="@color/text_gray" android:textColor="@color/text_gray"
completeSpace="@{5}" /> completeSpace="@{6}" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv" android:id="@+id/rv"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="10dp"
android:overScrollMode="never" android:overScrollMode="never"
itemLayoutId="@{viewModel.itemLayoutId}" itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}" viewHolder="@{viewModel.itemViewHolder}"

View File

@@ -211,7 +211,7 @@
title='@{"业务类型"}' title='@{"业务类型"}'
titleLength="@{5}" titleLength="@{5}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.businessType}' value='@{viewModel.dataBean.businessName}'
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />

View File

@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<import type="android.view.View" />
<import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" /> <import type="com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType" />
<variable <variable
@@ -114,14 +115,27 @@
android:padding="2dp" android:padding="2dp"
android:src="@drawable/img_search" /> android:src="@drawable/img_search" />
<ImageView <FrameLayout
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="@{()-> viewModel.filterClick()}" android:onClick="@{()-> viewModel.filterClick()}"
android:padding="5dp" android:padding="5dp"
android:src="@drawable/img_filter" /> android:src="@drawable/img_filter" />
<View
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_gravity="top|end"
android:background="@drawable/bg_red_dot"
android:visibility="@{viewModel.hasFilter ? View.VISIBLE : View.GONE}" />
</FrameLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="bean"
type="com.lukouguoji.module_base.bean.FileBean" />
</data>
<ImageView
android:id="@+id/iv_thumbnail"
loadImage="@{bean.path}"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="8dp"
android:scaleType="centerCrop" />
</layout>