Compare commits

..

7 Commits

Author SHA1 Message Date
ea298ec322 feat: 开始计重新增托盘信息并自动计算运抵重量
国际出港开始计重页面新增托盘数量、托盘自重输入框(自重默认18可编辑),
运抵重量自动按“地磅称重 -(托盘数量 × 托盘自重)”计算;
分托计重与完成计重提交均携带 trayNumber、trayTotalWeight;
“托盘车号”“托盘车自重”标签改为“平板车”“平板车自重”。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 13:11:17 +08:00
b57a3cd133 fix: 计重明细托盘自重改取托盘重量并支持编辑联动
托盘自重原误取托盘车重(carWeight),改为托盘重量(trayTotalWeight)。
编辑模式下托盘自重可编辑,重量按编辑前后差值同步浮动,底部统计随之更新。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-02 11:59:13 +08:00
4e6e654558 feat: 国际出港运抵列表新增运抵状态筛选
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-02 11:34:46 +08:00
b65e61595b feat: 国际进港舱单编辑支持交接图片新增与删除
编辑/新增表单页增加交接图片区(选图、上传、长按删除),
保存时随舱单提交 pic/originalPic/picNumber 三字段,
编辑模式回填已有图片且不重复上传。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-02 11:23:43 +08:00
86f77353c0 feat: 国际出港板箱过磅通道号默认沿用上一次选择
称重提交成功后本地记住通道号,后续新增称重自动默认选中;
重置时不再清空通道号,编辑模式仍按记录自带通道号回填。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 10:44:31 +08:00
39b5cc3689 feat: 国际出港板箱过磅挂签支持多选批量打印
多选时逐张串行发送,张间节流留出出纸时间,写入失败即中止
并提示已打印张数与失败的 ULD 号;单选与详情页打印路径不变。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 10:44:25 +08:00
a1b55f656f feat: 航班管理列表新增增删改功能
- 列表页右上角新增加号入口,新增/编辑共用表单页(HbFlightEditActivity)
- 列表项支持侧滑编辑、删除,删除使用 ConfirmDialogModel 二次确认
- 接入 /flt/saveFlight、/flt/updateFlight、/flt/delete 接口
- 编辑回填增加枚举中文名转代码处理;FlightBean 地区类型兼容中文值显示

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-02 10:13:37 +08:00
24 changed files with 1229 additions and 62 deletions

View File

@@ -234,7 +234,10 @@ class FlightBean : ICheck {
return when (countryType) { return when (countryType) {
"0" -> "国内" "0" -> "国内"
"1" -> "国际" "1" -> "国际"
else -> "" "2" -> "地区"
"3" -> "混合"
// 部分接口直接返回中文名,原样显示
else -> countryType
} }
} }

View File

@@ -22,7 +22,9 @@ data class GjcCheckInRecord(
var weight: Double = 0.0, // 运抵重量 var weight: Double = 0.0, // 运抵重量
var volume: 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 = "" // 托盘 var carWeight: String = "", // 托盘
var trayNumber: Int = 0, // 托盘数量
var trayTotalWeight: Double = 0.0 // 托盘重量(托盘自重)
) : BaseObservable() { ) : BaseObservable() {
// 数据变化回调 // 数据变化回调
@@ -53,4 +55,28 @@ data class GjcCheckInRecord(
onDataChanged?.invoke() onDataChanged?.invoke()
} }
} }
// 托盘自重编辑过程中的原始输入串(保持回显与输入一致,避免光标跳位)
// 不作为构造参数data class copy 备份/恢复时自动丢弃,回退到 trayTotalWeight 显示
// @Transient仅界面内部状态不参与 Gson 序列化提交
@Transient
private var trayTotalWeightInput: String? = null
// 托盘自重的字符串表示(用于双向绑定)
// 业务规则:重量包含托盘自重,托盘自重编辑后,重量同步增减相同差值
@get:Bindable
var trayTotalWeightStr: String
get() = trayTotalWeightInput
?: if (trayTotalWeight == 0.0) "" else trayTotalWeight.toString()
set(value) {
trayTotalWeightInput = value
val newTrayWeight = value.toDoubleOrNull() ?: 0.0
if (trayTotalWeight != newTrayWeight) {
weight = Math.round((weight + newTrayWeight - trayTotalWeight) * 100) / 100.0
trayTotalWeight = newTrayWeight
notifyPropertyChanged(BR.trayTotalWeightStr)
notifyPropertyChanged(BR.weightStr)
onDataChanged?.invoke()
}
}
} }

View File

