Compare commits

..

69 Commits

Author SHA1 Message Date
ca20d70e8a feat: opt 出港组装 2026-01-30 18:28:25 +08:00
bad565085a feat: opt 开始组装 2026-01-28 17:12:40 +08:00
6ea6833396 feat: optimize home icons 2026-01-28 15:34:20 +08:00
3da68b1eed feat: reorder home icon 2026-01-28 11:14:40 +08:00
e4bf1f1fee feat: fix home icon cache 2026-01-28 10:55:21 +08:00
69812bcc0b feat: some new home icon 2026-01-28 10:44:15 +08:00
65c91a0233 feat: rm useless file 2026-01-27 14:15:44 +08:00
2ed1f06a9d feat: fix print 2026-01-27 14:13:06 +08:00
9eb9278676 feat: opt 开始组装 2026-01-27 14:04:50 +08:00
a7b560b048 feat: button icon 2026-01-27 10:30:58 +08:00
1deca69a67 feat: opt print 2026-01-27 08:39:30 +08:00
8e2f584f3a feat: fix bugs 2026-01-26 16:22:27 +08:00
5e1e9e58a2 feat: fix int exp arrive sub list 2026-01-26 13:43:49 +08:00
5f31cf5274 feat: 出港组装 回填重量 2026-01-26 12:34:01 +08:00
d3ea88db08 feat: fix 国际出港 v2 2026-01-24 17:44:12 +08:00
ff2649e063 feat: fix 国际出港 2026-01-24 17:07:35 +08:00
6b655348e1 feat: 国际出港 出港组装 2026-01-21 13:51:58 +08:00
0f1dbe4e05 feat: 国际出港 出港组装 列表 2026-01-21 11:37:08 +08:00
a3fda12fd8 feat: 国际出港 附件查看 2026-01-21 10:49:03 +08:00
b0b109de9a feat: 国际出港 fix bugs 2026-01-20 18:34:45 +08:00
9a034c1653 feat: 国际出港 出港运抵、理货 状态重置 2026-01-20 12:02:59 +08:00
de8e49389a feat: fix sub rv 2026-01-20 11:19:36 +08:00
a52259f951 feat: 国际出港 出港计重 2026-01-17 21:05:48 +08:00
d6be019c3a feat: 国际出港 出港计重 opt v 2026-01-17 20:40:09 +08:00
80a0983459 feat: 国际出港 出港计重 opt 2026-01-17 19:48:33 +08:00
dbfcdb4a01 feat: 国际出港 板箱过磅 fix v2 2026-01-17 19:10:01 +08:00
672c8308b8 feat: 国际出港 板箱过磅 fix 2026-01-17 18:53:38 +08:00
9a327975bd feat: 国际出港 出港运抵 2026-01-17 18:36:19 +08:00
cd0cd89a66 feat: 国际出港 出港运抵 状态重置 2026-01-17 18:10:53 +08:00
b37f330414 feat: 国际出港 装货交接 待配运 2026-01-17 17:33:04 +08:00
675b9d234e feat: 国际出港 装货卸货 2026-01-17 17:01:47 +08:00
8b00597763 Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-17 16:30:33 +08:00
e2f6cdde04 feat: 国际出港 出港仓库 finish 2026-01-17 16:30:29 +08:00
ac4cd63abc feat: opt vfox 2026-01-17 13:26:18 +08:00
7a5e06a5af Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-16 20:02:47 +08:00
3eee861486 feat: ops 2026-01-16 20:02:42 +08:00
dfddf646f5 feat: 国际出港 出港仓库 出库 2026-01-16 17:53:46 +08:00
a8d125ef9d feat: 国际出港 出港仓库 清仓 2026-01-15 17:23:48 +08:00
0a506617c7 feat: 国际出港 出港仓库 仓位修改 2026-01-14 18:39:15 +08:00
76183823b0 feat: 国际出港 出港仓库 清仓 v2 2026-01-14 17:24:50 +08:00
557874ab88 feat: 国际出港 出港仓库 清仓 2026-01-14 17:21:01 +08:00
49151d0066 feat: 国际出港 出港仓库 opt filter 2026-01-13 11:54:26 +08:00
4e34a8f406 feat: 国际出港 出港移库 2026-01-13 11:49:38 +08:00
ab4b1618c8 feat: 国际出港 出港仓库 2026-01-13 11:33:23 +08:00
ca81d8f8bb feat: 国际出港-出港仓库 2026-01-13 11:16:21 +08:00
caeb68f9fd feat: 国际出港 出港仓库 2026-01-12 19:27:14 +08:00
23a6f1b596 feat: gjc move 2026-01-12 15:28:03 +08:00
8e3e604ed3 feat: no -> whNo 2026-01-12 11:30:07 +08:00
fa6e5f01b4 feat: opt attachment preview 2026-01-10 17:50:29 +08:00
f19b0d7c68 feat: pdf viewer 2026-01-10 17:28:06 +08:00
085af11706 feat: 模糊搜索运单号 2026-01-10 16:27:19 +08:00
65ba31f9df feat: 出港待计重-输入运单号 2026-01-10 15:46:09 +08:00
38625562ef feat: 国际出港-出港装载 opt 2026-01-07 21:27:35 +08:00
2b5e0a45aa feat: up case for fno input 2026-01-07 20:42:19 +08:00
774ba91aad feat: opt 出港查询-修改 2026-01-07 19:05:21 +08:00
ff1c69f1dd feat: opt 国际出港 2026-01-05 16:23:56 +08:00
ac0ade9eab feat: opt 国际出港 2026-01-05 13:19:21 +08:00
c3c700c111 feat: 开始组装 opt 2026-01-05 12:13:31 +08:00
e083165553 feat: 国际出港-开始组装-组装人 auto select 2026-01-05 12:06:41 +08:00
30678a54be feat: 国际出港-出港运抵 sub list 2026-01-05 11:29:09 +08:00
7f812abc29 feat: opt 出港运抵 ui 2026-01-05 11:00:25 +08:00
7560dcc980 feat: 国际出港理货 sub list opt 2026-01-04 17:25:27 +08:00
3c35adc0ed feat: 国际出港理货 sub list 2026-01-04 16:36:07 +08:00
28159ce738 feat: opt qr scan icons 2026-01-04 15:08:22 +08:00
0a27fed728 feat: fdate set now 2026-01-04 12:34:54 +08:00
72a8c3107d feat: 航班号大写,过滤字母数字之外的字符 2026-01-04 11:55:57 +08:00
978a9af821 feat: remove scan support for all fno 2026-01-04 11:33:58 +08:00
85ea4649ef Merge branch 'main' of ssh://git.njcqit.com:2222/eric/aerologic-app 2026-01-04 10:19:34 +08:00
f4606b7ba5 feat: fix 2026-01-04 10:19:19 +08:00
156 changed files with 7672 additions and 1204 deletions

View File

@@ -42,7 +42,17 @@
"Bash(vfox list:*)",
"Bash(vf list:*)",
"Bash(vfox use:*)",
"Bash(wait)"
"Bash(wait)",
"Bash(while read file)",
"Bash(do sed -i '' 's/MutableLiveData\\(DateUtils\\\\.getCurrentTime\\(\\)\\\\.formatDate\\(\\)\\)/MutableLiveData<String>\\(DateUtils.getCurrentTime\\(\\).formatDate\\(\\)\\)/g' \"$file\" echo \"Modified: $file\" done)",
"Bash(xargs sed:*)",
"Bash(while IFS= read -r file)",
"Bash(do if ! grep -q \"import com.lukouguoji.module_base.ktx.formatDate\" \"$file\")",
"Bash(then sed -i '' '/import dev.utils.common.DateUtils/a\\\\\nimport com.lukouguoji.module_base.ktx.formatDate\n' \"$file\" echo \"Added formatDate import to: $file\" fi done)",
"Bash(identify:*)",
"WebFetch(domain:github.com)",
"Bash(file:*)",
"Bash(xargs:*)"
],
"deny": [],
"ask": []

1
.gitignore vendored
View File

@@ -191,3 +191,4 @@ fabric.properties
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,java,kotlin
.vfox/

2
.vfox.toml Normal file
View File

@@ -0,0 +1,2 @@
[tools]
java = "17.0.17+10-amzn"

1
.vfox/sdks/java Symbolic link
View File

@@ -0,0 +1 @@
/Users/kid/.version-fox/cache/java/v-17.0.17+10-amzn/java-17.0.17+10-amzn

View File

