feat: 新增 goodsCode 字段并同步迁移 DB v11,修复 IPv6 WebSocket 地址格式,重构 VectorCollectionFragment 相机回调为协程

This commit is contained in:
2026-05-09 10:51:43 +08:00
parent 7a03ab8108
commit bb1ecbde43
7 changed files with 78 additions and 50 deletions
@@ -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()
}
}
@@ -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,
@@ -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,
@@ -58,6 +58,9 @@ data class SeasoningEntity(
var materId: String? = "",
/** 物料编码 */
var goodsCode: String? = "",
var sort: Int = 9999,
/**
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
@@ -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() {
@@ -98,7 +98,7 @@ class SettingActivity : BaseActivity() {
.setTitle("数据清除")
.setContent("将物理删除全部 4 张表数据(菜品、食材、调料、槽位),此操作不可恢复,同时重置菜品模式和食堂id,确认继续?")
.setNegativeButton("取消")
.setPositiveButton("确认清除") {
.setPositiveButton("确认") {
appViewModel.clearAllData {
SpTool.cookMode = -1
SpTool.canteenId = "0"
@@ -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<FragmentVectorCollectionBinding>()
//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<FragmentVectorCollectionBinding>()
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()
}