@@ -57,6 +57,8 @@ data class GjcMaWb(
var arriveWeight: Double? = null, // 运抵重量 var arriveWeight: Double? = null, // 运抵重量
var arriveVolume: Double? = null, // 运抵体积 var arriveVolume: Double? = null, // 运抵体积
var arriveFlag: String? = null, // 运抵状态0正常运抵1提前运抵 var arriveFlag: String? = null, // 运抵状态0正常运抵1提前运抵
var trayNumber: Int? = null, // 托盘数量
var trayTotalWeight: Double? = null, // 托盘重量(托盘数量 × 托盘自重)
// ==================== 特码与收货人 ==================== // ==================== 特码与收货人 ====================
var spCode: String? = null, // 特码 var spCode: String? = null, // 特码

View File

@@ -1391,6 +1391,24 @@ interface Api {
@POST("flt/listFlightDest") @POST("flt/listFlightDest")
suspend fun listFlightDest(@Body data: RequestBody): BaseResultBean<List<String>> suspend fun listFlightDest(@Body data: RequestBody): BaseResultBean<List<String>>
/**
* 航班管理-新增航班
*/
@POST("flt/saveFlight")
suspend fun saveFlight(@Body data: RequestBody): BaseResultBean<String>
/**
* 航班管理-修改航班
*/
@POST("flt/updateFlight")
suspend fun updateFlight(@Body data: RequestBody): BaseResultBean<String>
/**
* 航班管理-删除航班(请求体为航班对象数组)
*/
@POST("flt/delete")
suspend fun deleteFlightList(@Body data: RequestBody): BaseResultBean<String>
/** /**
* 新增或修航司屏蔽 * 新增或修航司屏蔽
*/ */

View File

@@ -27,7 +27,12 @@ import com.lukouguoji.module_base.ktx.permission
import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.model.LoadingModel import com.lukouguoji.module_base.model.LoadingModel
import dev.DevUtils import dev.DevUtils
import dev.utils.app.HandlerUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import okio.internal.commonToUtf8String import okio.internal.commonToUtf8String
import java.io.IOException
object PrinterUtils { object PrinterUtils {
@@ -315,6 +320,50 @@ object PrinterUtils {
* 打印国际出港板箱过磅挂签2列布局支持合并单元格 * 打印国际出港板箱过磅挂签2列布局支持合并单元格
*/ */
fun printGjcBoxWeighing(bean: GjcUldUseBean) { fun printGjcBoxWeighing(bean: GjcUldUseBean) {
printMergedGrid(buildGjcBoxWeighingRows(bean))
}
/**
* 批量打印国际出港板箱过磅挂签
* 逐张串行发送,张间节流,任一张发送失败即中止并提示已完成张数
*/
suspend fun printGjcBoxWeighingBatch(beans: List<GjcUldUseBean>) = withContext(Dispatchers.IO) {
var printed = 0
loading.showLoading()
try {
for ((index, bean) in beans.withIndex()) {
// 连接断开(含上一张写入异常后 SDK 置为断开)则不再继续发送
if (portManager?.connectStatus != true) break
HandlerUtils.postRunnable {
loading.setMessage("正在打印 ${index + 1}/${beans.size}")
}
val bytes = buildMergedGridBytes(buildGjcBoxWeighingRows(bean))
val ok = try {
portManager?.writeDataImmediately(bytes) == true
} catch (e: IOException) {
showLog("批量打印第 ${index + 1} 张写入异常 : ${e.message}")
false
}
if (!ok) break
printed++
// 张间留出出纸时间,避免连续灌入导致打印机缓冲溢出
if (index < beans.lastIndex) delay(800)
}
} finally {
loading.dismissLoading()
}
if (printed == beans.size) {
showToast("${beans.size} 张挂签打印完成")
} else {
val failBean = beans[printed]
showToast("已打印 $printed 张,第 ${printed + 1}ULD ${failBean.uld})发送失败,请检查打印机后重试")
}
}
/**
* 组装板箱过磅挂签的表格行数据
*/
private fun buildGjcBoxWeighingRows(bean: GjcUldUseBean): ArrayList<GridRow> {
val rows = arrayListOf<GridRow>() val rows = arrayListOf<GridRow>()
// 第1行日期 | 航班号 // 第1行日期 | 航班号
@@ -345,7 +394,7 @@ object PrinterUtils {
merged = CellData("备注:", "NOTES:", bean.remark) merged = CellData("备注:", "NOTES:", bean.remark)
)) ))
printMergedGrid(rows) return rows
} }
private fun printCommonGrid(rowItem: ArrayList<List<String>>) { private fun printCommonGrid(rowItem: ArrayList<List<String>>) {
@@ -456,6 +505,13 @@ object PrinterUtils {
* 打印支持合并单元格的表格 * 打印支持合并单元格的表格
*/ */
private fun printMergedGrid(rows: ArrayList<GridRow>) { private fun printMergedGrid(rows: ArrayList<GridRow>) {
portManager?.writeDataImmediately(buildMergedGridBytes(rows))
}
/**
* 生成支持合并单元格的表格标签字节流(自包含一张完整标签的 TSPL 指令)
*/
private fun buildMergedGridBytes(rows: ArrayList<GridRow>): ByteArray {
val gridStartX = 30 val gridStartX = 30
val titleTopMargin = 50 // 标题顶部间距 val titleTopMargin = 50 // 标题顶部间距
val titleFontSize = 130 // 标题字体大小 val titleFontSize = 130 // 标题字体大小
@@ -536,7 +592,7 @@ object PrinterUtils {
addPrint(1) addPrint(1)
}.bytes }.bytes
portManager?.writeDataImmediately(bytes) return bytes
} }
/** /**

View File

@@ -20,6 +20,7 @@ import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.model.BluetoothDialogModel import com.lukouguoji.module_base.model.BluetoothDialogModel
import com.lukouguoji.module_base.model.ScanModel import com.lukouguoji.module_base.model.ScanModel
import com.lukouguoji.module_base.util.Common import com.lukouguoji.module_base.util.Common
@@ -34,6 +35,11 @@ import java.util.Date
*/ */
class GjcBoxWeighingAddViewModel : BaseViewModel() { class GjcBoxWeighingAddViewModel : BaseViewModel() {
companion object {
// 记录上一次称重完成时使用的通道号,后续称重默认选中
private const val KEY_LAST_PASSAGEWAY_ID = "gjc_box_weighing_last_passageway_id"
}
// 数据Bean // 数据Bean
val dataBean = MutableLiveData(GjcUldUseBean()) val dataBean = MutableLiveData(GjcUldUseBean())
@@ -117,7 +123,9 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
loadPassagewayList(editBean.passagewayId) loadPassagewayList(editBean.passagewayId)
loadPlCloseList(editBean.plClose) loadPlCloseList(editBean.plClose)
} else { } else {
loadPassagewayList(null) // 新增模式:默认选中上一次称重使用的通道号
val lastPassagewayId = SharedPreferenceUtil.getString(KEY_LAST_PASSAGEWAY_ID)
loadPassagewayList(lastPassagewayId.ifEmpty { null })
loadPlCloseList(null) loadPlCloseList(null)
} }
} }
@@ -338,7 +346,7 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
netWeight.value = "0" netWeight.value = "0"
cargoWeight.value = "0" cargoWeight.value = "0"
consumeWeight.value = "0" consumeWeight.value = "0"
channel.value = "" // 通道号不清空:保留当前选择,与 Spinner 显示保持一致,后续称重沿用上一次的通道号
printTag.value = false printTag.value = false
// 重置独立字段 // 重置独立字段
@@ -441,6 +449,11 @@ class GjcBoxWeighingAddViewModel : BaseViewModel() {
if (result.verifySuccess()) { if (result.verifySuccess()) {
showToast("完成复磅") showToast("完成复磅")
// 记住本次使用的通道号,后续称重默认选中
if (bean.passagewayId.isNotEmpty()) {
SharedPreferenceUtil.addString(KEY_LAST_PASSAGEWAY_ID, bean.passagewayId)
}
// 如果勾选了打印挂签,则执行打印 // 如果勾选了打印挂签,则执行打印
if (printTag.value == true) { if (printTag.value == true) {
executePrint() executePrint()

View File

@@ -3,6 +3,7 @@ package com.lukouguoji.gjc.viewModel
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.gjc.R import com.lukouguoji.gjc.R
import com.lukouguoji.gjc.holder.GjcBoxWeighingViewHolder import com.lukouguoji.gjc.holder.GjcBoxWeighingViewHolder
import com.lukouguoji.module_base.base.BasePageViewModel import com.lukouguoji.module_base.base.BasePageViewModel
@@ -20,6 +21,7 @@ import com.lukouguoji.module_base.util.PrinterUtils
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch
/** /**
* 国际出港板箱过磅 ViewModel * 国际出港板箱过磅 ViewModel
@@ -96,16 +98,19 @@ class GjcBoxWeighingViewModel : BasePageViewModel() {
return return
} }
// 校验多选 if (selectedItems.size == 1) {
if (selectedItems.size > 1) { // 单条:保持原有打印路径
showToast("只能选择一条记录进行打印") val bean = selectedItems.first()
return BluetoothDialogModel().showCallBack {
} PrinterUtils.printGjcBoxWeighing(bean)
}
// 执行打印 } else {
val bean = selectedItems.first() // 多条:逐张串行批量打印
BluetoothDialogModel().showCallBack { BluetoothDialogModel().showCallBack {
PrinterUtils.printGjcBoxWeighing(bean) viewModelScope.launch {
PrinterUtils.printGjcBoxWeighingBatch(selectedItems)
}
}
} }
} }

View File

@@ -66,6 +66,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
val pageRemark = MutableLiveData("") // 备注 val pageRemark = MutableLiveData("") // 备注
// 托盘信息(运抵重量 = 地磅称重 - 托盘数量 × 托盘自重)
val trayNumber = MutableLiveData("") // 托盘数量
val trayWeight = MutableLiveData("18") // 托盘自重默认18可编辑
// 下拉选择数据源 // 下拉选择数据源
val channelList = MutableLiveData<List<KeyValue>>() // 通道号列表 val channelList = MutableLiveData<List<KeyValue>>() // 通道号列表
val agentList = MutableLiveData<List<KeyValue>>() // 代理人列表 val agentList = MutableLiveData<List<KeyValue>>() // 代理人列表
@@ -82,11 +86,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
// 获取传入的运单ID // 获取传入的运单ID
maWbId = intent.getLongExtra(Constant.Key.MAWB_ID, 0) maWbId = intent.getLongExtra(Constant.Key.MAWB_ID, 0)
// 监听地磅称重输入,单向同步到运抵重量 // 监听地磅称重、托盘数量、托盘自重输入,自动计算运抵重量
diBangWeight.observe(activity as LifecycleOwner) { weight -> diBangWeight.observe(activity as LifecycleOwner) { recalculateArriveWeight() }
// 同步到运抵重量(单向,不做反向同步) trayNumber.observe(activity as LifecycleOwner) { recalculateArriveWeight() }
arriveWeight.value = weight ?: "0" trayWeight.observe(activity as LifecycleOwner) { recalculateArriveWeight() }
}
// 监听运抵重量变化,自动计算运抵体积 // 监听运抵重量变化,自动计算运抵体积
arriveWeight.observe(activity as LifecycleOwner) { weightStr -> arriveWeight.observe(activity as LifecycleOwner) { weightStr ->
@@ -113,6 +116,32 @@ class GjcWeighingStartViewModel : BaseViewModel() {
} }
} }
/**
* 自动计算运抵重量:地磅称重 - (托盘数量 × 托盘自重)
*/
private fun recalculateArriveWeight() {
val gross = diBangWeight.value?.toDoubleOrNull() ?: 0.0
val trayTotal = getTrayTotalWeight()
val net = gross - trayTotal
arriveWeight.value = if (net > 0) formatWeight(net) else "0"
}
/**
* 托盘总重量 = 托盘数量 × 托盘自重
*/
private fun getTrayTotalWeight(): Double {
val num = trayNumber.value?.toIntOrNull() ?: 0
val weight = trayWeight.value?.toDoubleOrNull() ?: 0.0
return num * weight
}
/**
* 重量格式化最多保留两位小数去掉多余的0
*/
private fun formatWeight(value: Double): String {
return String.format("%.2f", value).trimEnd('0').trimEnd('.')
}
/** /**
* 加载通道号列表 (国际出港专用) * 加载通道号列表 (国际出港专用)
*/ */
@@ -391,6 +420,10 @@ class GjcWeighingStartViewModel : BaseViewModel() {
passageWay = this@GjcWeighingStartViewModel.channelList.value?.firstOrNull{ it.value == channel.value }?.key ?: "" passageWay = this@GjcWeighingStartViewModel.channelList.value?.firstOrNull{ it.value == channel.value }?.key ?: ""
passageWayId = this@GjcWeighingStartViewModel.channel.value passageWayId = this@GjcWeighingStartViewModel.channel.value
// 托盘信息
trayNumber = this@GjcWeighingStartViewModel.trayNumber.value?.toIntOrNull()
trayTotalWeight = getTrayTotalWeight()
remark = pageRemark.value remark = pageRemark.value
} }
@@ -417,6 +450,8 @@ class GjcWeighingStartViewModel : BaseViewModel() {
arrivePc.value = "" arrivePc.value = ""
arriveWeight.value = "" arriveWeight.value = ""
arriveVolume.value = "" arriveVolume.value = ""
trayNumber.value = ""
trayWeight.value = "18"
pageRemark.value = "" pageRemark.value = ""
@@ -499,6 +534,8 @@ class GjcWeighingStartViewModel : BaseViewModel() {
"goodsCn" to bean.goodsCn, "goodsCn" to bean.goodsCn,
"businessType" to bean.businessType, "businessType" to bean.businessType,
"carId" to bean.carId, "carId" to bean.carId,
"trayNumber" to trayNumber.value?.toIntOrNull(),
"trayTotalWeight" to getTrayTotalWeight(),
"remark" to pageRemark.value, "remark" to pageRemark.value,
"checkIn" to "1", // 收运状态设置为已收运 "checkIn" to "1", // 收运状态设置为已收运
"passageWayId" to channel.value, // 通道号参数 "passageWayId" to channel.value, // 通道号参数

View File

@@ -20,6 +20,7 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.model.ScanModel import com.lukouguoji.module_base.model.ScanModel
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils import dev.utils.common.DateUtils
import com.lukouguoji.module_base.ktx.formatDate import com.lukouguoji.module_base.ktx.formatDate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -34,6 +35,16 @@ class IntExpArriveViewModel : BasePageViewModel() {
val flightNo = MutableLiveData("") // 航班号 val flightNo = MutableLiveData("") // 航班号
val waybillNo = MutableLiveData("") // 运单号 val waybillNo = MutableLiveData("") // 运单号
val hno = MutableLiveData("") // 分单号 val hno = MutableLiveData("") // 分单号
val arrivalStatus = MutableLiveData("") // 运抵状态0未运抵1已运抵
// 运抵状态下拉列表
val arrivalStatusList = MutableLiveData(
listOf(
KeyValue("全部", ""),
KeyValue("已运抵", "1"),
KeyValue("未运抵", "0")
)
)
// ========== 统计信息 ========== // ========== 统计信息 ==========
val totalCount = MutableLiveData("0") // 合计票数 val totalCount = MutableLiveData("0") // 合计票数
@@ -301,7 +312,8 @@ class IntExpArriveViewModel : BasePageViewModel() {
"fdate" to flightDate.value?.ifEmpty { null }, "fdate" to flightDate.value?.ifEmpty { null },
"fno" to flightNo.value?.ifEmpty { null }, "fno" to flightNo.value?.ifEmpty { null },
"wbNo" to waybillNo.value?.ifEmpty { null }, "wbNo" to waybillNo.value?.ifEmpty { null },
"hno" to hno.value?.ifEmpty { null } "hno" to hno.value?.ifEmpty { null },
"arrivalStatus" to arrivalStatus.value?.ifEmpty { null }
) )
// 列表参数 (含分页) // 列表参数 (含分页)

View File

@@ -313,7 +313,45 @@
</LinearLayout> </LinearLayout>
<!-- 第6行运抵件数、运抵重量、运抵体积 --> <!-- 第6行托盘数量、托盘自重(用于自动计算运抵重量) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入托盘数量"}'
inputType="@{android.text.InputType.TYPE_CLASS_NUMBER}"
title='@{"托盘数量"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.trayNumber}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入托盘自重"}'
inputType="@{android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL}"
title='@{"托盘自重"}'
titleLength="@{5}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.trayWeight}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第7行运抵件数、运抵重量、运抵体积 -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -356,7 +394,7 @@
</LinearLayout> </LinearLayout>
<!--7行:托盘车号、托盘车自重、业务类型 --> <!--8行:平板车、平板车自重、业务类型 -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -364,9 +402,9 @@
android:orientation="horizontal"> android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入托盘车号"}' hint='@{"请输入平板车号"}'
setRefreshCallBack="@{viewModel::onCarIdInputComplete}" setRefreshCallBack="@{viewModel::onCarIdInputComplete}"
title='@{"托盘车号"}' title='@{"平 板 车"}'
titleLength="@{5}" titleLength="@{5}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@={viewModel.maWbBean.carId}' value='@={viewModel.maWbBean.carId}'
@@ -376,7 +414,7 @@
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
enable="@{false}" enable="@{false}"
title='@{"托盘车自重"}' title='@{"平板车自重"}'
titleLength="@{5}" titleLength="@{5}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@{viewModel.carWeight}' value='@{viewModel.carWeight}'
@@ -399,7 +437,7 @@
</LinearLayout> </LinearLayout>
<!--8行:备注 --> <!--9行:备注 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入备注"}' hint='@{"请输入备注"}'
inputHeight="@{80}" inputHeight="@{80}"

View File

@@ -79,6 +79,16 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />
<!-- 运抵状态 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
hint='@{"请选择运抵状态"}'
list="@{viewModel.arrivalStatusList}"
type="@{SearchLayoutType.SPINNER}"
value="@={viewModel.arrivalStatus}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<!-- 搜索按钮 --> <!-- 搜索按钮 -->
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
@@ -151,7 +161,7 @@
<ImageView <ImageView
android:id="@+id/checkIcon" android:id="@+id/checkIcon"
setIVCheckAllImage="@{viewModel.isAllChecked}" setIVCheckAllImage="@{viewModel.isAllChecked}"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"

View File

@@ -67,17 +67,19 @@
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value="@={record.weightStr}" /> value="@={record.weightStr}" />
<!-- 托盘自重字段(只读 --> <!-- 托盘自重字段(取托盘重量 trayTotalWeight可编辑 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_weight="1" android:layout_weight="1"
enable="@{false}" enable="@{isEditMode}"
hint="@{`托盘自重`}"
inputType="@{android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL}"
title="@{`托盘自重`}" title="@{`托盘自重`}"
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value="@{record.carWeight}" /> value="@={record.trayTotalWeightStr}" />
<!-- 计重时间字段(只读) --> <!-- 计重时间字段(只读) -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew

View File

@@ -10,6 +10,7 @@ import com.lukouguoji.gjj.viewModel.GjjManifestAddViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.DetailsPageType import com.lukouguoji.module_base.common.DetailsPageType
import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.ktx.noNull import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
@@ -31,6 +32,10 @@ class GjjManifestAddActivity :
binding.actualWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL binding.actualWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
binding.billingWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL binding.billingWeightInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
// 交接图片列表
viewModel.rv = binding.rvPic
binding.rvPic.addOnItemClickListener(viewModel)
viewModel.initOnCreated(intent) viewModel.initOnCreated(intent)
// 动态设置标题(必须在 initOnCreated 之后pageType 已从 Intent 解析) // 动态设置标题(必须在 initOnCreated 之后pageType 已从 Intent 解析)

View File

@@ -5,26 +5,45 @@ import android.view.View
import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.RecyclerView
import com.lukouguoji.gjj.R
import com.lukouguoji.module_base.base.BaseViewModel import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.common.DetailsPageType import com.lukouguoji.module_base.common.DetailsPageType
import com.lukouguoji.module_base.http.net.NetApply import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.ImageSelectNewViewHolder
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.finish import com.lukouguoji.module_base.ktx.finish
import com.lukouguoji.module_base.ktx.formatDate import com.lukouguoji.module_base.ktx.formatDate
import com.lukouguoji.module_base.ktx.launchCollect import com.lukouguoji.module_base.ktx.launchCollect
import com.lukouguoji.module_base.ktx.launchLoadingCollect import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.loadMore
import com.lukouguoji.module_base.ktx.noNull import com.lukouguoji.module_base.ktx.noNull
import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.ktx.toRequestBody
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty import com.lukouguoji.module_base.ktx.verifyNullOrEmpty
import com.lukouguoji.module_base.util.DictUtils import com.lukouguoji.module_base.util.DictUtils
import com.lukouguoji.module_base.util.MediaUtil
import com.lukouguoji.module_base.util.UploadUtil
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils import dev.utils.common.DateUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class GjjManifestAddViewModel : BaseViewModel() { class GjjManifestAddViewModel : BaseViewModel(), IOnItemClickListener {
// 页面类型必须用LiveData // 页面类型必须用LiveData
val pageType = MutableLiveData(DetailsPageType.Add) val pageType = MutableLiveData(DetailsPageType.Add)
@@ -167,6 +186,11 @@ class GjjManifestAddViewModel : BaseViewModel() {
val waybillTypeList = MutableLiveData<List<KeyValue>>() val waybillTypeList = MutableLiveData<List<KeyValue>>()
val waybillType = MutableLiveData("") val waybillType = MutableLiveData("")
// ========== 交接图片 ==========
val itemLayoutId = R.layout.item_image_select_new
val itemViewHolder = ImageSelectNewViewHolder::class.java
var rv: RecyclerView? = null
/** /**
* 初始化从Intent获取参数 * 初始化从Intent获取参数
*/ */
@@ -205,6 +229,9 @@ class GjjManifestAddViewModel : BaseViewModel() {
} }
} }
// 交接图片:末尾追加空白占位项作为添加按钮
rv?.post { rv?.commonAdapter()?.addItem(FileBean()) }
// 加载下拉列表(在编辑数据加载之后,以便使用 checkedValue 将选中项置顶) // 加载下拉列表(在编辑数据加载之后,以便使用 checkedValue 将选中项置顶)
loadDictLists() loadDictLists()
} }
@@ -302,6 +329,21 @@ class GjjManifestAddViewModel : BaseViewModel() {
businessType.value = manifest.businessType businessType.value = manifest.businessType
goodsType.value = manifest.cargoType goodsType.value = manifest.cargoType
waybillType.value = manifest.awbType waybillType.value = manifest.awbType
// 回填交接图片path 取原图 URL 确保预览清晰url 取缩略图用于提交
val picList = manifest.pic.noNull().split(",").filter { it.isNotEmpty() }
val originalList = manifest.originalPic.noNull().split(",").filter { it.isNotEmpty() }
if (picList.isNotEmpty()) {
val images = picList.mapIndexed { index, picUrl ->
val originalUrl = originalList.getOrElse(index) { picUrl }
FileBean(
path = MediaUtil.fillUrl(originalUrl),
url = picUrl,
originalPic = originalUrl
)
}
rv?.loadMore(images)
}
} }
/** /**
@@ -411,30 +453,82 @@ class GjjManifestAddViewModel : BaseViewModel() {
paramsMap["prefix"] = prefix paramsMap["prefix"] = prefix
} }
val params = paramsMap.toRequestBody() // 先上传本地新增的交接图片,全部完成后再组装三字段提交表单
var uploadFailed = false
launchLoadingCollect({ (rv?.commonAdapter()?.items ?: emptyList())
if (isModify) { .asFlow()
NetApply.api.gjjManifestUpdate(params) .map { it as FileBean }
} else { .filter { it.path.isNotEmpty() && it.url.isEmpty() }
NetApply.api.gjjManifestInsert(params) .onEach {
val data = UploadUtil.upload(it.path).data
// UploadUtil 返回newName=原图(较大)zipFileName=缩略图(较小)
// FileBean.url 用作缩略图标识FileBean.originalPic 用作原图标识
it.url = data?.zipFileName ?: ""
it.originalPic = data?.newName ?: ""
} }
}) { .flowOn(Dispatchers.IO)
onSuccess = { .onStart { showLoading() }
if (it.verifySuccess()) { .catch {
val successMsg = if (isModify) "修改成功" else "保存成功" uploadFailed = true
showToast(successMsg) showToast(it.message.noNull("上传图片失败"))
dismissLoading()
}
.onCompletion {
if (uploadFailed) return@onCompletion
// 发送刷新事件 // 交接图片三字段pic=缩略图、originalPic=原图、picNumber=数量
viewModelScope.launch { val picBeans = (rv?.commonAdapter()?.items ?: emptyList())
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh") .filterIsInstance<FileBean>()
.filter { it.path.isNotEmpty() }
paramsMap["picNumber"] = picBeans.size.toString()
paramsMap["pic"] = picBeans.joinToString(",") { MediaUtil.removeUrl(it.url) }
paramsMap["originalPic"] =
picBeans.joinToString(",") { MediaUtil.removeUrl(it.originalPic) }
val params = paramsMap.toRequestBody()
launchLoadingCollect({
if (isModify) {
NetApply.api.gjjManifestUpdate(params)
} else {
NetApply.api.gjjManifestInsert(params)
} }
}) {
onSuccess = {
if (it.verifySuccess()) {
val successMsg = if (isModify) "修改成功" else "保存成功"
showToast(successMsg)
view.context.finish() // 发送刷新事件
} else { viewModelScope.launch {
showToast(it.msg.noNull("保存失败")) FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
view.context.finish()
} else {
showToast(it.msg.noNull("保存失败"))
}
}
} }
} }
.launchIn(viewModelScope)
}
/**
* 交接图片列表点击回调
* 长按图片R.id.rl切换删除按钮显隐点击删除按钮R.id.iv_delete移除该图片
*/
override fun onItemClick(position: Int, type: Int) {
val adapter = rv?.commonAdapter() ?: return
val bean = adapter.getItem(position) as? FileBean ?: return
when (type) {
R.id.rl -> {
bean.canDelete.set(!bean.canDelete.get())
}
R.id.iv_delete -> {
adapter.removeItem(position)
}
else -> {}
} }
} }

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
@@ -356,7 +357,37 @@
android:layout_weight="0.605" /> android:layout_weight="0.605" />
</LinearLayout> </LinearLayout>
<!-- -->
<!-- 第8行交接图片 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="15dp"
android:orientation="horizontal">
<TextView
completeSpace="@{5}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="交接图片"
android:textColor="@color/text_gray" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pic"
itemLayoutId="@{viewModel.itemLayoutId}"
viewHolder="@{viewModel.itemViewHolder}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="9"
tools:listitem="@layout/item_image_select_new" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,53 @@
package com.lukouguoji.hangban.page.edit
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.lukouguoji.hangban.R
import com.lukouguoji.hangban.databinding.ActivityHbFlightEditBinding
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.DetailsPageType
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
class HbFlightEditActivity :
BaseBindingActivity<ActivityHbFlightEditBinding, HbFlightEditViewModel>() {
override fun layoutId() = R.layout.activity_hb_flight_edit
override fun viewModelClass() = HbFlightEditViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
binding.viewModel = viewModel
binding.flightNoInput.et.setUpperCaseAlphanumericFilter()
binding.fdepInput.et.setUpperCaseAlphanumericFilter()
binding.fdestInput.et.setUpperCaseAlphanumericFilter()
binding.registrationInput.et.setUpperCaseAlphanumericFilter()
viewModel.initOnCreated(intent)
// 动态设置标题(必须在 initOnCreated 之后pageType 已从 Intent 解析)
val title = when (viewModel.pageType.value) {
DetailsPageType.Modify -> "编辑航班"
else -> "新增航班"
}
setBackArrow(title)
}
companion object {
/**
* fid 为空表示新增,非空表示编辑
*/
@JvmStatic
fun start(context: Context, fid: String = "") {
val pageType =
if (fid.isEmpty()) DetailsPageType.Add.name else DetailsPageType.Modify.name
context.startActivity(
Intent(context, HbFlightEditActivity::class.java)
.putExtra(Constant.Key.PAGE_TYPE, pageType)
.putExtra(Constant.Key.ID, fid)
)
}
}
}

View File

@@ -0,0 +1,280 @@
package com.lukouguoji.hangban.page.edit
import android.content.Intent
import android.view.View
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.lukouguoji.module_base.base.BaseViewModel
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.common.DetailsPageType
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.ktx.formatDate
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.ktx.verifyNullOrEmpty
import com.lukouguoji.module_base.util.DictUtils
import dev.DevUtils
import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils
import kotlinx.coroutines.launch
class HbFlightEditViewModel : BaseViewModel() {
val pageType = MutableLiveData(DetailsPageType.Add)
// 航班主键(编辑模式)
private var fid = ""
///////////////////////////////////////////////////////////////////////////
// 表单字段
///////////////////////////////////////////////////////////////////////////
val flightDate = MutableLiveData("")
val flightNo = MutableLiveData("")
val aircraftCode = MutableLiveData("")
val fdep = MutableLiveData("")
val jtz = MutableLiveData("")
val fdest = MutableLiveData("")
val countryType = MutableLiveData("")
val serviceType = MutableLiveData("")
val status = MutableLiveData("")
val scheduledTackOff = MutableLiveData("")
val estimatedTakeOff = MutableLiveData("")
val actualTakeOff = MutableLiveData("")
val scheduledArrival = MutableLiveData("")
val estimatedArrival = MutableLiveData("")
val actualArrival = MutableLiveData("")
val standId = MutableLiveData("")
val registration = MutableLiveData("")
val prefix = MutableLiveData("")
val flightStatus = MutableLiveData("")
val cmFlag = MutableLiveData("")
val delayFreeText = MutableLiveData("")
///////////////////////////////////////////////////////////////////////////
// 下拉列表
///////////////////////////////////////////////////////////////////////////
val countryTypeList = MutableLiveData<List<KeyValue>>(emptyList())
val serviceTypeList = MutableLiveData<List<KeyValue>>(emptyList())
val statusList = MutableLiveData<List<KeyValue>>(emptyList())
val flightStatusList = MutableLiveData<List<KeyValue>>(emptyList())
val cmFlagList = MutableLiveData<List<KeyValue>>(emptyList())
///////////////////////////////////////////////////////////////////////////
// 选项定义(服务端入库/过滤用 code详情接口返回中文名回填时需反查
///////////////////////////////////////////////////////////////////////////
private val countryTypeOptions = listOf(
KeyValue("国内", "0"),
KeyValue("国际", "1"),
KeyValue("地区", "2"),
KeyValue("混合", "3"),
)
private val serviceTypeOptions = listOf(
KeyValue("客机", "0"),
KeyValue("货机", "1"),
KeyValue("卡车", "2"),
)
private val statusOptions = listOf(
KeyValue("出港", "0"),
KeyValue("进港", "1"),
)
private val flightStatusOptions = listOf(
KeyValue("正常", "0"),
KeyValue("登机", "1"),
KeyValue("登机结束", "2"),
KeyValue("起飞", "3"),
KeyValue("到达", "4"),
KeyValue("取消", "5"),
KeyValue("备降", "6"),
KeyValue("删除发布", "7"),
KeyValue("延误", "8"),
KeyValue("未知", "9"),
)
private val cmFlagOptions = listOf(
KeyValue("有货邮", "0"),
KeyValue("无货邮", "1"),
)
///////////////////////////////////////////////////////////////////////////
// 方法区
///////////////////////////////////////////////////////////////////////////
fun initOnCreated(intent: Intent) {
pageType.value =
if (intent.getStringExtra(Constant.Key.PAGE_TYPE) == DetailsPageType.Modify.name)
DetailsPageType.Modify else DetailsPageType.Add
fid = intent.getStringExtra(Constant.Key.ID) ?: ""
if (pageType.value == DetailsPageType.Modify && fid.isNotEmpty()) {
loadDetails()
} else {
flightDate.value = DateUtils.getCurrentTime().formatDate()
loadDictLists()
}
}
/**
* 编辑模式:加载航班详情
*/
private fun loadDetails() {
launchLoadingCollect({
NetApply.api.getFlightDetails(fid)
}) {
onSuccess = {
it.data?.let { bean ->
flightDate.value = bean.fdate
flightNo.value = bean.fno
aircraftCode.value = bean.aircraftCode
fdep.value = bean.fdep
jtz.value = bean.jtz
fdest.value = bean.fdest
countryType.value = toCode(countryTypeOptions, bean.countryType)
serviceType.value = toCode(serviceTypeOptions, bean.serviceType)
status.value = toCode(statusOptions, bean.status)
scheduledTackOff.value = bean.scheduledTackOff
estimatedTakeOff.value = bean.estimatedTakeOff
actualTakeOff.value = bean.actualTakeOff
scheduledArrival.value = bean.scheduledArrival
estimatedArrival.value = bean.estimatedArrival
actualArrival.value = bean.actualArrival
standId.value = bean.standId
registration.value = bean.registration
prefix.value = bean.prefix
flightStatus.value = toCode(flightStatusOptions, bean.flightStatus)
cmFlag.value = toCode(cmFlagOptions, bean.cmFlag)
delayFreeText.value = bean.delayFreeText
}
// 字典加载必须在编辑数据回填之后,保证 checkedValue 可用
loadDictLists()
}
}
}
/**
* 加载下拉列表(编辑模式传 checkedValue 置顶选中项)
*/
private fun loadDictLists() {
val isModify = pageType.value == DetailsPageType.Modify
DictUtils.getCountryTypeList(
addAll = false,
checkedValue = if (isModify) countryType.value else null
) {
countryTypeList.postValue(if (isModify) it else listOf(KeyValue("", "")) + it)
}
serviceTypeList.value =
buildStaticList(serviceTypeOptions, if (isModify) serviceType.value else null)
statusList.value =
buildStaticList(statusOptions, if (isModify) status.value else null)
flightStatusList.value =
buildStaticList(flightStatusOptions, if (isModify) flightStatus.value else null)
cmFlagList.value =
buildStaticList(cmFlagOptions, if (isModify) cmFlag.value else null)
}
/**
* 值转 code详情接口可能返回中文名如“国际”“客机”统一转为 code 用于回填与提交
*/
private fun toCode(options: List<KeyValue>, raw: String?): String {
if (raw.isNullOrEmpty()) return ""
return options.firstOrNull { it.value == raw }?.value
?: options.firstOrNull { it.key == raw }?.value
?: raw
}
/**
* 静态下拉列表:新增模式首位置空项;编辑模式将选中项置顶
*/
private fun buildStaticList(list: List<KeyValue>, checkedValue: String?): List<KeyValue> {
if (checkedValue.isNullOrEmpty()) {
return listOf(KeyValue("", "")) + list
}
return list.filter { it.value == checkedValue } + list.filter { it.value != checkedValue }
}
/**
* 保存 点击
*/
fun onSaveClick(view: View) {
if (flightDate.value.verifyNullOrEmpty("请选择航班日期")) return
if (flightNo.value.verifyNullOrEmpty("请输入航班号")) return
if (fdep.value.verifyNullOrEmpty("请输入始发港")) return
if (fdest.value.verifyNullOrEmpty("请输入目的港")) return
val isModify = pageType.value == DetailsPageType.Modify
val dep = fdep.value?.trim().noNull()
val dest = fdest.value?.trim().noNull()
val params = mutableMapOf<String, Any?>(
"fdate" to flightDate.value,
"fno" to flightNo.value?.trim(),
"aircraftCode" to aircraftCode.value?.trim(),
"fdep" to dep,
"jtz" to jtz.value?.trim(),
"fdest" to dest,
"range" to "$dep-$dest",
"countryType" to countryType.value,
"countryName" to countryTypeList.value
?.firstOrNull { it.value == countryType.value }?.key,
"serviceType" to serviceType.value,
"status" to status.value,
"scheduledTackOff" to scheduledTackOff.value,
"estimatedTakeOff" to estimatedTakeOff.value,
"actualTakeOff" to actualTakeOff.value,
"scheduledArrival" to scheduledArrival.value,
"estimatedArrival" to estimatedArrival.value,
"actualArrival" to actualArrival.value,
"standId" to standId.value?.trim(),
"registration" to registration.value?.trim(),
"prefix" to prefix.value?.trim(),
"flightStatus" to flightStatus.value,
"cmFlag" to cmFlag.value,
"delayFreeText" to delayFreeText.value?.trim(),
)
if (isModify) {
params["fid"] = fid.toLongOrNull() ?: fid
}
launchLoadingCollect({
if (isModify) {
NetApply.api.updateFlight(params.toRequestBody())
} else {
NetApply.api.saveFlight(params.toRequestBody())
}
}) {
onSuccess = {
if (it.verifySuccess()) {
showToast(if (isModify) "修改成功" else "新增成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
DevUtils.getTopActivity().finish()
} else {
showToast(it.msg.noNull("保存失败"))
}
}
}
}
/**
* 取消 点击
*/
fun onCancelClick(view: View) {
DevUtils.getTopActivity().finish()
}
}

View File

@@ -5,6 +5,9 @@ import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.hangban.R import com.lukouguoji.hangban.R
import com.lukouguoji.hangban.databinding.ActivityHbQueryListBinding import com.lukouguoji.hangban.databinding.ActivityHbQueryListBinding
import com.lukouguoji.module_base.base.BaseBindingActivity import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.ConstantEvent
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.observe
import com.lukouguoji.module_base.ktx.addOnItemClickListener import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.ktx.getLifecycleOwner import com.lukouguoji.module_base.ktx.getLifecycleOwner
import com.lukouguoji.module_base.router.ARouterConstants import com.lukouguoji.module_base.router.ARouterConstants
@@ -26,6 +29,11 @@ class HbQueryListActivity :
binding.rv.addOnItemClickListener(viewModel) binding.rv.addOnItemClickListener(viewModel)
// 新增、编辑保存后刷新列表
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
viewModel.refresh() viewModel.refresh()
} }

View File

@@ -28,6 +28,8 @@ class HbQueryListViewHolder(view: View) :
} }
notifyItemClick(position, binding.ll) notifyItemClick(position, binding.ll)
notifyItemClick(position, binding.tvModify)
notifyItemClick(position, binding.tvDelete)
} }
private fun setTextColorRecursive(view: View, color: Int) { private fun setTextColorRecursive(view: View, color: Int) {

View File

@@ -3,6 +3,7 @@ package com.lukouguoji.hangban.page.query.list
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.alibaba.android.arouter.launcher.ARouter import com.alibaba.android.arouter.launcher.ARouter
import com.lukouguoji.hangban.R import com.lukouguoji.hangban.R
import com.lukouguoji.hangban.page.edit.HbFlightEditActivity
import com.lukouguoji.module_base.base.BasePageViewModel import com.lukouguoji.module_base.base.BasePageViewModel
import com.lukouguoji.module_base.bean.FlightBean import com.lukouguoji.module_base.bean.FlightBean
import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.Constant
@@ -10,9 +11,13 @@ import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.commonAdapter import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ktx.formatDate import com.lukouguoji.module_base.ktx.formatDate
import com.lukouguoji.module_base.ktx.launchLoadingCollect 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.ktx.toRequestBody
import com.lukouguoji.module_base.model.ConfirmDialogModel
import com.lukouguoji.module_base.router.ARouterConstants import com.lukouguoji.module_base.router.ARouterConstants
import com.lukouguoji.module_base.util.DictUtils import com.lukouguoji.module_base.util.DictUtils
import dev.DevUtils
import dev.utils.app.info.KeyValue import dev.utils.app.info.KeyValue
import dev.utils.common.DateUtils import dev.utils.common.DateUtils
@@ -80,11 +85,55 @@ class HbQueryListViewModel : BasePageViewModel() {
return "${d ?: ""}-${a ?: ""}" return "${d ?: ""}-${a ?: ""}"
} }
/**
* 新增 点击
*/
fun addClick() {
HbFlightEditActivity.start(DevUtils.getTopActivity())
}
override fun onItemClick(position: Int, type: Int) { override fun onItemClick(position: Int, type: Int) {
val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as FlightBean val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as FlightBean
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_FLIGHT_QUERY_DETAILS) when (type) {
.withString(Constant.Key.ID, bean.fid) R.id.tv_modify -> {
.navigation() HbFlightEditActivity.start(DevUtils.getTopActivity(), bean.fid)
}
R.id.tv_delete -> {
ConfirmDialogModel(
message = "是否确认删除航班【${bean.fno}】?",
) {
onDelete(bean)
}.show(DevUtils.getTopActivity())
}
else -> {
ARouter.getInstance().build(ARouterConstants.ACTIVITY_URL_FLIGHT_QUERY_DETAILS)
.withString(Constant.Key.ID, bean.fid)
.navigation()
}
}
}
/**
* 删除航班
*/
private fun onDelete(bean: FlightBean) {
launchLoadingCollect({
NetApply.api.deleteFlightList(
listOf(mapOf("fid" to bean.fid)).toRequestBody()
)
}) {
onSuccess = {
if (it.verifySuccess()) {
// 服务端删除接口的 msg 为“修改成功”,此处固定提示删除成功
showToast("删除成功")
refresh()
} else {
showToast(it.msg.noNull("删除失败"))
}
}
}
} }
} }