@@ -26,8 +26,8 @@ android {
applicationId "com.lukouguoji.aerologic"
minSdkVersion 24
targetSdkVersion 30
versionCode 84
versionName "1.8.4"
versionCode 85
versionName "1.8.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -109,6 +109,12 @@
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
<!-- 国际出港修改组装重量 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcAssembleWeightEditActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
<!-- 国际出港出库交接 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity"
@@ -133,6 +139,12 @@
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
<!-- 国际出港仓库 -->
<activity
android:name="com.lukouguoji.gjc.activity.IntExpStorageUseActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
<!-- 国际出港查询 -->
<activity
android:name="com.lukouguoji.gjc.activity.GjcQueryActivity"

View File

@@ -1,6 +1,5 @@
package com.lukouguoji.aerologic.ui.fragment
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -15,19 +14,15 @@ import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.lukouguoji.aerologic.R
import com.lukouguoji.aerologic.page.accident.visa.list.AccidentVisaListActivity
import com.lukouguoji.aerologic.page.car.list.CarListActivity
import com.lukouguoji.aerologic.page.flight.query.list.FlightQueryListActivity
import com.lukouguoji.aerologic.page.gnj.jiaojie.GnjHandoverListActivity
import com.lukouguoji.aerologic.page.gnj.manifest.list.GnjManifestListActivity
import com.lukouguoji.aerologic.page.gnj.stash.list.GnjStashListActivity
import com.lukouguoji.aerologic.page.gnj.move.stash.list.GnjMoveStashListActivity
import com.lukouguoji.aerologic.page.gnj.query.details.GnjQueryDetailsActivity
import com.lukouguoji.aerologic.page.gnj.query.list.GnjQueryListActivity
import com.lukouguoji.aerologic.page.gnj.stash.list.GnjStashListActivity
import com.lukouguoji.aerologic.page.gnj.unload.list.GnjUnloadListActivity
import com.lukouguoji.aerologic.page.log.list.LogListActivity
import com.lukouguoji.aerologic.page.message.list.MessageListActivity
@@ -40,7 +35,6 @@ import com.lukouguoji.gnc.page.distribution.home.GncDistributionHomeActivity
import com.lukouguoji.gnc.page.fubang.list.GncFuBangListActivity
import com.lukouguoji.gnc.page.shouyun.unlist.GncShouYunUnListActivity
import com.lukouguoji.module_base.MyApplication
import com.lukouguoji.module_base.adapter.loadImage
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.router.ARouterConstants
@@ -179,7 +173,8 @@ class HomeFragment : Fragment() {
// 跳过特殊菜单(航班查询、货物查询)
if (Constant.AuthName.Flight == leftMenuTemp.id ||
Constant.AuthName.CargoStatus == leftMenuTemp.id) {
Constant.AuthName.CargoStatus == leftMenuTemp.id
) {
return
}
@@ -424,6 +419,12 @@ class HomeFragment : Fragment() {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE)
.navigation()
}
// 出港仓库
Constant.AuthName.GjcIntExpStorageUse -> {
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_EXP_STORAGE_USE)
.navigation()
}
/**
* 国际进港
*/
@@ -434,7 +435,8 @@ class HomeFragment : Fragment() {
}
// 原始舱单
Constant.AuthName.IntArrAirManifest -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_INT_ARR_AIR_MANIFEST)
ARouter.getInstance()
.build(ARouterConstants.ACTIVITY_URL_INT_ARR_AIR_MANIFEST)
.navigation()
}
// 进港舱单
@@ -531,23 +533,10 @@ class HomeFragment : Fragment() {
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var item = rightMenuList[position]
val item = rightMenuList[position]
holder.itemText.text = item.text
// holder.itemImg.setImageResource(item.img)
loadPreviewImage(holder.itemView.context, item.img, holder.itemImg)
}
private fun loadPreviewImage(context: Context, url: Any, target: ImageView) {
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(
com.bumptech.glide.request.target.Target.SIZE_ORIGINAL,
com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
)
Glide.with(context)
.setDefaultRequestOptions(requestOptions)
.load(url)
.into(target)
// 直接设置本地资源,避免 Glide 缓存导致的图标错乱问题
holder.itemImg.setImageResource(item.img)
}
override fun getItemCount() = rightMenuList.size
@@ -556,7 +545,7 @@ class HomeFragment : Fragment() {
inner class RightMenu(val id: String, val img: Int, val text: String)
private fun getRightMenu4Id(id: String): List<RightMenu> {
var list = arrayListOf<RightMenu>()
val list = arrayListOf<RightMenu>()
when (id) {
Constant.AuthName.DomExp -> {
list.add(
@@ -573,7 +562,6 @@ class HomeFragment : Fragment() {
"出港复磅"
)
)
// list.add(RightMenu(Constant.AuthName.AppDomExpTransport, R.mipmap.gnc_zhuanyun, "转运确认"))
list.add(
RightMenu(
Constant.AuthName.AppDomExpAssemble,
@@ -648,99 +636,111 @@ class HomeFragment : Fragment() {
}
Constant.AuthName.IntExp -> {
// list.add(
// RightMenu(
// Constant.AuthName.GjcAppDomExpCheckin,
// R.mipmap.gjc_shou_yun_icon,
// "出港收运"
// )
// )
// 1. 收运检查
list.add(
RightMenu(
Constant.AuthName.GjcInspectionActivity,
R.mipmap.gnc_cha,
R.drawable.img_gjc_shouyunjiancha,
"收运检查"
)
)
// 2. 出港计重
list.add(
RightMenu(
Constant.AuthName.GjcCheckWeighing,
R.mipmap.gjc_fu_bang_icon,
R.drawable.img_gjc_chugangjizhong,
"出港计重"
)
)
// 3. 出港运抵
list.add(
RightMenu(
Constant.AuthName.GjcFuBangActivity,
R.mipmap.gjc_fu_bang_icon,
"板箱过磅"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcIntExpAssembleActivity,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
"出港组装"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcQueryListActivity,
R.mipmap.gjc_query_icon,
"出港查询"
)
)
list.add(
RightMenu(
Constant.AuthName.GjcYiKuListActivity,
R.mipmap.gjc_yi_ku_icon,
"出港移库"
Constant.AuthName.GjcIntExpArrive,
R.drawable.img_gjc_chugang_diyun,
"出港运抵"
)
)
// 4. 组装分配
list.add(
RightMenu(
Constant.AuthName.GjcAssembleAllocateActivity,
com.lukouguoji.module_base.R.drawable.img_gjc_banxiangzuzhuang,
R.drawable.img_gjc_zuzhuangfenpei,
"组装分配"
)
)
// 5. 出港组装
list.add(
RightMenu(
Constant.AuthName.GjcIntExpOutHandover,
com.lukouguoji.module_base.R.drawable.img_gjc_chuku_jiaojie,
"库交接"
Constant.AuthName.GjcIntExpAssembleActivity,
R.drawable.img_gjc_banxiangzuzhuang,
"港组装"
)
)
// 6. 板箱过磅
list.add(
RightMenu(
Constant.AuthName.GjcFuBangActivity,
R.drawable.img_gjc_banxiangguobang,
"板箱过磅"
)
)
// 7. 出港装载
list.add(
RightMenu(
Constant.AuthName.GjcIntExpLoad,
com.lukouguoji.module_base.R.drawable.img_gjc_chugang_zhuangzai,
R.drawable.img_gjc_chugang_zhuangzai,
"出港装载"
)
)
// 8. 出库交接
list.add(
RightMenu(
Constant.AuthName.GjcIntExpOutHandover,
R.drawable.img_gjc_chuku_jiaojie,
"出库交接"
)
)
// 9. 出港理货
list.add(
RightMenu(
Constant.AuthName.GjcIntExpTally,
com.lukouguoji.module_base.R.drawable.img_gjc_chugang_lihuo,
R.drawable.img_gjc_chugang_lihuo,
"出港理货"
)
)
// 10. 出港移库
list.add(
RightMenu(
Constant.AuthName.GjcIntExpArrive,
com.lukouguoji.module_base.R.drawable.img_gjc_chugang_diyun,
"出港运抵"
Constant.AuthName.GjcYiKuListActivity,
R.drawable.gjc_yi_ku_icon,
"出港移库"
)
)
// 11. 出港仓库
list.add(
RightMenu(
Constant.AuthName.GjcIntExpStorageUse,
R.drawable.gjc_cang_ku_icon,
"出港仓库"
)
)
// 12. 出港查询
list.add(
RightMenu(
Constant.AuthName.GjcQueryListActivity,
R.drawable.gjc_query_icon,
"出港查询"
)
)

View File

@@ -7,7 +7,6 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.java.home=/Users/kid/.version-fox/cache/java/v-17.0.16+8-amzn/java-17.0.16+8-amzn
kapt.use.worker.api=false
kapt.include.compile.classpath=false
# When configured, Gradle will run in incubating parallel mode.

View File

@@ -138,6 +138,9 @@ dependencies {
api 'io.github.lucksiege:pictureselector:v3.11.2'
api "com.github.bumptech.glide:glide:4.15.1"
// PDF预览库
api 'com.github.barteksc:android-pdf-viewer:2.8.2'
// DevApp - Android 工具类库
api 'io.github.afkt:DevAppX:2.4.0'
@@ -153,7 +156,7 @@ dependencies {
// exclude group: 'com.jcraft'
// }
// api(name: 'sdk2-2.1.6-20250901.051214-1', ext: 'aar') {
api(name: 'sdk2-2.0.3', ext: 'aar') {
api(name: 'sdk2-2.0.4', ext: 'aar') {
exclude group: 'com.jcraft'
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -41,7 +41,7 @@ import me.jessyan.autosize.internal.CustomAdapt
* ========== 开发调试开关 ==========
* TODO: 正式发布前务必设置为 false
*/
private const val DEV_AUTO_LOGIN = true // 自动登录开关
private const val DEV_AUTO_LOGIN = false // 自动登录开关
@Route(path = ARouterConstants.ACTIVITY_URL_LOGIN)
class LoginActivity : BaseActivity(),

View File

@@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit
class MyApplication : Application() {
//ARouter debug开关true-open;false-close
private val isDebugARouter = true
private val isDebugARouter = false
companion object {
lateinit var context: Context

View File

@@ -7,7 +7,7 @@ package com.lukouguoji.module_base.bean
data class GjcCheckInPage(
var fdate: String? = null, // 航班日期
var fno: String? = null, // 航班号
var no: String? = null, // 运单号
var wbNo: String? = null, // 运单号
var hno: String? = null, // 分单号
var pageNum: Int = 1, // 页码
var pageSize: Int = 10 // 每页条数

View File

@@ -21,7 +21,8 @@ data class GjcCheckInRecord(
var pc: Long = 0, // 运抵件数
var weight: Double = 0.0, // 运抵重量
var volume: Double = 0.0, // 运抵体积
var whId: Long = 0 // GJC_WAREHOUSE.ID
var whId: Long = 0, // GJC_WAREHOUSE.ID
var carWeight: String = "" // 托盘自重
) : BaseObservable() {
// 数据变化回调

View File

@@ -47,17 +47,4 @@ class GjcExportLoad {
fun getFullWaybillNo(): String {
return if (prefix.isNotEmpty()) "$prefix$no" else no
}
/**
* 获取装载状态显示文字
*/
fun getLoadStatusText(): String {
return when (loadStatus) {
"01" -> "已申报"
"02" -> "申报中"
"03" -> "申报失败"
"04" -> "已删除"
else -> loadStatus
}
}
}

View File

@@ -60,9 +60,9 @@ class GjcInspectionBean : ICheck {
*/
fun getReviewStatusName(): String {
return when (reviewStatus) {
"1" -> "已通过"
"2" -> "退回"
"0" -> "未审核"
"1" -> "未审核"
"2" -> "已通过"
"3" -> "退回"
else -> "未知"
}
}
@@ -72,9 +72,10 @@ class GjcInspectionBean : ICheck {
*/
fun getReviewStatusColor(): String {
return when (reviewStatus) {
"1" -> "#4CAF50" // 绿色-已通过
"2" -> "#F44336" // 色-退回
else -> "#9E9E9E" // 色-未审核
"1" -> "#9E9E9E" // 色-未审核
"2" -> "#4CAF50" // 绿色-已通过
"3" -> "#F44336" // 色-退回
else -> "#9E9E9E" // 灰色-未知
}
}

View File

@@ -1,7 +1,9 @@
package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* 国际出港主单数据模型
@@ -72,12 +74,14 @@ data class GjcMaWb(
var carId: String? = null, // 平板车号
var carNumber: String? = null, // 车牌号
var passageWay: String? = null, // 通道号
var passageWayId: String? = null,
// ==================== 状态信息 ====================
var checkIn: String? = null, // 收运状态。0待收运1已收运2收运中
var declareStatus: String? = null, // 申报状态
var reviewStatus: String? = null, // 审核状态0未审核1通过2退回
var tranFlag: String? = null, // 转运标志
var clearNormal: String? = null, // 清仓正常01
// ==================== 操作信息 ====================
var opDate: String? = null, // 操作时间(入库时间)
@@ -107,10 +111,14 @@ data class GjcMaWb(
// ==================== 关联列表(非数据库字段,用于展示) ====================
var haWbList: List<GjcHaWb>? = null, // 分单列表
var storageUseList: List<GjcStorageUse>? = null, // 库位使用列表
@Transient
var attachList: List<ComAttach>? = null // 附件列表
) {
// ==================== UI扩展字段 ====================
@Transient
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
@Transient
val showMore: ObservableBoolean = ObservableBoolean(false) // 展开状态
// 兼容现有API的isSelected属性
var isSelected: Boolean
@@ -140,6 +148,34 @@ data class GjcMaWb(
"1" -> "提前运抵"
else -> arriveFlag ?: ""
}
/**
* 航班信息(格式化后)
* 格式: yyyyMMdd/航班号
* 示例: 20260108/MU2025
*/
val flightInfo: String
get() {
val dateFormat = SimpleDateFormat("yyyyMMdd", Locale.getDefault())
val formattedDate = fdate?.let { dateFormat.format(it) } ?: ""
val flightNo = fno ?: ""
return if (formattedDate.isNotEmpty() && flightNo.isNotEmpty()) {
"$formattedDate/$flightNo"
} else {
flight ?: "" // 如果无法格式化回退到原始flight字段
}
}
/**
* 清仓正常状态中文
* 0-否1-是
*/
val clearNormalText: String
get() = when (clearNormal) {
"0" -> ""
"1" -> ""
else -> clearNormal ?: ""
}
}
/**
@@ -221,6 +257,7 @@ data class GjcHaWb(
var activeId: Long? = null // 活动ID
) {
// ==================== UI扩展字段 ====================
@Transient
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
// 兼容现有API的isSelected属性
@@ -237,9 +274,24 @@ data class GjcStorageUse(
var maWbId: Long? = null, // 运单id
var prefix: String? = null, // 运单前缀
var no: String? = null, // 运单号
var storageCode: String? = null, // 库位号
var inDate: Date? = null, // 入库时间
var inId: String? = null, // 入库人
var outDate: Date? = null, // 出库时间
var outId: String? = null // 出库人
)
var location: String? = null, // 库位号
var locationId: Long? = null, // 库位id
var storageCode: String? = null, // 库位号(兼容字段)
var uld: String? = null, // 板箱号
var inDate: String? = null, // 入库时间
var inOpId: String? = null, // 入库人
var inId: String? = null, // 入库人(兼容字段)
var outDate: String? = null, // 出库时间
var outOpId: String? = null, // 出库人
var outId: String? = null, // 出库人(兼容字段)
var cargoStatus: String? = null // 货物状态
) {
// ==================== UI扩展字段 ====================
@Transient
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
// 兼容现有API的isSelected属性
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
}

View File

@@ -43,6 +43,8 @@ data class GjcMove(
var remark: String = "", // 备注
var likeNo: String = "", // 部分运单号no模糊查询
var range: String = "",
// UI扩展字段 - 使用ObservableBoolean实现自动UI更新
val checked: ObservableBoolean = ObservableBoolean(false)
) : Serializable, ICheck {

View File

@@ -1,12 +1,13 @@
package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean
import java.io.Serializable
/**
* 国际出港板箱过磅-ULD使用记录Bean
* 对应API: IntExpWeighting/pageQuery
*/
class GjcUldUseBean {
class GjcUldUseBean : Serializable {
var useId: Long = 0 // 使用id
var uld: String = "" // uld编号
var carId: String = "" // 板车号
@@ -26,6 +27,8 @@ class GjcUldUseBean {
var fdep: String = "" // 起始站
var fdest: String = "" // 目的港
var fClose: String = "" // 航班关闭时间
var flight: String = "" // 航班信息
var transArea: String = "" // 库位号
// 格式化后的航班日期(只保留年月日)
val fdateFormatted: String
@@ -50,22 +53,59 @@ class GjcUldUseBean {
var dgrCode: String = "" // IMP代码
var height: String = "" // 高度
var passageway: String = "" // 通道号
var passagewayId: String = ""
var passagewayName: String = "" // 通道号(中文)
var piClose: String = "" // 探板/收口
var piCloseSize: String = "" // 探板尺寸(CM)
var plClose: String = "" // 探板/收口
var plCloseSize: String = "" // 探板尺寸(CM)
var location: String = "" // 位置
var pieces: String = "" // 件数
var pieces: String = "" // 件数(字符串格式)
var remark: String = "" // 备注
var checkFlag: String = "" // 检查标记
var emptyUld: String = "" // 空ULD
var loadArea: String = "" // 组装区
var pc: Long = 0 // 组装件数
var fillWeightFlag: String = "" // 回填状态0-未回填1-部分回填2-已回填
// 回填状态文本
val fillWeightFlagText: String
get() = when (fillWeightFlag) {
"0" -> "未回填"
"1" -> "部分回填"
"2" -> "已回填"
else -> ""
}
// 回填状态是否显示绿色(已回填时显示绿色)
val isFillWeightGreen: Boolean
get() = fillWeightFlag == "2"
// ========== 出港组装页面扩展字段 ==========
var isExpanded: Boolean = false // 展开状态
var isExpanded: Boolean = false // 展开状态(旧版保留)
@Transient
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态(Observable)
@Transient
val showMore: ObservableBoolean = ObservableBoolean(false) // 展开状态(Observable)
@Transient
val isLoading: ObservableBoolean = ObservableBoolean(false) // 子列表加载中状态
@Transient
var waybillDetailsLoaded: Boolean = false // 子列表是否已加载过(用于区分"未加载"和"加载后为空"
var waybillDetails: MutableList<GjcWarehouse>? = null // 运单明细缓存
// 子列表是否有数据
val hasWaybillDetails: Boolean
get() = waybillDetails != null && waybillDetails!!.isNotEmpty()
// 是否显示"暂无数据"(已加载但无数据)
val showEmptyView: Boolean
get() = waybillDetailsLoaded && !hasWaybillDetails
// 兼容原有代码的isSelected属性
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
// 复磅状态文本
val wtStatusText: String
get() = if (wtDate.isNotEmpty()) "已复磅" else "未复磅"
}

View File

@@ -1,16 +1,22 @@
package com.lukouguoji.module_base.bean
import java.io.Serializable
/**
* 国际出港-运单明细Bean
* 对应API: IntExpAssemble/queryAssembled
*/
class GjcWarehouse {
class GjcWarehouse : Serializable {
var whId: Long = 0 // ID
var no: String = "" // 运单号11位
var prefix: String = "" // 运单前缀
var wbNo: String = "" // 主运单编号
var pc: Long = 0 // 件数
var weight: Double = 0.0 // 重量
set(value) {
field = value
onDataChanged?.invoke()
}
var volume: Double = 0.0 // 体积
var agentCode: String = "" // 代理code
var agentName: String = "" // 代理名称
@@ -33,5 +39,26 @@ class GjcWarehouse {
var location: String = "" // uld
var checkInPc: Long = 0 // 入库件数
var checkInWeight: Double = 0.0 // 入库重量
set(value) {
field = value
onDataChanged?.invoke()
}
var assembleCount: Int = 0 // 已经组装的数量
// ========== UI扩展字段 ==========
/**
* 重量字符串用于双向绑定EditText
*/
var checkInWeightStr: String
get() = if (checkInWeight == 0.0) "" else checkInWeight.toString()
set(value) {
checkInWeight = value.toDoubleOrNull() ?: 0.0
}
/**
* 数据变化回调(用于实时计算统计)
*/
@Transient
var onDataChanged: (() -> Unit)? = null
}

View File

@@ -1,5 +1,6 @@
package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean
import com.lukouguoji.module_base.ktx.noNull
import dev.utils.DevFinal
import dev.utils.common.DateUtils
@@ -96,6 +97,14 @@ class GjcWeighingBean {
var storageUseList: List<Any>? = null // 库位使用列表
var attachList: List<Any>? = null // 附件列表
// ========== UI扩展字段 ==========
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
// 兼容现有API的isSelected属性
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
/**
* 预计起飞时间 - 仅时分格式 (HH:mm)
*/

View File

@@ -1,5 +1,6 @@
package com.lukouguoji.module_base.bean
import androidx.databinding.ObservableBoolean
import java.io.Serializable
/**
@@ -98,4 +99,12 @@ class GjcWeighingRecordBean : Serializable {
var haWbList: List<Any>? = null // 分单列表
var storageUseList: List<Any>? = null // 库位使用列表
var attachList: List<Any>? = null // 附件列表
// ========== UI扩展字段 ==========
val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态
// 兼容现有API的isSelected属性
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
}

View File

@@ -12,5 +12,5 @@ class UldInfoBean {
var totalPieces: String = "" // 总件数
var totalWeight: String = "" // 总重量
var status: String = "" // 状态(旧字段,保留兼容)
var useId: Long = 0 // ULD使用ID来自getUld接口
var useId: Long? = null // ULD使用ID来自getUld接口
}

View File

@@ -0,0 +1,18 @@
package com.lukouguoji.module_base.cache
import dev.DevUtils
/**
* PDF文件缓存管理
* 负责PDF文件的下载和本地缓存
*/
object PdfCacheModel : FileCacheModel() {
/**
* PDF文件缓存目录
* 路径: /data/data/com.lukouguoji.aerologic/files/cacheFiles/pdf/
*/
override fun folderPath(): String {
return DevUtils.getContext().filesDir.toString() + "/cacheFiles/pdf/"
}
}

View File

@@ -253,6 +253,7 @@ interface Constant {
const val GjcIntExpLoad = "AppIntExpLoad" //出港装载
const val GjcIntExpTally = "AppIntExpTally" //出港理货
const val GjcIntExpArrive = "AppIntExpArrive" //出港运抵
const val GjcIntExpStorageUse = "AppIntExpStorageUse" //仓库
/**
* 国际进港

View File

@@ -432,7 +432,8 @@ interface Api {
* 接口路径: /IntExpCheckInCheck/pass
*/
@POST("IntExpCheckInCheck/pass")
suspend fun passGjcInspection(@Body data: RequestBody): BaseResultBean<Boolean>
suspend fun passGjcInspection(
@Query("reason") reason: String, @Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港收运审核-退回运单列表maWbId、wbNo必填
@@ -553,6 +554,13 @@ interface Api {
@POST("IntExpWeighting/weight")
suspend fun submitGjcBoxWeighing(@Body data: RequestBody): BaseResultBean<SimpleResultBean>
/**
* 国际出港板箱过磅-根据ULD查询正在使用的记录
* 接口路径: /IntExpWeighting/queryUsingUldByUld
*/
@GET("IntExpWeighting/queryUsingUldByUld")
suspend fun queryUsingUldByUld(@Query("uld") uld: String): BaseResultBean<GjcUldUseBean>
// ==================== 国际出港-出港组装 ====================
/**
@@ -580,10 +588,10 @@ interface Api {
/**
* 国际出港组装-删除记录
* 接口路径: /IntExpAssemble/delete
* @param ids ULD使用记录ID多个用逗号分隔
* @param data GjcUldUseBean数组
*/
@POST("IntExpAssemble/delete")
suspend fun deleteIntExpAssemble(@Query("ids") ids: String): BaseResultBean<SimpleResultBean>
suspend fun deleteIntExpAssemble(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港组装-回填重量
@@ -653,6 +661,14 @@ interface Api {
@POST("IntExpAssemble/queryAssembledByUld")
suspend fun getAssembledWaybillsByUld(@Body data: RequestBody): BaseResultBean<List<GjcWarehouse>>
/**
* 国际出港组装 - 修改列表(批量更新运单重量)
* 接口路径: /IntExpAssemble/update
* @param data GjcWarehouse数组
*/
@POST("IntExpAssemble/update")
suspend fun updateIntExpAssemble(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港出库交接-分页查询
* 接口路径: /IntExpOutHandover/pageQuery
@@ -675,6 +691,14 @@ interface Api {
@POST("IntExpOutHandover/handover")
suspend fun completeHandover(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港出库交接-待配运
* 接口路径: /IntExpOutHandover/waitingTrans
* @param data 请求参数GjcStorageParam (包含库位信息和ULD列表)
*/
@POST("IntExpOutHandover/waitingTrans")
suspend fun waitingTrans(@Body data: RequestBody): BaseResultBean<String>
/**
* 国际出港-出港装载 分页查询
* 接口路径: /IntExpLoad/pageQuery
@@ -810,6 +834,63 @@ interface Api {
@POST("IntExpMove/move")
suspend fun submitIntExpMove(@Body data: RequestBody): BaseResultBean<SimpleResultBean>
/**
* 国际出港库位操作-清仓
* 接口路径: /IntExpStorageUse/updateClear
*/
@POST("IntExpStorageUse/updateClear")
suspend fun clearIntExpStorage(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港库位操作-修改库位
* 接口路径: /IntExpStorageUse/modifyStorage
*/
@POST("IntExpStorageUse/modifyStorage")
suspend fun modifyIntExpStorage(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港库位操作-出库
* 接口路径: /IntExpStorageUse/outStorage
*/
@POST("IntExpStorageUse/outStorage")
suspend fun outIntExpStorage(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港库位操作-入库
* 接口路径: /IntExpStorageUse/inStorage
*/
@POST("IntExpStorageUse/inStorage")
suspend fun inIntExpStorage(@Body data: RequestBody): BaseResultBean<Boolean>
/**
* 国际出港仓库-分页查询
* 接口路径: /IntExpStorageUse/pageQuery
*/
@POST("IntExpStorageUse/pageQuery")
suspend fun getIntExpStorageUseList(@Body data: RequestBody): PageInfo<GjcMaWb>
/**
* 国际出港仓库-分页合计
* 接口路径: /IntExpStorageUse/pageQueryTotal
*/
@POST("IntExpStorageUse/pageQueryTotal")
suspend fun getIntExpStorageUseTotal(@Body data: RequestBody): BaseResultBean<ManifestTotalDto>
/**
* 国际出港仓库-运单号模糊查询(后四位)
* 接口路径: /IntExpStorageUse/queryWbNoList
*/
@POST("IntExpStorageUse/queryWbNoList")
suspend fun getIntExpStorageWbNoList(@Body data: RequestBody): BaseResultBean<List<String>>
/**
* 获取库位列表
* 接口路径: /typeCode/locationByFlag
* @param flag 库位标志2表示国际出港库位
*/
@GET("typeCode/locationByFlag")
suspend fun getLocationList(@Query("flag") flag: Int): BaseResultBean<List<DictLocationBean>>
/**
* 国际出港待计重-分页搜索
* 接口路径: /IntExpCheckIn/pageQuery
@@ -824,6 +905,21 @@ interface Api {
@POST("IntExpCheckIn/pageQueryTotal")
suspend fun getGjcWeighingStatistics(@Body data: RequestBody): BaseResultBean<GjcWeighingStatisticsBean>
/**
* 国际出港待计重-运单号模糊查询(用于自动补全)
* 接口路径: /IntExpCheckIn/queryWbNoList
*/
@POST("IntExpCheckIn/queryWbNoList")
suspend fun getIntExpCheckInWbNoList(@Body data: RequestBody): BaseResultBean<List<String>>
/**
* 国际出港待计重-根据11位具体运单号查询运单信息
* 接口路径: /IntExpCheckIn/queryWbByNo
* @param wbNo 11位运单号
*/
@POST("IntExpCheckIn/queryWbByNo")
suspend fun getIntExpCheckInWbByNo(@Query("wbNo") wbNo: String): BaseResultBean<GjcMaWb>
/**
* 国际出港待计重-开始计重-根据wbId查询详情
* 接口路径: /IntExpCheckIn/queryWbById
@@ -885,6 +981,14 @@ interface Api {
@POST("IntExpCheckIn/updateRecordList")
suspend fun updateGjcCheckInRecordList(@Body data: List<GjcCheckInRecord>): BaseResultBean<Boolean>
/**
* 国际出港待计重-提前运抵
* 接口路径: /IntExpCheckIn/preArrive
* 参数: List<GjcMaWb> - 运单列表
*/
@POST("IntExpCheckIn/preArrive")
suspend fun preArrive(@Body data: List<GjcMaWb>): BaseResultBean<String>
///////////////////////////////////////////////////////////////////////////
// 国际进-电报解析
///////////////////////////////////////////////////////////////////////////

View File

@@ -146,6 +146,8 @@ object ARouterConstants {
const val ACTIVITY_URL_INT_EXP_LOAD = "/gjc/IntExpLoadActivity" //国际出港 出港装载
const val ACTIVITY_URL_INT_EXP_TALLY = "/gjc/IntExpTallyActivity" //国际出港 出港理货
const val ACTIVITY_URL_INT_EXP_ARRIVE = "/gjc/IntExpArriveActivity" //国际出港 出港运抵
const val ACTIVITY_URL_INT_EXP_STORAGE_USE = "/gjc/IntExpStorageUseActivity" //国际出港 仓库
const val ACTIVITY_URL_GJC_ASSEMBLE_WEIGHT_EDIT = "/gjc/GjcAssembleWeightEditActivity" //国际出港 修改组装重量
///////////////// 国际进港模块
/**

View File

@@ -0,0 +1,223 @@
package com.lukouguoji.module_base.ui.page.preview
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
import com.github.barteksc.pdfviewer.listener.OnErrorListener
import com.lukouguoji.module_base.R
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.cache.PdfCacheModel
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.databinding.ActivityPdfPreviewBinding
import com.lukouguoji.module_base.impl.EmptyViewModel
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.util.MediaUtil
import dev.utils.app.BarUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
/**
* PDF预览界面
* 支持在线PDF下载缓存和本地PDF文件预览
*/
class PdfPreviewActivity : BaseBindingActivity<ActivityPdfPreviewBinding, EmptyViewModel>() {
// PDF文件路径 (可能是在线URL或本地路径)
private var pdfPath: String = ""
// 当前页码 (从0开始)
private var currentPage = 0
// 总页数
private var totalPages = 0
override fun layoutId() = R.layout.activity_pdf_preview
override fun viewModelClass() = EmptyViewModel::class.java
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 强制横屏显示 (与项目其他页面保持一致)
resources?.let {
if (it.displayMetrics.widthPixels < it.displayMetrics.heightPixels) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
}
@SuppressLint("SourceLockedOrientationActivity")
override fun initOnCreate(savedInstanceState: Bundle?) {
// 设置沉浸式状态栏
BarUtils.transparentStatusBar(this)
// 获取传递的PDF路径
pdfPath = intent.getStringExtra(Constant.Key.DATA) ?: ""
if (pdfPath.isEmpty()) {
showToast("PDF路径为空")
finish()
return
}
// 设置关闭按钮
binding.ivClose.setOnClickListener {
finish()
}
// 加载PDF文件
loadPdfFile()
}
/**
* 加载PDF文件 (支持在线和本地)
*/
private fun loadPdfFile() {
// val fullUrl = MediaUtil.fillUrl(pdfPath)
val fullUrl = pdfPath
// 判断是否为在线资源
if (isOnlineResource(fullUrl)) {
// 在线PDF: 先下载再加载
downloadAndLoadPdf(fullUrl)
} else {
// 本地PDF: 直接加载
loadLocalPdf(File(pdfPath))
}
}
/**
* 判断是否为在线资源
*/
private fun isOnlineResource(url: String): Boolean {
return url.startsWith("http://") || url.startsWith("https://")
}
/**
* 下载在线PDF并加载
*/
private fun downloadAndLoadPdf(url: String) {
// 显示Loading
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
try {
// 使用PdfCacheModel下载PDF
PdfCacheModel.saveFile(
url = url,
onProgress = { progress ->
// 更新下载进度
lifecycleScope.launch(Dispatchers.Main) {
binding.tvLoadingTip.text = "正在下载PDF... $progress%"
}
},
onSuccess = { file ->
// 下载成功切换到主线程加载PDF
lifecycleScope.launch(Dispatchers.Main) {
hideLoading()
loadLocalPdf(file)
}
},
onError = { errorMsg ->
// 下载失败
lifecycleScope.launch(Dispatchers.Main) {
hideLoading()
showToast("PDF下载失败: $errorMsg")
finish()
}
}
)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
hideLoading()
showToast("PDF下载异常: ${e.message}")
finish()
}
}
}
}
/**
* 加载本地PDF文件
*/
private fun loadLocalPdf(file: File) {
if (!file.exists()) {
showToast("PDF文件不存在")
finish()
return
}
try {
binding.pdfView.fromFile(file)
.defaultPage(0) // 默认显示第一页
.enableSwipe(true) // 启用滑动翻页
.swipeHorizontal(false) // 垂直滑动
.enableDoubletap(true) // 启用双击缩放
.enableAnnotationRendering(false) // 禁用注释渲染(提升性能)
.password(null) // PDF密码(如需要)
.scrollHandle(null) // 禁用默认滚动条
.onLoad(OnLoadCompleteListener { nbPages ->
// PDF加载完成
totalPages = nbPages
updatePageIndicator()
})
.onPageChange(OnPageChangeListener { page, pageCount ->
// 页面切换
currentPage = page
updatePageIndicator()
})
.onError(OnErrorListener { t ->
// 加载错误
showToast("PDF加载失败: ${t.message}")
finish()
})
.load()
} catch (e: Exception) {
showToast("PDF渲染异常: ${e.message}")
finish()
}
}
/**
* 更新页码指示器
*/
private fun updatePageIndicator() {
binding.tvPageIndicator.text = "${currentPage + 1} / $totalPages"
}
/**
* 显示Loading
*/
private fun showLoading() {
binding.loadingLayout.visibility = View.VISIBLE
}
/**
* 隐藏Loading
*/
private fun hideLoading() {
binding.loadingLayout.visibility = View.GONE
}
companion object {
/**
* 启动PDF预览
* @param context 上下文
* @param pdfPath PDF路径 (支持在线URL或本地路径)
*/
@JvmStatic
fun start(context: Context, pdfPath: String) {
val starter = Intent(context, PdfPreviewActivity::class.java)
.putExtra(Constant.Key.DATA, pdfPath)
context.startActivity(starter)
}
}
}

View File

@@ -21,6 +21,8 @@ import com.lukouguoji.module_base.ktx.formatDate
import com.lukouguoji.module_base.ktx.getActivity
import com.lukouguoji.module_base.ktx.loge
import com.lukouguoji.module_base.ktx.tryCatch
import com.lukouguoji.module_base.ui.weight.data.layout.AutoQueryConfig
import com.lukouguoji.module_base.ui.weight.search.layout.manager.SearchAutoQueryManager
import com.lukouguoji.module_base.util.Common
import dev.utils.app.info.KeyValue
import java.util.Calendar
@@ -102,6 +104,16 @@ class PadSearchLayout : LinearLayout {
loadImage(iv, value)
}
/**
* 自动查询配置
*/
var autoQueryConfig: AutoQueryConfig = AutoQueryConfig()
/**
* 自动查询管理器(延迟初始化)
*/
private var autoQueryManager: SearchAutoQueryManager? = null
// 选择日期
private val dateClick: (v: View) -> Unit = {
if (enable) {
@@ -158,6 +170,30 @@ class PadSearchLayout : LinearLayout {
setForType()
}
/**
* 启用自动查询功能
*/
fun enableAutoQuery(config: AutoQueryConfig) {
this.autoQueryConfig = config
if (config.isValid()) {
// 初始化查询管理器
autoQueryManager = SearchAutoQueryManager(et, config) { newValue ->
// 更新值的回调
this.value = newValue
}
autoQueryManager?.attach()
}
}
/**
* 销毁时清理资源
*/
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
autoQueryManager?.detach()
autoQueryManager = null
}
private fun setForType() {
when (type) {
SearchLayoutType.INPUT -> {

View File

@@ -23,6 +23,8 @@ import com.lukouguoji.module_base.ktx.formatDate
import com.lukouguoji.module_base.ktx.getActivity
import com.lukouguoji.module_base.ktx.loge
import com.lukouguoji.module_base.ktx.tryCatch
import com.lukouguoji.module_base.ui.weight.data.layout.AutoQueryConfig
import com.lukouguoji.module_base.ui.weight.search.layout.manager.SearchAutoQueryManager
import com.lukouguoji.module_base.util.Common
import dev.utils.app.info.KeyValue
import java.util.Calendar
@@ -104,6 +106,16 @@ class PadSearchLayoutNew : LinearLayout {
loadImage(iv, value)
}
/**
* 自动查询配置
*/
var autoQueryConfig: AutoQueryConfig = AutoQueryConfig()
/**
* 自动查询管理器(延迟初始化)
*/
private var autoQueryManager: SearchAutoQueryManager? = null
// 选择日期
private val dateClick: (v: View) -> Unit = {
if (enable) {
@@ -179,6 +191,30 @@ class PadSearchLayoutNew : LinearLayout {
setForType()
}
/**
* 启用自动查询功能
*/
fun enableAutoQuery(config: AutoQueryConfig) {
this.autoQueryConfig = config
if (config.isValid()) {
// 初始化查询管理器
autoQueryManager = SearchAutoQueryManager(et, config) { newValue ->
// 更新值的回调
this.value = newValue
}
autoQueryManager?.attach()
}
}
/**
* 销毁时清理资源
*/
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
autoQueryManager?.detach()
autoQueryManager = null
}
private fun setForType() {
when (type) {
SearchLayoutType.INPUT -> {

View File

@@ -8,6 +8,7 @@ import androidx.core.widget.doOnTextChanged
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import com.lukouguoji.module_base.ktx.UpperCaseAlphanumericInputFilter
import dev.utils.app.EditTextUtils
import dev.utils.app.info.KeyValue
@@ -289,3 +290,243 @@ fun setSearchLayoutNewInputWaybill(layout: PadSearchLayoutNew, isWaybill: Boolea
}
}
}
///////////////////////////////////////////////////////////////////////////
// 大写字母+数字输入限制 BindingAdapter
///////////////////////////////////////////////////////////////////////////
/**
* 为PadSearchLayout设置大写字母+数字输入限制
* 自动转换小写为大写,过滤中文、特殊符号、空格
*/
@BindingAdapter("setUpperCaseAlphanumeric", requireAll = false)
fun setUpperCaseAlphanumeric(layout: PadSearchLayout, enabled: Boolean) {
if (enabled) {
layout.et.filters = arrayOf(UpperCaseAlphanumericInputFilter())
}
}
///////////////////////////////////////////////////////////////////////////
// PadSearchLayout 自动查询功能 BindingAdapter
///////////////////////////////////////////////////////////////////////////
/**
* 启用自动查询功能
* @param enabled 是否启用
*/
@BindingAdapter("autoQueryEnabled")
fun setSearchLayoutAutoQueryEnabled(layout: PadSearchLayout, enabled: Boolean) {
layout.autoQueryConfig.enabled = enabled
}
/**
* 设置查询接口地址
* @param url 接口地址(如:/IntExpCheckIn/queryWbNoList
*/
@BindingAdapter("autoQueryUrl")
fun setSearchLayoutAutoQueryUrl(layout: PadSearchLayout, url: String?) {
layout.autoQueryConfig.url = url ?: ""
}
/**
* 设置查询参数的 key 名称
* @param paramKey 参数名(默认 "value"
*/
@BindingAdapter("autoQueryParamKey")
fun setSearchLayoutAutoQueryParamKey(layout: PadSearchLayout, paramKey: String?) {
layout.autoQueryConfig.paramKey = paramKey ?: "value"
}
/**
* 设置触发查询的最小长度
* @param minLength 最小长度(默认 4
*/
@BindingAdapter("autoQueryMinLength")
fun setSearchLayoutAutoQueryMinLength(layout: PadSearchLayout, minLength: Int?) {
layout.autoQueryConfig.minLength = minLength ?: 4
}
/**
* 设置触发查询的最大长度
* @param maxLength 最大长度(默认 8
*/
@BindingAdapter("autoQueryMaxLength")
fun setSearchLayoutAutoQueryMaxLength(layout: PadSearchLayout, maxLength: Int?) {
layout.autoQueryConfig.maxLength = maxLength ?: 8
}
/**
* 设置弹框标题
* @param title 标题(默认 "请选择"
*/
@BindingAdapter("autoQueryTitle")
fun setSearchLayoutAutoQueryTitle(layout: PadSearchLayout, title: String?) {
layout.autoQueryConfig.title = title ?: "请选择"
}
/**
* 设置防抖延迟
* @param debounceMillis 延迟毫秒数(默认 300ms
*/
@BindingAdapter("autoQueryDebounce")
fun setSearchLayoutAutoQueryDebounce(layout: PadSearchLayout, debounceMillis: Long?) {
layout.autoQueryConfig.debounceMillis = debounceMillis ?: 300L
}
/**
* 统一配置自动查询(所有属性设置完成后调用)
*
* ⚠️ 重要:必须在所有 autoQuery* 属性之后绑定,使用 requireAll = false
*/
@BindingAdapter(
"autoQueryEnabled",
"autoQueryUrl",
"autoQueryParamKey",
"autoQueryMinLength",
"autoQueryMaxLength",
"autoQueryTitle",
"autoQueryDebounce",
requireAll = false
)
fun configureSearchLayoutAutoQuery(
layout: PadSearchLayout,
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)
}
}
/**
* 为PadSearchLayoutNew设置大写字母+数字输入限制
* 自动转换小写为大写,过滤中文、特殊符号、空格
*/
@BindingAdapter("setUpperCaseAlphanumeric", requireAll = false)
fun setSearchLayoutNewUpperCaseAlphanumeric(layout: PadSearchLayoutNew, enabled: Boolean) {
if (enabled) {
layout.et.filters = arrayOf(UpperCaseAlphanumericInputFilter())
}
}
///////////////////////////////////////////////////////////////////////////
// PadSearchLayoutNew 自动查询功能 BindingAdapter
///////////////////////////////////////////////////////////////////////////
/**
* 启用自动查询功能
* @param enabled 是否启用
*/
@BindingAdapter("autoQueryEnabled")
fun setSearchLayoutNewAutoQueryEnabled(layout: PadSearchLayoutNew, enabled: Boolean) {
layout.autoQueryConfig.enabled = enabled
}
/**
* 设置查询接口地址
* @param url 接口地址(如:/IntExpCheckIn/checked/queryWbNoList
*/
@BindingAdapter("autoQueryUrl")
fun setSearchLayoutNewAutoQueryUrl(layout: PadSearchLayoutNew, url: String?) {
layout.autoQueryConfig.url = url ?: ""
}
/**
* 设置查询参数的 key 名称
* @param paramKey 参数名(默认 "value"
*/
@BindingAdapter("autoQueryParamKey")
fun setSearchLayoutNewAutoQueryParamKey(layout: PadSearchLayoutNew, paramKey: String?) {
layout.autoQueryConfig.paramKey = paramKey ?: "value"
}
/**
* 设置触发查询的最小长度
* @param minLength 最小长度(默认 4
*/
@BindingAdapter("autoQueryMinLength")
fun setSearchLayoutNewAutoQueryMinLength(layout: PadSearchLayoutNew, minLength: Int?) {
layout.autoQueryConfig.minLength = minLength ?: 4
}
/**
* 设置触发查询的最大长度
* @param maxLength 最大长度(默认 8
*/
@BindingAdapter("autoQueryMaxLength")
fun setSearchLayoutNewAutoQueryMaxLength(layout: PadSearchLayoutNew, maxLength: Int?) {
layout.autoQueryConfig.maxLength = maxLength ?: 8
}
/**
* 设置弹框标题
* @param title 标题(默认 "请选择"
*/
@BindingAdapter("autoQueryTitle")
fun setSearchLayoutNewAutoQueryTitle(layout: PadSearchLayoutNew, title: String?) {
layout.autoQueryConfig.title = title ?: "请选择"
}
/**
* 设置防抖延迟
* @param debounceMillis 延迟毫秒数(默认 300ms
*/
@BindingAdapter("autoQueryDebounce")
fun setSearchLayoutNewAutoQueryDebounce(layout: PadSearchLayoutNew, debounceMillis: Long?) {
layout.autoQueryConfig.debounceMillis = debounceMillis ?: 300L
}
/**
* 统一配置自动查询(所有属性设置完成后调用)
*
* ⚠️ 重要:必须在所有 autoQuery* 属性之后绑定,使用 requireAll = false
*/
@BindingAdapter(
"autoQueryEnabled",
"autoQueryUrl",
"autoQueryParamKey",
"autoQueryMinLength",
"autoQueryMaxLength",
"autoQueryTitle",
"autoQueryDebounce",
requireAll = false
)
fun configureSearchLayoutNewAutoQuery(
layout: PadSearchLayoutNew,
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

@@ -0,0 +1,181 @@
package com.lukouguoji.module_base.ui.weight.search.layout.manager
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
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.toJson
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ui.weight.data.layout.AutoQueryConfig
import com.lukouguoji.module_base.util.Common
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* 搜索框专用自动查询管理器
* 负责处理输入监听、防抖、查询请求、结果处理
*
* 功能:
* 1. 监听 EditText 输入变化
* 2. 防抖延迟(避免频繁请求)
* 3. 防重复查询(相同值不重复请求)
* 4. 调用接口查询数据
* 5. 处理查询结果(单条填充、多条弹框)
* 6. 自动管理协程生命周期
*/
class SearchAutoQueryManager(
private val editText: EditText,
private val config: AutoQueryConfig,
private val onValueSelected: (String) -> Unit
) {
/** 协程作用域(从 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(editText)
if (lifecycleOwner == null) {
// 延迟绑定(等待 ViewTree 附加)
editText.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)
}
}
editText.addTextChangedListener(textWatcher)
}
/**
* 解绑(移除监听、取消协程)
*/
fun detach() {
textWatcher?.let { editText.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 -> {
onValueSelected(results[0])
}
// 多条结果:显示弹框选择
results.size > 1 -> {
showSelectionDialog(results)
}
// 0 条结果:不做处理
else -> {
// 可选showToast("未找到匹配数据")
}
}
}
/**
* 显示选择弹框
*/
private fun showSelectionDialog(results: List<String>) {
val activity = editText.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, _ ->
// 用户选择后更新值
onValueSelected(results[position])
}
}
}

View File

@@ -459,7 +459,7 @@ object PrinterUtils {
addSize(100, 100)
addGap(3)
addCls()
addTextByBitmap(80, 80, 0, 130, "扬州泰州机场", Typeface.DEFAULT)
addTextByBitmap(80, 80, 0, 130, "合肥新桥国际机场", Typeface.DEFAULT)
// 绘制表格横线
for (i in 0..rows.size) {
@@ -567,7 +567,7 @@ object PrinterUtils {
fun printTest() {
getConnectedPrinters().forEach {
val bytes = v(Instruction.TSC.toString(), null).bytes
val bytes = v(Instruction.TSC).bytes
showLog("打印测试页 - ${it.printerDevice.printerName} \n${bytes.commonToUtf8String()}")
it.print(bytes, null)
}

View File

@@ -27,6 +27,13 @@
android:configChanges="orientation|keyboardHidden"
android:exported="true"/>
<!-- PDF预览Activity -->
<activity
android:name=".ui.page.preview.PdfPreviewActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
<!-- 屏幕适配 -->
<meta-data
android:name="design_width_in_dp"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="8dp" />
<solid android:color="#008000" />
<solid android:color="#E8F5E9" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1408dp"
android:height="1024dp"
android:viewportWidth="1408"
android:viewportHeight="1024">
<path
android:fillColor="#599dff"
android:pathData="M1327.05296 200.971963H427.464174c-38.280374 0-76.560748-38.280374-76.560747-76.560748s31.900312-76.560748 76.560747-76.560748h899.588786c38.280374 0 76.560748 38.280374 76.560747 76.560748s-31.900312 76.560748-76.560747 76.560748zM669.906542 519.975078v-12.760125c-6.380062-19.140187-12.760125-31.900312-25.520249-44.660436l-25.520249-25.520249h708.186916c44.660436 0 76.560748 31.900312 76.560747 76.560748s-31.900312 76.560748-76.560747 76.560747H618.866044l25.520249-25.520249c12.760125-12.760125 19.140187-25.520249 25.520249-44.660436zM350.903427 513.595016L25.520249 169.071651C12.760125 149.931464 0 130.791277 0 98.890966s12.760125-51.040498 25.520249-70.180686c38.280374-38.280374 95.700935-38.280374 133.981309 0l382.803738 401.943926 25.520249 25.520249c12.760125 12.760125 19.140187 25.520249 25.520249 44.660436V513.595016c-6.380062 25.520249-12.760125 38.280374-25.520249 51.040498L542.305296 590.155763 159.501558 992.099688c-19.140187 19.140187-44.660436 31.900312-63.800623 31.900312-25.520249 0-51.040498-12.760125-63.800623-31.900312-19.140187-12.760125-31.900312-38.280374-31.900312-63.800623s12.760125-51.040498 25.520249-70.180685L350.903427 513.595016z m76.560747 312.623053h899.588786c38.280374 0 76.560748 38.280374 76.560747 76.560747s-31.900312 76.560748-76.560747 76.560748H427.464174c-38.280374 0-76.560748-38.280374-76.560747-76.560748s38.280374-76.560748 76.560747-76.560747z" />
</vector>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 圆形大小 -->
<size
android:width="15dp"
android:height="15dp" />
<!-- 深灰色填充 -->
<solid android:color="#808080" />
<!-- 黑色边框 -->
<stroke
android:width="1dp"
android:color="@color/black" />
</shape>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.lukouguoji.module_base.impl.EmptyViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<!-- PDF视图 -->
<com.github.barteksc.pdfviewer.PDFView
android:id="@+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Loading蒙层 (下载时显示) -->
<RelativeLayout
android:id="@+id/loadingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_tran50"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/black_tran70"
android:gravity="center"
android:orientation="vertical"
android:padding="20dp">
<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminateTint="@color/white" />
<TextView
android:id="@+id/tvLoadingTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="正在加载PDF..."
android:textColor="@color/white"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>
<!-- 页码指示器 -->
<TextView
android:id="@+id/tvPageIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:background="@color/black_tran50"
android:paddingLeft="15dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="5dp"
android:text="1 / 10"
android:textColor="@color/white"
android:textSize="16sp" />
<!-- 关闭按钮 -->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/ivClose"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_marginTop="60dp"
android:layout_marginRight="30dp"
android:background="@color/black_tran30"
android:padding="4dp"
android:src="@drawable/img_close" />
</RelativeLayout>
</layout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -41,4 +41,9 @@
<color name="bottom_tool_tips_text_color">#797979</color>
<!-- 半透明黑色 (用于PDF预览) -->
<color name="black_tran30">#4D000000</color> <!-- 30%透明度 -->
<color name="black_tran50">#80000000</color> <!-- 50%透明度 -->
<color name="black_tran70">#B3000000</color> <!-- 70%透明度 -->
</resources>

View File

@@ -0,0 +1,69 @@
package com.lukouguoji.gjc.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ActivityGjcAssembleWeightEditBinding
import com.lukouguoji.gjc.holder.GjcAssembleWeightEditViewHolder
import com.lukouguoji.gjc.viewModel.GjcAssembleWeightEditViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.base.CommonAdapter
import com.lukouguoji.module_base.bean.GjcUldUseBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.router.ARouterConstants
/**
* 国际出港-修改组装重量页面
*/
@Route(path = ARouterConstants.ACTIVITY_URL_GJC_ASSEMBLE_WEIGHT_EDIT)
class GjcAssembleWeightEditActivity :
BaseBindingActivity<ActivityGjcAssembleWeightEditBinding, GjcAssembleWeightEditViewModel>() {
private lateinit var adapter: CommonAdapter
override fun layoutId() = R.layout.activity_gjc_assemble_weight_edit
override fun viewModelClass() = GjcAssembleWeightEditViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("修改组装重量")
binding.viewModel = viewModel
// 初始化RecyclerView
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
// 添加分割线
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
binding.recyclerView.addItemDecoration(divider)
adapter = CommonAdapter(
this,
R.layout.item_gjc_assemble_weight_edit,
GjcAssembleWeightEditViewHolder::class.java
)
binding.recyclerView.adapter = adapter
// 监听数据变化更新RecyclerView
viewModel.waybillList.observe(this) { records ->
adapter.refresh(records)
}
// 初始化数据
viewModel.initOnCreated(intent)
}
companion object {
@JvmStatic
fun start(context: Context, bean: GjcUldUseBean) {
val starter = Intent(context, GjcAssembleWeightEditActivity::class.java)
.putExtra(Constant.Key.BEAN, bean)
context.startActivity(starter)
}
}
}

View File

@@ -24,10 +24,11 @@ class GjcBoxWeighingAddActivity :
binding.viewModel = viewModel
viewModel.initOnCreated(this)
// 为架子车号、ULD编码、IMP代码添加大写字母和数字的输入限制
// 为架子车号、ULD编码、IMP代码、航班号添加大写字母和数字的输入限制
binding.carIdInput.et.setUpperCaseAlphanumericFilter()
binding.uldNoInput.et.setUpperCaseAlphanumericFilter()
binding.impCodeInput.et.setUpperCaseAlphanumericFilter()
binding.flightNoInput.et.setUpperCaseAlphanumericFilter()
}
companion object {

View File

@@ -30,6 +30,11 @@ class GjcWeighingListActivity :
binding.viewModel = viewModel
// 观察全选状态,更新图标透明度
viewModel.isAllChecked.observe(this) { isAllChecked ->
binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
}
// 初始化代理人列表从API获取
viewModel.initAgentList()

View File

@@ -3,7 +3,9 @@ package com.lukouguoji.gjc.activity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ActivityIntExpArriveBinding
import com.lukouguoji.gjc.viewModel.IntExpArriveViewModel
@@ -22,13 +24,26 @@ import com.lukouguoji.module_base.router.ARouterConstants
class IntExpArriveActivity :
BaseBindingActivity<ActivityIntExpArriveBinding, IntExpArriveViewModel>() {
@JvmField
@Autowired
var wbNoParam: String? = null
override fun layoutId() = R.layout.activity_int_exp_arrive
override fun viewModelClass() = IntExpArriveViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
// 注入 ARouter 参数
ARouter.getInstance().inject(this)
setBackArrow("出港运抵")
binding.viewModel = viewModel
// 如果有传入运单号,自动填充并触发搜索
if (!wbNoParam.isNullOrEmpty()) {
viewModel.waybillNo.value = wbNoParam
viewModel.searchClick()
}
// 观察全选状态,更新图标透明度
viewModel.isAllChecked.observe(this) { isAllChecked ->
binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
@@ -45,8 +60,10 @@ class IntExpArriveActivity :
viewModel.refresh()
}
// 初始加载数据
viewModel.refresh()
// 初始加载数据(如果没有传入运单号,才执行初始加载)
if (wbNoParam.isNullOrEmpty()) {
viewModel.refresh()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@@ -59,11 +59,6 @@ class IntExpLoadActivity :
viewModel.waybillNo.value = codedContent
viewModel.searchClick()
}
// 扫码分单号
Constant.RequestCode.CODE -> {
viewModel.houseWaybillNo.value = codedContent
viewModel.searchClick()
}
}
}
}

View File

@@ -0,0 +1,198 @@
package com.lukouguoji.gjc.activity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ActivityIntExpStorageUseBinding
import com.lukouguoji.gjc.dialog.IntExpMoveClearDialogModel
import com.lukouguoji.gjc.dialog.IntExpModifyStorageDialogModel
import com.lukouguoji.gjc.dialog.IntExpInStorageDialogModel
import com.lukouguoji.gjc.viewModel.IntExpStorageUseViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
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.commonAdapter
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.router.ARouterConstants
/**
* 国际出港-仓库
*/
@Route(path = ARouterConstants.ACTIVITY_URL_INT_EXP_STORAGE_USE)
class IntExpStorageUseActivity :
BaseBindingActivity<ActivityIntExpStorageUseBinding, IntExpStorageUseViewModel>() {
override fun layoutId() = R.layout.activity_int_exp_storage_use
override fun viewModelClass() = IntExpStorageUseViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("国际出港仓库")
binding.viewModel = viewModel
binding.activity = this
// 观察全选状态,更新图标透明度
viewModel.isAllChecked.observe(this) { isAllChecked ->
binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
}
// 绑定分页
viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)
// 监听刷新事件
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
// 初始加载数据
viewModel.refresh()
}
/**
* 显示清仓操作对话框
*/
fun showClearDialog() {
val list = viewModel.pageModel.rv?.commonAdapter()?.items as? List<*> ?: return
val allItems = list.filterIsInstance<com.lukouguoji.module_base.bean.GjcMaWb>()
// 构建清仓数据:保留主列表结构,但只包含选中的子列表项
val maWbListForClear = allItems.mapNotNull { maWb ->
// 过滤出选中的子列表项
val selectedStorageList = maWb.storageUseList?.filter { it.isSelected } ?: emptyList()
// 只添加有选中子列表项的主列表项
if (selectedStorageList.isNotEmpty() || maWb.isSelected) {
// 创建主列表项的副本,只包含选中的子列表
maWb.copy(storageUseList = selectedStorageList)
} else {
null
}
}
if (maWbListForClear.isEmpty()) {
showToast("请至少选择一个库位")
return
}
// 显示清仓对话框
IntExpMoveClearDialogModel { dialog ->
// 用户点击保存后,执行清仓操作
val clearNormal = dialog.clearNormal.value ?: ""
viewModel.performClear(clearNormal, maWbListForClear)
}.show(this)
}
/**
* 显示修改库位对话框
*/
fun showModifyStorageDialog() {
val list = viewModel.pageModel.rv?.commonAdapter()?.items as? List<*> ?: return
val allItems = list.filterIsInstance<com.lukouguoji.module_base.bean.GjcMaWb>()
// 收集所有选中的子列表项(库位)
val selectedStorageUseList = mutableListOf<com.lukouguoji.module_base.bean.GjcStorageUse>()
allItems.forEach { maWb ->
maWb.storageUseList?.filter { it.isSelected }?.let { selectedStorageUseList.addAll(it) }
}
// 校验:必须且只能选中一个库位
when {
selectedStorageUseList.isEmpty() -> {
showToast("请选择要修改的库位")
return
}
selectedStorageUseList.size > 1 -> {
showToast("只能选择一个库位进行修改")
return
}
}
val selectedStorage = selectedStorageUseList[0]
// 显示修改库位对话框
IntExpModifyStorageDialogModel { dialog ->
// 用户点击保存后,执行修改库位操作
val locationName = dialog.locationName
val locationId = dialog.locationId
viewModel.performModifyStorage(locationName, locationId, selectedStorage)
}.show(this)
}
/**
* 显示出库二次确认对话框
*/
fun showOutStorageDialog() {
val list = viewModel.pageModel.rv?.commonAdapter()?.items as? List<*> ?: return
val allItems = list.filterIsInstance<com.lukouguoji.module_base.bean.GjcMaWb>()
// 收集所有选中的子列表项(库位)
val selectedStorageUseList = mutableListOf<com.lukouguoji.module_base.bean.GjcStorageUse>()
allItems.forEach { maWb ->
maWb.storageUseList?.filter { it.isSelected }?.let { selectedStorageUseList.addAll(it) }
}
// 校验:必须至少选中一个库位
if (selectedStorageUseList.isEmpty()) {
showToast("请选择要出库的库位")
return
}
// 显示二次确认对话框
AlertDialog.Builder(this)
.setTitle("出库确认")
.setMessage("确定要将选中的 ${selectedStorageUseList.size} 个库位执行出库操作吗?")
.setPositiveButton("确定") { _, _ ->
// 用户确认后,执行出库操作
viewModel.performOutStorage(selectedStorageUseList)
}
.setNegativeButton("取消", null)
.show()
}
/**
* 显示入库操作对话框
*/
fun showInStorageDialog() {
val list = viewModel.pageModel.rv?.commonAdapter()?.items as? List<*> ?: return
val allItems = list.filterIsInstance<com.lukouguoji.module_base.bean.GjcMaWb>()
// 构建入库数据:保留主列表结构,但只包含选中的子列表项
val maWbListForInStorage = allItems.mapNotNull { maWb ->
// 过滤出选中的子列表项
val selectedStorageList = maWb.storageUseList?.filter { it.isSelected } ?: emptyList()
// 只添加有选中子列表项的主列表项
if (selectedStorageList.isNotEmpty() || maWb.isSelected) {
// 创建主列表项的副本,只包含选中的子列表
maWb.copy(storageUseList = selectedStorageList)
} else {
null
}
}
if (maWbListForInStorage.isEmpty()) {
showToast("请至少选择一个单据")
return
}
// 显示入库对话框
IntExpInStorageDialogModel { dialog ->
// 用户点击保存后,执行入库操作
val locationName = dialog.locationName
val locationId = dialog.locationId
viewModel.performInStorage(locationName, locationId, maWbListForInStorage)
}.show(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) {
viewModel.wbNo.value = data?.getStringExtra(Constant.Result.CODED_CONTENT)
viewModel.searchClick()
}
}
}

View File

@@ -0,0 +1,66 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpArriveResetBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港运抵 - 状态重置对话框
*/
class IntExpArriveResetDialogModel(
private val callback: (IntExpArriveResetDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpArriveResetBinding>(DIALOG_TYPE_CENTER) {
// 重置状态列表
val resetStatusList = MutableLiveData<List<KeyValue>>()
// 选中的重置状态存储的是value
val selectedResetStatus = MutableLiveData("")
// 重置状态code (传给后端的restStatus参数)
var resetStatusCode: String? = null
override fun layoutId(): Int {
return R.layout.dialog_int_exp_arrive_reset
}
override fun onDialogCreated(context: Context) {
binding.model = this
initResetStatusList()
// 监听选择变化更新resetStatusCode
selectedResetStatus.observeForever { value ->
resetStatusCode = when (value) {
"01" -> "01" // 正常
"02" -> null // 未申报
else -> null
}
}
}
/**
* 初始化重置状态列表
*/
private fun initResetStatusList() {
val list = listOf(
KeyValue("正常", "01"),
KeyValue("未申报", "02")
)
resetStatusList.value = list
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (selectedResetStatus.value.verifyNullOrEmpty("请选择重置状态")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,74 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpInStorageBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港仓库 - 入库操作对话框
*/
class IntExpInStorageDialogModel(
private val callback: (IntExpInStorageDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpInStorageBinding>(DIALOG_TYPE_CENTER) {
// 库位列表
val locationList = MutableLiveData<List<KeyValue>>()
// 选中的库位存储的是code
val selectedLocationCode = MutableLiveData("")
// 库位ID (后端需要的code)
var locationId: String = ""
// 库位名称 (后端需要的name)
var locationName: String = ""
override fun layoutId(): Int {
return R.layout.dialog_int_exp_in_storage
}
override fun onDialogCreated(context: Context) {
binding.model = this
loadLocationList()
// 监听选择变化更新locationId和locationName
selectedLocationCode.observeForever { code ->
val selectedItem = locationList.value?.find { it.value == code }
locationId = selectedItem?.value ?: ""
locationName = selectedItem?.key ?: ""
}
}
/**
* 加载库位列表
*/
private fun loadLocationList() {
launchCollect({ NetApply.api.getLocationList(flag = 2) }) {
onSuccess = { result ->
val list = result.data?.map { it.toKeyValue() } ?: emptyList()
locationList.value = list
}
onFailed = { _, msg ->
showToast(msg ?: "加载库位列表失败")
}
}
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (selectedLocationCode.value.verifyNullOrEmpty("请选择库位")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,74 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpModifyStorageBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港 - 修改库位对话框
*/
class IntExpModifyStorageDialogModel(
private val callback: (IntExpModifyStorageDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpModifyStorageBinding>(DIALOG_TYPE_CENTER) {
// 库位列表
val locationList = MutableLiveData<List<KeyValue>>()
// 选中的库位存储的是code
val selectedLocationCode = MutableLiveData("")
// 库位ID (后端需要的code)
var locationId: String = ""
// 库位名称 (后端需要的name)
var locationName: String = ""
override fun layoutId(): Int {
return R.layout.dialog_int_exp_modify_storage
}
override fun onDialogCreated(context: Context) {
binding.model = this
loadLocationList()
// 监听选择变化更新locationId和locationName
selectedLocationCode.observeForever { code ->
val selectedItem = locationList.value?.find { it.value == code }
locationId = selectedItem?.value ?: ""
locationName = selectedItem?.key ?: ""
}
}
/**
* 加载库位列表
*/
private fun loadLocationList() {
launchCollect({ NetApply.api.getLocationList(flag = 2) }) {
onSuccess = { result ->
val list = result.data?.map { it.toKeyValue() } ?: emptyList()
locationList.value = list
}
onFailed = { _, msg ->
showToast(msg ?: "加载库位列表失败")
}
}
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (selectedLocationCode.value.verifyNullOrEmpty("请选择库位")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,47 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpMoveClearBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港移库 - 清仓操作对话框
*/
class IntExpMoveClearDialogModel(
private val callback: (IntExpMoveClearDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpMoveClearBinding>(DIALOG_TYPE_CENTER) {
// 清仓正常(存储的是 code"0" 或 "1"
val clearNormal = MutableLiveData("")
// 清仓正常选项列表
val clearNormalList = MutableLiveData<List<KeyValue>>().apply {
value = listOf(
KeyValue("", "1"),
KeyValue("", "0")
)
}
override fun layoutId(): Int {
return R.layout.dialog_int_exp_move_clear
}
override fun onDialogCreated(context: Context) {
binding.model = this
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (clearNormal.value.verifyNullOrEmpty("请选择清仓正常")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,74 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpOutWaitingTransBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港出库交接 - 待配运操作对话框
*/
class IntExpOutWaitingTransDialogModel(
private val callback: (IntExpOutWaitingTransDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpOutWaitingTransBinding>(DIALOG_TYPE_CENTER) {
// 库位列表
val locationList = MutableLiveData<List<KeyValue>>()
// 选中的库位存储的是code
val selectedLocationCode = MutableLiveData("")
// 库位ID (后端需要的code)
var locationId: String = ""
// 库位名称 (后端需要的name)
var locationName: String = ""
override fun layoutId(): Int {
return R.layout.dialog_int_exp_out_waiting_trans
}
override fun onDialogCreated(context: Context) {
binding.model = this
loadLocationList()
// 监听选择变化更新locationId和locationName
selectedLocationCode.observeForever { code ->
val selectedItem = locationList.value?.find { it.value == code }
locationId = selectedItem?.value ?: ""
locationName = selectedItem?.key ?: ""
}
}
/**
* 加载库位列表
*/
private fun loadLocationList() {
launchCollect({ NetApply.api.getLocationList(flag = 2) }) {
onSuccess = { result ->
val list = result.data?.map { it.toKeyValue() } ?: emptyList()
locationList.value = list
}
onFailed = { _, msg ->
showToast(msg ?: "加载库位列表失败")
}
}
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (selectedLocationCode.value.verifyNullOrEmpty("请选择库位")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,66 @@
package com.lukouguoji.gjc.dialog
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.DialogIntExpTallyResetBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
/**
* 国际出港理货 - 状态重置对话框
*/
class IntExpTallyResetDialogModel(
private val callback: (IntExpTallyResetDialogModel) -> Unit
) : BaseDialogModel<DialogIntExpTallyResetBinding>(DIALOG_TYPE_CENTER) {
// 重置状态列表
val resetStatusList = MutableLiveData<List<KeyValue>>()
// 选中的重置状态存储的是value
val selectedResetStatus = MutableLiveData("")
// 重置状态code (传给后端的restStatus参数)
var resetStatusCode: String? = null
override fun layoutId(): Int {
return R.layout.dialog_int_exp_tally_reset
}
override fun onDialogCreated(context: Context) {
binding.model = this
initResetStatusList()
// 监听选择变化更新resetStatusCode
selectedResetStatus.observeForever { value ->
resetStatusCode = when (value) {
"01" -> "01" // 正常
"02" -> null // 未申报
else -> null
}
}
}
/**
* 初始化重置状态列表
*/
private fun initResetStatusList() {
val list = listOf(
KeyValue("正常", "01"),
KeyValue("未申报", "02")
)
resetStatusList.value = list
}
/**
* 保存按钮点击
*/
fun onSaveClick() {
if (selectedResetStatus.value.verifyNullOrEmpty("请选择重置状态")) {
return
}
dismiss()
callback(this)
}
}

View File

@@ -0,0 +1,21 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.databinding.ItemGjcAssembleWeightEditBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcWarehouse
/**
* 国际出港-修改组装重量列表 ViewHolder
*/
class GjcAssembleWeightEditViewHolder(view: View) :
BaseViewHolder<GjcWarehouse, ItemGjcAssembleWeightEditBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val warehouse = getItemBean(item) ?: return
binding.bean = warehouse
binding.position = position
binding.executePendingBindings()
}
}

View File

@@ -15,6 +15,16 @@ class GjcWeighingRecordViewHolder(view: View) :
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)!!
binding.bean = bean
binding.executePendingBindings()
// 图标点击 - 切换选择状态
binding.ivIcon.setOnClickListener {
// 反转checked状态
bean.checked.set(!bean.checked.get())
// 立即更新UI (图片自动切换)
binding.executePendingBindings()
}
// 整行点击跳转到计重明细页
binding.ll.setOnClickListener {

View File

@@ -15,6 +15,17 @@ class GjcWeighingViewHolder(view: View) :
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item)!!
binding.bean = bean
binding.position = position
binding.executePendingBindings()
// 选择图标点击事件 - 切换选择状态
binding.ivIcon.setOnClickListener {
// 反转checked状态
bean.checked.set(!bean.checked.get())
// 立即更新UI (图片自动切换)
binding.executePendingBindings()
}
// 整行点击跳转到开始计重页面
binding.ll.setOnClickListener {

View File

@@ -0,0 +1,26 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.databinding.ItemIntExpArriveSubBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcHaWb
/**
* 国际出港-出港理货 子订单(分单) ViewHolder
*/
class IntExpArriveSubViewHolder(view: View) :
BaseViewHolder<GjcHaWb, ItemIntExpArriveSubBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position + 1 // 序号从 1 开始
binding.executePendingBindings()
// checkbox点击切换选择状态
binding.ivCheckbox.setOnClickListener {
bean.checked.set(!bean.checked.get())
binding.executePendingBindings()
}
}
}

View File

@@ -1,9 +1,12 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ItemIntExpArriveBinding
import com.lukouguoji.module_base.adapter.setCommonAdapter
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.ktx.refresh
/**
* 国际出港-出港运抵 列表项ViewHolder
@@ -25,5 +28,20 @@ class IntExpArriveViewHolder(view: View) :
// 立即更新UI图片自动切换
binding.executePendingBindings()
}
// ========== 展开按钮点击事件 ==========
binding.ivShow.setOnClickListener {
bean.showMore.set(!bean.showMore.get())
}
// ========== 初始化子列表 RecyclerView ==========
setCommonAdapter(
binding.rvSub,
IntExpArriveSubViewHolder::class.java,
R.layout.item_int_exp_arrive_sub
)
// 刷新子列表数据
binding.rvSub.refresh(bean.haWbList ?: emptyList())
}
}

View File

@@ -0,0 +1,62 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ItemIntExpAssembleBinding
import com.lukouguoji.module_base.adapter.setCommonAdapter
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcUldUseBean
import com.lukouguoji.module_base.ktx.refresh
/**
* 国际出港-出港组装 列表项ViewHolder
* 参考出港运抵页面的实现
*/
class IntExpAssembleItemViewHolder(view: View) :
BaseViewHolder<GjcUldUseBean, ItemIntExpAssembleBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
binding.executePendingBindings()
// 添加图标点击事件 - 切换选择状态
binding.ivIcon.setOnClickListener {
// 反转checked状态
bean.checked.set(!bean.checked.get())
// 立即更新UI图片自动切换
binding.executePendingBindings()
}
// ========== 展开按钮点击事件 ==========
// 通过回调通知ViewModel处理展开逻辑需要加载数据
binding.ivShow.setOnClickListener {
clickListener?.onItemClick(position, 1000) // type=1000表示展开操作
}
// ========== 侧滑菜单按钮点击事件 ==========
// 修改按钮
binding.btnEdit.setOnClickListener {
binding.swipeMenu.quickClose()
clickListener?.onItemClick(position, 2000) // type=2000表示修改操作
}
// 删除按钮
binding.btnDelete.setOnClickListener {
binding.swipeMenu.quickClose()
clickListener?.onItemClick(position, 2001) // type=2001表示删除操作
}
// ========== 初始化子列表 RecyclerView ==========
setCommonAdapter(
binding.rvSub,
IntExpAssembleSubViewHolder::class.java,
R.layout.item_int_exp_assemble_sub
)
// 刷新子列表数据
binding.rvSub.refresh(bean.waybillDetails ?: emptyList())
}
}

View File

@@ -0,0 +1,21 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.databinding.ItemIntExpAssembleSubBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcWarehouse
/**
* 国际出港-出港组装 子列表ViewHolder
* 显示运单明细信息
*/
class IntExpAssembleSubViewHolder(view: View) :
BaseViewHolder<GjcWarehouse, ItemIntExpAssembleSubBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position + 1 // 序号从1开始
binding.executePendingBindings()
}
}

View File

@@ -0,0 +1,51 @@
package com.lukouguoji.gjc.holder
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.gjc.databinding.ItemIntExpStorageUseSubBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.bean.GjcStorageUse
/**
* 国际出港-仓库 库位明细行 ViewHolder
*/
class IntExpStorageUseSubViewHolder(view: View) :
BaseViewHolder<GjcStorageUse, ItemIntExpStorageUseSubBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
binding.executePendingBindings()
// 单选框点击切换选择状态(反向联动主列表)
binding.ivCheckbox.setOnClickListener {
// 切换子列表项的选择状态
val newCheckedState = !bean.checked.get()
bean.checked.set(newCheckedState)
binding.executePendingBindings()
// 反向联动主列表项(仅在勾选时联动)
updateParentCheckState(newCheckedState)
}
}
/**
* 更新父列表项的选择状态
* 规则:
* - 如果子项被勾选newCheckedState = true则自动勾选父项
* - 如果子项被取消勾选newCheckedState = false则不改变父项状态
*/
private fun updateParentCheckState(newCheckedState: Boolean) {
// 从RecyclerView的tag获取父Bean引用
val recyclerView = itemView.parent as? RecyclerView ?: return
val parentBean = recyclerView.tag as? GjcMaWb ?: return
// 只有当子项被勾选时,才联动勾选父项
if (newCheckedState) {
parentBean.checked.set(true)
}
// 当子项被取消勾选时,不影响父项状态
}
}

View File

@@ -0,0 +1,57 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ItemIntExpStorageUseBinding
import com.lukouguoji.module_base.adapter.setCommonAdapter
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.ktx.refresh
/**
* 国际出港-仓库 ViewHolder
*/
class IntExpStorageUseViewHolder(view: View) :
BaseViewHolder<GjcMaWb, ItemIntExpStorageUseBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position
binding.executePendingBindings()
// 图标点击切换选择状态(联动子列表)
binding.ivIcon.setOnClickListener {
// 切换主列表项的选择状态
val newCheckedState = !bean.checked.get()
bean.checked.set(newCheckedState)
// 联动勾选/取消所有子列表项
bean.storageUseList?.forEach { storageUse ->
storageUse.checked.set(newCheckedState)
}
// 刷新UI
binding.executePendingBindings()
binding.rvSub.adapter?.notifyDataSetChanged()
}
// 展开按钮点击事件
binding.ivShow.setOnClickListener {
bean.showMore.set(!bean.showMore.get())
}
// 初始化库位明细子列表 RecyclerView
setCommonAdapter(
binding.rvSub,
IntExpStorageUseSubViewHolder::class.java,
R.layout.item_int_exp_storage_use_sub
)
// 刷新库位明细数据传递父Bean引用
val storageUseList = bean.storageUseList ?: emptyList()
// 为每个子列表项设置父Bean引用通过tag传递
binding.rvSub.tag = bean
binding.rvSub.refresh(storageUseList)
}
}

View File

@@ -0,0 +1,26 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.databinding.ItemIntExpTallySubBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcHaWb
/**
* 国际出港-出港理货 子订单(分单) ViewHolder
*/
class IntExpTallySubViewHolder(view: View) :
BaseViewHolder<GjcHaWb, ItemIntExpTallySubBinding>(view) {
override fun onBind(item: Any?, position: Int) {
val bean = getItemBean(item) ?: return
binding.bean = bean
binding.position = position + 1 // 序号从 1 开始
binding.executePendingBindings()
// checkbox点击切换选择状态
binding.ivCheckbox.setOnClickListener {
bean.checked.set(!bean.checked.get())
binding.executePendingBindings()
}
}
}

View File

@@ -1,9 +1,12 @@
package com.lukouguoji.gjc.holder
import android.view.View
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.databinding.ItemIntExpTallyBinding
import com.lukouguoji.module_base.adapter.setCommonAdapter
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.ktx.refresh
/**
* 国际出港-出港理货 ViewHolder
@@ -17,10 +20,28 @@ class IntExpTallyViewHolder(view: View) :
binding.position = position
binding.executePendingBindings()
// 图标点击切换选择状态
// 图标点击切换选择状态(主单和分单独立,互不干扰)
binding.ivIcon.setOnClickListener {
// 只切换主单自己的选择状态,不同步到分单
bean.checked.set(!bean.checked.get())
// 刷新UI
binding.executePendingBindings()
}
// ========== 新增:展开按钮点击事件 ==========
binding.ivShow.setOnClickListener {
bean.showMore.set(!bean.showMore.get())
}
// ========== 新增:初始化子列表 RecyclerView ==========
setCommonAdapter(
binding.rvSub,
IntExpTallySubViewHolder::class.java,
R.layout.item_int_exp_tally_sub
)
// 刷新子列表数据
binding.rvSub.refresh(bean.haWbList ?: emptyList())
}
}

View File

@@ -12,6 +12,9 @@ import com.lukouguoji.gjc.viewModel.IntExpAssembleStartViewModel
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.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.observe
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
@@ -30,11 +33,29 @@ class IntExpAssembleStartActivity :
private var waybillAdapter: CommonAdapter? = null
companion object {
private const val EXTRA_ULD_NO = "uld"
private const val EXTRA_EDIT_MODE = "edit_mode"
private const val EXTRA_LOAD_AREA = "load_area"
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, IntExpAssembleStartActivity::class.java)
context.startActivity(starter)
}
/**
* 从列表页"修改"模式启动
* @param uldNo ULD编号将被锁定整个页面生命周期内不可编辑
* @param loadArea 组装位置(用于自动选中对应的组装位置)
*/
@JvmStatic
fun startForEdit(context: Context, uldNo: String, loadArea: String) {
val starter = Intent(context, IntExpAssembleStartActivity::class.java)
.putExtra(EXTRA_ULD_NO, uldNo)
.putExtra(EXTRA_EDIT_MODE, true)
.putExtra(EXTRA_LOAD_AREA, loadArea)
context.startActivity(starter)
}
}
override fun layoutId() = R.layout.activity_int_exp_assemble_start
@@ -60,8 +81,30 @@ class IntExpAssembleStartActivity :
// 加载组装人列表
viewModel.loadAssemblerList()
// 监听登出事件,清空缓存
FlowBus.with<Any>(ConstantEvent.LOGOUT).observe(this) {
viewModel.clearCachedOperator()
}
// 观察数据变化
observeData()
// 处理修改模式(从列表页侧滑"修改"跳转)
handleEditModeIntent()
}
/**
* 处理修改模式的 Intent 参数
*/
private fun handleEditModeIntent() {
val isEditMode = intent.getBooleanExtra(EXTRA_EDIT_MODE, false)
if (isEditMode) {
val uldNo = intent.getStringExtra(EXTRA_ULD_NO) ?: ""
val loadArea = intent.getStringExtra(EXTRA_LOAD_AREA) ?: ""
if (uldNo.isNotEmpty()) {
viewModel.initFromEditMode(uldNo, loadArea)
}
}
}
/**

View File

@@ -79,6 +79,9 @@ class IntExpMoveActivity : BaseBindingActivity<ActivityIntExpMoveBinding, IntExp
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
// 初始加载数据
viewModel.refresh()
}
/**

View File

@@ -21,6 +21,8 @@ import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.util.CheckUtil
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/**
@@ -29,7 +31,7 @@ import kotlinx.coroutines.launch
class GjcAssembleAllocateViewModel : BasePageViewModel() {
// 搜索条件
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val destAirport = MutableLiveData("") // 目的站
val assembler = MutableLiveData("") // 组装人

View File

@@ -0,0 +1,220 @@
package com.lukouguoji.gjc.viewModel
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.GjcUldUseBean
import com.lukouguoji.module_base.bean.GjcWarehouse
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import kotlinx.coroutines.launch
/**
* 国际出港-修改组装重量 ViewModel
*/
class GjcAssembleWeightEditViewModel : BaseViewModel() {
///////////////////////////////////////////////////////////////////////////
// 数据字段
///////////////////////////////////////////////////////////////////////////
// ULD信息Bean从列表页传入
val uldBean = MutableLiveData<GjcUldUseBean>()
// 运单列表
val waybillList = MutableLiveData<List<GjcWarehouse>>(emptyList())
///////////////////////////////////////////////////////////////////////////
// ULD信息显示字段
///////////////////////////////////////////////////////////////////////////
val uldNo = MutableLiveData("") // ULD编号
val totalPc = MutableLiveData("") // 总件数
val totalWeight = MutableLiveData("") // 总重量
val cargoWeight = MutableLiveData("") // 货重
///////////////////////////////////////////////////////////////////////////
// 底部统计字段
///////////////////////////////////////////////////////////////////////////
val sumCargoWeight = MutableLiveData("0") // 总货重
val weightError = MutableLiveData("0%") // 重量误差百分比
// 传递参数用于API调用
private var useId: Long = 0
private var uld: String = ""
private var fdate: String = ""
private var fno: String = ""
init {
// 监听 waybillList 变化,自动触发统计计算
waybillList.observeForever {
calculateStatistics()
}
}
///////////////////////////////////////////////////////////////////////////
// 初始化
///////////////////////////////////////////////////////////////////////////
fun initOnCreated(intent: Intent) {
// 接收传入的完整 Bean 对象
val bean = intent.getSerializableExtra(Constant.Key.BEAN) as? GjcUldUseBean
bean?.let {
uldBean.value = it
// 提取ULD信息字段
uldNo.value = it.uld
totalPc.value = it.pc.toString()
totalWeight.value = it.totalWeight.toString()
cargoWeight.value = it.cargoWeight.toString()
// 保存参数用于API调用
useId = it.useId
uld = it.uld
fdate = it.fdate
fno = it.fno
// 加载运单数据
loadData()
} ?: run {
showToast("参数错误")
}
}
///////////////////////////////////////////////////////////////////////////
// 数据加载
///////////////////////////////////////////////////////////////////////////
private fun loadData() {
val params = mapOf(
"useId" to useId,
"uld" to uld,
"fdate" to fdate,
"fno" to fno
).toRequestBody()
launchLoadingCollect({ NetApply.api.getAssembledWaybillsByUld(params) }) {
onSuccess = { result ->
val list = result.data ?: emptyList()
// 为每个运单设置数据变化回调
list.forEach { warehouse ->
warehouse.onDataChanged = {
// 数据变化时重新计算统计
calculateStatistics()
}
}
waybillList.value = list
if (list.isEmpty()) {
showToast("暂无运单数据")
}
}
onFailed = { code, msg ->
showToast("加载失败:$msg")
}
}
}
/**
* 计算统计数据:总货重、重量误差
* - 总货重:运单列表中所有 checkInWeight入库重量/组装重量)之和
* - 重量误差:(总货重 - ULD货重) / ULD货重 * 100%
*/
private fun calculateStatistics() {
val records = waybillList.value ?: emptyList()
// 计算总货重(所有运单 checkInWeight 之和,即列表中显示的重量)
val sumWeight = records.sumOf { it.checkInWeight }
sumCargoWeight.value = String.format("%.2f", sumWeight)
// 计算重量误差百分比相对于ULD的货重
val uldCargoWeight = cargoWeight.value?.toDoubleOrNull() ?: 0.0
val error = if (sumWeight > 0 && uldCargoWeight > 0) {
((sumWeight - uldCargoWeight) / uldCargoWeight * 100)
} else {
0.0
}
weightError.value = String.format("%.1f%%", error)
}
///////////////////////////////////////////////////////////////////////////
// 按钮事件
///////////////////////////////////////////////////////////////////////////
/**
* 点击"取消"按钮
*/
fun onCancelClick() {
getTopActivity().finish()
}
/**
* 点击"保存"按钮
*/
fun onSaveClick() {
val records = waybillList.value ?: return
if (records.isEmpty()) {
showToast("没有可保存的数据")
return
}
// 验证数据
if (!validateRecords(records)) return
// 调用批量更新接口
saveRecords(records)
}
///////////////////////////////////////////////////////////////////////////
// 业务逻辑
///////////////////////////////////////////////////////////////////////////
/**
* 验证数据
*/
private fun validateRecords(records: List<GjcWarehouse>): Boolean {
records.forEachIndexed { index, record ->
if (record.weight < 0) {
showToast("${index + 1}条记录的重量不能为负数")
return false
}
}
return true
}
/**
* 批量保存运单重量
*/
private fun saveRecords(records: List<GjcWarehouse>) {
launchLoadingCollect({
NetApply.api.updateIntExpAssemble(records.toRequestBody())
}) {
onSuccess = { result ->
if (result.verifySuccess()) {
showToast("保存成功")
// 发送刷新事件通知列表页
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
// 关闭页面
getTopActivity().finish()
} else {
showToast("保存失败")
}
}
onFailed = { code, msg ->
showToast("保存失败:$msg")
}
}
}
}

View File

@@ -10,7 +10,6 @@ import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.GjcUldUseBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.http.net.DiBangWeightModel
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.formatDate
@@ -24,7 +23,6 @@ import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import com.lukouguoji.module_base.model.BluetoothDialogModel
import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.Common
import com.lukouguoji.module_base.util.DictUtils
import com.lukouguoji.module_base.util.PrinterUtils
import dev.utils.app.info.KeyValue
import kotlinx.coroutines.launch
@@ -39,6 +37,9 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
// 数据Bean
val dataBean = MutableLiveData(GjcUldUseBean())
// 保存 queryUsingUldByUld 返回的数据(用于复磅场景)
private var usingUldData: GjcUldUseBean? = null
// 地磅集成 - 暂时注释
// val diBangModel = DiBangWeightModel()
val channel = MutableLiveData("") // 通道号用于控制地磅key
@@ -59,7 +60,7 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
// 下拉选择数据源
val passagewayList = MutableLiveData<List<KeyValue>>() // 通道号列表
val piCloseList = MutableLiveData<List<KeyValue>>() // 探板收口列表
val plCloseList = MutableLiveData<List<KeyValue>>() // 探板收口列表
// 打印挂签
val printTag = MutableLiveData(false)
@@ -105,7 +106,7 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
// 加载下拉列表数据
loadPassagewayList()
loadPiCloseList()
loadPlCloseList()
// 初始化航班日期为今天
val today = Date().formatDate()
@@ -204,12 +205,18 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
/**
* 加载探板收口列表
*/
private fun loadPiCloseList() {
// 探板收口选项(是/否)
piCloseList.value = listOf(
KeyValue("1", ""),
KeyValue("0", "")
)
private fun loadPlCloseList() {
launchCollect({
NetApply.api.getDictList("PICLOSE")
}) {
onSuccess = {
// 将 DictIdValueBean 转换为 KeyValue
// 显示和提交都使用 value 字段
plCloseList.value = (it.data ?: emptyList()).map { b ->
KeyValue(b.value, b.value)
}
}
}
}
/**
@@ -289,6 +296,9 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
lastQueriedFlight = ""
lastQueriedCarId = ""
lastQueriedUld = ""
// 重置 ULD 使用记录
usingUldData = null
}
/**
@@ -302,21 +312,31 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
bean.uld = uldNo.value ?: ""
bean.fno = flightNo.value ?: ""
bean.fdate = flightDate.value ?: ""
bean.passageway = passagewayList.value?.firstOrNull{ it.value == channel.value }?.key ?: ""
bean.passagewayId = channel.value ?: ""
// 验证必填字段
if (bean.carId.verifyNullOrEmpty("请输入架子车号")) return
if (bean.uld.verifyNullOrEmpty("请输入ULD编号")) return
if (bean.passageway.verifyNullOrEmpty("请选择通道号")) return
// 提交数据
val params = mapOf(
// 验证是否已组装(复磅场景必须有 useId
if (usingUldData == null || usingUldData?.useId == 0L) {
showToast("未组装,不可复磅")
return
}
// 构建基础提交参数
val baseParams = mutableMapOf(
"carId" to bean.carId,
"passageway" to bean.passageway,
"passagewayId" to bean.passagewayId,
"uld" to bean.uld,
"dgrCode" to bean.dgrCode,
"boardType" to bean.boardType,
"height" to bean.height,
"piClose" to bean.piClose,
"piCloseSize" to bean.piCloseSize,
"plClose" to bean.plClose,
"plCloseSize" to bean.plCloseSize,
"carWeight" to bean.carWeight,
"uldWeight" to bean.uldWeight,
"fdate" to bean.fdate,
@@ -327,7 +347,38 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
"netWeight" to bean.netWeight,
"cargoWeight" to bean.cargoWeight,
"printTag" to printTag.value
).toRequestBody(removeEmptyOrNull = true)
)
// 如果有 usingUldData复磅场景将其所有字段添加到提交参数
usingUldData?.let { usingData ->
baseParams["useId"] = usingData.useId
baseParams["cargoType"] = usingData.cargoType
baseParams["consumeWeight"] = usingData.consumeWeight
baseParams["volume"] = usingData.volume
baseParams["maxVolume"] = usingData.maxVolume
baseParams["maxWeight"] = usingData.maxWeight
baseParams["fdep"] = usingData.fdep
baseParams["fClose"] = usingData.fClose
baseParams["wtId"] = usingData.wtId
baseParams["wtUsername"] = usingData.wtUsername
baseParams["wtDate"] = usingData.wtDate
baseParams["ldId"] = usingData.ldId
baseParams["ldUserName"] = usingData.ldUserName
baseParams["ldDate"] = usingData.ldDate
baseParams["hoId"] = usingData.hoId
baseParams["hoUserName"] = usingData.hoUserName
baseParams["hoDate"] = usingData.hoDate
baseParams["uldCarrier"] = usingData.uldCarrier
baseParams["uldFlag"] = usingData.uldFlag
baseParams["status"] = usingData.status
baseParams["passagewayName"] = usingData.passagewayName
baseParams["location"] = usingData.location
baseParams["checkFlag"] = usingData.checkFlag
baseParams["emptyUld"] = usingData.emptyUld
}
// 转换为 RequestBody
val params = baseParams.toRequestBody(removeEmptyOrNull = true)
launchLoadingCollect({
NetApply.api.submitGjcBoxWeighing(params)
@@ -345,7 +396,7 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
getTopActivity().finish()
// getTopActivity().finish()
} else {
showToast(result.msg.noNull("添加失败"))
}
@@ -410,6 +461,26 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
}
}
}
// 查询正在使用的 ULD 记录(复磅场景)
launchCollect({ NetApply.api.queryUsingUldByUld(uldNo) }) {
onSuccess = { result ->
val usingData = result.data
if (usingData != null && usingData.useId > 0) {
// 保存返回的数据,用于提交时传递
usingUldData = usingData
} else {
// 没有 useId说明未组装不可复磅
usingUldData = null
showToast("未组装,不可复磅")
}
}
onFailed = { _, _ ->
// 接口报错时也认为未组装
usingUldData = null
showToast("未组装,不可复磅")
}
}
}
/**

View File

@@ -12,6 +12,8 @@ import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
/**
* 国际出港板箱过磅 ViewModel
@@ -19,7 +21,7 @@ import com.lukouguoji.module_base.model.ScanModel
class GjcBoxWeighingViewModel : BasePageViewModel() {
// 搜索条件
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val dest = MutableLiveData("") // 目的站
val carId = MutableLiveData("") // 架子车号
@@ -31,20 +33,14 @@ class GjcBoxWeighingViewModel : BasePageViewModel() {
// 统计数据
val totalCount = MutableLiveData("0") // 合计票数
val totalPc = MutableLiveData("0") // 总件数
val totalPc = MutableLiveData("0")
val cargoWeight = MutableLiveData("0")// 总件数
val totalWeight = MutableLiveData("0") // 总重量
///////////////////////////////////////////////////////////////////////////
// 方法区
///////////////////////////////////////////////////////////////////////////
/**
* 扫码输入航班号
*/
fun flightNoScanClick() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.FLIGHT_NO)
}
/**
* 扫码输入架子车号
*/
@@ -121,6 +117,7 @@ class GjcBoxWeighingViewModel : BasePageViewModel() {
val data = result.data
totalCount.value = (data?.wbNumber ?: 0).toString()
totalPc.value = (data?.totalPc ?: 0).toString()
cargoWeight.value = (data?.cargoWeight ?: 0).toString()
totalWeight.value = (data?.totalWeight ?: 0.0).toString()
}
}
@@ -132,10 +129,6 @@ class GjcBoxWeighingViewModel : BasePageViewModel() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
Constant.RequestCode.FLIGHT_NO -> {
flightNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()
}
Constant.RequestCode.CAR -> {
carId.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()

View File

@@ -17,6 +17,7 @@ import com.lukouguoji.module_base.ktx.showConfirmDialog
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ui.page.preview.PreviewActivity
import com.lukouguoji.module_base.ui.page.preview.PdfPreviewActivity
import com.lukouguoji.module_base.util.MediaUtil
import kotlinx.coroutines.launch
@@ -65,8 +66,9 @@ class GjcInspectionDetailsViewModel : BaseViewModel() {
*/
fun auditPass() {
val bean = dataBean.value ?: return
val reason = returnReason.value ?: ""
getTopActivity().showConfirmDialog("确定要通过该单证吗?") {
performAudit(bean, true, "通过", "")
performAudit(bean, true, "通过", reason)
}
}
@@ -108,7 +110,7 @@ class GjcInspectionDetailsViewModel : BaseViewModel() {
// 根据审核状态调用不同接口
launchLoadingCollect({
if (isPass) {
NetApply.api.passGjcInspection(requestData)
NetApply.api.passGjcInspection(reason, requestData)
} else {
NetApply.api.backGjcInspection(reason, requestData)
}
@@ -142,20 +144,24 @@ class GjcInspectionDetailsViewModel : BaseViewModel() {
* @param attach 附件对象
*/
fun onAttachClick(attach: ComAttach) {
// 处理URL先处理转义字符再判断是否需要组装完整URL
val url = processAttachUrl(attach.path)
when {
// 图片格式使用PreviewActivity预览
attach.name.endsWith(".jpg", true) ||
attach.name.endsWith(".png", true) ||
attach.name.endsWith(".jpeg", true) -> {
val fileBean = FileBean(
url = MediaUtil.fillUrl(attach.path),
originalPic = attach.path
url = url,
path = url,
originalPic = url
)
PreviewActivity.start(getTopActivity(), listOf(fileBean))
}
// PDF格式提示暂不支持
// PDF格式使用PdfPreviewActivity预览
attach.name.endsWith(".pdf", true) -> {
showToast("PDF文件预览功能开发中")
PdfPreviewActivity.start(getTopActivity(), url)
}
// 其他格式
else -> {
@@ -163,4 +169,21 @@ class GjcInspectionDetailsViewModel : BaseViewModel() {
}
}
}
/**
* 处理附件URL
* 1. 处理转义字符(如 \/ 替换为 /
* 2. 如果不是以http开头则使用MediaUtil.fillUrl组装完整URL
*/
private fun processAttachUrl(path: String): String {
// 先处理转义字符:\/ -> /
val cleanPath = path.replace("\\/", "/")
// 判断是否已是完整URL
return if (cleanPath.startsWith("http://", true) || cleanPath.startsWith("https://", true)) {
cleanPath
} else {
MediaUtil.fillUrl(cleanPath)
}
}
}

View File

@@ -25,6 +25,7 @@ import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.CheckUtil
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import kotlinx.coroutines.launch
import java.util.Date
@@ -34,7 +35,7 @@ import java.util.Date
class GjcInspectionViewModel : BasePageViewModel() {
// 搜索条件
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val agentId = MutableLiveData("") // 代理ID
val auditStatus = MutableLiveData("") // 审核状态
@@ -47,8 +48,9 @@ class GjcInspectionViewModel : BasePageViewModel() {
val auditStatusList = MutableLiveData(
listOf(
KeyValue("全部", ""),
KeyValue("未审核", "0"),
KeyValue("通过", "1"),
KeyValue("未审核", "1"),
KeyValue("通过", "2"),
KeyValue("退回", "3"),
)
)
@@ -212,7 +214,7 @@ class GjcInspectionViewModel : BasePageViewModel() {
// 根据审核状态调用不同接口
launchLoadingCollect({
if (isPass) {
NetApply.api.passGjcInspection(requestData)
NetApply.api.passGjcInspection("", requestData)
} else {
NetApply.api.backGjcInspection(reason, requestData)
}

View File

@@ -11,9 +11,11 @@ import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import dev.utils.app.info.KeyValue
import kotlinx.coroutines.launch
/**
@@ -24,6 +26,15 @@ class GjcQueryEditViewModel : BaseViewModel() {
// 数据Bean
val dataBean = MutableLiveData(GjcMaWb())
// 运单ID用于查询详情
var maWbId: Long = 0
// 包装类型下拉列表
val packageTypeList = MutableLiveData<List<KeyValue>>(emptyList())
// 运单类型下拉列表
val waybillTypeList = MutableLiveData<List<KeyValue>>(emptyList())
/**
* 初始化数据
*/
@@ -31,7 +42,17 @@ class GjcQueryEditViewModel : BaseViewModel() {
val jsonData = intent.getStringExtra(Constant.Key.DATA) ?: ""
if (jsonData.isNotEmpty()) {
try {
dataBean.value = Gson().fromJson(jsonData, GjcMaWb::class.java)
val tempBean = Gson().fromJson(jsonData, GjcMaWb::class.java)
maWbId = tempBean.maWbId ?: 0 // 保存ID用于查询详情
// 加载包装类型下拉列表
loadPackageTypeList()
// 加载运单类型下拉列表
loadWaybillTypeList()
// 调用详情接口获取完整数据
loadDetails()
} catch (e: Exception) {
showToast("数据解析失败")
getTopActivity().finish()
@@ -42,6 +63,165 @@ class GjcQueryEditViewModel : BaseViewModel() {
}
}
/**
* 加载包装类型下拉列表
*/
private fun loadPackageTypeList() {
launchCollect({ NetApply.api.getPackTypeList() }) {
onSuccess = { result ->
// 转换为 KeyValue 列表key 和 value 都使用 name忽略 code
val keyValueList = result.data?.mapNotNull { bean ->
bean.name?.let { name -> KeyValue(name, name) }
} ?: emptyList()
packageTypeList.value = keyValueList
}
}
}
/**
* 加载运单类型下拉列表
* 使用接口: POST /typeCode/awb?type=10 (10代表国际出港)
*/
private fun loadWaybillTypeList() {
launchCollect({ NetApply.api.getWaybillTypeList("10") }) {
onSuccess = { result ->
// 转换为 KeyValue 列表key 使用 name显示value 使用 code存储和提交
val keyValueList = result.data?.mapNotNull { bean ->
if (bean.name != null && bean.code != null) {
KeyValue(bean.name, bean.code)
} else null
} ?: emptyList()
waybillTypeList.value = keyValueList
}
}
}
/**
* 加载运单详情数据
*/
private fun loadDetails() {
if (maWbId == 0L) {
showToast("运单ID不存在")
getTopActivity().finish()
return
}
launchLoadingCollect({ NetApply.api.getGjcQueryDetails(maWbId) }) {
onSuccess = { result ->
val data = result.data ?: emptyMap()
// 1. 提取 maWb 对象(主要数据)
val maWb = data["maWb"] as? Map<String, Any> ?: emptyMap()
// 2. 提取 maWbM 对象(海关扩展数据)
val maWbM = data["maWbM"] as? Map<String, Any> ?: emptyMap()
// 3. 提取 warehouseList仓库列表用于计算入库件数和入库重量
val warehouseList = data["warehouseList"] as? List<Map<String, Any>> ?: emptyList()
// 4. 合并两个对象
val mergedData = mutableMapOf<String, Any>()
mergedData.putAll(maWb)
mergedData.putAll(maWbM)
// 5. 特殊字段处理
// 5.1 运单号: 组合 prefix + no
val prefix = maWb["prefix"] as? String ?: ""
val no = maWb["no"] as? String ?: ""
if (prefix.isNotEmpty() && no.isNotEmpty()) {
mergedData["wbNo"] = "$prefix$no"
}
// 5.2 代理人: 如果 agentName 为空,使用 agentCode
if (!mergedData.containsKey("agentName") || (mergedData["agentName"] as? String).isNullOrEmpty()) {
maWb["agentCode"]?.let { mergedData["agentName"] = it }
}
// 5.3 入库件数和入库重量: 从 warehouseList 计算总和
if (warehouseList.isNotEmpty()) {
var totalPc = 0L
var totalWeight = 0.0
warehouseList.forEach { warehouse ->
// 提取 pc件数并累加
val pc = warehouse["pc"]
when (pc) {
is Number -> totalPc += pc.toLong()
is String -> totalPc += pc.toLongOrNull() ?: 0L
}
// 提取 weight重量并累加
val weight = warehouse["weight"]
when (weight) {
is Number -> totalWeight += weight.toDouble()
is String -> totalWeight += weight.toDoubleOrNull() ?: 0.0
}
}
// 覆盖原有的 arrivePc 和 arriveWeight
mergedData["arrivePc"] = totalPc
mergedData["arriveWeight"] = totalWeight
}
// 6. 转换为 GjcMaWb 对象
val bean = Gson().fromJson(Gson().toJson(mergedData), GjcMaWb::class.java)
// 7. 匹配包装类型(支持 contains
matchPackageType(bean)
// 8. 匹配运单类型(支持 contains
matchWaybillType(bean)
dataBean.value = bean
}
}
}
/**
* 匹配包装类型
* 使用 contains 匹配,支持简写值和完整值
*/
private fun matchPackageType(bean: GjcMaWb) {
val currentPackageType = bean.packageType
if (currentPackageType.isNullOrEmpty()) return
val packageList = packageTypeList.value ?: emptyList()
if (packageList.isEmpty()) return
// 使用 contains 匹配(完整值和简写值都支持)
val match = packageList.find { keyValue ->
keyValue.value?.contains(currentPackageType) == true
}
if (match != null) {
// 使用匹配到的完整值
bean.packageType = match.value
}
}
/**
* 匹配运单类型
* 使用 code 进行匹配bean.awbType 存储的是 code
* 显示时使用 name但实际存储和提交的是 code
*/
private fun matchWaybillType(bean: GjcMaWb) {
val currentAwbTypeCode = bean.awbType
if (currentAwbTypeCode.isNullOrEmpty()) return
val waybillList = waybillTypeList.value ?: emptyList()
if (waybillList.isEmpty()) return
// 使用 code 进行精确匹配value 字段存储的是 code
val match = waybillList.find { keyValue ->
keyValue.value == currentAwbTypeCode
}
if (match != null) {
// 使用匹配到的 code 值(确保存储的是 code而不是 name
bean.awbType = match.value
}
}
/**
* 保存修改
*/
@@ -53,7 +233,7 @@ class GjcQueryEditViewModel : BaseViewModel() {
// 调用更新接口
launchLoadingCollect({
NetApply.api.updateGjcMaWb(Gson().toJson(bean).toRequestBody())
NetApply.api.updateGjcMaWb(bean.toRequestBody())
}) {
onSuccess = {
showToast("修改成功")

View File

@@ -15,6 +15,8 @@ import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
/**
* 国际出港查询ViewModel
@@ -22,7 +24,7 @@ import dev.utils.app.info.KeyValue
class GjcQueryViewModel : BasePageViewModel() {
// ==================== 搜索条件 ====================
val flightDateStart = MutableLiveData("") // 航班日期起
val flightDateStart = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期起
val flightDateEnd = MutableLiveData("") // 航班日期止
val agentId = MutableLiveData("") // 代理ID
val outStatus = MutableLiveData("") // 出库状态
@@ -101,8 +103,8 @@ class GjcQueryViewModel : BasePageViewModel() {
val listParams = mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit,
"fdateStart" to flightDateStart.value!!.ifEmpty { null },
"fdateEnd" to flightDateEnd.value!!.ifEmpty { null },
"beginDate" to flightDateStart.value!!.ifEmpty { null },
"endDate" to flightDateEnd.value!!.ifEmpty { null },
"agentCode" to agentId.value!!.ifEmpty { null },
"outState" to outStatus.value!!.ifEmpty { null },
"wbNo" to waybillNo.value!!.ifEmpty { null },

View File

@@ -7,13 +7,18 @@ import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.holder.GjcWeighingRecordViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.bean.GjcWeighingRecordBean
import com.lukouguoji.module_base.http.net.NetApply
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.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.DictUtils
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
/**
* 国际出港计重记录 ViewModel
@@ -21,7 +26,7 @@ import dev.utils.app.info.KeyValue
class GjcWeighingRecordViewModel : BasePageViewModel() {
// 搜索条件
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val agentCode = MutableLiveData("") // 代理人
val spCode = MutableLiveData("") // 特码
@@ -75,13 +80,6 @@ class GjcWeighingRecordViewModel : BasePageViewModel() {
}
}
/**
* 扫码输入航班号
*/
fun flightNoScanClick() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.FLIGHT_NO)
}
/**
* 扫码输入运单号
*/
@@ -97,10 +95,35 @@ class GjcWeighingRecordViewModel : BasePageViewModel() {
}
/**
* 运抵申报按钮点击(暂未实现)
* 运抵申报按钮点击
*/
fun declareClick() {
// TODO: 跳转到运抵申报页面(待实现)
// 获取列表数据
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcWeighingRecordBean> ?: emptyList()
// 过滤选中项
val selectedItems = list.filter { it.isSelected }
when (selectedItems.size) {
0 -> {
// 没有选择单据:直接跳转(不带参数)
com.alibaba.android.arouter.launcher.ARouter.getInstance()
.build(com.lukouguoji.module_base.router.ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE)
.navigation()
}
1 -> {
// 选择一个单据:跳转到出港运抵页面,携带运单号
val waybillNo = selectedItems[0].wbNo
com.alibaba.android.arouter.launcher.ARouter.getInstance()
.build(com.lukouguoji.module_base.router.ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE)
.withString("wbNoParam", waybillNo)
.navigation()
}
else -> {
// 选择多个单据Toast 提示
showToast("只能选择一个单据")
}
}
}
/**
@@ -115,7 +138,7 @@ class GjcWeighingRecordViewModel : BasePageViewModel() {
"fno" to flightNo.value!!.ifEmpty { null },
"agentCode" to agentCode.value!!.ifEmpty { null },
"spCode" to spCode.value!!.ifEmpty { null },
"likeNo" to waybillNo.value!!.ifEmpty { null },
"wbNo" to waybillNo.value!!.ifEmpty { null },
).toRequestBody()
// 构建查询参数(统计接口 - 使用相同的搜索条件)
@@ -124,7 +147,7 @@ class GjcWeighingRecordViewModel : BasePageViewModel() {
"fno" to flightNo.value!!.ifEmpty { null },
"agentCode" to agentCode.value!!.ifEmpty { null },
"spCode" to spCode.value!!.ifEmpty { null },
"likeNo" to waybillNo.value!!.ifEmpty { null },
"wbNo" to waybillNo.value!!.ifEmpty { null },
).toRequestBody()
// 获取列表数据显示loading
@@ -156,10 +179,6 @@ class GjcWeighingRecordViewModel : BasePageViewModel() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
Constant.RequestCode.FLIGHT_NO -> {
flightNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()
}
Constant.RequestCode.WAYBILL -> {
waybillNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()

View File

@@ -26,6 +26,7 @@ import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.Common
import com.lukouguoji.module_base.util.DictUtils
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Date
@@ -35,20 +36,24 @@ import java.util.Date
*/
class GjcWeighingStartViewModel : BaseViewModel() {
// 运单ID从列表页传入
// 运单ID从列表页传入,或通过运单号查询获得
var maWbId: Long = 0
// 运单数据Bean
val maWbBean = MutableLiveData(GjcMaWb())
// 用于防止重复查询的标记
private var lastQueriedWbNo: String = ""
private var lastQueriedCarId: String = ""
// 航班日期格式化为字符串用于DataBinding
val flightDate = MutableLiveData("")
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate())
// 地磅集成
val diBangModel = DiBangWeightModel()
val channel = MutableLiveData("") // 通道号用于控制地磅key
// 地磅集成(已改为手动输入,暂时注释)
// val diBangModel = DiBangWeightModel()
val channel = MutableLiveData("") // 通道号
// 地磅称重显示
// 地磅称重显示(手动输入,单向同步到运抵重量)
val diBangWeight = MutableLiveData("0") // 地磅重量(右上角黄色显示区域)
// 可编辑字段(用于双向绑定)
@@ -59,6 +64,8 @@ class GjcWeighingStartViewModel : BaseViewModel() {
val arriveWeight = MutableLiveData("") // 运抵重量
val arriveVolume = MutableLiveData("") // 运抵体积
val pageRemark = MutableLiveData("") // 备注
// 下拉选择数据源
val channelList = MutableLiveData<List<KeyValue>>() // 通道号列表
val agentList = MutableLiveData<List<KeyValue>>() // 代理人列表
@@ -75,15 +82,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
// 获取传入的运单ID
maWbId = intent.getLongExtra(Constant.Key.MAWB_ID, 0)
// 监听地磅重量变化
diBangModel.weight.observe(activity as LifecycleOwner) { weight ->
val w = weight?.toDoubleOrNull() ?: 0.0
diBangWeight.value = if (w > 0) String.format("%.0f", w) else "0"
}
// 监听通道号变化设置地磅的key
channel.observe(activity as LifecycleOwner) {
diBangModel.key = it ?: ""
// 监听地磅称重输入,单向同步到运抵重量
diBangWeight.observe(activity as LifecycleOwner) { weight ->
// 同步到运抵重量(单向,不做反向同步)
arriveWeight.value = weight ?: "0"
}
// 监听运抵重量变化,自动计算运抵体积
@@ -166,6 +168,7 @@ class GjcWeighingStartViewModel : BaseViewModel() {
launchLoadingCollect({ NetApply.api.getIntExpCheckInWbById(maWbId) }) {
onSuccess = { result ->
maWbBean.value = result.data ?: GjcMaWb()
pageRemark.value = maWbBean.value?.remark
// 更新航班日期字符串
flightDate.value = result.data?.fdate?.formatDate() ?: ""
}
@@ -186,6 +189,67 @@ class GjcWeighingStartViewModel : BaseViewModel() {
}
}
/**
* 运单号输入完成时调用(添加模式专用)
* 当用户输入完整运单号后,自动根据运单号查询并填充表单
*/
fun onWaybillNoInputComplete() {
val wbNo = maWbBean.value?.wbNo ?: ""
// 验证运单号是否为空
if (wbNo.isEmpty()) {
return
}
// 防止重复查询
if (wbNo == lastQueriedWbNo) {
return
}
// 验证运单号长度11位
if (wbNo.length != 11) {
showToast("请输入完整的11位运单号")
return
}
// 更新查询标记
lastQueriedWbNo = wbNo
// 直接根据运单号查询运单详情
launchLoadingCollect({ NetApply.api.getIntExpCheckInWbByNo(wbNo) }) {
onSuccess = { result ->
val data = result.data
if (data != null) {
// 保存 maWbId
maWbId = data.maWbId ?: 0
// 更新整个 maWbBean
maWbBean.value = data
// 更新航班日期字符串
flightDate.value = data.fdate?.formatDate() ?: ""
// 更新备注
pageRemark.value = data.remark
// 加载实时计重数据(如果 maWbId 有效)
if (maWbId > 0) {
loadRealTimeRecord()
}
} else {
showToast("未找到该运单信息")
// 清空查询标记,允许重新查询
lastQueriedWbNo = ""
}
}
onFailed = { code, msg ->
showToast("查询失败: $msg")
// 清空查询标记,允许重新查询
lastQueriedWbNo = ""
}
}
}
/**
* 航班日期点击
*/
@@ -223,6 +287,30 @@ class GjcWeighingStartViewModel : BaseViewModel() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.CAR)
}
/**
* 托盘车号输入完成时调用
* 当用户输入完整托盘车号后,自动查询平板车信息并填充自重
*/
fun onCarIdInputComplete() {
val carId = maWbBean.value?.carId ?: ""
// 验证托盘车号是否为空
if (carId.isEmpty()) {
return
}
// 防止重复查询
if (carId == lastQueriedCarId) {
return
}
// 更新查询标记
lastQueriedCarId = carId
// 调用接口查询平板车信息
queryCarWeight(carId)
}
/**
* 托盘车号输入后查询自重
*/
@@ -232,18 +320,26 @@ class GjcWeighingStartViewModel : BaseViewModel() {
return
}
val params = mapOf(
"carId" to carId
).toRequestBody()
launchCollect({
// 查询托盘车自重接口(待确认具体接口)
// 暂时使用模拟数据
// NetApply.api.queryCarWeight(params)
carWeight.value = "0" // 模拟返回
NetApply.api.getFlatcarInfo(carId)
}) {
onSuccess = {
// carWeight.value = it.data?.weight?.toString() ?: "0"
onSuccess = { result ->
val data = result.data
if (data != null) {
// 填充托盘车自重
carWeight.value = data.carWeight.ifEmpty { "0" }
} else {
showToast("未找到该平板车信息")
carWeight.value = "0"
// 清空查询标记,允许重新查询
lastQueriedCarId = ""
}
}
onFailed = { code, msg ->
showToast("查询平板车失败: $msg")
carWeight.value = "0"
// 清空查询标记,允许重新查询
lastQueriedCarId = ""
}
}
}
@@ -259,7 +355,17 @@ class GjcWeighingStartViewModel : BaseViewModel() {
if (bean.fno.verifyNullOrEmpty("请输入航班号")) return
if (channel.value.verifyNullOrEmpty("请选择通道号")) return
// 2. 收集当前表单数据,更新到 bean
// 2. 校验件数: 运抵件数 + 实时件数 不能大于 预配件数
val realTimePcVal = realTimePc.value?.toLongOrNull() ?: 0L
val arrivePcVal = arrivePc.value?.toLongOrNull() ?: bean.arrivePc ?: 0L
val preallocatedPc = bean.pc ?: 0L
if (arrivePcVal + realTimePcVal > preallocatedPc) {
showToast("运抵件数 + 实时件数不能大于预配件数")
return
}
// 3. 收集当前表单数据,更新到 bean
bean.apply {
// 更新运抵数据(如果用户已编辑)
arrivePc = this@GjcWeighingStartViewModel.arrivePc.value?.toLongOrNull() ?: arrivePc
@@ -268,7 +374,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
arriveVolume =
this@GjcWeighingStartViewModel.arriveVolume.value?.toDoubleOrNull() ?: arriveVolume
// 添加通道号
passageWay = this@GjcWeighingStartViewModel.channel.value
passageWay = this@GjcWeighingStartViewModel.channelList.value?.firstOrNull{ it.value == channel.value }?.key ?: ""
passageWayId = this@GjcWeighingStartViewModel.channel.value
remark = pageRemark.value
}
// 3. 构建请求数据GjcMaWb转RequestBody包含通道号
@@ -294,6 +403,9 @@ class GjcWeighingStartViewModel : BaseViewModel() {
arrivePc.value = ""
arriveWeight.value = ""
arriveVolume.value = ""
pageRemark.value = ""
maWbBean.value = maWbBean.value?.apply {
carId = null
remark = null
@@ -317,8 +429,17 @@ class GjcWeighingStartViewModel : BaseViewModel() {
if (bean.fno.verifyNullOrEmpty("请输入航班号")) return
if (channel.value.verifyNullOrEmpty("请选择通道号")) return
// 校验件数: 运抵件数 + 实时件数 不能大于 预配件数
val realTimePcVal = realTimePc.value?.toLongOrNull() ?: 0L
val arrivePcVal = arrivePc.value?.toLongOrNull() ?: bean.arrivePc ?: 0L
val preallocatedPc = bean.pc ?: 0L
if (arrivePcVal + realTimePcVal > preallocatedPc) {
showToast("运抵件数 + 实时件数不能大于预配件数")
return
}
// 从编辑字段获取数值
val arrivePcVal = arrivePc.value?.toLongOrNull() ?: bean.arrivePc
val arriveWeightVal = arriveWeight.value?.toDoubleOrNull() ?: bean.arriveWeight
val arriveVolumeVal = arriveVolume.value?.toDoubleOrNull() ?: bean.arriveVolume
@@ -350,9 +471,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
"goodsCn" to bean.goodsCn,
"businessType" to bean.businessType,
"carId" to bean.carId,
"remark" to bean.remark,
"remark" to pageRemark.value,
"checkIn" to "1", // 收运状态设置为已收运
"passageWay" to channel.value // 通道号参数
"passageWayId" to channel.value, // 通道号参数
"passageWay" to (channelList.value?.firstOrNull{ it.value == channel.value }?.key ?: "")
).toRequestBody(removeEmptyOrNull = true)
launchLoadingCollect({
@@ -406,6 +528,6 @@ class GjcWeighingStartViewModel : BaseViewModel() {
*/
override fun onCleared() {
super.onCleared()
diBangModel.cancelLooperGet()
// diBangModel.cancelLooperGet() // 已改为手动输入,不再使用地磅自动读取
}
}

View File

@@ -3,20 +3,30 @@ package com.lukouguoji.gjc.viewModel
import android.app.Activity
import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.activity.GjcWeighingRecordListActivity
import com.lukouguoji.gjc.activity.GjcWeighingStartActivity
import com.lukouguoji.gjc.holder.GjcWeighingViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.bean.GjcWeighingBean
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
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.formatDate
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.model.ScanModel
import com.lukouguoji.module_base.util.DictUtils
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import kotlinx.coroutines.launch
/**
* 国际出港计重 ViewModel
@@ -24,7 +34,7 @@ import dev.utils.app.info.KeyValue
class GjcWeighingViewModel : BasePageViewModel() {
// 搜索条件
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val agentCode = MutableLiveData("") // 代理人
val spCode = MutableLiveData("") // 特码
@@ -43,6 +53,18 @@ class GjcWeighingViewModel : BasePageViewModel() {
val totalPc = MutableLiveData("0") // 总件数
val totalWeight = MutableLiveData("0") // 总重量
// 全选状态
val isAllChecked = MutableLiveData(false)
init {
// 监听全选状态,自动更新所有列表项
isAllChecked.observeForever { checked ->
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcWeighingBean> ?: return@observeForever
list.forEach { it.checked.set(checked) }
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
}
///////////////////////////////////////////////////////////////////////////
// 方法区
///////////////////////////////////////////////////////////////////////////
@@ -79,13 +101,6 @@ class GjcWeighingViewModel : BasePageViewModel() {
}
}
/**
* 扫码输入航班号
*/
fun flightNoScanClick() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.FLIGHT_NO)
}
/**
* 扫码输入运单号
*/
@@ -114,11 +129,56 @@ class GjcWeighingViewModel : BasePageViewModel() {
GjcWeighingRecordListActivity.start(getTopActivity())
}
/**
* 全选按钮点击 (切换全选状态)
*/
fun checkAllClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcWeighingBean> ?: return
// 切换全选状态
val shouldCheckAll = !isAllChecked.value!!
list.forEach { it.checked.set(shouldCheckAll) }
isAllChecked.value = shouldCheckAll
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 提前运抵
*/
fun arrivedAhead() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcWeighingBean> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
showToast("请选择要提前运抵的运单")
return
}
// 转换为GjcMaWb对象列表只传递必要字段
val maWbList = selectedItems.map { bean ->
GjcMaWb(
maWbId = bean.maWbId,
no = bean.no,
prefix = bean.prefix,
wbNo = bean.wbNo,
arriveFlag = "1" // 设置提前运抵标识
)
}
launchLoadingCollect({ NetApply.api.preArrive(maWbList) }) {
onSuccess = {
showToast("提前运抵成功")
// 发送刷新事件
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
onFailed = { code, msg ->
showToast(msg.noNull("提前运抵失败"))
}
}
}
/**
@@ -133,7 +193,7 @@ class GjcWeighingViewModel : BasePageViewModel() {
"fno" to flightNo.value!!.ifEmpty { null },
"agentCode" to agentCode.value!!.ifEmpty { null },
"spCode" to spCode.value!!.ifEmpty { null },
"likeNo" to waybillNo.value!!.ifEmpty { null },
"wbNo" to waybillNo.value!!.ifEmpty { null },
).toRequestBody()
// 构建查询参数(统计接口 - 使用相同的搜索条件)
@@ -142,7 +202,7 @@ class GjcWeighingViewModel : BasePageViewModel() {
"fno" to flightNo.value!!.ifEmpty { null },
"agentCode" to agentCode.value!!.ifEmpty { null },
"spCode" to spCode.value!!.ifEmpty { null },
"likeNo" to waybillNo.value!!.ifEmpty { null },
"wbNo" to waybillNo.value!!.ifEmpty { null },
).toRequestBody()
// 获取列表数据显示loading
@@ -173,10 +233,6 @@ class GjcWeighingViewModel : BasePageViewModel() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
Constant.RequestCode.FLIGHT_NO -> {
flightNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()
}
Constant.RequestCode.WAYBILL -> {
waybillNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT)
refresh()

View File

@@ -4,9 +4,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.dialog.IntExpArriveDeleteDialogModel
import com.lukouguoji.gjc.dialog.IntExpArriveResetDialogModel
import com.lukouguoji.gjc.holder.IntExpArriveViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcDeclareParam
import com.lukouguoji.module_base.bean.GjcHaWb
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
@@ -18,6 +20,8 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/**
@@ -26,7 +30,7 @@ import kotlinx.coroutines.launch
class IntExpArriveViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val waybillNo = MutableLiveData("") // 运单号
val hno = MutableLiveData("") // 分单号
@@ -39,6 +43,14 @@ class IntExpArriveViewModel : BasePageViewModel() {
// ========== 全选状态 ==========
val isAllChecked = MutableLiveData(false)
// ========== 全局展开状态 ==========
/**
* 全局展开状态
* - true: 全部展开
* - false: 全部收起
*/
val isAllExpanded = MutableLiveData(false)
init {
// 监听全选状态,自动更新所有列表项
isAllChecked.observeForever { checked ->
@@ -73,6 +85,28 @@ class IntExpArriveViewModel : BasePageViewModel() {
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 切换全局展开/收起状态
*/
fun toggleAllExpand() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
// 切换全局状态
val shouldExpand = !isAllExpanded.value!!
isAllExpanded.value = shouldExpand
// 更新所有列表项的 showMore 状态
list.forEach { bean ->
// 只有当有子列表时才设置展开状态
if (!bean.haWbList.isNullOrEmpty()) {
bean.showMore.set(shouldExpand)
}
}
// 刷新列表UI
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 扫码运单号
*/
@@ -92,24 +126,59 @@ class IntExpArriveViewModel : BasePageViewModel() {
*/
fun resetDeclareClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要重置的运单")
return
}
val requestData = mapOf("maWbList" to selectedItems).toRequestBody()
// 创建并显示弹框
val dialog = IntExpArriveResetDialogModel { dialogModel ->
// 弹框确认后的回调
// 构建请求参数(区分主单和分单)
val params = mutableMapOf<String, Any?>()
if (selectedMaWbList.isNotEmpty()) {
params["maWbList"] = selectedMaWbList
}
if (selectedHaWbList.isNotEmpty()) {
params["haWbList"] = selectedHaWbList
}
launchLoadingCollect({ NetApply.api.resetArriveDeclare(requestData) }) {
onSuccess = {
showToast("状态重置成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
// 根据选择添加restStatus参数
// 选择"正常"时传递 "01",选择"未申报"时不传递此参数
if (dialogModel.resetStatusCode != null) {
params["restStatus"] = dialogModel.resetStatusCode
}
val requestData = params.toRequestBody()
// 调用重置接口
launchLoadingCollect({ NetApply.api.resetArriveDeclare(requestData) }) {
onSuccess = {
showToast("状态重置成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
refresh()
}
}
dialog.show()
}
/**
@@ -117,9 +186,22 @@ class IntExpArriveViewModel : BasePageViewModel() {
*/
fun deleteDeclareClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项(主单和分单分开判断,互不影响)
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要删除申报的记录")
return
}
@@ -131,12 +213,13 @@ class IntExpArriveViewModel : BasePageViewModel() {
// 创建并显示弹框
val dialog = IntExpArriveDeleteDialogModel(changeReasonList) { dialogModel ->
// 弹框确认后的回调
// 弹框确认后的回调(区分主单和分单)
val param = GjcDeclareParam(
dcode = dialogModel.changeReason.value,
dcontactsName = dialogModel.contactName.value,
dcontactsTel = dialogModel.contactPhone.value,
maWbList = selectedItems
maWbList = if (selectedMaWbList.isNotEmpty()) selectedMaWbList else null,
haWbList = if (selectedHaWbList.isNotEmpty()) selectedHaWbList else null
)
val requestData = param.toRequestBody()
@@ -163,14 +246,36 @@ class IntExpArriveViewModel : BasePageViewModel() {
*/
fun arriveReportClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要申报的运单")
return
}
val requestData = mapOf("maWbList" to selectedItems).toRequestBody()
// 构建请求参数(区分主单和分单)
val params = mutableMapOf<String, Any?>()
if (selectedMaWbList.isNotEmpty()) {
params["maWbList"] = selectedMaWbList
}
if (selectedHaWbList.isNotEmpty()) {
params["haWbList"] = selectedHaWbList
}
val requestData = params.toRequestBody()
launchLoadingCollect({ NetApply.api.arriveDeclare(requestData) }) {
onSuccess = {
@@ -191,7 +296,7 @@ class IntExpArriveViewModel : BasePageViewModel() {
val filterParams = mapOf(
"fdate" to flightDate.value?.ifEmpty { null },
"fno" to flightNo.value?.ifEmpty { null },
"likeNo" to waybillNo.value?.ifEmpty { null },
"wbNo" to waybillNo.value?.ifEmpty { null },
"hno" to hno.value?.ifEmpty { null }
)
@@ -206,7 +311,11 @@ class IntExpArriveViewModel : BasePageViewModel() {
// 获取列表 (带Loading)
launchLoadingCollect({ NetApply.api.getIntExpArriveList(listParams) }) {
onSuccess = { pageModel.handleListBean(it) }
onSuccess = {
pageModel.handleListBean(it)
// 数据加载完成后,重置全局展开状态为收起
isAllExpanded.value = false
}
}
// 获取统计信息 (后台请求)

View File

@@ -1,6 +1,7 @@
package com.lukouguoji.gjc.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.holder.AssembleInfoViewHolder
import com.lukouguoji.gjc.holder.AssemblePositionViewHolder
@@ -8,20 +9,31 @@ import com.lukouguoji.gjc.holder.AssembleWaybillViewHolder
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.*
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.app.info.KeyValue
import kotlinx.coroutines.launch
/**
* 国际出港-开始组装ViewModel静态数据
*/
class IntExpAssembleStartViewModel : BaseViewModel() {
companion object {
/**
* 缓存用户上次选择的组装人
* 生命周期本次App打开期间有效退出登录后清空
*/
var lastSelectedOperator: String? = null
}
// ========== 搜索条件 ==========
val searchText = MutableLiveData("")
val uldSearchText = MutableLiveData("") // ULD搜索框
@@ -57,17 +69,73 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
// ========== 运单信息 ==========
val waybillInfo = MutableLiveData(WaybillInfoBean())
// ========== 组装人独立的LiveData用于监听变化并更新缓存==========
val operator = MutableLiveData<String>("")
// ========== 组装人列表 ==========
val assemblerList = MutableLiveData<List<KeyValue>>(emptyList())
// ========== 标记位,避免重复查询 ==========
private var lastQueriedUldNo = ""
// ========== 修改模式待匹配的组装位置 ==========
private var pendingLoadArea: String = ""
/**
* ULD编号锁定状态
* ULD编号锁定状态(页面内编辑模式)
*/
val isUldNoLocked = MutableLiveData(false)
/**
* 卸货时选中的组装信息子运单数据
* 用于:
* 1. 卸货校验时对比组装件数/重量
* 2. 卸货提交时构建 wbInfo
*/
private var selectedAssembleWaybill: AssembleInfoBean? = null
/**
* 是否从列表页"修改"模式进入
* 与 isUldNoLocked 的区别:
* - isFromEditMode整个页面生命周期内 ULD 编号锁定,但不影响装货/卸货按钮
* - isUldNoLocked页面内编辑模式影响装货/卸货按钮
*/
val isFromEditMode = MutableLiveData(false)
/**
* 装货按钮启用状态(非编辑模式时启用)
*/
val isLoadEnabled = MutableLiveData(true)
/**
* 卸货按钮启用状态(编辑模式时启用)
*/
val isUnloadEnabled = MutableLiveData(false)
init {
// 监听组装人变化自动更新缓存和waybillInfo
operator.observeForever { value ->
if (!value.isNullOrEmpty()) {
lastSelectedOperator = value
// 同步到 waybillInfo
waybillInfo.value?.operator = value
}
}
// 监听ULD锁定状态自动更新按钮启用状态
isUldNoLocked.observeForever { isLocked ->
if (isLocked == true) {
// 编辑模式:只能卸货
isLoadEnabled.value = false
isUnloadEnabled.value = true
} else {
// 非编辑模式:只能装货
isLoadEnabled.value = true
isUnloadEnabled.value = false
}
}
}
/**
* 加载组装位置列表
@@ -82,12 +150,30 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
}
}?.toMutableList() ?: mutableListOf()
assemblePositionList.value = list
// 保存默认选中的第一项
if (list.isNotEmpty()) {
// 检查是否有待匹配的组装位置(修改模式)
if (pendingLoadArea.isNotEmpty() && list.isNotEmpty()) {
// 取消所有选中状态
list.forEach { it.isSelected = false }
// 查找并选中匹配项
val matchedItem = list.find { it.positionName == pendingLoadArea }
if (matchedItem != null) {
matchedItem.isSelected = true
selectedPosition.value = matchedItem
} else {
// 未找到匹配项,选中第一项
list[0].isSelected = true
selectedPosition.value = list[0]
}
// 清空待匹配标记
pendingLoadArea = ""
// 触发组装信息列表查询
loadAssembledList()
} else if (list.isNotEmpty()) {
// 非修改模式,选中第一项
selectedPosition.value = list[0]
}
assemblePositionList.value = list
}
}
}
@@ -116,6 +202,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
/**
* 运单点击(单选切换)
* 装货模式:从运单列表选择运单
*/
fun onWaybillItemClick(position: Int) {
val list = waybillList.value ?: return
@@ -133,12 +220,13 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
if (selectedWaybill != null) {
selectedWaybill.isSelected.set(true)
// 自动提取航班号和航班日期
assembleFlightNo.value = selectedWaybill.fno
assembleFlightDate.value = selectedWaybill.fdate
// 【删除】不再从运单提取航班信息航班信息改为从ULD查询接口获取
// 保存当前的组装人(始终保留
val previousOperator = waybillInfo.value?.operator ?: ""
// 【新增】清空卸货选中的子运单(切换到装货模式
selectedAssembleWaybill = null
// 保存当前的组装人始终保留从operator LiveData读取
val previousOperator = operator.value ?: ""
// 判断是否需要保留组装件数和重量
val previousAssembleCount: String
@@ -164,7 +252,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
waybillWeight = selectedWaybill.weight
assembleCount = previousAssembleCount
assembleWeight = previousAssembleWeight
operator = previousOperator
operator = previousOperator // 从operator LiveData获取的值
}
}
@@ -191,13 +279,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
fun loadWaitingAssembleWaybills() {
val wbNo = searchText.value?.trim() ?: ""
// 验证运单号不能为空
if (wbNo.isEmpty()) {
showToast("请输入运单号")
return
}
// 发起网络请求
// 发起网络请求(空参数时相当于刷新列表)
launchLoadingCollect({ NetApply.api.queryWaitingAssemble(wbNo) }) {
onSuccess = { result ->
// 数据转换: GjcWarehouse -> AssembleWaybillBean
@@ -224,7 +306,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
val existsInNewList = waybillBeanList.any { it.waybillNo == currentWaybillNo }
if (!existsInNewList) {
// 当前运单不在新列表中,清空表单
val previousOperator = waybillInfo.value?.operator ?: ""
val previousOperator = operator.value ?: ""
waybillInfo.value = WaybillInfoBean().apply {
operator = previousOperator
}
@@ -273,7 +355,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
val existsInNewList = waybillBeanList.any { it.waybillNo == currentWaybillNo }
if (!existsInNewList) {
// 当前运单不在新列表中,清空表单
val previousOperator = waybillInfo.value?.operator ?: ""
val previousOperator = operator.value ?: ""
waybillInfo.value = WaybillInfoBean().apply {
operator = previousOperator
}
@@ -302,15 +384,11 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
val keyValueList = list.map { KeyValue(it, it) }
assemblerList.value = keyValueList
// 获取当前登录用户名
val currentUserName = SharedPreferenceUtil.getString(Constant.Share.userName)
// 如果当前用户名在列表中,设置为默认值;否则保持为空(显示 hint
if (currentUserName.isNotEmpty() && list.contains(currentUserName)) {
waybillInfo.value?.operator = currentUserName
waybillInfo.value = waybillInfo.value // 触发LiveData更新
// 使用缓存的组装人,如果缓存存在且在列表中则自动填充
if (!lastSelectedOperator.isNullOrEmpty() && list.contains(lastSelectedOperator)) {
operator.value = lastSelectedOperator // 设置到operator LiveData会自动触发observer更新缓存和waybillInfo
}
// 注意:不匹配时不需要显式设置为空,因为初始值已经是空字符串
// 如果缓存为空或不在列表中保持为空显示hint "请选择组装人"
}
onFailed = { code, message ->
showToast("加载组装人列表失败: $message")
@@ -333,6 +411,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
/**
* 查询ULD信息状态和耗材重量
* 同时从ULD信息提取航班信息并触发组装信息列表查询
*/
private fun queryUldInfo(uldNo: String) {
launchCollect({ NetApply.api.getUldWithConsumeWeight(uldNo) }) {
@@ -354,6 +433,20 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
// 保存useId用于装货/卸货接口)
useId = uldBean.useId
}
// 【新增】从ULD信息提取航班信息
if (uldBean.fno.isNotEmpty() && uldBean.fdate.isNotEmpty()) {
assembleFlightNo.value = uldBean.fno
assembleFlightDate.value = uldBean.fdateFormatted // 使用格式化后的日期(只保留年月日)
// 清除防抖标记,触发组装信息列表查询
lastQueriedAssembledParams = ""
loadAssembledList()
} else {
// 航班信息为空,清空组装信息列表,但允许继续装货
assembleFlightNo.value = ""
assembleFlightDate.value = ""
assembleInfoList.value = mutableListOf()
}
}
}
onFailed = { code, message ->
@@ -379,6 +472,14 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
/**
* 执行组装操作(卸货或装货)
* @param isLoad true-装货false-卸货
*
* 装货与卸货的区别:
* 1. 校验逻辑不同:
* - 装货:与运单列表中的件数/重量对比
* - 卸货:与组装信息中的组装件数/重量对比
* 2. wbInfo 来源不同:
* - 装货:从运单列表选中项获取
* - 卸货:从组装信息选中的子运单获取
*/
private fun performAssembleOperation(isLoad: Boolean) {
// 1. 验证必填字段
@@ -402,15 +503,62 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
val assembleCount = waybillInfo.value?.assembleCount?.trim() ?: ""
if (assembleCount.isEmpty()) {
showToast("请输入组装件数")
val countFieldName = if (isLoad) "装货件数" else "卸货件数"
showToast("请输入${countFieldName}")
return
}
val assembleWeight = waybillInfo.value?.assembleWeight?.trim() ?: ""
// 组装重量为非必填,不进行验证
val operator = waybillInfo.value?.operator?.trim() ?: ""
if (operator.isEmpty()) {
// 【修改】区分校验逻辑
val assembleCountInt = assembleCount.toLongOrNull() ?: 0L
val assembleWeightDouble = assembleWeight.toDoubleOrNull() ?: 0.0
if (isLoad) {
// 装货校验:与运单列表中的件数/重量对比
val waybillPieces = waybillInfo.value?.waybillPieces?.trim() ?: ""
val waybillPiecesInt = waybillPieces.toLongOrNull() ?: 0L
if (waybillPiecesInt > 0 && assembleCountInt > waybillPiecesInt) {
showToast("装货件数不能大于运单件数")
return
}
if (assembleWeight.isNotEmpty()) {
val waybillWeight = waybillInfo.value?.waybillWeight?.trim() ?: ""
val waybillWeightDouble = waybillWeight.toDoubleOrNull() ?: 0.0
if (waybillWeightDouble > 0 && assembleWeightDouble > waybillWeightDouble) {
showToast("装货重量不能大于运单重量")
return
}
}
} else {
// 卸货校验:与组装信息中的组装件数/重量对比
val assembleInfo = selectedAssembleWaybill?.waybillData
if (assembleInfo == null) {
showToast("请从组装信息列表选择要卸货的运单")
return
}
// originalPieces 对应 checkInPc已组装件数
val maxPieces = assembleInfo.originalPieces.toLongOrNull() ?: 0L
// originalWeight 对应 checkInWeight已组装重量
val maxWeight = assembleInfo.originalWeight.toDoubleOrNull() ?: 0.0
if (maxPieces > 0 && assembleCountInt > maxPieces) {
showToast("卸货件数不能大于已组装件数")
return
}
if (assembleWeight.isNotEmpty() && maxWeight > 0 && assembleWeightDouble > maxWeight) {
showToast("卸货重量不能大于已组装重量")
return
}
}
val operatorValue = operator.value?.trim() ?: ""
if (operatorValue.isEmpty()) {
showToast("请选择组装人")
return
}
@@ -421,21 +569,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
return
}
// 2. 获取或构建运单Bean
// 从运单列表中获取选中的运单
val currentWaybillList = waybillList.value
if (currentWaybillList == null) {
showToast("运单列表为空")
return
}
val selectedWaybill = currentWaybillList.firstOrNull { it.isSelected.get() }
if (selectedWaybill == null) {
showToast("请选择运单")
return
}
// 3. 构建useInfoULD信息
// 2. 构建useInfoULD信息
val useInfo = mapOf(
"uld" to uldNo,
"consumeWeight" to materialWeight.toDoubleOrNull(),
@@ -445,44 +579,77 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
else -> ""
},
"loadArea" to loadArea,
"useId" to uldInfo.value?.useId // 添加useId来自getUld接口
"useId" to if (uldInfo.value?.useId == 0L) null else uldInfo.value?.useId // 添加useId来自getUld接口
)
// 4. 构建wbInfo运单信息
// 使用原始运单件数/重量(如果有),否则使用当前件数/重量
val waybillPc = if (selectedWaybill.originalPieces.isNotEmpty()) {
selectedWaybill.originalPieces.toLongOrNull()
// 3. 【修改】区分 wbInfo 构建
val wbInfo: Map<String, Any?>
if (isLoad) {
// 装货:从运单列表获取
val currentWaybillList = waybillList.value
if (currentWaybillList == null) {
showToast("运单列表为空")
return
}
val selectedWaybill = currentWaybillList.firstOrNull { it.isSelected.get() }
if (selectedWaybill == null) {
showToast("请选择运单")
return
}
// 使用原始运单件数/重量(如果有),否则使用当前件数/重量
val waybillPc = if (selectedWaybill.originalPieces.isNotEmpty()) {
selectedWaybill.originalPieces.toLongOrNull()
} else {
selectedWaybill.pieces.toLongOrNull()
}
val waybillWeight = if (selectedWaybill.originalWeight.isNotEmpty()) {
selectedWaybill.originalWeight.toDoubleOrNull()
} else {
selectedWaybill.weight.toDoubleOrNull()
}
wbInfo = mapOf(
"wbNo" to selectedWaybill.waybillNo,
"pc" to waybillPc,
"weight" to waybillWeight,
"fdate" to selectedWaybill.fdate,
"fno" to selectedWaybill.fno,
"whId" to selectedWaybill.whId
)
} else {
selectedWaybill.pieces.toLongOrNull()
}
val waybillWeight = if (selectedWaybill.originalWeight.isNotEmpty()) {
selectedWaybill.originalWeight.toDoubleOrNull()
} else {
selectedWaybill.weight.toDoubleOrNull()
// 卸货:从组装信息子运单获取
val assembleWaybill = selectedAssembleWaybill?.waybillData
if (assembleWaybill == null) {
showToast("请从组装信息列表选择要卸货的运单")
return
}
wbInfo = mapOf(
"wbNo" to assembleWaybill.waybillNo,
"pc" to assembleWaybill.pieces.toLongOrNull(), // 运单件数
"weight" to assembleWaybill.weight.toDoubleOrNull(), // 运单重量
"fdate" to assembleWaybill.fdate,
"fno" to assembleWaybill.fno,
"whId" to assembleWaybill.whId
)
}
val wbInfo = mapOf(
"wbNo" to selectedWaybill.waybillNo,
"pc" to waybillPc,
"weight" to waybillWeight,
"fdate" to selectedWaybill.fdate,
"fno" to selectedWaybill.fno,
"whId" to selectedWaybill.whId
)
// 5. 构建完整请求参数
// 4. 构建完整请求参数
val params = mapOf(
"abPc" to assembleCount.toLongOrNull(),
"abWeight" to assembleWeight.toDoubleOrNull(),
"consumeWeight" to materialWeight.toDoubleOrNull(),
"ldId" to operator,
"ldId" to operatorValue,
"loadArea" to loadArea,
"useInfo" to useInfo,
"wbInfo" to wbInfo,
"userId" to SharedPreferenceUtil.getString(Constant.Share.account)
).toRequestBody()
// 6. 调用接口带Loading等待接口返回
// 5. 调用接口带Loading等待接口返回
val operationName = if (isLoad) "装货" else "卸货"
launchLoadingCollect({
if (isLoad) {
@@ -493,7 +660,7 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
}) {
onSuccess = { result ->
// 接口成功后才显示成功提示并刷新列表
handleOperationSuccess(operationName, isLoad, uldNo, selectedWaybill, assembleCount, assembleWeight)
handleOperationSuccess(operationName, isLoad, uldNo, assembleCount, assembleWeight)
}
onFailed = { code, message ->
showToast("${operationName}失败: $message")
@@ -508,20 +675,55 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
operationName: String,
isLoad: Boolean,
uldNo: String,
selectedWaybill: AssembleWaybillBean,
assembleCount: String,
assembleWeight: String
) {
showToast("${operationName}成功")
// 重新查询组装信息列表(刷新数据
// 发送刷新事件,通知出港组装列表页面更新数据
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
// 清空表单(在刷新数据前清空,避免刷新时的状态冲突)
clearForm()
// 清除防抖标记,强制刷新组装信息列表
lastQueriedAssembledParams = ""
loadAssembledList()
// 刷新运单列表
loadInitialWaitingAssemble()
// 刷新运单列表(使用当前搜索条件)
refreshWaybillList()
}
// 清空表单
clearForm()
/**
* 刷新运单列表(保留当前搜索条件)
*/
private fun refreshWaybillList() {
val wbNo = searchText.value?.trim() ?: ""
launchLoadingCollect({ NetApply.api.queryWaitingAssemble(wbNo) }) {
onSuccess = { result ->
val warehouseList = result.data ?: mutableListOf()
val waybillBeanList = warehouseList.map { warehouse ->
AssembleWaybillBean().apply {
waybillNo = warehouse.wbNo
pieces = warehouse.pc.toString()
weight = String.format("%.1f", warehouse.weight)
flight = warehouse.flight
fno = warehouse.fno
fdate = warehouse.fdate
whId = warehouse.whId
isMarked = false
}
}.toMutableList()
waybillList.value = waybillBeanList
}
onFailed = { code, message ->
showToast("刷新运单列表失败: $message")
}
}
}
/**
@@ -610,8 +812,8 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
itemType = AssembleInfoBean.ItemType.WAYBILL_DETAIL
parentUldNo = uldItem.uldNo
wbNo = warehouse.wbNo
waybillPieces = warehouse.pc.toInt()
waybillWeight = warehouse.weight
waybillPieces = warehouse.checkInPc.toInt()
waybillWeight = warehouse.checkInWeight
showIndent = true
showIndex = false
@@ -624,6 +826,9 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
fno = warehouse.fno
fdate = warehouse.fdate
whId = warehouse.whId
// 设置原始运单件数/重量使用checkInPc/checkInWeight
originalPieces = warehouse.checkInPc.toString()
originalWeight = String.format("%.1f", warehouse.checkInWeight)
}
}
}
@@ -650,31 +855,39 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
}
/**
* 从二级运单行填充表单(编辑模式)
* 1. 填充ULD信息并锁定ULD编号
* 2. 填充运单信息(使用原始运单件数/重量,只读
* 3. 保留组装件数、组装重量、组装人为可编辑
* 从二级运单行填充表单(卸货模式)
* 1. 存储选中的组装信息子运单(用于卸货校验和提交
* 2. 填充ULD信息并锁定ULD编号
* 3. 填充运单信息(字段对调:运单件数/组装件数对调,运单重量/组装重量对调)
*
* 卸货时字段映射:
* - 运单件数 ← pieces (pc原始运单件数)
* - 运单重量 ← weight (weight原始运单重量)
* - 组装件数 ← originalPieces (checkInPc已组装件数可编辑卸货数量)
* - 组装重量 ← originalWeight (checkInWeight已组装重量可编辑卸货数量)
*/
private fun fillFormFromWaybillDetail(item: AssembleInfoBean) {
val waybill = item.waybillData ?: return
// 【新增】存储选中的组装信息子运单(用于卸货校验和提交)
selectedAssembleWaybill = item
// 1. 填充ULD信息
uldInfo.value = uldInfo.value?.apply {
uldNo = item.parentUldNo
// 耗材重量、ULD状态保持不变
}
// 保存当前的组装人(在创建新对象前)
val previousOperator = waybillInfo.value?.operator ?: ""
// 保存当前的组装人(在创建新对象前从operator LiveData读取
val previousOperator = operator.value ?: ""
// 2. 填充运单信息
// 2. 填充运单信息(【修改】对调字段填充)
waybillInfo.value = WaybillInfoBean().apply {
waybillNo = waybill.waybillNo
waybillPieces = waybill.originalPieces // 使用原始运单件数
waybillWeight = waybill.originalWeight // 使用原始运单重量
// 填充已累积的组装件数和组装重量(可编辑)
assembleCount = waybill.pieces
assembleWeight = waybill.weight
waybillPieces = waybill.pieces // 【对调】显示运单件数pc
waybillWeight = waybill.weight // 【对调】显示运单重量weight
assembleCount = waybill.originalPieces // 【对调】显示组装件数checkInPc可编辑卸货数量
assembleWeight = waybill.originalWeight // 【对调】显示组装重量checkInWeight可编辑卸货数量
operator = previousOperator // 保留之前选择的组装人
}
@@ -763,9 +976,9 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
uldBean.waybillDetails = warehouseList
}
// 计算总件数和总重量(从运单列表求和)
val calculatedPieces = warehouseList?.sumOf { it.pc.toInt() } ?: 0
val calculatedWeight = warehouseList?.sumOf { it.weight } ?: 0.0
// 计算总件数和总重量(从子列表的checkInPc、checkInWeight求和)
val calculatedPieces = warehouseList?.sumOf { it.checkInPc.toInt() } ?: 0
val calculatedWeight = warehouseList?.sumOf { it.checkInWeight } ?: 0.0
AssembleInfoBean().apply {
itemType = AssembleInfoBean.ItemType.ULD_HEADER
@@ -799,12 +1012,52 @@ class IntExpAssembleStartViewModel : BaseViewModel() {
* 清空表单并退出编辑模式
*/
private fun clearForm() {
// 保留组装人的值(用户期望保留上一次的选择)
val previousOperator = waybillInfo.value?.operator ?: ""
// 保留组装人的值(用户期望保留上一次的选择从operator LiveData读取
val previousOperator = operator.value ?: ""
waybillInfo.value = WaybillInfoBean().apply {
operator = previousOperator // 恢复组装人
}
isUldNoLocked.value = false
// 【新增】清空卸货选中的子运单
selectedAssembleWaybill = null
}
/**
* 清空缓存的组装人(登出时调用)
*/
fun clearCachedOperator() {
lastSelectedOperator = null
}
/**
* 从列表页"修改"模式初始化
* @param uldNo ULD编号
* @param loadArea 组装位置(用于自动选中对应的组装位置)
*/
fun initFromEditMode(uldNo: String, loadArea: String = "") {
if (uldNo.isEmpty()) return
// 标记为修改模式(整个页面生命周期内 ULD 编号锁定)
isFromEditMode.value = true
// 保存待匹配的组装位置(在组装位置列表加载完成后进行匹配选择)
if (loadArea.isNotEmpty()) {
pendingLoadArea = loadArea
}
// 填充 ULD 编号
uldInfo.value = uldInfo.value?.apply {
this.uldNo = uldNo
} ?: UldInfoBean().apply {
this.uldNo = uldNo
}
// 更新防抖标记,确保查询能触发
lastQueriedUldNo = ""
// 触发 ULD 编码查询
onUldNoInputComplete()
}
}

View File

@@ -1,25 +1,22 @@
package com.lukouguoji.gjc.viewModel
import android.text.InputType
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.alibaba.android.arouter.launcher.ARouter
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.holder.IntExpAssembleViewHolder
import com.lukouguoji.gjc.holder.IntExpAssembleItemViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcUldUseBean
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.commonAdapter
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.showConfirmDialog
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.router.ARouterConstants
import dev.utils.app.info.KeyValue
import kotlinx.coroutines.launch
import dev.utils.common.DateUtils
/**
* 国际出港-出港组装ViewModel
@@ -27,23 +24,24 @@ import kotlinx.coroutines.launch
class IntExpAssembleViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val uldNo = MutableLiveData("") // ULD编号
val reweighStatus = MutableLiveData("全部") // 复磅状态
val reweighStatus = MutableLiveData("") // 复磅状态
val reweighStatusList = MutableLiveData( // 复磅状态选项
listOf(
KeyValue("全部", "全部"),
KeyValue("未复磅", "未复磅"),
KeyValue("已复磅", "已复磅")
KeyValue("全部", ""),
KeyValue("未复磅", "0"),
KeyValue("已复磅", "1")
)
)
val assembler = MutableLiveData("") // 组装人
val assemblerList = MutableLiveData<List<KeyValue>>(emptyList()) // 组装人列表
// ========== 适配器配置 ==========
val itemViewHolder = IntExpAssembleViewHolder::class.java
val itemLayoutId = R.layout.item_int_exp_assemble_uld
val itemViewHolder = IntExpAssembleItemViewHolder::class.java
val itemLayoutId = R.layout.item_int_exp_assemble
// ========== 底部统计 ==========
val totalCount = MutableLiveData("0") // 合计票数
@@ -74,33 +72,54 @@ class IntExpAssembleViewModel : BasePageViewModel() {
/**
* 切换展开/收起状态
* 首次展开时加载运单明细数据
*/
fun toggleExpand(position: Int) {
val bean = pageModel.rv?.commonAdapter()?.getItem(position) as? GjcUldUseBean ?: return
bean.isExpanded = !bean.isExpanded
val isCurrentlyExpanded = bean.showMore.get()
if (bean.isExpanded && bean.waybillDetails == null) {
// 首次展开,加载运单明细
loadWaybillDetails(position, bean)
if (isCurrentlyExpanded) {
// 当前是展开状态,收起
bean.showMore.set(false)
} else {
// 已有数据直接刷新item显示/隐藏
pageModel.rv?.commonAdapter()?.notifyItemChanged(position)
// 当前是收起状态,展开
bean.showMore.set(true)
// 如果未加载过数据,则加载
if (!bean.waybillDetailsLoaded) {
loadWaybillDetails(position, bean)
}
}
}
/**
* 加载运单明细
* 使用接口: /IntExpAssemble/queryAssembledByUld
*/
private fun loadWaybillDetails(position: Int, bean: GjcUldUseBean) {
val params = mapOf("useId" to bean.useId).toRequestBody()
// 设置加载中状态
bean.isLoading.set(true)
pageModel.rv?.commonAdapter()?.notifyItemChanged(position)
launchCollect({ NetApply.api.getIntExpAssembleWaybillDetails(params) }) {
// 构建请求参数 - 传递完整的GjcUldUseBean信息
val params = mapOf(
"useId" to bean.useId,
"uld" to bean.uld,
"fdate" to bean.fdate,
"fno" to bean.fno
).toRequestBody()
launchCollect({ NetApply.api.getAssembledWaybillsByUld(params) }) {
onSuccess = { result ->
bean.waybillDetails = result.data ?: mutableListOf()
bean.waybillDetails = result.data?.toMutableList()
bean.waybillDetailsLoaded = true
bean.isLoading.set(false)
pageModel.rv?.commonAdapter()?.notifyItemChanged(position)
}
onFailed = { _, msg ->
bean.isExpanded = false // 加载失败,恢复展开状态
bean.waybillDetailsLoaded = true // 标记为已加载(即使失败也显示暂无数据)
bean.waybillDetails = mutableListOf() // 设置空列表
bean.isLoading.set(false)
pageModel.rv?.commonAdapter()?.notifyItemChanged(position)
showToast(msg)
}
}
@@ -147,8 +166,49 @@ class IntExpAssembleViewModel : BasePageViewModel() {
* Item点击事件处理
*/
override fun onItemClick(position: Int, type: Int) {
if (type == 1000) { // 展开/收起操作
toggleExpand(position)
when (type) {
1000 -> toggleExpand(position) // 展开/收起操作
2000 -> onEditItem(position) // 修改操作
2001 -> onDeleteSingleItem(position) // 删除操作
}
}
/**
* 修改单个列表项 - 跳转到开始组装页面(修改模式)
* ULD 编号将被锁定,整个页面生命周期内不可编辑
* 同时传递组装位置,用于自动选中对应项
*/
private fun onEditItem(position: Int) {
val bean = pageModel.rv?.commonAdapter()?.getItem(position) as? GjcUldUseBean ?: return
com.lukouguoji.gjc.page.assemble.IntExpAssembleStartActivity.startForEdit(
getTopActivity(),
bean.uld,
bean.loadArea
)
}
/**
* 删除单个列表项(侧滑操作)
*/
private fun onDeleteSingleItem(position: Int) {
val bean = pageModel.rv?.commonAdapter()?.getItem(position) as? GjcUldUseBean ?: return
getTopActivity().showConfirmDialog("确定要删除该条记录吗?") {
deleteSingleItem(bean)
}
}
/**
* 执行单项删除
*/
private fun deleteSingleItem(bean: GjcUldUseBean) {
val requestData = listOf(bean).toRequestBody()
launchLoadingCollect({ NetApply.api.deleteIntExpAssemble(requestData) }) {
onSuccess = {
showToast("删除成功")
refresh()
}
}
}
@@ -178,24 +238,18 @@ class IntExpAssembleViewModel : BasePageViewModel() {
}
// 显示确认对话框
val activity = getTopActivity()
AlertDialog.Builder(activity)
.setTitle("删除确认")
.setMessage("确定要删除选中的 ${selectedItems.size} 条记录吗?")
.setPositiveButton("删除") { _, _ ->
deleteSelectedItems(selectedItems)
}
.setNegativeButton("取消", null)
.show()
getTopActivity().showConfirmDialog("确定要删除选中的 ${selectedItems.size} 条记录吗?") {
deleteSelectedItems(selectedItems)
}
}
/**
* 删除选中项
*/
private fun deleteSelectedItems(items: List<GjcUldUseBean>) {
val ids = items.mapNotNull { it.useId }.joinToString(",")
val requestData = items.toRequestBody()
launchLoadingCollect({ NetApply.api.deleteIntExpAssemble(ids) }) {
launchLoadingCollect({ NetApply.api.deleteIntExpAssemble(requestData) }) {
onSuccess = {
showToast("删除成功")
refresh()
@@ -204,7 +258,7 @@ class IntExpAssembleViewModel : BasePageViewModel() {
}
/**
* 回填重量按钮点击
* 回填重量按钮点击 - 跳转到修改组装重量页面
*/
fun onBackfillWeightClick() {
// 获取选中的数据
@@ -215,55 +269,17 @@ class IntExpAssembleViewModel : BasePageViewModel() {
.filter { it.isSelected }
if (selectedItems.isEmpty()) {
showToast("请选择要回填重量的记录")
showToast("请选择要修改重量的ULD")
return
}
// 显示输入对话框
showBackfillWeightDialog(selectedItems)
}
/**
* 显示回填重量对话框
*/
private fun showBackfillWeightDialog(items: List<GjcUldUseBean>) {
val activity = getTopActivity()
// 创建输入框
val input = EditText(activity).apply {
hint = "请输入重量(KG)"
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
setPadding(40, 40, 40, 40)
if (selectedItems.size > 1) {
showToast("每次只能选择一个ULD进行重量修改")
return
}
AlertDialog.Builder(activity)
.setTitle("回填重量")
.setMessage("共选中 ${items.size} 条记录")
.setView(input)
.setPositiveButton("确定") { _, _ ->
val weight = input.text.toString()
if (weight.verifyNullOrEmpty("请输入重量")) return@setPositiveButton
backfillWeight(items, weight)
}
.setNegativeButton("取消", null)
.show()
}
/**
* 回填重量API调用
*/
private fun backfillWeight(items: List<GjcUldUseBean>, weight: String) {
val ids = items.mapNotNull { it.useId }.joinToString(",")
val params = mapOf(
"ids" to ids,
"weight" to weight
).toRequestBody()
launchLoadingCollect({ NetApply.api.backfillIntExpAssembleWeight(params) }) {
onSuccess = {
showToast("回填重量成功")
refresh()
}
}
// 跳转到修改组装重量页面
val bean = selectedItems.first()
com.lukouguoji.gjc.activity.GjcAssembleWeightEditActivity.start(getTopActivity(), bean)
}
}

View File

@@ -20,6 +20,8 @@ import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/**
@@ -28,10 +30,9 @@ import kotlinx.coroutines.launch
class IntExpLoadViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val waybillNo = MutableLiveData("") // 运单号
val houseWaybillNo = MutableLiveData("") // 分单号
// ========== 统计信息 ==========
val totalCount = MutableLiveData("0") // 合计票数
@@ -82,13 +83,6 @@ class IntExpLoadViewModel : BasePageViewModel() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL)
}
/**
* 扫码分单号
*/
fun scanHouseWaybill() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.CODE)
}
/**
* 状态重置 (批量操作)
*/
@@ -195,8 +189,7 @@ class IntExpLoadViewModel : BasePageViewModel() {
val pageParams = GjcCheckInPage(
fdate = flightDate.value?.ifEmpty { null },
fno = flightNo.value?.ifEmpty { null },
no = waybillNo.value?.ifEmpty { null },
hno = houseWaybillNo.value?.ifEmpty { null },
wbNo = waybillNo.value?.ifEmpty { null },
pageNum = pageModel.page,
pageSize = pageModel.limit
)
@@ -208,8 +201,7 @@ class IntExpLoadViewModel : BasePageViewModel() {
val totalParams = GjcCheckInPage(
fdate = flightDate.value?.ifEmpty { null },
fno = flightNo.value?.ifEmpty { null },
no = waybillNo.value?.ifEmpty { null },
hno = houseWaybillNo.value?.ifEmpty { null }
wbNo = waybillNo.value?.ifEmpty { null }
).toRequestBody()
// 获取列表 (带Loading)

View File

@@ -31,18 +31,17 @@ class IntExpMoveViewModel : BasePageViewModel(), IOnItemClickListener {
// ========== 运单类型下拉数据 ==========
val awbTypeList = MutableLiveData<List<KeyValue>>().apply {
value = listOf(
KeyValue("", "全部"),
KeyValue("IOCO", "国际出港(经国内航班出境)"),
KeyValue("IOSO", "国际出港(国际航班出境)")
KeyValue("全部", ""),
KeyValue("转国内出港", "IOCO")
)
}
// ========== 移库状态下拉数据 ==========
val moveStateList = MutableLiveData<List<KeyValue>>().apply {
value = listOf(
KeyValue("", "全部"),
KeyValue("0", "未移交"),
KeyValue("1", "已移交")
KeyValue("全部", ""),
KeyValue("未移库", "0"),
KeyValue("已移库", "1")
)
}
@@ -146,14 +145,14 @@ class IntExpMoveViewModel : BasePageViewModel(), IOnItemClickListener {
* 获取列表数据
*/
override fun getData() {
// 构建筛选参数
val filterParams = mapOf(
"awbType" to awbType.value.noNull(),
"by1" to by1.value.noNull(),
"dest1" to dest1.value.noNull(),
"moveState" to moveState.value.noNull(),
"likeNo" to waybillNo.value.noNull()
)
// 构建筛选参数(只传递非空值)
val filterParams = mutableMapOf<String, Any>()
awbType.value?.takeIf { it.isNotEmpty() }?.let { filterParams["awbType"] = it }
by1.value?.takeIf { it.isNotEmpty() }?.let { filterParams["by1"] = it }
dest1.value?.takeIf { it.isNotEmpty() }?.let { filterParams["dest1"] = it }
moveState.value?.takeIf { it.isNotEmpty() }?.let { filterParams["moveState"] = it }
waybillNo.value?.takeIf { it.isNotEmpty() }?.let { filterParams["wbNo"] = it }
// 列表参数(含分页)
val listParams = (filterParams + mapOf(

View File

@@ -4,6 +4,7 @@ import android.app.Activity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.dialog.IntExpOutWaitingTransDialogModel
import com.lukouguoji.gjc.holder.IntExpOutHandoverViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcUldUseBean
@@ -17,6 +18,8 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/**
@@ -25,7 +28,7 @@ import kotlinx.coroutines.launch
class IntExpOutHandoverViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val fdest = MutableLiveData("") // 目的站
val uldNo = MutableLiveData("") // ULD编号
@@ -103,6 +106,59 @@ class IntExpOutHandoverViewModel : BasePageViewModel() {
}
}
/**
* 待配运按钮点击
*/
fun waitingTransClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcUldUseBean> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
showToast("请选择要待配运的ULD")
return
}
// 显示待配运弹框
showWaitingTransDialog(selectedItems)
}
/**
* 显示待配运弹框
*/
private fun showWaitingTransDialog(selectedItems: List<GjcUldUseBean>) {
val dialog = IntExpOutWaitingTransDialogModel { model ->
// 弹框确定回调
performWaitingTrans(selectedItems, model.locationId, model.locationName)
}
dialog.show()
}
/**
* 执行待配运操作
*/
private fun performWaitingTrans(
selectedItems: List<GjcUldUseBean>,
locationId: String,
locationName: String
) {
// 构建请求参数
val params = mapOf(
"location" to locationName,
"locationId" to locationId.toLongOrNull(),
"uldUseList" to selectedItems
).toRequestBody()
launchLoadingCollect({ NetApply.api.waitingTrans(params) }) {
onSuccess = {
showToast("待配运操作成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
}
}
/**
* 获取数据 (重写BasePageViewModel)
*/

View File

@@ -0,0 +1,342 @@
package com.lukouguoji.gjc.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.holder.IntExpStorageUseViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.commonAdapter
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.noNull
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.app.info.KeyValue
import kotlinx.coroutines.launch
import java.util.Date
/**
* 国际出港-仓库 ViewModel
*/
class IntExpStorageUseViewModel : BasePageViewModel() {
// ========== 筛选条件 ==========
val flightDate = MutableLiveData(Date().formatDate()) // 航班日期,默认今天
val flightNo = MutableLiveData("") // 航班号
val clearResult = MutableLiveData("") // 清仓综合结果
val clearResultList = MutableLiveData<List<KeyValue>>() // 清仓综合结果列表
val wbNo = MutableLiveData("") // 运单号
val location = MutableLiveData("") // 库位号
// ========== 统计信息 ==========
val totalWbNumber = MutableLiveData("0") // 总票数
val totalPc = MutableLiveData("0") // 总件数
val totalWeight = MutableLiveData("0") // 总重量
// ========== 全选状态 ==========
val isAllChecked = MutableLiveData(false)
// ========== 全局展开状态 ==========
/**
* 全局展开状态
* - true: 全部展开
* - false: 全部收起
*/
val isAllExpanded = MutableLiveData(false)
init {
// 初始化清仓综合结果列表(根据实际需求配置)
clearResultList.value = listOf(
KeyValue("全部", ""),
KeyValue("正常", "0"),
KeyValue("异常", "1")
)
// 监听全选状态,自动更新所有列表项
isAllChecked.observeForever { checked ->
val list =
pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return@observeForever
list.forEach { it.checked.set(checked) }
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
}
// ========== 适配器配置 ==========
val itemViewHolder = IntExpStorageUseViewHolder::class.java
val itemLayoutId = R.layout.item_int_exp_storage_use
/**
* 搜索按钮点击
*/
fun searchClick() {
refresh()
}
/**
* 全选按钮点击(联动勾选所有子列表项)
*/
fun checkAllClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val shouldCheckAll = !isAllChecked.value!!
// 联动勾选/取消主列表和子列表
list.forEach { maWb ->
maWb.checked.set(shouldCheckAll)
// 同时联动勾选/取消所有子列表项
maWb.storageUseList?.forEach { storageUse ->
storageUse.checked.set(shouldCheckAll)
}
}
isAllChecked.value = shouldCheckAll
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 切换全局展开/收起状态
*/
fun toggleAllExpand() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
// 切换全局状态
val shouldExpand = !isAllExpanded.value!!
isAllExpanded.value = shouldExpand
// 更新所有列表项的 showMore 状态
list.forEach { bean ->
// 只有当有子列表时才设置展开状态
if (!bean.storageUseList.isNullOrEmpty()) {
bean.showMore.set(shouldExpand)
}
}
// 刷新列表UI
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 扫码运单号
*/
fun scanWbNo() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL)
}
/**
* 清仓操作在Activity中调用会显示对话框
*/
fun clearStorage() {
// 由Activity显示对话框
}
/**
* 执行清仓操作
* @param clearNormal 清仓正常("0"或"1"
* @param maWbListForClear 包含选中子列表项的主列表数据
*/
fun performClear(clearNormal: String, maWbListForClear: List<GjcMaWb>) {
if (maWbListForClear.isEmpty()) {
showToast("请至少选择一个库位")
return
}
// 构建请求参数:完整的主子列表结构
val params = mapOf(
"clearNormal" to clearNormal,
"maWbList" to maWbListForClear
).toRequestBody()
launchLoadingCollect({ NetApply.api.clearIntExpStorage(params) }) {
onSuccess = {
showToast("清仓成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh() // 刷新列表
}
onFailed = { _, msg ->
showToast(msg.noNull("清仓失败"))
}
}
}
/**
* 修改库位
*/
fun modifyStorage() {
// 由Activity显示对话框
}
/**
* 执行修改库位操作
* @param locationName 新的库位名称 (后端的location字段)
* @param locationId 新的库位ID (后端的locationId字段)
* @param storageUse 选中的单个库位使用对象
*/
fun performModifyStorage(
locationName: String,
locationId: String,
storageUse: com.lukouguoji.module_base.bean.GjcStorageUse
) {
if (locationName.isEmpty() || locationId.isEmpty()) {
showToast("请选择库位")
return
}
// 创建更新后的库位对象(覆盖 location 和 locationId 字段)
val updatedStorage = storageUse.copy(
location = locationName,
locationId = locationId.toLongOrNull() ?: 0
)
// 直接使用更新后的对象构建请求参数
val params = updatedStorage.toRequestBody()
launchLoadingCollect({ NetApply.api.modifyIntExpStorage(params) }) {
onSuccess = {
showToast("修改库位成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh() // 刷新列表
}
onFailed = { _, msg ->
showToast(msg.noNull("修改库位失败"))
}
}
}
/**
* 出库操作在Activity中调用会显示二次确认对话框
*/
fun outStorage() {
// 由Activity显示二次确认对话框
}
/**
* 执行出库操作
* @param selectedStorageList 选中的子列表项(库位)
*/
fun performOutStorage(selectedStorageList: List<com.lukouguoji.module_base.bean.GjcStorageUse>) {
if (selectedStorageList.isEmpty()) {
showToast("请选择要出库的库位")
return
}
// 将选中的子列表项转换为RequestBody
val params = selectedStorageList.toRequestBody()
launchLoadingCollect({ NetApply.api.outIntExpStorage(params) }) {
onSuccess = {
showToast("出库成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh() // 刷新列表
}
onFailed = { _, msg ->
showToast(msg.noNull("出库失败"))
}
}
}
/**
* 入库操作
*/
fun inStorage() {
// 由Activity显示对话框
}
/**
* 执行入库操作
* @param locationName 库位名称 (后端的location字段)
* @param locationId 库位ID (后端的locationId字段)
* @param maWbListForInStorage 包含选中子列表项的主列表数据
*/
fun performInStorage(
locationName: String,
locationId: String,
maWbListForInStorage: List<GjcMaWb>
) {
if (maWbListForInStorage.isEmpty()) {
showToast("请至少选择一个单据")
return
}
if (locationName.isEmpty() || locationId.isEmpty()) {
showToast("请选择库位")
return
}
// 构建请求参数:库位名称 + 库位ID + 完整的主子列表结构
val params = mapOf(
"location" to locationName,
"locationId" to locationId.toLongOrNull(),
"maWbList" to maWbListForInStorage
).toRequestBody()
launchLoadingCollect({ NetApply.api.inIntExpStorage(params) }) {
onSuccess = {
showToast("入库成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh() // 刷新列表
}
onFailed = { _, msg ->
showToast(msg.noNull("入库失败"))
}
}
}
/**
* 获取数据 (重写BasePageViewModel)
*/
override fun getData() {
// 构建搜索条件
val filterParams = mapOf(
"fdate" to flightDate.value?.ifEmpty { null },
"fno" to flightNo.value?.ifEmpty { null },
"wbNo" to wbNo.value?.ifEmpty { null },
"location" to location.value?.ifEmpty { null }
)
// 列表参数 (含分页)
val listParams = (filterParams + mapOf(
"pageNum" to pageModel.page,
"pageSize" to pageModel.limit
)).toRequestBody()
// 统计参数 (无分页)
val totalParams = filterParams.toRequestBody()
// 获取列表 (带Loading)
launchLoadingCollect({ NetApply.api.getIntExpStorageUseList(listParams) }) {
onSuccess = { result ->
// 手动处理 PageInfo 数据
pageModel.handleDataList(result.list)
pageModel.haveMore.postValue((result.pages) > pageModel.page)
// 数据加载完成后,重置全局展开状态为收起
isAllExpanded.value = false
}
}
// 获取统计信息 (后台请求,不阻塞列表)
launchCollect({ NetApply.api.getIntExpStorageUseTotal(totalParams) }) {
onSuccess = { result ->
val data = result.data
totalWbNumber.value = (data?.wbNumber ?: 0).toString()
totalPc.value = (data?.totalPc ?: 0).toString()
totalWeight.value = (data?.totalWeight ?: 0.0).toString()
}
}
}
}

View File

@@ -4,10 +4,12 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.dialog.IntExpTallyDeleteDialogModel
import com.lukouguoji.gjc.dialog.IntExpTallyResetDialogModel
import com.lukouguoji.gjc.holder.IntExpTallyViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.GjcCheckInPage
import com.lukouguoji.module_base.bean.GjcDeclareParam
import com.lukouguoji.module_base.bean.GjcHaWb
import com.lukouguoji.module_base.bean.GjcMaWb
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
@@ -19,6 +21,8 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel
import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/**
@@ -27,7 +31,7 @@ import kotlinx.coroutines.launch
class IntExpTallyViewModel : BasePageViewModel() {
// ========== 搜索条件 ==========
val flightDate = MutableLiveData("") // 航班日期
val flightDate = MutableLiveData<String>(DateUtils.getCurrentTime().formatDate()) // 航班日期
val flightNo = MutableLiveData("") // 航班号
val waybillNo = MutableLiveData("") // 运单号
val houseWaybillNo = MutableLiveData("") // 分单号
@@ -40,6 +44,14 @@ class IntExpTallyViewModel : BasePageViewModel() {
// ========== 全选状态 ==========
val isAllChecked = MutableLiveData(false)
// ========== 全局展开状态 ==========
/**
* 全局展开状态
* - true: 全部展开
* - false: 全部收起
*/
val isAllExpanded = MutableLiveData(false)
init {
// 监听全选状态,自动更新所有列表项
isAllChecked.observeForever { checked ->
@@ -61,12 +73,12 @@ class IntExpTallyViewModel : BasePageViewModel() {
}
/**
* 全选按钮点击 (切换全选状态)
* 全选按钮点击 (切换主单的全选状态,分单选择独立)
*/
fun checkAllClick() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
// 切换全选状态
// 切换全选状态(只针对主单)
val shouldCheckAll = !isAllChecked.value!!
list.forEach { it.checked.set(shouldCheckAll) }
isAllChecked.value = shouldCheckAll
@@ -74,6 +86,28 @@ class IntExpTallyViewModel : BasePageViewModel() {
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 切换全局展开/收起状态
*/
fun toggleAllExpand() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
// 切换全局状态
val shouldExpand = !isAllExpanded.value!!
isAllExpanded.value = shouldExpand
// 更新所有列表项的 showMore 状态
list.forEach { bean ->
// 只有当有子列表时才设置展开状态
if (!bean.haWbList.isNullOrEmpty()) {
bean.showMore.set(shouldExpand)
}
}
// 刷新列表UI
pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
}
/**
* 扫码运单号
*/
@@ -93,39 +127,95 @@ class IntExpTallyViewModel : BasePageViewModel() {
*/
fun resetDeclare() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要重置的记录")
return
}
val requestData = selectedItems.toRequestBody()
// 创建并显示弹框
val dialog = IntExpTallyResetDialogModel { dialogModel ->
// 弹框确认后的回调
// 构建请求参数(区分主单和分单)
val params = mutableMapOf<String, Any?>()
if (selectedMaWbList.isNotEmpty()) {
params["maWbList"] = selectedMaWbList
}
if (selectedHaWbList.isNotEmpty()) {
params["haWbList"] = selectedHaWbList
}
launchLoadingCollect({ NetApply.api.resetTallyDeclare(requestData) }) {
onSuccess = {
showToast("状态重置成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
// 根据选择添加restStatus参数
// 选择"正常"时传递 "01",选择"未申报"时不传递此参数
if (dialogModel.resetStatusCode != null) {
params["restStatus"] = dialogModel.resetStatusCode
}
val requestData = params.toRequestBody()
launchLoadingCollect({ NetApply.api.resetTallyDeclare(requestData) }) {
onSuccess = {
showToast("状态重置成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
refresh()
}
refresh()
}
}
dialog.show()
}
/**
* 理货申报 (批量操作)
* 理货申报 (批量操作,主单和分单分开)
*/
fun declareTally() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要理货的记录")
return
}
val requestData = selectedItems.toRequestBody()
// 构建请求参数(区分主单和分单)
val params = mutableMapOf<String, Any?>()
if (selectedMaWbList.isNotEmpty()) {
params["maWbList"] = selectedMaWbList
}
if (selectedHaWbList.isNotEmpty()) {
params["haWbList"] = selectedHaWbList
}
val requestData = params.toRequestBody()
launchLoadingCollect({ NetApply.api.declareTally(requestData) }) {
onSuccess = {
@@ -143,9 +233,22 @@ class IntExpTallyViewModel : BasePageViewModel() {
*/
fun deleteTally() {
val list = pageModel.rv?.commonAdapter()?.items as? List<GjcMaWb> ?: return
val selectedItems = list.filter { it.isSelected }
if (selectedItems.isEmpty()) {
// 收集选中的主单
val selectedMaWbList = list.filter { it.isSelected }
// 收集选中的分单
val selectedHaWbList = mutableListOf<GjcHaWb>()
list.forEach { maWb ->
maWb.haWbList?.forEach { haWb ->
if (haWb.isSelected) {
selectedHaWbList.add(haWb)
}
}
}
// 检查是否有选中项(主单和分单分开判断,互不影响)
if (selectedMaWbList.isEmpty() && selectedHaWbList.isEmpty()) {
showToast("请选择要删除申报的记录")
return
}
@@ -157,12 +260,13 @@ class IntExpTallyViewModel : BasePageViewModel() {
// 创建并显示弹框
val dialog = IntExpTallyDeleteDialogModel(changeReasonList) { dialogModel ->
// 弹框确认后的回调
// 弹框确认后的回调(区分主单和分单)
val param = GjcDeclareParam(
dcode = dialogModel.changeReason.value,
dcontactsName = dialogModel.contactName.value,
dcontactsTel = dialogModel.contactPhone.value,
maWbList = selectedItems
maWbList = if (selectedMaWbList.isNotEmpty()) selectedMaWbList else null,
haWbList = if (selectedHaWbList.isNotEmpty()) selectedHaWbList else null
)
val requestData = param.toRequestBody()
@@ -192,7 +296,7 @@ class IntExpTallyViewModel : BasePageViewModel() {
val pageParams = GjcCheckInPage(
fdate = flightDate.value?.ifEmpty { null },
fno = flightNo.value?.ifEmpty { null },
no = waybillNo.value?.ifEmpty { null },
wbNo = waybillNo.value?.ifEmpty { null },
hno = houseWaybillNo.value?.ifEmpty { null },
pageNum = pageModel.page,
pageSize = pageModel.limit
@@ -205,13 +309,17 @@ class IntExpTallyViewModel : BasePageViewModel() {
val totalParams = GjcCheckInPage(
fdate = flightDate.value?.ifEmpty { null },
fno = flightNo.value?.ifEmpty { null },
no = waybillNo.value?.ifEmpty { null },
wbNo = waybillNo.value?.ifEmpty { null },
hno = houseWaybillNo.value?.ifEmpty { null }
).toRequestBody()
// 获取列表 (带Loading)
launchLoadingCollect({ NetApply.api.getIntExpTallyList(listParams) }) {
onSuccess = { pageModel.handleListBean(it) }
onSuccess = {
pageModel.handleListBean(it)
// 数据加载完成后,重置全局展开状态为收起
isAllExpanded.value = false
}
}
// 获取统计信息 (后台请求,不阻塞列表)

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show More