README.md
@@ -741,6 +741,589 @@ - 错误信息使用带标题的Dialog,提供清晰的错误分类 - 长时间操作显示加载Dialog,避免用户误操作 ### 金额格式化最佳实践 在处理金额相关的数据时,应该统一使用两位小数格式,确保显示的一致性和准确性。 #### 金额格式化方法 ```kotlin // 使用String.format格式化金额为两位小数 val amount = 123.4 val formattedAmount = String.format("%.2f", amount) // 结果: "123.40" // 在显示时添加货币单位 binding.balanceText.text = "${formattedAmount} 元" ``` #### 金额输入验证 ```kotlin private fun validateAmount(amountStr: String): Double? { if (amountStr.isEmpty()) { ToastUtil.show("请输入金额") return null } val amount = try { amountStr.toDouble() } catch (e: NumberFormatException) { ToastUtil.show("请输入有效的金额") return null } if (amount <= 0) { ToastUtil.show("金额必须大于0") return null } // 检查小数位数不超过2位 val decimalPlaces = amountStr.substringAfter('.', "").length if (decimalPlaces > 2) { ToastUtil.show("金额最多保留两位小数") return null } return amount } ``` #### 金额计算和显示示例 ```kotlin private fun calculateAndDisplayAmounts() { val rechargeAmount = validateAmount(binding.rechargeAmount.text.toString()) ?: return val bonusAmount = validateAmount(binding.bonusAmount.text.toString()) ?: 0.0 // 计算总金额 val totalAmount = rechargeAmount + bonusAmount // 格式化显示 val formattedRecharge = String.format("%.2f", rechargeAmount) val formattedBonus = String.format("%.2f", bonusAmount) val formattedTotal = String.format("%.2f", totalAmount) // 更新UI显示 binding.rechargeAmountText.text = "${formattedRecharge} 元" binding.bonusAmountText.text = "${formattedBonus} 元" binding.totalAmountText.text = "${formattedTotal} 元" } ``` #### EditText金额输入限制 在布局文件中设置金额输入的限制: ```xml <EditText android:id="@+id/amountEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberDecimal" android:digits="0123456789." android:maxLength="10" android:hint="请输入金额" /> ``` #### TextWatcher实现小数位限制 使用TextWatcher来限制用户只能输入最多两位小数: ```kotlin /** * 设置金额输入限制,最多保留两位小数 */ private fun setupAmountInputLimit(editText: EditText) { editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { val text = s.toString() if (text.isEmpty()) return // 检查是否包含小数点 if (text.contains(".")) { val parts = text.split(".") if (parts.size == 2) { val decimalPart = parts[1] // 如果小数位超过2位,截取前两位 if (decimalPart.length > 2) { val newText = "${parts[0]}.${decimalPart.substring(0, 2)}" editText.removeTextChangedListener(this) editText.setText(newText) editText.setSelection(newText.length) editText.addTextChangedListener(this) } } } // 防止输入多个小数点 val dotCount = text.count { it == '.' } if (dotCount > 1) { val newText = text.substring(0, text.lastIndexOf('.')) editText.removeTextChangedListener(this) editText.setText(newText) editText.setSelection(newText.length) editText.addTextChangedListener(this) } // 防止以小数点开头 if (text.startsWith(".")) { editText.removeTextChangedListener(this) editText.setText("0$text") editText.setSelection(editText.text.length) editText.addTextChangedListener(this) } } }) } // 在initView中调用 setupAmountInputLimit(binding.rechargeAmount) setupAmountInputLimit(binding.bonusAmount) ``` #### 金额格式化注意事项 1. **统一格式**: 所有金额显示都使用两位小数格式 2. **输入验证**: 验证用户输入的金额格式和范围 3. **计算精度**: 使用Double类型进行金额计算,避免精度丢失 4. **显示单位**: 金额显示时统一添加货币单位(如"元") 5. **边界检查**: 检查金额的最大值和最小值限制 ## 充值接口使用说明 项目中实现了完整的充值功能,包括充值接口调用和NFC写卡操作。 ### 充值接口详情 **接口地址**: `/terminal/card/termRecharge` **请求方式**: POST **接口描述**: 终端充值接口,用于创建充值订单并返回写卡所需信息 #### 请求参数 ```kotlin data class RechargeRequest( val rechargeType: Int, // 充值类型 (固定值: 2) val cardNum: String, // 卡号 val money: String, // 充值金额 (格式: "300.0") val amount: String, // 充值数量 val gift: String, // 赠送金额 val paymentId: String, // 支付方式ID val price: String, // 单价 (格式: "0.90") val remarks: String, // 备注 (默认: "充值") val operator: Long // 操作员ID ) ``` #### 请求示例 ```json { "rechargeType": 2, "cardNum": "53232810100600001", "money": "300.0", "amount": "50", "gift": "5", "paymentId": "1838466162264350722", "price": "0.90", "remarks": "充值", "operator": 2024090516595200300 } ``` #### 返回参数 ```kotlin data class RechargeResult( val projectNo: Int, // 项目编号 val cardNum: String, // 卡号 val orderNo: String, // 订单号 val waterPrice: Double, // 水价 val time: String // 时间 ) ``` #### 返回示例 ```json { "code": "0001", "content": { "projectNo": 10, "cardNum": "53232810100600001", "orderNo": "2506041414250065", "waterPrice": 0.9, "time": "2025-05-08 17:31:02" }, "msg": "请求成功", "success": true } ``` ### 充值功能实现 #### 1. 在Activity中调用充值接口 ```kotlin /** * 调用充值接口 */ private fun callRechargeApi(rechargeAmount: Double, bonusAmount: Double) { val cardNum = cardInfo?.cardNum ?: cardAddress ?: "" // 构建充值请求参数 val params = mapOf( "rechargeType" to 2, "cardNum" to cardNum, "money" to String.format("%.1f", rechargeAmount), "amount" to String.format("%.0f", bonusAmount), "gift" to String.format("%.0f", bonusAmount), "paymentId" to paymentId.toString(), "price" to "0.90", "remarks" to "充值", "operator" to 2024090516595200300L ) ApiManager.getInstance().requestPostLoading( this, "terminal/card/termRecharge", RechargeResult::class.java, params, object : SubscriberListener<BaseResponse<RechargeResult>>() { override fun onNext(response: BaseResponse<RechargeResult>) { if (response.success && response.code == "0001") { // 充值成功,跳转到写卡界面 response.content?.let { rechargeResult -> startWriteCardActivity(rechargeResult, rechargeAmount, bonusAmount) } } else { ToastUtil.show("充值失败: ${response.msg ?: "未知错误"}") } } override fun onError(e: Throwable?) { super.onError(e) ToastUtil.show("充值失败: ${e?.message ?: "网络异常"}") } } ) } ``` #### 2. 跳转到写卡界面 ```kotlin /** * 启动写卡界面 */ private fun startWriteCardActivity(rechargeResult: RechargeResult, rechargeAmount: Double, bonusAmount: Double) { // 创建UserCard对象用于写卡 val userCard = UserCard().apply { cardInfo?.let { info -> userCode = info.cardNum ?: "" balance = ((rechargeAmount + bonusAmount) * 100).toInt() // 转换为分 } // 设置其他必要信息 projectCode = rechargeResult.projectNo waterPrice = rechargeResult.waterPrice.toFloat() rechargeDate = java.util.Calendar.getInstance() } // 启动写卡Activity val intent = Intent(this, NfcWreatActivity::class.java).apply { putExtra("cardType", "USER_CARD") putExtra("cardAddr", cardAddress) putExtra("operationTypeCode", CardOperationType.Recharge.code) putExtra("orderNumber", rechargeResult.orderNo) putExtra("userCard", userCard) } startActivity(intent) } ``` #### 3. NFC写卡处理 在`NfcWreatActivity`中,充值操作会被自动处理: ```kotlin CardOperationType.Recharge -> { nfcWreatHelper.writeUserDataAsync(userCard, object : NFCCallBack { override fun isSusses(flag: Boolean, msg: String?) { runOnUiThread { if (flag) { postCardData(cardType, cardAddr) ToastUtil.show("充值写卡成功!") } else { ToastUtil.show("充值写卡失败: ${msg ?: "未知错误"}") } } } }) } ``` ### 充值流程说明 1. **用户输入**: 用户在充值界面输入充值金额和赠送金额 2. **参数验证**: 验证输入的金额格式和有效性 3. **接口调用**: 调用`/terminal/card/termRecharge`接口创建充值订单 4. **订单创建**: 服务器创建充值订单并返回订单信息 5. **跳转写卡**: 成功后跳转到NFC写卡界面 6. **NFC写卡**: 用户贴卡进行NFC写卡操作 7. **写卡完成**: 写卡成功后更新卡片余额 ### 注意事项 1. **金额格式**: 充值金额使用一位小数格式,如"300.0" 2. **操作员ID**: 需要根据实际登录用户设置正确的操作员ID 3. **支付方式**: 支付方式ID需要从支付方式接口获取 4. **错误处理**: 充值失败时要提供明确的错误信息 5. **写卡验证**: 写卡前要验证卡号一致性 6. **订单跟踪**: 保存订单号用于后续查询和跟踪 ### 相关数据类 ```kotlin // 充值请求数据类 data class RechargeRequest( val rechargeType: Int, val cardNum: String, val money: String, val amount: String, val gift: String, val paymentId: String, val price: String, val remarks: String, val operator: Long ) // 充值结果数据类 data class RechargeResult( val projectNo: Int, val cardNum: String, val orderNo: String, val waterPrice: Double, val time: String ) ``` ## 水价接口使用说明 项目中实现了水价的动态获取和管理功能,支持从服务器获取最新水价并在全局范围内使用。 ### 水价接口详情 **接口地址**: `/terminal/client/getWaterPrice` **请求方式**: GET **接口描述**: 获取当前系统水价信息 #### 返回参数 ```kotlin data class WaterPriceResult( val price: Double // 水价 ) ``` #### 返回示例 ```json { "code": "0001", "content": { "price": 0.9 }, "msg": "请求成功", "success": true } ``` ### 水价功能实现 #### 1. BaseApplication中的水价存储 ```kotlin class BaseApplication { companion object { // 水价信息 var waterPrice: Double = 0.0 } } ``` #### 2. MainActivity中延时获取水价 ```kotlin class MainActivity : BaseNfcActivity() { private val handler = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... 其他初始化代码 ... // 延时20秒后获取水价,避免影响启动性能 handler.postDelayed({ getWaterPrice() }, 20000) // 20秒延时 } /** * 获取水价信息 */ private fun getWaterPrice() { ApiManager.getInstance().requestGetLoading( this, "terminal/client/getWaterPrice", WaterPriceResult::class.java, null, object : SubscriberListener<BaseResponse<WaterPriceResult>>() { override fun onNext(response: BaseResponse<WaterPriceResult>) { if (response.success && response.code == "0001") { // 获取水价成功,保存到BaseApplication response.content?.let { waterPriceResult -> BaseApplication.waterPrice = waterPriceResult.price } } } override fun onError(e: Throwable?) { super.onError(e) // 网络异常时不显示错误信息,避免影响用户体验 } } ) } override fun onDestroy() { super.onDestroy() // 清理Handler回调,防止内存泄漏 handler.removeCallbacksAndMessages(null) } } ``` #### 3. RechargeDetailActivity中的水价检查和使用 ```kotlin class RechargeDetailActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... 其他初始化代码 ... // 检查并获取水价 checkAndGetWaterPrice() } /** * 检查并获取水价 */ private fun checkAndGetWaterPrice() { // 如果BaseApplication中的水价为空或为0,则重新获取 if (BaseApplication.waterPrice <= 0.0) { getWaterPrice() } } /** * 获取水价信息 */ private fun getWaterPrice() { ApiManager.getInstance().requestGetLoading( this, "terminal/client/getWaterPrice", WaterPriceResult::class.java, null, object : SubscriberListener<BaseResponse<WaterPriceResult>>() { override fun onNext(response: BaseResponse<WaterPriceResult>) { if (response.success && response.code == "0001") { // 获取水价成功,保存到BaseApplication response.content?.let { waterPriceResult -> BaseApplication.waterPrice = waterPriceResult.price } } else { // 获取水价失败,使用默认值 if (BaseApplication.waterPrice <= 0.0) { BaseApplication.waterPrice = 0.9 // 设置默认水价 } } } override fun onError(e: Throwable?) { super.onError(e) // 网络异常,使用默认值 if (BaseApplication.waterPrice <= 0.0) { BaseApplication.waterPrice = 0.9 // 设置默认水价 } } } ) } /** * 在充值接口中使用水价 */ private fun callRechargeApi(rechargeAmount: Double, bonusAmount: Double) { // 获取当前水价,如果为0则使用默认值 val currentWaterPrice = if (BaseApplication.waterPrice > 0.0) { BaseApplication.waterPrice } else { 0.9 // 默认水价 } // 构建充值请求参数 val params = mapOf( // ... 其他参数 ... "price" to String.format("%.2f", currentWaterPrice), // 使用获取到的水价 // ... 其他参数 ... ) // ... 接口调用代码 ... } } ``` ### 水价管理流程 1. **应用启动**: MainActivity启动后延时20秒获取水价,避免影响启动性能 2. **全局存储**: 获取到的水价存储在BaseApplication.waterPrice中 3. **按需获取**: 在需要使用水价的界面(如充值界面)检查水价是否为空 4. **重新获取**: 如果水价为空或为0,则重新调用接口获取 5. **默认处理**: 如果获取失败,使用默认水价0.9元 6. **实时使用**: 在充值等业务中使用最新的水价信息 ### 技术特点 1. **性能优化**: 延时获取避免影响应用启动速度 2. **全局共享**: 水价信息在BaseApplication中全局共享 3. **按需刷新**: 在需要时检查并重新获取水价 4. **容错处理**: 获取失败时使用默认水价,确保业务正常进行 5. **内存管理**: 正确处理Handler回调,防止内存泄漏 ### 使用注意事项 1. **延时设置**: MainActivity中的20秒延时可根据实际需求调整 2. **默认水价**: 默认水价0.9元可根据实际业务需求修改 3. **错误处理**: 水价获取失败时不显示错误信息,避免影响用户体验 4. **数据格式**: 水价使用Double类型存储,使用时格式化为两位小数 5. **生命周期**: 注意在Activity销毁时清理相关资源 ### 相关数据类 ```kotlin // 水价结果数据类 data class WaterPriceResult( val price: Double // 水价 ) ``` ## 贡献指南 1. Fork 项目 baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/BaseNfcReadHelper.java
@@ -5,6 +5,8 @@ import com.dayu.baselibrary.bean.BaseManagerToUserCard; import com.dayu.baselibrary.bean.BaseUserCardCard; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map; @@ -49,7 +51,18 @@ * * @return */ public abstract String getCradTypeAndCardNumber(); public String getCradTypeAndCardNumber(){ return getCradTypeAndCardNumber(1); }; /** * 获取卡片类型和卡号 * * @return */ public String getCradTypeAndCardNumber(int sectorIndex){ return null; }; /** * 读取NFC卡的特定扇区信息 @@ -81,6 +94,8 @@ return null; } /** * 返回监听类 */ baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NativeNfcReadHelper.java
@@ -7,6 +7,8 @@ import android.nfc.tech.MifareClassic; import android.util.Log; import androidx.annotation.NonNull; import com.dayu.baselibrary.bean.BaseManagerToUserCard; import com.dayu.baselibrary.bean.BaseUserCardCard; import com.dayu.baselibrary.tools.HexUtil; @@ -29,8 +31,6 @@ private Tag tag; // private NFCCallback callback; private static NativeNfcReadHelper helper; @Override @@ -435,9 +435,6 @@ } public String getCardNumberNoClose() { if (tag == null) { return ""; @@ -483,13 +480,19 @@ return ""; } @Override public String getCradTypeAndCardNumber() { return getCradTypeAndCardNumber(1); } /** * 获取卡片类型和卡号 * * @return */ @Override public String getCradTypeAndCardNumber() { public String getCradTypeAndCardNumber(int sectorIndex) { MifareClassic mfc = MifareClassic.get(tag); if (null != mfc) { @@ -498,10 +501,18 @@ StringBuilder strData = new StringBuilder(); //获取当前卡号 boolean isOpen = false; for (int i = 0; i < listKeyA.size(); i++) { if (mfc.authenticateSectorWithKeyA(0, listKeyA.get(i))) { if (!listKeyA.isEmpty()) { for (int i = 0; i < listKeyA.size(); i++) { if (mfc.authenticateSectorWithKeyA(0, listKeyA.get(i))) { isOpen = true; break; } } } else if (!listA_PS.isEmpty()) { if (mfc.authenticateSectorWithKeyA(0, listA_PS.get(0))) { isOpen = true; break; } else if (mfc.authenticateSectorWithKeyA(0, defauleKey)) { isOpen = true; } } if (isOpen) { @@ -517,15 +528,23 @@ } } //获取卡片类型 for (int i = 0; i < listKeyA.size(); i++) { if (mfc.authenticateSectorWithKeyA(1, listKeyA.get(i))) { if (!listKeyA.isEmpty()) { for (int i = 0; i < listKeyA.size(); i++) { if (mfc.authenticateSectorWithKeyA(sectorIndex, listKeyA.get(i))) { isOpen = true; break; } } } else if (!listA_PS.isEmpty()) { if (mfc.authenticateSectorWithKeyA(sectorIndex, listA_PS.get(sectorIndex))) { isOpen = true; break; } else if (mfc.authenticateSectorWithKeyA(sectorIndex, defauleKey)) { isOpen = true; } } if (isOpen) { int bIndex = mfc.sectorToBlock(1); byte[] data = mfc.readBlock(bIndex + 0); int bIndex = mfc.sectorToBlock(sectorIndex); byte[] data = mfc.readBlock(bIndex + sectorIndex); if (data != null && data.length > 0) { String hex = HexUtil.byteToHex(data[0]); strData.append(hex); @@ -676,4 +695,6 @@ } } baselibrary/src/main/java/com/dayu/baselibrary/tools/nfc/NfcReadAdapter.java
@@ -3,9 +3,13 @@ import android.app.Activity; import android.content.Intent; import androidx.annotation.NonNull; import com.dayu.baselibrary.activity.BaseNfcActivity; import com.dayu.baselibrary.bean.BaseUserCardCard; import com.dayu.baselibrary.utils.ModelUtils; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -92,8 +96,15 @@ switch (BaseNfcActivity.adapterType) { case ModelUtils.defaultType: return nativeNfcReadHelper.getCradTypeAndCardNumber(); } return ""; } @Override public String getCradTypeAndCardNumber(int sectorIndex) { switch (BaseNfcActivity.adapterType) { case ModelUtils.defaultType: return nativeNfcReadHelper.getCradTypeAndCardNumber(sectorIndex); } return ""; } @@ -126,4 +137,8 @@ } return null; } } generallibrary/src/main/java/com/dayu/general/BaseApplication.kt
@@ -27,6 +27,11 @@ var blockId:String="" var blockName:String="" // 水价信息 var waterPrice: Double = 0.0 // MainActivity的引用,用于调用水价获取方法 private var mainActivityInstance: com.dayu.general.activity.MainActivity? = null var projectDataDao: ProjectDataDao? = null @@ -42,6 +47,25 @@ return myApplication as BaseApplication } /** * 设置MainActivity实例 */ fun setMainActivity(activity: com.dayu.general.activity.MainActivity?) { mainActivityInstance = activity } /** * 请求获取水价,如果为空则调用MainActivity的获取方法 */ fun requestWaterPrice(): Double { if (waterPrice <= 0.0) { // 如果水价为空且MainActivity实例存在,则调用获取方法 mainActivityInstance?.getWaterPriceFromActivity() } // 如果水价仍为0,返回默认值 return if (waterPrice > 0.0) waterPrice else 0.9 } generallibrary/src/main/java/com/dayu/general/activity/MainActivity.kt
@@ -2,6 +2,8 @@ import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper import android.view.KeyEvent import android.view.LayoutInflater import android.widget.Toast @@ -13,6 +15,7 @@ import com.dayu.general.R import com.dayu.general.adapter.TabAdapter import com.dayu.general.bean.net.UserInfoResult import com.dayu.general.bean.net.WaterPriceResult import com.dayu.general.databinding.ActivityMainBinding import com.dayu.general.net.ApiManager import com.dayu.general.net.BaseResponse @@ -22,15 +25,33 @@ var binding: ActivityMainBinding? = null private val fragments: ArrayList<Fragment> = ArrayList() var mExitTime: Long = 0 private val handler = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(LayoutInflater.from(this)) setContentView(binding?.root) // 注册MainActivity实例到BaseApplication BaseApplication.setMainActivity(this) setupFragments() initView() initTab() getUserInfo() // 延时20秒后获取水价 handler.postDelayed({ getWaterPriceFromActivity() }, 20000) // 20秒延时 } override fun onDestroy() { super.onDestroy() // 清理Handler回调,防止内存泄漏 handler.removeCallbacksAndMessages(null) // 清理BaseApplication中的MainActivity引用 BaseApplication.setMainActivity(null) } override fun onNfcBack(intent: Intent) { @@ -45,6 +66,37 @@ } } /** * 获取水价信息 - 公开方法供其他地方调用 */ fun getWaterPriceFromActivity() { // 如果水价已存在且大于0,则不重复获取 if (BaseApplication.waterPrice > 0.0) { return } ApiManager.getInstance().requestGetHideLoading( this, "terminal/client/getWaterPrice", WaterPriceResult::class.java, null, object : SubscriberListener<BaseResponse<WaterPriceResult>>() { override fun onNext(response: BaseResponse<WaterPriceResult>) { if (response.success && response.code == "0001") { // 获取水价成功,保存到BaseApplication response.content?.let { waterPriceResult -> BaseApplication.waterPrice = waterPriceResult.price } } } override fun onError(e: Throwable?) { super.onError(e) // 网络异常时不显示错误信息,避免影响用户体验 } } ) } private fun getUserInfo() { // 使用正确的类型参数 generallibrary/src/main/java/com/dayu/general/activity/NfcWreatActivity.kt
@@ -83,10 +83,21 @@ binding?.cardData?.text = textData.toString() } CardOperationType.Recharge -> { var textData = StringBuilder() textData.append("用户充值\n") textData.append("订单号:" + orderNumber + "\n") if (userCard.balance != 0) { val balanceInYuan = userCard.balance / 100.0 // 转换为元 textData.append("充值金额:" + String.format("%.2f", balanceInYuan) + "元") } binding?.cardData?.text = textData.toString() } CardOperationType.CancelCard -> TODO() CardOperationType.CheckCard -> TODO() CardOperationType.DeductCard -> TODO() CardOperationType.Recharge -> TODO() CardOperationType.ReplaceCard -> TODO() null -> TODO() } @@ -136,10 +147,25 @@ } CardOperationType.Recharge -> { nfcWreatHelper.writeUserDataAsync(userCard, object : NFCCallBack { override fun isSusses(flag: Boolean, msg: String?) { // 确保Toast在主线程中调用 runOnUiThread { if (flag) { postCardData(cardType, cardAddr) ToastUtil.show("充值写卡成功!") } else { ToastUtil.show("充值写卡失败: ${msg ?: "未知错误"}") } } } }) } CardOperationType.CancelCard -> TODO() CardOperationType.CheckCard -> TODO() CardOperationType.DeductCard -> TODO() CardOperationType.Recharge -> TODO() CardOperationType.ReplaceCard -> TODO() null -> TODO() } generallibrary/src/main/java/com/dayu/general/activity/RechargeDetailActivity.kt
@@ -3,27 +3,36 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.TypedValue import android.view.View import android.widget.EditText import android.widget.RadioButton import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.dayu.baselibrary.net.subscribers.SubscriberListener import com.dayu.baselibrary.utils.ToastUtil import com.dayu.baselibrary.view.TitleBar import com.dayu.general.BaseApplication import com.dayu.general.R import com.dayu.general.bean.net.CardInfoResult import com.dayu.general.bean.net.PaymentMethod import com.dayu.general.bean.net.PaymentMethodResponse import com.dayu.general.bean.net.RechargeRequest import com.dayu.general.bean.net.RechargeResult import com.dayu.general.bean.card.UserCard import com.dayu.general.databinding.ActivityRechargeDetailBinding import com.dayu.general.net.ApiManager import com.dayu.general.net.BaseResponse import com.dayu.general.tool.CardOperationType class RechargeDetailActivity : AppCompatActivity() { private lateinit var binding: ActivityRechargeDetailBinding private var cardInfo: CardInfoResult? = null private var cardAddress: String? = null private var userCard: UserCard? = null // 支付方式相关属性 private var paymentMethod: String = "现金" @@ -33,11 +42,13 @@ companion object { private const val EXTRA_CARD_INFO = "extra_card_info" private const val EXTRA_CARD_ADDRESS = "extra_card_address" private const val EXTRA_USER_CARD = "extra_user_card" fun start(context: Context, cardInfo: CardInfoResult?, cardAddress: String?) { fun start(context: Context, cardInfo: CardInfoResult?, cardAddress: String?, userCard: UserCard?) { val intent = Intent(context, RechargeDetailActivity::class.java) intent.putExtra(EXTRA_CARD_INFO, cardInfo) intent.putExtra(EXTRA_CARD_ADDRESS, cardAddress) intent.putExtra(EXTRA_USER_CARD, userCard) context.startActivity(intent) } } @@ -53,11 +64,14 @@ // 获取传递的数据 cardInfo = intent.getSerializableExtra(EXTRA_CARD_INFO) as? CardInfoResult cardAddress = intent.getStringExtra(EXTRA_CARD_ADDRESS) userCard = intent.getSerializableExtra(EXTRA_USER_CARD) as? UserCard initView() displayCardInfo() // 获取支付方式 getPaymentMethods() // 确保水价可用(如果为空会自动触发MainActivity获取) BaseApplication.requestWaterPrice() } private fun initView() { @@ -68,9 +82,63 @@ // 设置按钮点击事件 binding.rechargeRegistBtn.setOnClickListener { // 处理写卡逻辑 handleWriteCard() // 处理充值逻辑 handleRecharge() } // 设置金额输入限制 setupAmountInputLimit(binding.rechargeMorny) setupAmountInputLimit(binding.rechargeWater) } /** * 设置金额输入限制,最多保留两位小数 */ private fun setupAmountInputLimit(editText: EditText) { editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { val text = s.toString() if (text.isEmpty()) return // 检查是否包含小数点 if (text.contains(".")) { val parts = text.split(".") if (parts.size == 2) { val decimalPart = parts[1] // 如果小数位超过2位,截取前两位 if (decimalPart.length > 2) { val newText = "${parts[0]}.${decimalPart.substring(0, 2)}" editText.removeTextChangedListener(this) editText.setText(newText) editText.setSelection(newText.length) editText.addTextChangedListener(this) } } } // 防止输入多个小数点 val dotCount = text.count { it == '.' } if (dotCount > 1) { val newText = text.substring(0, text.lastIndexOf('.')) editText.removeTextChangedListener(this) editText.setText(newText) editText.setSelection(newText.length) editText.addTextChangedListener(this) } // 防止以小数点开头 if (text.startsWith(".")) { editText.removeTextChangedListener(this) editText.setText("0$text") editText.setSelection(editText.text.length) editText.addTextChangedListener(this) } } }) } /** @@ -180,7 +248,16 @@ binding.redInitCode.text = cardAddress ?: "" binding.userName.text = info.userName ?: "" binding.redCardNum.text = info.cardNum ?: "" binding.redRemainderBlance.text = "${info.balance ?: 0} 元" // 使用用户卡中的余额显示 val balance = userCard?.let { // 将分转换为元,保留两位小数 String.format("%.2f", it.balance / 100.0) } ?: run { // 如果用户卡为空,则使用服务器返回的余额 String.format("%.2f", info.balance ?: 0.0) } binding.redRemainderBlance.text = "$balance 元" // 设置卡状态和对应颜色 val cardStatus = when (info.status) { @@ -203,7 +280,10 @@ } } private fun handleWriteCard() { /** * 处理充值逻辑 */ private fun handleRecharge() { // 验证充值金额 val rechargeAmountStr = binding.rechargeMorny.text.toString().trim() if (rechargeAmountStr.isEmpty()) { @@ -235,20 +315,115 @@ 0.0 } // 显示确认信息 val totalAmount = rechargeAmount + bonusAmount val confirmMessage = """ 确认充值信息: 卡号:${cardAddress} 充值金额:${rechargeAmount}元 赠送金额:${bonusAmount}元 总金额:${totalAmount}元 支付方式:${paymentMethod} """.trimIndent() // 调用充值接口 callRechargeApi(rechargeAmount, bonusAmount) } ToastUtil.show(confirmMessage) /** * 调用充值接口 */ private fun callRechargeApi(rechargeAmount: Double, bonusAmount: Double) { val cardNum = cardInfo?.cardNum ?: cardAddress ?: "" if (cardNum.isEmpty()) { ToastUtil.show("卡号信息缺失") return } // TODO: 调用写卡API // 这里可以添加实际的写卡逻辑 // 获取水价(如果为空会自动触发MainActivity获取) val currentWaterPrice = BaseApplication.requestWaterPrice() // 构建充值请求参数 val rechargeRequest = RechargeRequest( rechargeType = 2, cardNum = cardNum, money = String.format("%.0f", rechargeAmount), amount = String.format("%.0f", bonusAmount), gift = String.format("%.0f", bonusAmount), paymentId = paymentId.toString(), price = String.format("%.2f", currentWaterPrice), // 使用统一获取的水价 remarks = "充值", operator = BaseApplication.userId // 默认操作员ID,可以根据实际情况调整 ) // 转换为Map格式 val params = mapOf( "rechargeType" to rechargeRequest.rechargeType, "cardNum" to rechargeRequest.cardNum, "money" to rechargeRequest.money, "amount" to rechargeRequest.amount, "gift" to rechargeRequest.gift, "paymentId" to rechargeRequest.paymentId, "price" to rechargeRequest.price, "remarks" to rechargeRequest.remarks, "operator" to rechargeRequest.operator ) ApiManager.getInstance().requestPostLoading( this, "terminal/card/termRecharge", RechargeResult::class.java, params, object : SubscriberListener<BaseResponse<RechargeResult>>() { override fun onNext(response: BaseResponse<RechargeResult>) { if (response.success && response.code == "0001") { // 充值成功,跳转到写卡界面 response.content?.let { rechargeResult -> startWriteCardActivity(rechargeResult, rechargeAmount, bonusAmount) } ?: run { ToastUtil.show("充值成功但返回数据为空") } } else { ToastUtil.show("充值失败: ${response.msg ?: "未知错误"}") } } override fun onError(e: Throwable?) { super.onError(e) ToastUtil.show("充值失败: ${e?.message ?: "网络异常"}") } } ) } /** * 启动写卡界面 */ private fun startWriteCardActivity(rechargeResult: RechargeResult, rechargeAmount: Double, bonusAmount: Double) { try { // 创建UserCard对象用于写卡 val userCard = UserCard().apply { // 设置用户卡信息 cardInfo?.let { info -> userCode = info.cardNum ?: "" balance = ((rechargeAmount + bonusAmount) * 100).toInt() // 转换为分 } // 设置其他必要信息 projectCode = rechargeResult.projectNo waterPrice = rechargeResult.waterPrice.toFloat() rechargeDate = java.util.Calendar.getInstance() } // 启动写卡Activity val intent = Intent(this, NfcWreatActivity::class.java).apply { putExtra("cardType", "USER_CARD") // 用户卡类型 putExtra("cardAddr", cardAddress) putExtra("operationTypeCode", CardOperationType.Recharge.code) putExtra("orderNumber", rechargeResult.orderNo) putExtra("userCard", userCard) } startActivity(intent) // 显示成功信息 val formattedRecharge = String.format("%.2f", rechargeAmount) val formattedBonus = String.format("%.2f", bonusAmount) val formattedTotal = String.format("%.2f", rechargeAmount + bonusAmount) ToastUtil.show("充值订单创建成功\n订单号: ${rechargeResult.orderNo}\n充值金额: ${formattedRecharge}元\n赠送金额: ${formattedBonus}元\n总金额: ${formattedTotal}元\n请贴卡进行写卡操作") } catch (e: Exception) { ToastUtil.show("启动写卡界面失败: ${e.message}") } } } generallibrary/src/main/java/com/dayu/general/activity/RechargeFragment.kt
@@ -10,11 +10,14 @@ import com.dayu.baselibrary.tools.nfc.NfcReadAdapter import com.dayu.baselibrary.utils.ToastUtil import com.dayu.baselibrary.view.ConfirmDialog import com.dayu.baselibrary.view.TipDialog import com.dayu.general.bean.net.CardInfoResult import com.dayu.general.databinding.FragmentRechargeBinding import com.dayu.general.net.ApiManager import com.dayu.general.net.BaseResponse import com.dayu.general.tool.NfcReadHelper import com.dayu.general.bean.card.UserCard import com.dayu.general.tool.CardCommon class RechargeFragment : Fragment() { var binding: FragmentRechargeBinding? = null @@ -28,25 +31,25 @@ binding = FragmentRechargeBinding.inflate(inflater, container, false) return binding?.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initView() } private fun initView() { // 初始化界面显示读卡状态 binding?.rechargeReadLL?.visibility = View.VISIBLE } private fun resetView() { // 重置界面显示读卡状态 binding?.rechargeReadLL?.visibility = View.VISIBLE /** * 显示确认对话框 */ private fun showConfirmDialog(message: String, onConfirm: () -> Unit) { activity?.let { activity -> val confirmDialog = TipDialog(activity, message) { onConfirm() } confirmDialog.show() } } /** * 处理NFC刷卡信息 * 该方法由MainActivity调用 @@ -56,32 +59,60 @@ try { // 检查intent中是否包含NFC Tag if (intent.getParcelableExtra<android.nfc.Tag>(android.nfc.NfcAdapter.EXTRA_TAG) == null) { ToastUtil.show("未检测到NFC卡片,请确保卡片已正确放置") showConfirmDialog("未检测到NFC卡片,请确保卡片已正确放置") { } return } // 使用NfcReadAdapter读取卡号 val nfcAdapter = NfcReadHelper.getInstance(intent, activity) cardNumber = nfcAdapter.getCardNumber() if (cardNumber.isNullOrEmpty()) { ToastUtil.show("读卡失败,请重新刷卡") val cardTypeAndCardNumber = nfcAdapter.getCardTypeAndCardNumber() if (cardTypeAndCardNumber.isNullOrBlank() || !cardTypeAndCardNumber.contains(",")) { showConfirmDialog("卡片信息读取失败,请重新刷卡") { } return } val parts = cardTypeAndCardNumber.split(",") if (parts.size < 2) { showConfirmDialog("卡片信息格式异常,请重新刷卡") { } return } val cardNumber = parts[0] val cardType = parts[1] this.cardNumber = cardNumber if (cardNumber.isBlank()) { showConfirmDialog("卡号为空,无法进行充值,请重新刷卡") { } return } if (cardType != CardCommon.USER_CARD_TYPE_1) { showConfirmDialog("该卡片不是用户卡,请使用正确的用户卡进行充值操作。") { } return } // 解析用户卡数据 val userCard = nfcAdapter.getUserCardData() if (userCard == null) { showConfirmDialog("解析卡片数据失败,请重新刷卡") { } return } // 根据卡号获取卡片详细信息 getCardInfo(cardNumber!!) getCardInfo(cardNumber, userCard) } catch (e: Exception) { ToastUtil.show("读卡异常:${e.message}") showConfirmDialog("读卡异常:${e.message}") { } e.printStackTrace() } } } /** * 获取卡片详细信息 */ private fun getCardInfo(cardNumber: String) { private fun getCardInfo(cardNumber: String, userCard: UserCard) { activity?.let { activity -> val map = mutableMapOf<String, Any>() map["cardAddr"] = cardNumber @@ -93,8 +124,8 @@ object : SubscriberListener<BaseResponse<CardInfoResult>>() { override fun onNext(t: BaseResponse<CardInfoResult>) { if (t.success) { // 跳转到充值详情页面 RechargeDetailActivity.start(activity, t.content, cardNumber) // 跳转到充值详情页面,传递用户卡信息 RechargeDetailActivity.start(activity, t.content, cardNumber, userCard) } else { // 处理获取失败的情况 handleCardInfoError(t.code, t.msg) @@ -103,28 +134,28 @@ override fun onError(e: Throwable?) { super.onError(e) ToastUtil.show("获取卡信息失败: ${e?.message ?: "网络异常,请检查网络连接"}") // 重置界面状态 resetView() showConfirmDialog("获取卡信息失败: ${e?.message ?: "网络异常,请检查网络连接"}") { } } } ) } } /** * 处理卡信息获取错误 */ private fun handleCardInfoError(code: String?, msg: String?) { val errorTitle: String val errorMessage: String when (code) { "1001" -> { // 数据不存在的特殊处理 errorTitle = "卡片未注册" errorMessage = "该卡片未在系统中注册,请先进行开卡操作后再充值。" } else -> { // 其他错误的通用处理 errorTitle = "获取卡信息失败" @@ -137,14 +168,9 @@ } } } // 显示确认对话框 activity?.let { activity -> val confirmDialog = ConfirmDialog(activity, errorTitle, errorMessage) { // 点击确认按钮后关闭对话框并重置界面 resetView() } confirmDialog.show() showConfirmDialog(errorMessage) { } } } generallibrary/src/main/java/com/dayu/general/bean/net/RechargeRequest.kt
New file @@ -0,0 +1,18 @@ package com.dayu.general.bean.net import java.io.Serializable /** * 充值请求参数 */ data class RechargeRequest( val rechargeType: Int, // 充值类型 val cardNum: String, // 卡号 val money: String, // 充值金额 val amount: String, // 充值数量 val gift: String, // 赠送金额 val paymentId: String, // 支付方式ID val price: String, // 单价 val remarks: String, // 备注 val operator: String // 操作员ID ) : Serializable generallibrary/src/main/java/com/dayu/general/bean/net/RechargeResult.kt
New file @@ -0,0 +1,14 @@ package com.dayu.general.bean.net import java.io.Serializable /** * 充值接口返回结果 */ data class RechargeResult( val projectNo: Int, // 项目编号 val cardNum: String, // 卡号 val orderNo: String, // 订单号 val waterPrice: Double, // 水价 val time: String // 时间 ) : Serializable generallibrary/src/main/java/com/dayu/general/bean/net/WaterPriceResult.kt
New file @@ -0,0 +1,10 @@ package com.dayu.general.bean.net import java.io.Serializable /** * 水价接口返回结果 */ data class WaterPriceResult( val price: Double // 水价 ) : Serializable generallibrary/src/main/java/com/dayu/general/tool/CardOperationType.kt
@@ -21,6 +21,8 @@ 3 -> CancelCard 4 -> ReplaceCard 5 -> DeductCard 6 -> CleanCard 7 -> CheckCard else -> null } } generallibrary/src/main/java/com/dayu/general/tool/NfcReadHelper.kt
@@ -6,6 +6,7 @@ import android.widget.RelativeLayout import com.dayu.baselibrary.tools.nfc.BaseNfcReadHelper import com.dayu.baselibrary.tools.nfc.NfcReadAdapter import com.dayu.general.bean.card.UserCard import com.pnikosis.materialishprogress.ProgressWheel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable @@ -165,7 +166,7 @@ */ fun getCardTypeAndCardNumber(): String { return try { adapter.cradTypeAndCardNumber adapter.getCradTypeAndCardNumber(7) } catch (e: Exception) { e.printStackTrace() "" @@ -315,4 +316,27 @@ fun clearDisposables() { compositeDisposable.clear() } } /** * 读取用户卡数据 * @return UserCard对象,如果读取失败则返回null */ fun getUserCardData(): UserCard? { return try { // 获取基础卡数据 val baseCard = adapter.getUserCardData(UserCard()) // 如果获取成功且是UserCard类型,则返回 if (baseCard is UserCard) { baseCard } else { null } } catch (e: Exception) { e.printStackTrace() null } } } generallibrary/src/main/res/layout/activity_recharge_detail.xml
@@ -133,7 +133,7 @@ android:text="" android:textColor="#333333" android:textSize="14sp" android:textStyle="bold" /> /> </LinearLayout> <!-- 卡状态 --> @@ -266,6 +266,8 @@ android:background="@drawable/edit_text_bg" android:hint="请输入充值金额" android:inputType="numberDecimal" android:digits="0123456789." android:maxLength="10" android:padding="12dp" android:textSize="14sp" android:maxLines="1" /> @@ -293,6 +295,8 @@ android:background="@drawable/edit_text_bg" android:hint="赠送金额(选填)" android:inputType="numberDecimal" android:digits="0123456789." android:maxLength="10" android:padding="12dp" android:textSize="14sp" android:maxLines="1" /> generallibrary/src/main/res/layout/dialog_search.xml
@@ -29,7 +29,7 @@ android:id="@+id/tv_farmer_name_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginTop="20dp" android:text="农户名称" android:textColor="#666666" android:textSize="14sp" @@ -39,11 +39,15 @@ <EditText android:id="@+id/et_farmer_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:layout_height="48dp" android:layout_marginTop="8dp" android:background="@drawable/edit_text_bg" android:hint="请输入农户名称(选填)" android:inputType="text" android:paddingStart="16dp" android:paddingTop="12dp" android:paddingEnd="16dp" android:paddingBottom="12dp" android:singleLine="true" android:textColorHint="#BBBBBB" android:textSize="15sp" @@ -53,7 +57,7 @@ android:id="@+id/tv_farmer_id_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="14dp" android:layout_marginTop="18dp" android:text="农户编号" android:textColor="#666666" android:textSize="14sp" @@ -63,11 +67,15 @@ <EditText android:id="@+id/et_farmer_id" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:layout_height="48dp" android:layout_marginTop="8dp" android:background="@drawable/edit_text_bg" android:hint="请输入农户编号(选填)" android:inputType="text" android:paddingStart="16dp" android:paddingTop="12dp" android:paddingEnd="16dp" android:paddingBottom="12dp" android:singleLine="true" android:textColorHint="#BBBBBB" android:textSize="15sp" @@ -77,7 +85,7 @@ android:id="@+id/tv_card_number_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="14dp" android:layout_marginTop="18dp" android:text="卡号" android:textColor="#666666" android:textSize="14sp" @@ -87,11 +95,15 @@ <EditText android:id="@+id/et_card_number" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:layout_height="48dp" android:layout_marginTop="8dp" android:background="@drawable/edit_text_bg" android:hint="请输入卡号(选填)" android:inputType="text" android:paddingStart="16dp" android:paddingTop="12dp" android:paddingEnd="16dp" android:paddingBottom="12dp" android:singleLine="true" android:textColorHint="#BBBBBB" android:textSize="15sp" @@ -101,13 +113,13 @@ android:id="@+id/tv_cancel" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginTop="28dp" android:layout_marginEnd="6dp" android:background="@drawable/cancel_button_ripple" android:clickable="true" android:focusable="true" android:paddingTop="10dp" android:paddingBottom="10dp" android:paddingTop="12dp" android:paddingBottom="12dp" android:stateListAnimator="@null" android:text="取消" android:textColor="#666666" generallibrary/src/main/res/layout/fragment_recharge.xml
@@ -4,12 +4,19 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/base_green_bg"> <com.dayu.baselibrary.view.TitleBar android:id="@+id/titleBar" android:layout_width="match_parent" android:layout_height="@dimen/dimen_title_height" android:background="@color/title_bar_bg" android:elevation="4dp" app:centerText="充值" /> <LinearLayout android:id="@+id/recharge_read_LL" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:layout_below="@+id/titleBar" android:orientation="vertical" android:visibility="visible"> @@ -65,14 +72,13 @@ android:scaleType="fitCenter" android:src="@mipmap/nfc_write" /> <!-- 显示读到的卡号 --> <TextView android:id="@+id/red_initCode" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:gravity="center" android:text="" android:text="请保持手持机和卡片不要移动" android:textColor="#333333" android:textSize="@dimen/new_card_size" android:textStyle="bold" />