View File

@@ -18,6 +18,11 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape" android:screenOrientation="userLandscape"
android:exported="false" /> android:exported="false" />
<activity
android:name=".page.edit.HbFlightEditActivity"
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="userLandscape"
android:exported="false" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"

View File

@@ -0,0 +1,377 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType" />
<variable
name="viewModel"
type="com.lukouguoji.hangban.page.edit.HbFlightEditViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_f2"
android:orientation="vertical">
<include layout="@layout/title_tool_bar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_white_radius_8"
android:orientation="vertical"
android:padding="8dp">
<!-- 第1行航班日期、航班号、机型 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择航班日期"}'
required="@{true}"
title='@{"航班日期"}'
titleLength="@{4}"
type="@{DataLayoutType.DATE}"
value='@={viewModel.flightDate}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:id="@+id/flightNoInput"
hint='@{"请输入航班号"}'
required="@{true}"
title='@{"航班号"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.flightNo}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入机型"}'
title='@{"机型"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.aircraftCode}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第2行始发港、经停港、目的港 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:id="@+id/fdepInput"
hint='@{"请输入始发港"}'
required="@{true}"
title='@{"始发港"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.fdep}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"多个用英文逗号分隔"}'
title='@{"经停港"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.jtz}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:id="@+id/fdestInput"
hint='@{"请输入目的港"}'
required="@{true}"
title='@{"目的港"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.fdest}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第3行地区类型、服务类型、进出港 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择地区类型"}'
list="@{viewModel.countryTypeList}"
title='@{"地区类型"}'
titleLength="@{4}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.countryType}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择服务类型"}'
list="@{viewModel.serviceTypeList}"
title='@{"服务类型"}'
titleLength="@{4}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.serviceType}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择进出港"}'
list="@{viewModel.statusList}"
title='@{"进出港"}'
titleLength="@{4}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.status}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第4行计划起飞、预计起飞、实际起飞 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择计划起飞时间"}'
title='@{"计划起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.scheduledTackOff}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择预计起飞时间"}'
title='@{"预计起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.estimatedTakeOff}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择实际起飞时间"}'
title='@{"实际起飞"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.actualTakeOff}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第5行计划降落、预计降落、实际降落 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择计划降落时间"}'
title='@{"计划降落"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.scheduledArrival}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择预计降落时间"}'
title='@{"预计降落"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.estimatedArrival}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择实际降落时间"}'
title='@{"实际降落"}'
titleLength="@{4}"
type="@{DataLayoutType.DATETIME}"
value='@={viewModel.actualArrival}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第6行机位、机号、运单前缀 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入机位"}'
title='@{"机位"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.standId}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:id="@+id/registrationInput"
hint='@{"请输入机号"}'
title='@{"机号"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.registration}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入运单前缀"}'
title='@{"运单前缀"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.prefix}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
<!-- 第7行航班状态、货邮状态、延误原因 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择航班状态"}'
list="@{viewModel.flightStatusList}"
title='@{"航班状态"}'
titleLength="@{4}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.flightStatus}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请选择货邮状态"}'
list="@{viewModel.cmFlagList}"
title='@{"货邮状态"}'
titleLength="@{4}"
type="@{DataLayoutType.SPINNER}"
value='@={viewModel.cmFlag}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
hint='@{"请输入延误原因"}'
title='@{"延误原因"}'
titleLength="@{4}"
type="@{DataLayoutType.INPUT}"
value='@={viewModel.delayFreeText}'
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
<!-- 底部操作栏 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="15dp">
<TextView
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:background="@drawable/bg_primary_radius_4"
android:gravity="center"
android:onClick="@{viewModel::onCancelClick}"
android:text="取消"
android:textColor="@color/white"
android:textSize="18sp" />
<TextView
android:layout_width="150dp"
android:layout_height="50dp"
android:background="@drawable/bg_primary_radius_4"
android:gravity="center"
android:onClick="@{viewModel::onSaveClick}"
android:text="保存"
android:textColor="@color/white"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</layout>

