From bb1ecbde43a9f33ebbccd4e1953c1f9efff481a3 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Sat, 9 May 2026 10:51:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20goodsCode=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=B9=B6=E5=90=8C=E6=AD=A5=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=20DB=20v11=EF=BC=8C=E4=BF=AE=E5=A4=8D=20IPv6=20WebSocket=20?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E6=A0=BC=E5=BC=8F=EF=BC=8C=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20VectorCollectionFragment=20=E7=9B=B8=E6=9C=BA=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E4=B8=BA=E5=8D=8F=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/shuwei/dish/match/db/AppDatabase.kt | 11 ++- .../dish/match/entity/CookFoodGoodsEntity.kt | 4 + .../com/shuwei/dish/match/entity/GoodsItem.kt | 8 ++ .../dish/match/entity/SeasoningEntity.kt | 3 + .../dish/match/scale/ScaleWebSocketClient.kt | 4 +- .../shuwei/dish/match/ui/SettingActivity.kt | 2 +- .../ui/fragment/VectorCollectionFragment.kt | 96 ++++++++++--------- 7 files changed, 78 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/shuwei/dish/match/db/AppDatabase.kt b/app/src/main/java/com/shuwei/dish/match/db/AppDatabase.kt index 42ca1ec..fc73e3e 100644 --- a/app/src/main/java/com/shuwei/dish/match/db/AppDatabase.kt +++ b/app/src/main/java/com/shuwei/dish/match/db/AppDatabase.kt @@ -25,7 +25,7 @@ import com.shuwei.dish.match.entity.SeasoningSlotEntity CookFoodGoodsEntity::class, SeasoningSlotEntity::class, ], - version = 10, + version = 11, exportSchema = true ) abstract class AppDatabase : RoomDatabase() { @@ -232,6 +232,14 @@ val MIGRATION_9_10 = MigrationImpl(9, 10) { db -> db.execSQL("DROP INDEX IF EXISTS dm_cook_food_foodId_IDX") } +/** + * 版本10→11:为 dm_cook_food_goods 和 dm_seasoning 表新增 goodsCode(物料编码)字段 + */ +val MIGRATION_10_11 = MigrationImpl(10, 11) { db -> + db.execSQL("ALTER TABLE dm_cook_food_goods ADD COLUMN goodsCode TEXT DEFAULT ''") + db.execSQL("ALTER TABLE dm_seasoning ADD COLUMN goodsCode TEXT DEFAULT ''") +} + class DatabaseProvider(private val context: Context) { // 单例模式确保全局唯一实例‌ val instance: AppDatabase by lazy { @@ -253,6 +261,7 @@ class DatabaseProvider(private val context: Context) { .addMigrations(MIGRATION_7_8) .addMigrations(MIGRATION_8_9) .addMigrations(MIGRATION_9_10) + .addMigrations(MIGRATION_10_11) .build() } } diff --git a/app/src/main/java/com/shuwei/dish/match/entity/CookFoodGoodsEntity.kt b/app/src/main/java/com/shuwei/dish/match/entity/CookFoodGoodsEntity.kt index f0493d8..fbc4326 100644 --- a/app/src/main/java/com/shuwei/dish/match/entity/CookFoodGoodsEntity.kt +++ b/app/src/main/java/com/shuwei/dish/match/entity/CookFoodGoodsEntity.kt @@ -66,6 +66,9 @@ class CookFoodGoodsEntity( var materId: String? = "", + /** 物料编码 */ + var goodsCode: String? = "", + var isDel: Int = 0, var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now()) ) : BaseEntity, Serializable { @@ -80,6 +83,7 @@ class CookFoodGoodsEntity( popularName = popularName, zjmCode = zjmCode, materId = materId, + materCode = goodsCode, materialType = materialType, useWeight = useWeight, relateionType = relateionType, diff --git a/app/src/main/java/com/shuwei/dish/match/entity/GoodsItem.kt b/app/src/main/java/com/shuwei/dish/match/entity/GoodsItem.kt index fda36b3..afbadd4 100644 --- a/app/src/main/java/com/shuwei/dish/match/entity/GoodsItem.kt +++ b/app/src/main/java/com/shuwei/dish/match/entity/GoodsItem.kt @@ -13,6 +13,7 @@ data class GoodsItem( val popularName: String? = null, val zjmCode: String? = null, val materId: String? = null, + val materCode: String? = null, // 从 CookFoodGoodsEntity 迁移的数据字段(DishPartAdapter 显示及转换用) var materialType: Int = 0, @@ -33,6 +34,11 @@ data class GoodsItem( var foodScore: Int = -1 ) : Serializable { + /** + * 物料编码,与查询接口返回的 materCode 同值,统一对外暴露为 goodsCode 供下游使用 + */ + val goodsCode: String? get() = materCode + /** * 转换为 CookFoodGoodsEntity,用于写入数据库或传给 SubmitFoodActivity */ @@ -42,6 +48,7 @@ data class GoodsItem( popularName = popularName, zjmCode = zjmCode, materId = materId, + goodsCode = goodsCode, materialType = materialType, useWeight = useWeight, relateionType = relateionType, @@ -58,6 +65,7 @@ data class GoodsItem( popularName = popularName, zjmCode = zjmCode, materId = materId, + goodsCode = goodsCode, materialType = materialType, relateionType = relateionType, allEdible = allEdible, diff --git a/app/src/main/java/com/shuwei/dish/match/entity/SeasoningEntity.kt b/app/src/main/java/com/shuwei/dish/match/entity/SeasoningEntity.kt index 7b27aa1..60a3eec 100644 --- a/app/src/main/java/com/shuwei/dish/match/entity/SeasoningEntity.kt +++ b/app/src/main/java/com/shuwei/dish/match/entity/SeasoningEntity.kt @@ -58,6 +58,9 @@ data class SeasoningEntity( var materId: String? = "", + /** 物料编码 */ + var goodsCode: String? = "", + var sort: Int = 9999, /** * isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据 diff --git a/app/src/main/java/com/shuwei/dish/match/scale/ScaleWebSocketClient.kt b/app/src/main/java/com/shuwei/dish/match/scale/ScaleWebSocketClient.kt index 3ab7a41..fd79d90 100644 --- a/app/src/main/java/com/shuwei/dish/match/scale/ScaleWebSocketClient.kt +++ b/app/src/main/java/com/shuwei/dish/match/scale/ScaleWebSocketClient.kt @@ -86,7 +86,9 @@ class ScaleWebSocketClient { Log.d(TAG, "放弃过期连接任务: $deviceId, version=$version") return } - val url = "ws://$host:$port" + // IPv6 地址在 URL 中需用方括号包裹,否则 OkHttp 会将冒号后内容误解析为端口 + val formattedHost = if (host.contains(':')) "[$host]" else host + val url = "ws://$formattedHost:$port" val request = Request.Builder().url(url).build() val ws = httpClient.newWebSocket(request, object : WebSocketListener() { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt index f72660a..c55bb9e 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SettingActivity.kt @@ -98,7 +98,7 @@ class SettingActivity : BaseActivity() { .setTitle("数据清除") .setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?") .setNegativeButton("取消") - .setPositiveButton("确认清除") { + .setPositiveButton("确认") { appViewModel.clearAllData { SpTool.cookMode = -1 SpTool.canteenId = "0" diff --git a/app/src/main/java/com/shuwei/dish/match/ui/fragment/VectorCollectionFragment.kt b/app/src/main/java/com/shuwei/dish/match/ui/fragment/VectorCollectionFragment.kt index a44a723..fd7047f 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/fragment/VectorCollectionFragment.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/fragment/VectorCollectionFragment.kt @@ -14,7 +14,11 @@ import android.view.inputmethod.InputMethodManager import androidx.camera.view.PreviewView import androidx.core.view.doOnLayout import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import com.shuwei.dish.match.R import com.shuwei.dish.match.adapter.VectorCollectionAdapter import com.shuwei.dish.match.adapter.Food3Adapter @@ -111,15 +115,16 @@ class VectorCollectionFragment : BaseFragment() //typealias CameraCallback = (Uri) -> Unit - private val cameraCallback: (Uri) -> Unit = cameraCallback@{ uri -> - try { - val index = vectorList.indexOfFirst { it.imageFile == null } - if (index == -1) { - toast("每次只允许保存${MAX_COUNT}条数据") - hideWaitingDialog() - return@cameraCallback - } - activity?.runOnUiThread { + private val cameraCallback: (Uri) -> Unit = { uri -> + lifecycleScope.launch { + try { + val index = vectorList.indexOfFirst { it.imageFile == null } + if (index == -1) { + toast("每次只允许保存${MAX_COUNT}条数据") + hideWaitingDialog() + return@launch + } + // 主线程更新 UI vectorList[index].let { it.imageVector = null it.bitmap = null @@ -128,53 +133,50 @@ class VectorCollectionFragment : BaseFragment() it.imageUri = uri } vectorAdapter.notifyItemChanged(index) - } -// hideWaitingDialog() - Thread { - ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap -> - getImageVector(index, bitmap) + // 切到 IO 线程执行耗时的 Bitmap 解码 + val bitmap = withContext(Dispatchers.IO) { + ImageUtil.uriToBitmap(requireActivity(), uri) } - }.start() - } catch (e: Exception) { - e.printStackTrace() - hideWaitingDialog() - toast("程序异常${e.message}") - log("程序异常${e.message}") + bitmap?.let { getImageVector(index, it) } + } catch (e: Exception) { + e.printStackTrace() + hideWaitingDialog() + toast("程序异常${e.message}") + log("程序异常${e.message}") + } } } - private fun getImageVector(index: Int, bitmap: Bitmap) { -// val bitmap = BitmapCropper.cropCenter( -// original = srcBmp, -// targetWidth = 900, targetHeight = 900, -//// offsetX = 30, offsetY = 100 -// ) - val imageVector = try { - FoodModule.bitmap2FloatArray(bitmap, false) - } catch (e: Exception) { - e.printStackTrace() + private suspend fun getImageVector(index: Int, bitmap: Bitmap) { + // 切到 IO 线程执行耗时的向量计算和文件保存 + val (imageVector, file) = withContext(Dispatchers.IO) { + try { + val vector = FoodModule.bitmap2FloatArray(bitmap, false) + val file = BitmapSaver.saveToAppFilesDir( + bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg" + ) + log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}") + vector to file + } catch (e: Exception) { + e.printStackTrace() + null to null + } + } + // 回到主线程处理结果 + if (imageVector == null) { toast("操作失败") - log("操作失败:${e.message}") + log("操作失败") hideWaitingDialog() return } - val file = BitmapSaver.saveToAppFilesDir( - bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg" - ) - log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}") - - activity?.runOnUiThread { - vectorList[index].let { - it.imageVector = imageVector - it.bitmap = null - it.isShowCamera = false - it.imageFile = file - } - vectorAdapter.notifyItemChanged(index) - } - if (bitmap.isRecycled.not()) { - bitmap.recycle() + vectorList[index].let { + it.imageVector = imageVector + it.bitmap = null + it.isShowCamera = false + it.imageFile = file } + vectorAdapter.notifyItemChanged(index) + if (bitmap.isRecycled.not()) bitmap.recycle() hideWaitingDialog() }