diff --git a/app/src/main/java/com/sw/dualscreen/model/request/v2/BindUserOrderRequest.kt b/app/src/main/java/com/sw/dualscreen/model/request/v2/BindUserOrderRequest.kt new file mode 100644 index 0000000..19a74f1 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/request/v2/BindUserOrderRequest.kt @@ -0,0 +1,11 @@ +package com.sw.dualscreen.model.request.v2 + +/** + * 新系统绑定用户与订单请求参数 + * 对应接口:/neglect/booth/order/bind-user + */ +data class BindUserOrderRequest( + val userId: Long, + val orderNo: String, + val mode: Int? = null +) diff --git a/app/src/main/java/com/sw/dualscreen/model/request/v2/PlaceOrderRequest.kt b/app/src/main/java/com/sw/dualscreen/model/request/v2/PlaceOrderRequest.kt new file mode 100644 index 0000000..6510bd1 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/request/v2/PlaceOrderRequest.kt @@ -0,0 +1,24 @@ +package com.sw.dualscreen.model.request.v2 + +import java.math.BigDecimal + +/** + * 新系统开餐下单请求参数 + * 对应接口:/neglect/booth/order/place + */ +data class PlaceOrderRequest( + val foodId: Long, + val foodName: String, + val foodWeight: BigDecimal, + val eatWeight: BigDecimal, + val eatNum: Int, + val notPay: Boolean, + val mode: Int, + val specId: Long?, + val userId: Long?, + val paymentFrom: Int? = null, + val foodMaterialId: Long? = null, + val member: Boolean? = null, + val remark: String? = null, + val deviceId: String? = null +) diff --git a/app/src/main/java/com/sw/dualscreen/model/response/v2/CollectedFoodV2.kt b/app/src/main/java/com/sw/dualscreen/model/response/v2/CollectedFoodV2.kt new file mode 100644 index 0000000..20a744e --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/response/v2/CollectedFoodV2.kt @@ -0,0 +1,17 @@ +package com.sw.dualscreen.model.response.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * 新系统采集菜品信息 + * 对应接口:/neglect/booth/collect/page + */ +@Parcelize +data class CollectedFoodV2( + val foodId: String?, + val foodName: String?, + val version: String?, + val foodVector: String?, + val picUrls: List? +) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/model/response/v2/FaceVO.kt b/app/src/main/java/com/sw/dualscreen/model/response/v2/FaceVO.kt new file mode 100644 index 0000000..fb22a8c --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/response/v2/FaceVO.kt @@ -0,0 +1,19 @@ +package com.sw.dualscreen.model.response.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * 新系统人脸数据 VO + * 对应接口:/neglect/booth/face/page 和 /neglect/booth/face/increment + */ +@Parcelize +data class FaceVO( + val userFaceId: String?, + val userId: String?, + val faceFeature: String?, + val faceUpdateTimestamp: Long?, + val cardNo: String?, + val member: Boolean?, + val faceDeleted: Boolean? +) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/model/response/v2/NewFoodInfo.kt b/app/src/main/java/com/sw/dualscreen/model/response/v2/NewFoodInfo.kt new file mode 100644 index 0000000..22e8302 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/response/v2/NewFoodInfo.kt @@ -0,0 +1,33 @@ +package com.sw.dualscreen.model.response.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.math.BigDecimal + +/** + * 新系统菜品信息 + * 对应接口:/neglect/booth/food/by-names + */ +@Parcelize +data class NewFoodInfo( + val foodId: Long, + val foodName: String?, + val calorie: BigDecimal?, + val protein: BigDecimal?, + val fat: BigDecimal?, + val carbohydrate: BigDecimal?, + val price: BigDecimal?, + val specPrice: BigDecimal?, + val specWeight: BigDecimal?, + val stapleFood: BigDecimal?, + val fruitsVegetables: BigDecimal?, + val meatEggs: BigDecimal?, + val foodMaterialId: Long?, + val specId: Long?, + val foodImg: String?, + val foodLabel: String?, + val vipPrice: BigDecimal? = null, + val recommendCalorie: Int = 0, + val tablewareStatus: Boolean = false, + val tablewareWeight: Int = 0 +) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/model/response/v2/NewMemberInfo.kt b/app/src/main/java/com/sw/dualscreen/model/response/v2/NewMemberInfo.kt new file mode 100644 index 0000000..f094b7d --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/response/v2/NewMemberInfo.kt @@ -0,0 +1,21 @@ +package com.sw.dualscreen.model.response.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.math.BigDecimal + +/** + * 新系统会员信息 + * 对应接口:/neglect/booth/member/info 和 /neglect/booth/member/info-by-phone + */ +@Parcelize +data class NewMemberInfo( + val id: String?, + val phone: String?, + val name: String?, + val faceUrl: String?, + val topUpBalance: BigDecimal?, + val rewardBalance: BigDecimal? = BigDecimal.ZERO, + val integralBalance: Int?, + val member: Boolean? +) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/model/response/v2/SettlementOrder.kt b/app/src/main/java/com/sw/dualscreen/model/response/v2/SettlementOrder.kt new file mode 100644 index 0000000..a60e89f --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/model/response/v2/SettlementOrder.kt @@ -0,0 +1,31 @@ +package com.sw.dualscreen.model.response.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.math.BigDecimal + +/** + * 新系统结算订单数据 + * 对应接口:/neglect/booth/order/settlement + */ +@Parcelize +data class SettlementOrder( + val calorie: BigDecimal?, + val incomeSum: BigDecimal?, + val discountSum: BigDecimal?, + val eatWeightSum: Int?, + val orderNo: String?, + val list: List? +) : Parcelable + +@Parcelize +data class SettlementFoodItem( + val foodId: String?, + val foodName: String?, + val specId: String?, + val specName: String?, + val specWeight: BigDecimal?, + val eatNum: Int?, + val eatWeight: Int?, + val price: BigDecimal? +) : Parcelable diff --git a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt index dccc8f4..aec8e0a 100644 --- a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt +++ b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt @@ -3,7 +3,10 @@ package com.sw.dualscreen.network import com.sw.dualscreen.BuildConfig import com.sw.dualscreen.MyApp import com.sw.dualscreen.network.api.ApiService +import com.sw.dualscreen.network.api.ApiServiceV2 import com.sw.dualscreen.network.interceptor.RequestInterceptor +import com.sw.dualscreen.repository.RemoteRepository +import com.sw.dualscreen.repository.v2.RemoteRepositoryV2 import com.sw.dualscreen.utils.FileUtil import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -16,7 +19,7 @@ import java.util.concurrent.TimeUnit object ApiClient { private const val BASE_URL = "http://device.shuziweidao.com:8889/" - private const val TIME_OUT = 30L // 超时时间(秒) + private const val TIME_OUT = 30L private val okHttpClient = OkHttpClient.Builder() .connectTimeout(TIME_OUT, TimeUnit.SECONDS) @@ -27,6 +30,7 @@ object ApiClient { val url = request.url.toString() val tag = if (url.contains("terminal/neglect/common/app/faceFeature/increment/list")) "faceIncrement" else if (url.contains("terminal/neglect/pay/app/turnOrderInfo")) "turnOrderInfo" + else if (url.contains("neglect/booth/")) "ApiV2" else "ApiClient" val loggingInterceptor = HttpLoggingInterceptor(logger = { Timber.tag(tag).d("okhttp logger ==>${it}") @@ -47,10 +51,21 @@ object ApiClient { .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) -// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器 .build() val apiService: ApiService by lazy { retrofit.create(ApiService::class.java) } -} \ No newline at end of file + + val apiServiceV2: ApiServiceV2 by lazy { + retrofit.create(ApiServiceV2::class.java) + } + + val repository: RemoteRepository by lazy { + RemoteRepository(apiService) + } + + val repositoryV2: RemoteRepositoryV2 by lazy { + RemoteRepositoryV2(apiServiceV2) + } +} diff --git a/app/src/main/java/com/sw/dualscreen/network/api/ApiServiceV2.kt b/app/src/main/java/com/sw/dualscreen/network/api/ApiServiceV2.kt new file mode 100644 index 0000000..8735bb9 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/network/api/ApiServiceV2.kt @@ -0,0 +1,100 @@ +package com.sw.dualscreen.network.api + +import com.sw.dualscreen.GlobalData +import com.sw.dualscreen.model.request.v2.BindUserOrderRequest +import com.sw.dualscreen.model.request.v2.PlaceOrderRequest +import com.sw.dualscreen.model.response.ApiResponse +import com.sw.dualscreen.model.response.DeviceConfig +import com.sw.dualscreen.model.response.FoodSearchReq +import com.sw.dualscreen.model.response.UserNutrition +import com.sw.dualscreen.model.response.v2.CollectedFoodV2 +import com.sw.dualscreen.model.response.v2.FaceVO +import com.sw.dualscreen.model.response.v2.NewFoodInfo +import com.sw.dualscreen.model.response.v2.NewMemberInfo +import com.sw.dualscreen.model.response.v2.SettlementOrder +import okhttp3.MultipartBody +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.Part +import retrofit2.http.PartMap + +/** + * 新系统 API 接口服务 + * 基于 /neglect/booth 模块前缀 + * 文档版本:2026-05-27 + */ +interface ApiServiceV2 { + + @GET("/neglect/booth/device/config") + suspend fun getDeviceConfig(): ApiResponse + + @POST("/neglect/booth/face/page") + suspend fun getFacePage( + @Body request: Map + ): ApiResponse?> + + @POST("/neglect/booth/face/increment") + suspend fun getFaceIncrement( + @Body request: Map + ): ApiResponse?> + + @POST("/neglect/booth/food/by-names") + suspend fun getFoodByNames( + @Body request: FoodSearchReq + ): ApiResponse?> + + @POST("/neglect/booth/user/current-food") + suspend fun getUserCurrentFood( + @Body request: Map + ): ApiResponse + + @POST("/neglect/booth/order/place") + suspend fun placeOrder( + @Body request: PlaceOrderRequest + ): ApiResponse + + @POST("/neglect/booth/order/settlement") + suspend fun getSettlementOrders( + @Body request: Map + ): ApiResponse + + @POST("/neglect/booth/member/info") + suspend fun getMemberInfo( + @Body request: Map + ): ApiResponse + + @POST("/neglect/booth/member/info-by-phone") + suspend fun getMemberInfoByPhone( + @Body request: Map + ): ApiResponse + + @POST("/neglect/booth/member/discount") + suspend fun getMemberDiscount( + @Body request: Map + ): ApiResponse + + @POST("/neglect/booth/order/bind-user") + suspend fun bindUserOrder( + @Body request: BindUserOrderRequest + ): ApiResponse + + @POST("/neglect/booth/collect/page") + suspend fun getCollectPage( + @Body request: Map + ): ApiResponse?> + + @Multipart + @POST("/neglect/booth/collect/upload") + suspend fun uploadCollect( + @PartMap params: HashMap, + @Part foodPics: List + ): ApiResponse?> + + @POST("/neglect/booth/collect/delete") + suspend fun deleteCollect( + @Body request: Map + ): ApiResponse +} diff --git a/app/src/main/java/com/sw/dualscreen/repository/v2/RemoteRepositoryV2.kt b/app/src/main/java/com/sw/dualscreen/repository/v2/RemoteRepositoryV2.kt new file mode 100644 index 0000000..e42e533 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/repository/v2/RemoteRepositoryV2.kt @@ -0,0 +1,218 @@ +package com.sw.dualscreen.repository.v2 + +import com.sw.dualscreen.model.request.v2.BindUserOrderRequest +import com.sw.dualscreen.model.request.v2.PlaceOrderRequest +import com.sw.dualscreen.model.response.ApiResponse +import com.sw.dualscreen.model.response.DeviceConfig +import com.sw.dualscreen.model.response.FoodSearchReq +import com.sw.dualscreen.model.response.UserNutrition +import com.sw.dualscreen.model.response.v2.CollectedFoodV2 +import com.sw.dualscreen.model.response.v2.FaceVO +import com.sw.dualscreen.model.response.v2.NewFoodInfo +import com.sw.dualscreen.model.response.v2.NewMemberInfo +import com.sw.dualscreen.model.response.v2.SettlementOrder +import com.sw.dualscreen.network.api.ApiServiceV2 +import com.sw.dualscreen.repository.BaseRepository +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File + +class RemoteRepositoryV2 constructor( + private val apiService: ApiServiceV2 +) : BaseRepository() { + + suspend fun getDeviceConfig(): ApiResponse { + return safeApiCall { + apiService.getDeviceConfig() + } + } + + suspend fun getFacePage( + pageNum: Long = 1L, + pageSize: Long = 100L + ): ApiResponse?> { + return safeApiCall { + apiService.getFacePage( + request = mapOf( + "pageNum" to pageNum, + "pageSize" to pageSize + ) + ) + } + } + + suspend fun getFaceIncrement( + pageNum: Long = 1L, + pageSize: Long = 100L, + timestamp: Long + ): ApiResponse?> { + return safeApiCall { + apiService.getFaceIncrement( + request = mapOf( + "pageNum" to pageNum, + "pageSize" to pageSize, + "timestamp" to timestamp + ) + ) + } + } + + suspend fun getFoodByNames( + nameList: List, + deviceType: Int = 2 + ): ApiResponse?> { + return safeApiCall { + apiService.getFoodByNames( + request = FoodSearchReq(nameList, deviceType) + ) + } + } + + suspend fun getUserCurrentFood( + userId: Long + ): ApiResponse { + return safeApiCall { + apiService.getUserCurrentFood( + request = mapOf("id" to userId) + ) + } + } + + suspend fun placeOrder( + orderRequest: PlaceOrderRequest + ): ApiResponse { + return safeApiCall { + apiService.placeOrder(request = orderRequest) + } + } + + suspend fun getSettlementOrders( + userId: Long + ): ApiResponse { + return safeApiCall { + apiService.getSettlementOrders( + request = mapOf("id" to userId) + ) + } + } + + suspend fun getMemberInfo( + userId: Long + ): ApiResponse { + return safeApiCall { + apiService.getMemberInfo( + request = mapOf("id" to userId) + ) + } + } + + suspend fun getMemberInfoByPhone( + phone: String, + password: String = "" + ): ApiResponse { + return safeApiCall { + apiService.getMemberInfoByPhone( + request = mapOf( + "phone" to phone, + "password" to password + ) + ) + } + } + + suspend fun getMemberDiscount( + userId: Long + ): ApiResponse { + return safeApiCall { + apiService.getMemberDiscount( + request = mapOf("id" to userId) + ) + } + } + + suspend fun bindUserOrder( + userId: Long, + orderNo: String, + mode: Int? = null + ): ApiResponse { + return safeApiCall { + apiService.bindUserOrder( + request = BindUserOrderRequest(userId, orderNo, mode) + ) + } + } + + suspend fun getCollectPage( + pageNum: Long = 1L, + pageSize: Long = 100L, + foodName: String? = null + ): ApiResponse?> { + return safeApiCall { + val request = mutableMapOf( + "pageNum" to pageNum, + "pageSize" to pageSize + ) + if (!foodName.isNullOrEmpty()) { + request["foodName"] = foodName + } + apiService.getCollectPage(request = request) + } + } + + suspend fun uploadCollect( + foodId: Long, + foodName: String, + version: String, + foodVector: String, + fileList: List + ): ApiResponse?> { + val params = hashMapOf() + params["foodId"] = RequestBody.create( + "text/plain".toMediaTypeOrNull(), + foodId.toString() + ) + params["foodName"] = RequestBody.create( + "text/plain".toMediaTypeOrNull(), + foodName + ) + params["version"] = RequestBody.create( + "text/plain".toMediaTypeOrNull(), + version + ) + params["foodVector"] = RequestBody.create( + "text/plain".toMediaTypeOrNull(), + foodVector + ) + + val fileParts = mutableListOf() + fileList.forEach { file -> + val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull()) + val filePart = MultipartBody.Part.createFormData( + "foodPics", + file.name, + requestFile + ) + fileParts.add(filePart) + } + + return safeApiCall { + apiService.uploadCollect(params = params, foodPics = fileParts) + } + } + + suspend fun deleteCollect( + foodId: Long, + version: String + ): ApiResponse { + return safeApiCall { + apiService.deleteCollect( + request = mapOf( + "foodId" to foodId, + "version" to version + ) + ) + } + } +} diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/NetViewModelV2.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/NetViewModelV2.kt new file mode 100644 index 0000000..f6f50cd --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/NetViewModelV2.kt @@ -0,0 +1,346 @@ +package com.sw.dualscreen.viewmodel + +import androidx.lifecycle.viewModelScope +import com.sw.dualscreen.model.request.v2.BindUserOrderRequest +import com.sw.dualscreen.model.request.v2.PlaceOrderRequest +import com.sw.dualscreen.model.response.FoodSearchReq +import com.sw.dualscreen.model.response.UserNutrition +import com.sw.dualscreen.model.response.v2.CollectedFoodV2 +import com.sw.dualscreen.model.response.v2.FaceVO +import com.sw.dualscreen.model.response.v2.NewFoodInfo +import com.sw.dualscreen.model.response.v2.NewMemberInfo +import com.sw.dualscreen.model.response.v2.SettlementOrder +import com.sw.dualscreen.network.ApiClient +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import timber.log.Timber + +class NetViewModelV2 : BaseViewModel() { + + companion object { + private const val TAG = "NetViewModelV2" + const val PAGE_SIZE = 100L + } + + fun getDeviceConfig( + onSuccess: (config: com.sw.dualscreen.model.response.DeviceConfig?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getDeviceConfig() + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "获取设备配置失败") + } + } catch (e: Exception) { + Timber.e(e, "获取设备配置异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getFacePage( + pageNum: Long = 1L, + pageSize: Long = PAGE_SIZE, + onSuccess: (list: List, hasNext: Boolean) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getFacePage(pageNum, pageSize) + if (parseResponse(response)) { + val list = response.data ?: emptyList() + val hasNext = list.size >= pageSize + onSuccess(list, hasNext) + } else { + onFailure(response.msg ?: "获取人脸数据失败") + } + } catch (e: Exception) { + Timber.e(e, "获取全量人脸数据异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getFaceIncrement( + pageNum: Long = 1L, + pageSize: Long = PAGE_SIZE, + timestamp: Long, + onSuccess: (list: List, hasNext: Boolean) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp) + if (parseResponse(response)) { + val list = response.data ?: emptyList() + val hasNext = list.size >= pageSize + onSuccess(list, hasNext) + } else { + onFailure(response.msg ?: "获取增量人脸数据失败") + } + } catch (e: Exception) { + Timber.e(e, "获取增量人脸数据异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getFoodByNames( + nameList: List, + deviceType: Int = 2, + onSuccess: (list: List) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getFoodByNames(nameList, deviceType) + if (parseResponse(response)) { + onSuccess(response.data ?: emptyList()) + } else { + onFailure(response.msg ?: "查询菜品失败") + } + } catch (e: Exception) { + Timber.e(e, "查询菜品异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getUserCurrentFood( + userId: Long, + onSuccess: (nutrition: UserNutrition?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getUserCurrentFood(userId) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "获取就餐营养数据失败") + } + } catch (e: Exception) { + Timber.e(e, "获取就餐营养数据异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun placeOrder( + orderRequest: PlaceOrderRequest, + onSuccess: (orderNo: String?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launchWithLoading { + try { + val response = ApiClient.repositoryV2.placeOrder(orderRequest) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "下单失败") + } + } catch (e: Exception) { + Timber.e(e, "开餐下单异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getSettlementOrders( + userId: Long, + onSuccess: (order: SettlementOrder?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getSettlementOrders(userId) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "查询结算订单失败") + } + } catch (e: Exception) { + Timber.e(e, "查询结算订单异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getMemberInfo( + userId: Long, + onSuccess: (memberInfo: NewMemberInfo?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getMemberInfo(userId) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "获取会员信息失败") + } + } catch (e: Exception) { + Timber.e(e, "获取会员信息异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getMemberInfoByPhone( + phone: String, + password: String = "", + onSuccess: (memberInfo: NewMemberInfo?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getMemberInfoByPhone(phone, password) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "查询会员失败") + } + } catch (e: Exception) { + Timber.e(e, "查询会员异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getMemberDiscount( + userId: Long, + onSuccess: (discount: String?) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getMemberDiscount(userId) + if (parseResponse(response)) { + onSuccess(response.data) + } else { + onFailure(response.msg ?: "查询会员折扣失败") + } + } catch (e: Exception) { + Timber.e(e, "查询会员折扣异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun bindUserOrder( + userId: Long, + orderNo: String, + mode: Int? = null, + onSuccess: () -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.bindUserOrder(userId, orderNo, mode) + if (parseResponse(response)) { + onSuccess() + } else { + onFailure(response.msg ?: "绑定订单失败") + } + } catch (e: Exception) { + Timber.e(e, "绑定订单异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun getCollectPage( + pageNum: Long = 1L, + pageSize: Long = PAGE_SIZE, + foodName: String? = null, + onSuccess: (list: List) -> Unit, + onFailure: (message: String) -> Unit + ) { + launch { + try { + val response = ApiClient.repositoryV2.getCollectPage(pageNum, pageSize, foodName) + if (parseResponse(response)) { + onSuccess(response.data ?: emptyList()) + } else { + onFailure(response.msg ?: "获取采集列表失败") + } + } catch (e: Exception) { + Timber.e(e, "获取采集列表异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun uploadCollect( + foodId: Long, + foodName: String, + version: String, + foodVector: String, + fileList: List, + onSuccess: (picUrls: List) -> Unit, + onFailure: (message: String) -> Unit + ) { + launchWithLoading { + try { + val params = hashMapOf() + params["foodId"] = okhttp3.RequestBody.create( + okhttp3.MediaType.parse("text/plain"), + foodId.toString() + ) + params["foodName"] = okhttp3.RequestBody.create( + okhttp3.MediaType.parse("text/plain"), + foodName + ) + params["version"] = okhttp3.RequestBody.create( + okhttp3.MediaType.parse("text/plain"), + version + ) + params["foodVector"] = okhttp3.RequestBody.create( + okhttp3.MediaType.parse("text/plain"), + foodVector + ) + + val response = ApiClient.repositoryV2.uploadCollect( + foodId, foodName, version, foodVector, + fileList.map { it.body().contentType()?.let { ct -> + okhttp3.RequestBody.create(ct, (it.body() as okhttp3.ResponseBody).bytes()) + } ?: it.body() } as List + ) + + if (parseResponse(response)) { + onSuccess(response.data ?: emptyList()) + } else { + onFailure(response.msg ?: "上传采集数据失败") + } + } catch (e: Exception) { + Timber.e(e, "上传采集数据异常") + onFailure("网络异常: ${e.message}") + } + } + } + + fun deleteCollect( + foodId: Long, + version: String, + onSuccess: () -> Unit, + onFailure: (message: String) -> Unit + ) { + launchWithLoading { + try { + val response = ApiClient.repositoryV2.deleteCollect(foodId, version) + if (parseResponse(response)) { + onSuccess() + } else { + onFailure(response.msg ?: "删除采集菜品失败") + } + } catch (e: Exception) { + Timber.e(e, "删除采集菜品异常") + onFailure("网络异常: ${e.message}") + } + } + } +}