View File

@@ -104,6 +104,14 @@
android:padding="2dp" android:padding="2dp"
android:src="@drawable/img_search" /> android:src="@drawable/img_search" />
<!-- 新增按钮 -->
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
android:onClick="@{()-> viewModel.addClick()}"
android:src="@drawable/img_add" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -10,16 +10,26 @@
type="com.lukouguoji.module_base.bean.FlightBean" /> type="com.lukouguoji.module_base.bean.FlightBean" />
</data> </data>
<androidx.appcompat.widget.LinearLayoutCompat <!-- 外层容器承载间距,使侧滑按钮紧贴卡片(参考 item_int_imp_pick_up_record -->
android:id="@+id/ll" <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp" android:layout_marginHorizontal="15dp"
android:layout_marginVertical="5dp" android:layout_marginVertical="5dp"
android:background="@drawable/bg_item" android:orientation="vertical">
android:gravity="center_vertical"
android:orientation="horizontal" <com.mcxtzhang.swipemenulib.SwipeMenuLayout
android:padding="10dp"> android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_item"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<!-- 左侧飞机图标 --> <!-- 左侧飞机图标 -->
<ImageView <ImageView
@@ -317,5 +327,28 @@
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:src="@drawable/img_pda_right" /> android:src="@drawable/img_pda_right" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
<!-- 侧滑操作按钮 -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_modify"
style="@style/tv_item_action"
android:background="@color/colorPrimary"
android:text="编辑" />
<TextView
android:id="@+id/tv_delete"
style="@style/tv_item_action"
android:background="#df253c"
android:text="删除" />
</androidx.appcompat.widget.LinearLayoutCompat>
</com.mcxtzhang.swipemenulib.SwipeMenuLayout>
</LinearLayout>
</layout> </layout>