diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 09b069f..3a4df93 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -119,13 +119,21 @@
"mcp__apifox__read_project_oas_ruugy8",
"mcp__apifox__read_project_oas_ref_resources_ldmedm",
"mcp__apifox__read_project_oas_ldmedm",
- "mcp__apifox__refresh_project_oas_ldmedm"
+ "mcp__apifox__refresh_project_oas_ldmedm",
+ "Skill(update-config)",
+ "mcp__apidoc__get_project_overview",
+ "mcp__apidoc__search_endpoints",
+ "mcp__apidoc__list_endpoints",
+ "mcp__apidoc__get_endpoint_detail"
],
"deny": [],
"ask": []
},
+ "enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [
"空港集团 - API 文档",
- "apifox"
+ "apifox",
+ "aerologic-app",
+ "apidoc"
]
}
diff --git a/.mcp.json b/.mcp.json
index 9df3a46..de11a57 100644
--- a/.mcp.json
+++ b/.mcp.json
@@ -10,6 +10,13 @@
"env": {
"APIFOX_ACCESS_TOKEN": "APS-S2aVVwqasbdByzPLgSqryRC8BB0ZFqhQ"
}
+ },
+ "apidoc": {
+ "type": "http",
+ "url": "http://localhost:3001/mcp/c6a17835-6389-446c-8334-004b998835e5",
+ "headers": {
+ "Authorization": "Bearer afk_Snhv1JVACdbd91_NS699bb-2MN237Jww"
+ }
}
}
}
diff --git a/CLAUDE.md b/CLAUDE.md
index 8106b8a..6bb9ef7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -958,6 +958,93 @@ companion object {
---
+## 图片上传与展示规范
+
+### 图片上传三字段规范
+
+上传图片后提交表单时,**必须同时传 `pic`、`originalPic`、`picNumber` 三个字段**,缺一不可。
+
+**`UploadUtil.upload()` 返回值**:
+- `data?.newName` — 缩略图/压缩图文件名
+- `data?.zipFileName` — 原图文件名
+
+**提交时字段映射**(参考事故签证 `AccidentVisaDetailsViewModel`):
+
+```kotlin
+// FileBean 字段含义:
+// - FileBean.url = newName(缩略图文件名)
+// - FileBean.originalPic = zipFileName(原图文件名)
+
+// 上传新图片
+val data = UploadUtil.upload(fileBean.path).data
+fileBean.url = data?.newName ?: ""
+fileBean.originalPic = data?.zipFileName ?: ""
+
+// 提交时设置三个字段
+bean.picNumber = list.size.toString()
+bean.pic = list.joinToString(",") { MediaUtil.removeUrl(it.url) } // 缩略图
+bean.originalPic = list.joinToString(",") { MediaUtil.removeUrl(it.originalPic) } // 原图
+```
+
+**常见错误**:
+- ❌ 只传 `images` 或 `originalPic` 单个字段 — 接口不认或数据不完整
+- ❌ 只取 `newName` 不取 `zipFileName` — 丢失原图路径
+- ❌ 用 `fileBean.path.startsWith("http")` 判断已上传 — 应该用 `fileBean.url.isNotEmpty()`
+
+### 编辑页加载已有图片
+
+从详情接口获取图片后,需要同时解析 `pic`(缩略图)和 `originalPic`(原图),构建完整的 `FileBean`:
+
+```kotlin
+val picList = bean.pic.split(",").filter { it.isNotEmpty() }
+val originalPicList = bean.originalPic.split(",").filter { it.isNotEmpty() }
+val images = picList.mapIndexed { index, picUrl ->
+ val originalFile = originalPicList.getOrElse(index) { picUrl }
+ FileBean(
+ path = MediaUtil.fillUrl(picUrl), // 完整URL,用于显示
+ url = picUrl, // 相对路径,提交时用
+ originalPic = MediaUtil.fillUrl(originalFile) // 原图完整URL
+ )
+}.toMutableList()
+```
+
+### 图片加载必须带 Authorization Header
+
+`/file/getImg/` 接口需要鉴权,Glide 默认不带 token,直接用 `loadImage` BindingAdapter 会 **403 Forbidden**。
+
+**正确做法** — 在 ViewHolder 中使用 `GlideUrl` + `LazyHeaders`:
+
+```kotlin
+// 缩略图加载(ViewHolder 中)
+val glideUrl = GlideUrl(
+ bean.path,
+ LazyHeaders.Builder()
+ .addHeader("Authorization", SharedPreferenceUtil.getString(Constant.Share.token))
+ .build()
+)
+Glide.with(itemView.context).load(glideUrl).into(binding.ivThumbnail)
+```
+
+**同时必须去掉 XML 布局中的 `loadImage` 属性**,否则 BindingAdapter 会触发不带 token 的请求覆盖手动加载:
+
+```xml
+
+
+
+
+
+```
+
+**大图预览同理** — `PreviewImageViewHolder` 也需要用 `GlideUrl` 带 token 加载网络图片。
+
+**参考文件**:
+- 缩略图加载: `module_gjj/.../GjjManifestPicViewHolder.kt`
+- 大图预览: `module_base/.../PreviewImageViewHolder.kt`
+- 图片上传提交: `app/.../AccidentVisaDetailsViewModel.kt`
+- 带 token 的 Glide 加载: `module_mit/.../PictureAdapter.kt`
+
+---
+
## 开发原则
- 资源引用必须存在 — 创建布局前确认 drawable/color/string 资源真实存在或主动创建
diff --git a/module_base/src/main/java/com/lukouguoji/module_base/bean/GjjManifest.kt b/module_base/src/main/java/com/lukouguoji/module_base/bean/GjjManifest.kt
index 2121731..6ea086f 100644
--- a/module_base/src/main/java/com/lukouguoji/module_base/bean/GjjManifest.kt
+++ b/module_base/src/main/java/com/lukouguoji/module_base/bean/GjjManifest.kt
@@ -56,7 +56,10 @@ data class GjjManifest(
var subCode: String = "", // 子代码
var unNumber: String = "", // 危险品编号
var activeId: Long = 0, // 活动ID
- var locationTally: String = "" // 理货库位号
+ var locationTally: String = "", // 理货库位号
+ var pic: String = "", // 交接图片缩略图路径
+ var originalPic: String = "", // 交接图片原图地址
+ var picNumber: String = "" // 交接图片数量
) : Serializable {
// 分单列表
var haWbList: List? = null
diff --git a/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt b/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt
index 775ddf4..d95df45 100644
--- a/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt
+++ b/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt
@@ -1367,6 +1367,12 @@ interface Api {
@POST("flt/queryFlight")
suspend fun queryFlightByDateAndNo(@Body data: RequestBody): BaseResultBean
+ /**
+ * 根据航班日期、航班号、地区类型、进出港查询航班(返回列表)
+ */
+ @POST("flt/searchFlightList")
+ suspend fun searchFlightList(@Body data: RequestBody): BaseResultBean>
+
/**
* 获取航班目的站、经停站
*/
diff --git a/module_base/src/main/java/com/lukouguoji/module_base/ui/page/preview/PreviewImageViewHolder.kt b/module_base/src/main/java/com/lukouguoji/module_base/ui/page/preview/PreviewImageViewHolder.kt
index 1456c1d..c039697 100644
--- a/module_base/src/main/java/com/lukouguoji/module_base/ui/page/preview/PreviewImageViewHolder.kt
+++ b/module_base/src/main/java/com/lukouguoji/module_base/ui/page/preview/PreviewImageViewHolder.kt
@@ -1,9 +1,14 @@
package com.lukouguoji.module_base.ui.page.preview
import android.view.View
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.model.GlideUrl
+import com.bumptech.glide.load.model.LazyHeaders
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.FileBean
+import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.databinding.ItemPreviewImageBinding
+import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
/**
* @author:孟凡华
@@ -14,8 +19,26 @@ class PreviewImageViewHolder(view: View) :
BaseViewHolder(view) {
override fun onBind(item: Any?, position: Int) {
- binding.bean = getItemBean(item)
+ val bean = getItemBean(item) ?: return
+ binding.bean = bean
+ // 加载图片
+ val path = bean.path
+ if (path.isNotEmpty()) {
+ if (path.startsWith("http")) {
+ // 网络图片带 Authorization header
+ val glideUrl = GlideUrl(
+ path,
+ LazyHeaders.Builder()
+ .addHeader("Authorization", SharedPreferenceUtil.getString(Constant.Share.token))
+ .build()
+ )
+ Glide.with(itemView.context).load(glideUrl).into(binding.photoView)
+ } else {
+ // 本地图片直接加载
+ Glide.with(itemView.context).load(path).into(binding.photoView)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/module_base/src/main/res/layout/item_preview_image.xml b/module_base/src/main/res/layout/item_preview_image.xml
index 136d256..d53482e 100644
--- a/module_base/src/main/res/layout/item_preview_image.xml
+++ b/module_base/src/main/res/layout/item_preview_image.xml
@@ -13,7 +13,7 @@
android:layout_height="match_parent">
diff --git a/module_gjj/src/main/java/com/lukouguoji/gjj/activity/GjjManifestListActivity.kt b/module_gjj/src/main/java/com/lukouguoji/gjj/activity/GjjManifestListActivity.kt
index 7b5c313..4087a17 100644
--- a/module_gjj/src/main/java/com/lukouguoji/gjj/activity/GjjManifestListActivity.kt
+++ b/module_gjj/src/main/java/com/lukouguoji/gjj/activity/GjjManifestListActivity.kt
@@ -13,6 +13,7 @@ import com.lukouguoji.module_base.base.CommonAdapter
import com.lukouguoji.module_base.ktx.addOnItemClickListener
import com.lukouguoji.module_base.router.ARouterConstants
+@Deprecated("旧的实现")
@Route(path = ARouterConstants.ACTIVITY_URL_GJJ_MANIFEST)
class GjjManifestListActivity :
BaseBindingActivity() {
diff --git a/module_gjj/src/main/java/com/lukouguoji/gjj/activity/IntImpManifestDetailsActivity.kt b/module_gjj/src/main/java/com/lukouguoji/gjj/activity/IntImpManifestDetailsActivity.kt
index 14f3fd7..bb4e7e8 100644
--- a/module_gjj/src/main/java/com/lukouguoji/gjj/activity/IntImpManifestDetailsActivity.kt
+++ b/module_gjj/src/main/java/com/lukouguoji/gjj/activity/IntImpManifestDetailsActivity.kt
@@ -3,14 +3,20 @@ package com.lukouguoji.gjj.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpManifestDetailsBinding
+import com.lukouguoji.gjj.holder.GjjManifestPicViewHolder
import com.lukouguoji.gjj.viewModel.IntImpManifestDetailsViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
+import com.lukouguoji.module_base.base.CommonAdapter
+import com.lukouguoji.module_base.bean.FileBean
import com.lukouguoji.module_base.bean.GjjManifest
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.router.ARouterConstants
+import com.lukouguoji.module_base.util.MediaUtil
+import com.lukouguoji.module_base.ktx.noNull
/**
* 国际进港舱单详情
@@ -26,6 +32,29 @@ class IntImpManifestDetailsActivity :
setBackArrow("进港舱单详情")
binding.viewModel = viewModel
viewModel.initOnCreated(intent)
+
+ // 交接图片
+ val picAdapter = CommonAdapter(
+ this,
+ R.layout.item_gjj_manifest_pic,
+ GjjManifestPicViewHolder::class.java
+ )
+ binding.rvPic.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
+ binding.rvPic.adapter = picAdapter
+
+ viewModel.dataBean.observe(this) { bean ->
+ val picList = bean.pic.noNull().split(",").filter { it.isNotEmpty() }
+ val originalPicList = bean.originalPic.noNull().split(",").filter { it.isNotEmpty() }
+ val list = picList.mapIndexed { index, picUrl ->
+ val originalFile = originalPicList.getOrElse(index) { picUrl }
+ FileBean(
+ path = MediaUtil.fillUrl(picUrl),
+ url = picUrl,
+ originalPic = MediaUtil.fillUrl(originalFile)
+ )
+ }
+ picAdapter.refresh(list)
+ }
}
companion object {
diff --git a/module_gjj/src/main/java/com/lukouguoji/gjj/holder/GjjManifestPicViewHolder.kt b/module_gjj/src/main/java/com/lukouguoji/gjj/holder/GjjManifestPicViewHolder.kt
index 81c505d..d17c659 100644
--- a/module_gjj/src/main/java/com/lukouguoji/gjj/holder/GjjManifestPicViewHolder.kt
+++ b/module_gjj/src/main/java/com/lukouguoji/gjj/holder/GjjManifestPicViewHolder.kt
@@ -1,9 +1,14 @@
package com.lukouguoji.gjj.holder
import android.view.View
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.model.GlideUrl
+import com.bumptech.glide.load.model.LazyHeaders
import com.lukouguoji.gjj.databinding.ItemGjjManifestPicBinding
import com.lukouguoji.module_base.base.BaseViewHolder
import com.lukouguoji.module_base.bean.FileBean
+import com.lukouguoji.module_base.common.Constant
+import com.lukouguoji.module_base.db.perference.SharedPreferenceUtil
import com.lukouguoji.module_base.ktx.commonAdapter
import com.lukouguoji.module_base.ui.page.preview.PreviewActivity
@@ -14,6 +19,17 @@ class GjjManifestPicViewHolder(view: View) :
val bean = getItemBean(item)!!
binding.bean = bean
+ // 带 Authorization header 加载图片
+ if (bean.path.isNotEmpty()) {
+ val glideUrl = GlideUrl(
+ bean.path,
+ LazyHeaders.Builder()
+ .addHeader("Authorization", SharedPreferenceUtil.getString(Constant.Share.token))
+ .build()
+ )
+ Glide.with(itemView.context).load(glideUrl).into(binding.ivThumbnail)
+ }
+
binding.ivThumbnail.setOnClickListener {
val items = getRecyclerView()?.commonAdapter()?.items
?.filterIsInstance() ?: listOf(bean)
diff --git a/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/IntImpManifestViewModel.kt b/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/IntImpManifestViewModel.kt
index a5f4ae0..c4060cb 100644
--- a/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/IntImpManifestViewModel.kt
+++ b/module_gjj/src/main/java/com/lukouguoji/gjj/viewModel/IntImpManifestViewModel.kt
@@ -64,30 +64,40 @@ class IntImpManifestViewModel : BasePageViewModel() {
lastQueriedFlight = key
launchCollect({
- NetApply.api.getGjFlightBean(
+ NetApply.api.searchFlightList(
mapOf(
"fdate" to fdate,
"fno" to fno,
- "ieFlag" to "I",
+ "status" to "1",
).toRequestBody()
)
}) {
onSuccess = {
- if (it.verifySuccess() && it.data != null) {
- val flight = it.data!!
- fid = flight.fid.noNull()
- fdep = flight.fdep.noNull()
- fdest.value = flight.fdest.noNull()
+ if (it.verifySuccess() && !it.data.isNullOrEmpty()) {
+ val dataList = it.data!!
+ if (dataList.size > 1) {
+ showToast("存在多个航班记录,请核实")
+ fid = ""
+ fdep = ""
+ fdest.value = ""
+ sendAddressList.value = emptyList()
+ sendAddress.value = ""
+ } else {
+ val flight = dataList[0]
+ fid = flight.fid.noNull()
+ fdep = flight.fdep.noNull()
+ fdest.value = flight.fdest.noNull()
- // 构建始发站下拉列表:fdep + jtz(经停港)
- val list = mutableListOf(
- KeyValue(flight.fdep.noNull(), flight.fdep.noNull()),
- )
- if (!flight.jtz.isNullOrEmpty()) {
- list.add(KeyValue(flight.jtz.noNull(), flight.jtz.noNull()))
+ // 构建始发站下拉列表:fdep + jtz(经停港)
+ val list = mutableListOf(
+ KeyValue(flight.fdep.noNull(), flight.fdep.noNull()),
+ )
+ if (!flight.jtz.isNullOrEmpty()) {
+ list.add(KeyValue(flight.jtz.noNull(), flight.jtz.noNull()))
+ }
+ sendAddressList.value = list
+ sendAddress.value = flight.fdep.noNull()
}
- sendAddressList.value = list
- sendAddress.value = flight.fdep.noNull()
} else {
fid = ""
fdep = ""
diff --git a/module_gjj/src/main/res/layout/activity_int_imp_manifest_details.xml b/module_gjj/src/main/res/layout/activity_int_imp_manifest_details.xml
index 0a28dfd..73fb613 100644
--- a/module_gjj/src/main/res/layout/activity_int_imp_manifest_details.xml
+++ b/module_gjj/src/main/res/layout/activity_int_imp_manifest_details.xml
@@ -263,25 +263,14 @@
android:textColor="@color/text_gray"
android:textSize="14sp" />
-
-
-
-
-
-
+ android:orientation="horizontal" />
diff --git a/module_gjj/src/main/res/layout/item_gjj_manifest_pic.xml b/module_gjj/src/main/res/layout/item_gjj_manifest_pic.xml
index 0f2ee44..cf9f354 100644
--- a/module_gjj/src/main/res/layout/item_gjj_manifest_pic.xml
+++ b/module_gjj/src/main/res/layout/item_gjj_manifest_pic.xml
@@ -11,7 +11,6 @@
- FileBean(path = url)
+ // 处理图片列表:pic=缩略图(newName),originalPic=原图(zipFileName)
+ val picList = bean.pic.split(",").filter { it.isNotEmpty() }
+ val originalPicList = bean.originalPic.split(",").filter { it.isNotEmpty() }
+ val images = picList.mapIndexed { index, picUrl ->
+ val fb = FileBean(
+ path = MediaUtil.fillUrl(picUrl),
+ url = picUrl
+ )
+ if (index < originalPicList.size) {
+ fb.originalPic = MediaUtil.fillUrl(originalPicList[index])
+ }
+ fb
}.toMutableList()
// 如果是编辑模式,添加一个空的FileBean用于添加新图片
@@ -108,38 +118,24 @@ class GnjYiKuEditViewModel : BaseViewModel(), IOnItemClickListener {
launchLoadingCollect({
// 1. 上传图片
- val uploadedUrls = mutableListOf()
images.forEach { fileBean ->
- // 判断是否为已上传的图片(在线URL)
- if (fileBean.path.startsWith("http")) {
- uploadedUrls.add(fileBean.path)
+ if (fileBean.url.isNotEmpty()) {
+ // 已上传的图片,保持原有的 url 和 originalPic
} else {
- // 本地图片需要上传
- val result = UploadUtil.upload(fileBean.path)
- if (result.verifySuccess()) {
- uploadedUrls.add(result.data?.newName ?: "")
- }
+ // 本地新图片需要上传
+ val data = UploadUtil.upload(fileBean.path).data
+ fileBean.url = data?.newName ?: ""
+ fileBean.originalPic = data?.zipFileName ?: ""
}
}
- // 2. 提交表单数据
- val params = mapOf(
- "id" to id,
- "wbNo" to bean.wbNo,
- "pc" to bean.pc,
- "weight" to bean.weight,
- "spCode" to bean.spCode,
- "agentCode" to bean.agentCode,
- "goods" to bean.goods,
- "flight" to bean.flight,
- "route" to bean.route,
- "awbType" to bean.awbType,
- "telegramNo" to bean.telegramNo,
- "remark" to bean.remark,
- "images" to uploadedUrls.joinToString(","),
- ).toRequestBody(removeEmptyOrNull = true)
+ // 2. 设置图片字段
+ bean.picNumber = images.size.toString()
+ bean.pic = images.joinToString(",") { MediaUtil.removeUrl(it.url) }
+ bean.originalPic = images.joinToString(",") { MediaUtil.removeUrl(it.originalPic) }
- NetApply.api.saveGnjYiKu(params)
+ // 3. 提交表单数据
+ NetApply.api.saveGnjYiKu(bean.toRequestBody())
}) {
onSuccess = {
showToast(if (pageType.value == DetailsPageType.Add) "新增成功" else "保存成功")
diff --git a/module_gnj/src/main/java/com/lukouguoji/gnj/page/yiku/handover/GnjYiKuHandoverViewModel.kt b/module_gnj/src/main/java/com/lukouguoji/gnj/page/yiku/handover/GnjYiKuHandoverViewModel.kt
index b6f675e..fde767c 100644
--- a/module_gnj/src/main/java/com/lukouguoji/gnj/page/yiku/handover/GnjYiKuHandoverViewModel.kt
+++ b/module_gnj/src/main/java/com/lukouguoji/gnj/page/yiku/handover/GnjYiKuHandoverViewModel.kt
@@ -100,16 +100,14 @@ class GnjYiKuHandoverViewModel : BaseViewModel(), IOnItemClickListener {
launchLoadingCollect({
// 上传图片
- val uploadedUrls = mutableListOf()
images.forEach { fileBean ->
if (fileBean.url.isNotEmpty()) {
- // 已上传的图片,直接用文件名
- uploadedUrls.add(fileBean.url)
+ // 已上传的图片,保持原有的 url 和 originalPic
} else {
- val result = UploadUtil.upload(fileBean.path)
- if (result.verifySuccess()) {
- uploadedUrls.add(result.data?.newName ?: "")
- }
+ // 本地新图片需要上传
+ val data = UploadUtil.upload(fileBean.path).data
+ fileBean.url = data?.newName ?: ""
+ fileBean.originalPic = data?.zipFileName ?: ""
}
}
@@ -117,7 +115,9 @@ class GnjYiKuHandoverViewModel : BaseViewModel(), IOnItemClickListener {
val params = mapOf(
"mawbId" to mawbId,
"remark" to bean.remark,
- "originalPic" to uploadedUrls.joinToString(","),
+ "picNumber" to images.size.toString(),
+ "pic" to images.joinToString(",") { MediaUtil.removeUrl(it.url) },
+ "originalPic" to images.joinToString(",") { MediaUtil.removeUrl(it.originalPic) },
).toRequestBody(removeEmptyOrNull = true)
NetApply.api.modifyGnjMoveStash(params)