添加注释和日志,添加自采添加物品添加后删除图片

This commit is contained in:
zxj
2025-07-14 11:07:04 +08:00
parent f1283ba11e
commit ab55bb0fdc
9 changed files with 103 additions and 176 deletions
@@ -15,10 +15,29 @@ object GlobalData {
*/ */
var imageUri: Uri? = null var imageUri: Uri? = null
/**
* 存储方式列表
*/
var storageTypeList: List<DictType> = arrayListOf() var storageTypeList: List<DictType> = arrayListOf()
/**
* 物品类型列表
*/
var goodsTypeList: List<DictType> = arrayListOf() var goodsTypeList: List<DictType> = arrayListOf()
/**
* 仓库类型列表
*/
var warehouseTypeList: List<DictType> = arrayListOf() var warehouseTypeList: List<DictType> = arrayListOf()
/**
* 供应商列表
*/
var supplierTypeList: List<DictType> = arrayListOf() var supplierTypeList: List<DictType> = arrayListOf()
/**
* 单位类型列表
*/
var unitTypeList: List<DictType> = arrayListOf() var unitTypeList: List<DictType> = arrayListOf()
} }
+2 -1
View File
@@ -2,6 +2,7 @@ package com.sw.inbound
import android.app.Application import android.app.Application
import com.sw.inbound.utils.ContextUtils import com.sw.inbound.utils.ContextUtils
import com.sw.inbound.utils.CrashHandler
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber import timber.log.Timber
@@ -17,6 +18,6 @@ class MyApp : Application() {
ContextUtils.initAppContext(this) ContextUtils.initAppContext(this)
// 初始化崩溃处理器 // 初始化崩溃处理器
// CrashHandler.init(this) CrashHandler.init(this)
} }
} }
@@ -21,7 +21,6 @@ abstract class BaseRepository {
// 对于HTTP异常,尝试从响应体中获取错误信息 // 对于HTTP异常,尝试从响应体中获取错误信息
val errorBody = e.response()?.errorBody()?.string() val errorBody = e.response()?.errorBody()?.string()
val errorMsg = if (!errorBody.isNullOrEmpty()) { val errorMsg = if (!errorBody.isNullOrEmpty()) {
// 可以尝试解析errorBody为JSON获取更详细的错误信息
errorBody errorBody
} else { } else {
"HTTP Error: ${e.code()} - ${e.message()}" "HTTP Error: ${e.code()} - ${e.message()}"
@@ -15,12 +15,15 @@ import com.sw.inbound.model.response.SupplierResponse
import com.sw.inbound.model.response.User import com.sw.inbound.model.response.User
import com.sw.inbound.network.api.ApiService import com.sw.inbound.network.api.ApiService
import com.sw.inbound.utils.ContextUtils import com.sw.inbound.utils.ContextUtils
import com.sw.inbound.utils.ImageUtils import com.sw.inbound.utils.FileUtils
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
/**
* 远程数据处理
*/
class RemoteRepository @Inject constructor( class RemoteRepository @Inject constructor(
private val apiService: ApiService private val apiService: ApiService
) : BaseRepository() { ) : BaseRepository() {
@@ -86,11 +89,11 @@ class RemoteRepository @Inject constructor(
) )
val part = val part =
ImageUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri) FileUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri)
if (part == null) { if (part == null) {
ApiResponse(code = -1, msg = "解析图片失败", data = "") ApiResponse(code = -1, msg = "解析图片失败", data = "")
} else { } else {
apiService.uploadImage(codeRequestBody, file = part) apiService.uploadImage(code = codeRequestBody, file = part)
} }
} }
} }
@@ -78,8 +78,6 @@ fun SingleSelectButtonGroup(
var selectedOption by remember { mutableStateOf(options.firstOrNull() ?: "") } var selectedOption by remember { mutableStateOf(options.firstOrNull() ?: "") }
val lazyGridState = rememberLazyGridState() val lazyGridState = rememberLazyGridState()
// val canLoadMore by viewModel.canLoadMore.collectAsState()
// val isMoreLoading by viewModel.isMoreLoading.collectAsState()
var pageNum by remember { mutableIntStateOf(1) } var pageNum by remember { mutableIntStateOf(1) }
// 检测是否滚动到底部 // 检测是否滚动到底部
@@ -103,7 +101,6 @@ fun SingleSelectButtonGroup(
val selectedColor = Color(0xFFD9E3F9) val selectedColor = Color(0xFFD9E3F9)
val unselectedColor = Color(0xFFDCDCF0) val unselectedColor = Color(0xFFDCDCF0)
LazyVerticalGrid( LazyVerticalGrid(
state = lazyGridState, state = lazyGridState,
columns = GridCells.Fixed(2), // 每行2列 columns = GridCells.Fixed(2), // 每行2列
@@ -112,7 +109,7 @@ fun SingleSelectButtonGroup(
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing), horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
verticalArrangement = Arrangement.spacedBy(verticalSpacing) verticalArrangement = Arrangement.spacedBy(verticalSpacing)
) { ) {
items(items = options) { option -> items(items = options, key = { it.goodsId ?: it.goodsCode!! }) { option ->
Box( Box(
modifier = modifier modifier = modifier
.border( .border(
@@ -143,7 +140,6 @@ fun SingleSelectButtonGroup(
softWrap = false softWrap = false
) )
} }
} }
} }
} }
@@ -6,9 +6,66 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.RequiresApi 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 import java.io.File
object FileUtils { 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删除文件 * 通过Uri删除文件
* @param context 上下文 * @param context 上下文
@@ -16,6 +73,7 @@ object FileUtils {
* @return Boolean 是否删除成功 * @return Boolean 是否删除成功
*/ */
fun deleteFileWithUri(context: Context, uri: Uri): Boolean { fun deleteFileWithUri(context: Context, uri: Uri): Boolean {
Timber.d("deleteFileWithUri uri = ${uri.scheme}")
return when { return when {
// 1. 处理 content:// 类型的Uri (MediaStore) // 1. 处理 content:// 类型的Uri (MediaStore)
uri.scheme.equals("content", ignoreCase = true) -> { uri.scheme.equals("content", ignoreCase = true) -> {
@@ -34,6 +92,7 @@ object FileUtils {
// 删除Content Uri文件 // 删除Content Uri文件
private fun deleteContentUriFile(context: Context, uri: Uri): Boolean { private fun deleteContentUriFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteContentUriFile uri = ${uri.scheme}")
return try { return try {
context.contentResolver.delete(uri, null, null) > 0 context.contentResolver.delete(uri, null, null) > 0
} catch (e: SecurityException) { } catch (e: SecurityException) {
@@ -44,6 +103,7 @@ object FileUtils {
false false
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e)
false false
} }
} }
@@ -51,6 +111,7 @@ object FileUtils {
// Android 10+删除MediaStore文件 // Android 10+删除MediaStore文件
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean { private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteMediaStoreFile uri = ${uri.scheme}")
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
val projection = arrayOf(MediaStore.MediaColumns._ID) val projection = arrayOf(MediaStore.MediaColumns._ID)
@@ -66,24 +127,29 @@ object FileUtils {
} }
} ?: false } ?: false
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e)
false false
} }
} }
// 删除File Uri文件 // 删除File Uri文件
private fun deleteFileUriFile(uri: Uri): Boolean { private fun deleteFileUriFile(uri: Uri): Boolean {
Timber.d("deleteFileUriFile uri = $uri")
return try { return try {
File(uri.path ?: return false).delete() File(uri.path ?: return false).delete()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e)
false false
} }
} }
// 直接通过路径删除文件 // 直接通过路径删除文件
private fun deleteFileFromPath(path: String): Boolean { private fun deleteFileFromPath(path: String): Boolean {
Timber.d("deleteFileFromPath path = $path")
return try { return try {
File(path).delete() File(path).delete()
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e)
false false
} }
} }
@@ -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
}
}
@@ -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
}
}
@@ -11,6 +11,8 @@ import com.sw.inbound.model.response.DictType
import com.sw.inbound.model.response.SearchGoodsInfo import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.repository.RemoteRepository import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.sdk.SensorScaleUtils 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 com.sw.inbound.utils.ToastUtils
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -247,6 +249,9 @@ class SelfProcurementViewModel @Inject constructor(
_purchaseList.value = emptyList() _purchaseList.value = emptyList()
} }
/**
* 搜索物品
*/
fun searchGoodsInfoList( fun searchGoodsInfoList(
goodsName: String, goodsName: String,
pageNo: Int = 1, pageNo: Int = 1,
@@ -310,6 +315,9 @@ class SelfProcurementViewModel @Inject constructor(
_amountUserInput.value = boolean _amountUserInput.value = boolean
} }
/**
* 添加商品
*/
fun addGoodsInfo() { fun addGoodsInfo() {
val imageUri = GlobalData.imageUri val imageUri = GlobalData.imageUri
if (imageUri == null) { if (imageUri == null) {
@@ -336,6 +344,7 @@ class SelfProcurementViewModel @Inject constructor(
parseResponse(response) parseResponse(response)
return@launchWithLoading return@launchWithLoading
} }
FileUtils.deleteFileWithUri(context = ContextUtils.getAppContext(), uri = imageUri)
_showAddProductDialog.value = false _showAddProductDialog.value = false
_goodsAddParam.value = GoodsAddParam() _goodsAddParam.value = GoodsAddParam()
val searchFirst = response.data!!.records?.get(0) val searchFirst = response.data!!.records?.get(0)