From ab55bb0fdc1233292a243400a377f001c7813f39 Mon Sep 17 00:00:00 2001 From: zxj <275873859@qq.com> Date: Mon, 14 Jul 2025 11:07:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A=E5=92=8C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=87=AA=E9=87=87?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=89=A9=E5=93=81=E6=B7=BB=E5=8A=A0=E5=90=8E?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/sw/inbound/GlobalData.kt | 19 ++++ app/src/main/java/com/sw/inbound/MyApp.kt | 3 +- .../sw/inbound/repository/BaseRepository.kt | 1 - .../sw/inbound/repository/RemoteRepository.kt | 9 +- .../ui/weight/SingleSelectButtonGroup.kt | 6 +- .../java/com/sw/inbound/utils/FileUtils.kt | 66 +++++++++++ .../java/com/sw/inbound/utils/ImageUtils.kt | 61 ---------- .../com/sw/inbound/utils/TextFieldState.kt | 105 ------------------ .../viewmodel/SelfProcurementViewModel.kt | 9 ++ 9 files changed, 103 insertions(+), 176 deletions(-) delete mode 100644 app/src/main/java/com/sw/inbound/utils/ImageUtils.kt delete mode 100644 app/src/main/java/com/sw/inbound/utils/TextFieldState.kt diff --git a/app/src/main/java/com/sw/inbound/GlobalData.kt b/app/src/main/java/com/sw/inbound/GlobalData.kt index 05745d1..c30d910 100644 --- a/app/src/main/java/com/sw/inbound/GlobalData.kt +++ b/app/src/main/java/com/sw/inbound/GlobalData.kt @@ -15,10 +15,29 @@ object GlobalData { */ var imageUri: Uri? = null + /** + * 存储方式列表 + */ var storageTypeList: List = arrayListOf() + + /** + * 物品类型列表 + */ var goodsTypeList: List = arrayListOf() + + /** + * 仓库类型列表 + */ var warehouseTypeList: List = arrayListOf() + + /** + * 供应商列表 + */ var supplierTypeList: List = arrayListOf() + + /** + * 单位类型列表 + */ var unitTypeList: List = arrayListOf() } diff --git a/app/src/main/java/com/sw/inbound/MyApp.kt b/app/src/main/java/com/sw/inbound/MyApp.kt index f84a83d..1fbc521 100644 --- a/app/src/main/java/com/sw/inbound/MyApp.kt +++ b/app/src/main/java/com/sw/inbound/MyApp.kt @@ -2,6 +2,7 @@ package com.sw.inbound import android.app.Application import com.sw.inbound.utils.ContextUtils +import com.sw.inbound.utils.CrashHandler import dagger.hilt.android.HiltAndroidApp import timber.log.Timber @@ -17,6 +18,6 @@ class MyApp : Application() { ContextUtils.initAppContext(this) // 初始化崩溃处理器 -// CrashHandler.init(this) + CrashHandler.init(this) } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/repository/BaseRepository.kt b/app/src/main/java/com/sw/inbound/repository/BaseRepository.kt index 2f81754..05e8218 100644 --- a/app/src/main/java/com/sw/inbound/repository/BaseRepository.kt +++ b/app/src/main/java/com/sw/inbound/repository/BaseRepository.kt @@ -21,7 +21,6 @@ abstract class BaseRepository { // 对于HTTP异常,尝试从响应体中获取错误信息 val errorBody = e.response()?.errorBody()?.string() val errorMsg = if (!errorBody.isNullOrEmpty()) { - // 可以尝试解析errorBody为JSON获取更详细的错误信息 errorBody } else { "HTTP Error: ${e.code()} - ${e.message()}" diff --git a/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt b/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt index e50fe3b..38d742c 100644 --- a/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/inbound/repository/RemoteRepository.kt @@ -15,12 +15,15 @@ import com.sw.inbound.model.response.SupplierResponse import com.sw.inbound.model.response.User import com.sw.inbound.network.api.ApiService import com.sw.inbound.utils.ContextUtils -import com.sw.inbound.utils.ImageUtils +import com.sw.inbound.utils.FileUtils import okhttp3.MultipartBody import okhttp3.RequestBody import timber.log.Timber import javax.inject.Inject +/** + * 远程数据处理 + */ class RemoteRepository @Inject constructor( private val apiService: ApiService ) : BaseRepository() { @@ -86,11 +89,11 @@ class RemoteRepository @Inject constructor( ) val part = - ImageUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri) + FileUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri) if (part == null) { ApiResponse(code = -1, msg = "解析图片失败", data = "") } else { - apiService.uploadImage(codeRequestBody, file = part) + apiService.uploadImage(code = codeRequestBody, file = part) } } } diff --git a/app/src/main/java/com/sw/inbound/ui/weight/SingleSelectButtonGroup.kt b/app/src/main/java/com/sw/inbound/ui/weight/SingleSelectButtonGroup.kt index 73c138e..89f1edb 100644 --- a/app/src/main/java/com/sw/inbound/ui/weight/SingleSelectButtonGroup.kt +++ b/app/src/main/java/com/sw/inbound/ui/weight/SingleSelectButtonGroup.kt @@ -78,8 +78,6 @@ fun SingleSelectButtonGroup( var selectedOption by remember { mutableStateOf(options.firstOrNull() ?: "") } val lazyGridState = rememberLazyGridState() -// val canLoadMore by viewModel.canLoadMore.collectAsState() -// val isMoreLoading by viewModel.isMoreLoading.collectAsState() var pageNum by remember { mutableIntStateOf(1) } // 检测是否滚动到底部 @@ -103,7 +101,6 @@ fun SingleSelectButtonGroup( val selectedColor = Color(0xFFD9E3F9) val unselectedColor = Color(0xFFDCDCF0) - LazyVerticalGrid( state = lazyGridState, columns = GridCells.Fixed(2), // 每行2列 @@ -112,7 +109,7 @@ fun SingleSelectButtonGroup( horizontalArrangement = Arrangement.spacedBy(horizontalSpacing), verticalArrangement = Arrangement.spacedBy(verticalSpacing) ) { - items(items = options) { option -> + items(items = options, key = { it.goodsId ?: it.goodsCode!! }) { option -> Box( modifier = modifier .border( @@ -143,7 +140,6 @@ fun SingleSelectButtonGroup( softWrap = false ) } - } } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/utils/FileUtils.kt b/app/src/main/java/com/sw/inbound/utils/FileUtils.kt index 3b3f3af..d8191da 100644 --- a/app/src/main/java/com/sw/inbound/utils/FileUtils.kt +++ b/app/src/main/java/com/sw/inbound/utils/FileUtils.kt @@ -6,9 +6,66 @@ import android.net.Uri import android.os.Build import android.provider.MediaStore import androidx.annotation.RequiresApi +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import timber.log.Timber import java.io.File object FileUtils { + /** + * 从Uri获取File + * example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg + */ + private fun getFileFromUri(context: Context, uri: Uri): File? { + Timber.d("getFileFromUri uri = ${uri.scheme}") + return when (uri.scheme) { + "file" -> File(uri.path ?: return null) + "content" -> { + try { + val inputStream = context.contentResolver.openInputStream(uri) ?: return null + val cacheDir = context.cacheDir + val file = File.createTempFile( + "upload_${System.currentTimeMillis()}", + ".jpg", + cacheDir + ) + file.outputStream().use { output -> + inputStream.copyTo(output) + } + file + } catch (e: Exception) { + Timber.e(e) + null + } + } + + else -> null + } + } + + /** + * 通过uri生成http请求体 + */ + fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? { + Timber.d("genRequestPart imageUri = $imageUri") + // 1. 从Uri获取文件 + val file = getFileFromUri(context, imageUri) + if (file == null) { + Timber.e("getFileFromUri file is null") + return null + } + // 2. 创建请求体 + val requestFile = file + .asRequestBody("application/octet-stream".toMediaTypeOrNull()) + val imagePart = MultipartBody.Part.createFormData( + "file", + file.name, + requestFile + ) + return imagePart + } + /** * 通过Uri删除文件 * @param context 上下文 @@ -16,6 +73,7 @@ object FileUtils { * @return Boolean 是否删除成功 */ fun deleteFileWithUri(context: Context, uri: Uri): Boolean { + Timber.d("deleteFileWithUri uri = ${uri.scheme}") return when { // 1. 处理 content:// 类型的Uri (MediaStore) uri.scheme.equals("content", ignoreCase = true) -> { @@ -34,6 +92,7 @@ object FileUtils { // 删除Content Uri文件 private fun deleteContentUriFile(context: Context, uri: Uri): Boolean { + Timber.d("deleteContentUriFile uri = ${uri.scheme}") return try { context.contentResolver.delete(uri, null, null) > 0 } catch (e: SecurityException) { @@ -44,6 +103,7 @@ object FileUtils { false } } catch (e: Exception) { + Timber.e(e) false } } @@ -51,6 +111,7 @@ object FileUtils { // Android 10+删除MediaStore文件 @RequiresApi(Build.VERSION_CODES.Q) private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean { + Timber.d("deleteMediaStoreFile uri = ${uri.scheme}") val contentResolver = context.contentResolver val projection = arrayOf(MediaStore.MediaColumns._ID) @@ -66,24 +127,29 @@ object FileUtils { } } ?: false } catch (e: Exception) { + Timber.e(e) false } } // 删除File Uri文件 private fun deleteFileUriFile(uri: Uri): Boolean { + Timber.d("deleteFileUriFile uri = $uri") return try { File(uri.path ?: return false).delete() } catch (e: Exception) { + Timber.e(e) false } } // 直接通过路径删除文件 private fun deleteFileFromPath(path: String): Boolean { + Timber.d("deleteFileFromPath path = $path") return try { File(path).delete() } catch (e: Exception) { + Timber.e(e) false } } diff --git a/app/src/main/java/com/sw/inbound/utils/ImageUtils.kt b/app/src/main/java/com/sw/inbound/utils/ImageUtils.kt deleted file mode 100644 index 567a5bc..0000000 --- a/app/src/main/java/com/sw/inbound/utils/ImageUtils.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.sw.inbound.utils - -import android.content.Context -import android.net.Uri -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody -import timber.log.Timber -import java.io.File - -/** - * file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg - */ -object ImageUtils { - // 从Uri获取File - private fun getFileFromUri(context: Context, uri: Uri): File? { - return when (uri.scheme) { - "file" -> File(uri.path ?: return null) - "content" -> { - try { - val inputStream = context.contentResolver.openInputStream(uri) ?: return null - val cacheDir = context.cacheDir - val file = File.createTempFile( - "upload_${System.currentTimeMillis()}", - ".jpg", - cacheDir - ) - file.outputStream().use { output -> - inputStream.copyTo(output) - } - file - } catch (e: Exception) { - null - } - } - - else -> null - } - } - - fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? { - Timber.d("genRequestPart imageUri = $imageUri") - // 1. 从Uri获取文件 - val file = getFileFromUri(context, imageUri) - if (file == null) { - Timber.e("getFileFromUri file is null") - return null - } - // 2. 创建请求体 - val requestFile = RequestBody.create( - "application/octet-stream".toMediaTypeOrNull(), - file - ) - val imagePart = MultipartBody.Part.createFormData( - "file", - file.name, - requestFile - ) - return imagePart - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/utils/TextFieldState.kt b/app/src/main/java/com/sw/inbound/utils/TextFieldState.kt deleted file mode 100644 index 31f4d6c..0000000 --- a/app/src/main/java/com/sw/inbound/utils/TextFieldState.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.sw.inbound.utils - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.TextFieldValue - -open class TextFieldState( - initialText: String = "", - initialSelection: TextRange = TextRange(initialText.length) -) { - private var _value by mutableStateOf(TextFieldValue(initialText, initialSelection)) - - open var value: TextFieldValue - get() = _value - set(newValue) { - _value = newValue - } - - open fun updateFromString(text: String) { - _value = TextFieldValue(text, TextRange(text.length)) - } - - val text: String get() = _value.text -} - -class AmountFieldState(initialAmount: String = "") : TextFieldState(initialAmount) { - // 获取原始数字字符串(不含格式字符) - fun getRawAmount(): String = value.text.filter { it.isDigit() } - - // 获取Double类型的金额值 - fun getAmountValue(): Double = value.text - .filter { it.isDigit() || it == '.' } - .toDoubleOrNull() ?: 0.0 - - // 重写value的setter以实现金额格式化 - override var value: TextFieldValue - get() = super.value - set(newValue) { - super.value = formatAmountValue(newValue) - } - - // 从外部更新金额(如从数据库加载) - override fun updateFromString(amount: String) { - super.updateFromString(formatAmount(amount)) - } - - private fun formatAmountValue(input: TextFieldValue): TextFieldValue { - val filtered = input.text.filter { it.isDigit() } - val formatted = formatAmount(filtered) - - // 计算新光标位置 - val newCursorPos = calculateNewCursorPosition( - originalText = input.text, - originalSelection = input.selection, - filteredText = filtered, - formattedText = formatted - ) - - return TextFieldValue( - text = formatted, - selection = TextRange(newCursorPos) - ) - } - - private fun formatAmount(amount: String): String { - val filtered = amount.filter { it.isDigit() } - return when { - filtered.isEmpty() -> "0.00" - filtered.length <= 2 -> "0.${filtered.padStart(2, '0')}" - else -> "${filtered.dropLast(2)}.${filtered.takeLast(2)}" - } - } - - private fun calculateNewCursorPosition( - originalText: String, - originalSelection: TextRange, - filteredText: String, - formattedText: String - ): Int { - // 如果在末尾添加,保持光标在末尾 - if (originalSelection.start >= originalText.length) { - return formattedText.length - } - - // 计算原始文本中光标前的数字个数 - val digitsBeforeCursor = originalText - .substring(0, originalSelection.start) - .count { it.isDigit() } - - // 在格式化文本中找到对应位置 - var digitCount = 0 - formattedText.forEachIndexed { index, char -> - if (char.isDigit()) { - digitCount++ - if (digitCount > digitsBeforeCursor) { - return index - } - } - } - - return formattedText.length - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/inbound/viewmodel/SelfProcurementViewModel.kt b/app/src/main/java/com/sw/inbound/viewmodel/SelfProcurementViewModel.kt index eb6b130..915d646 100644 --- a/app/src/main/java/com/sw/inbound/viewmodel/SelfProcurementViewModel.kt +++ b/app/src/main/java/com/sw/inbound/viewmodel/SelfProcurementViewModel.kt @@ -11,6 +11,8 @@ import com.sw.inbound.model.response.DictType import com.sw.inbound.model.response.SearchGoodsInfo import com.sw.inbound.repository.RemoteRepository import com.sw.inbound.sdk.SensorScaleUtils +import com.sw.inbound.utils.ContextUtils +import com.sw.inbound.utils.FileUtils import com.sw.inbound.utils.ToastUtils import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay @@ -247,6 +249,9 @@ class SelfProcurementViewModel @Inject constructor( _purchaseList.value = emptyList() } + /** + * 搜索物品 + */ fun searchGoodsInfoList( goodsName: String, pageNo: Int = 1, @@ -310,6 +315,9 @@ class SelfProcurementViewModel @Inject constructor( _amountUserInput.value = boolean } + /** + * 添加商品 + */ fun addGoodsInfo() { val imageUri = GlobalData.imageUri if (imageUri == null) { @@ -336,6 +344,7 @@ class SelfProcurementViewModel @Inject constructor( parseResponse(response) return@launchWithLoading } + FileUtils.deleteFileWithUri(context = ContextUtils.getAppContext(), uri = imageUri) _showAddProductDialog.value = false _goodsAddParam.value = GoodsAddParam() val searchFirst = response.data!!.records?.get(0)