这是一个基于Android的充值系统项目,采用模块化架构设计,使用Room数据库进行数据持久化存储。
项目包含以下模块:
app
: 主应用模块baselibrary
: 基础库,包含基本工具类和通用组件generallibrary
: 通用功能库,包含数据库操作等通用功能henanlibrary
: 河南地区特定功能模块qihealonelibrary
: 齐河单机版功能模块qiheonlinelibrary
: 齐河在线版功能模块ocridcardlibrary
: 身份证识别模块easysocket
: Socket通信模块pickerviewlibrary
: 选择器视图库在模块级build.gradle中启用ViewBinding:gradle android { buildFeatures { viewBinding true } }
使用示例:
```kotlin
class ExampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityExampleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityExampleBinding.inflate(layoutInflater)
setContentView(binding.root)
// 直接访问视图
binding.textView.text = "Hello ViewBinding"
}
}
```
在模块级build.gradle中启用DataBinding:gradle android { buildFeatures { dataBinding true } }
使用示例:
1. 布局文件:xml <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.User" /> </data> <LinearLayout> <TextView android:text="@{user.name}" /> </LinearLayout> </layout>
Activity中使用:
```kotlin
class ExampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityExampleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_example)
binding.user = User("张三")
binding.lifecycleOwner = this
}DataBinding:
在 RecyclerView 适配器中,当列表数据为空时,显示一个空视图(EmptyView)的实现方式:
继承 BaseRecycleAdapter:kotlin class YourAdapter : BaseRecycleAdapter<RecyclerView.ViewHolder>() { // 实现必要的方法 }
在适配器中定义视图类型常量(已在 BaseRecycleAdapter 中定义):kotlin companion object { const val VIEW_TYPE_ITEM = 1 const val VIEW_TYPE_EMPTY = 0 }
重写 getItemViewType 方法:kotlin override fun getItemViewType(position: Int): Int { if (dataList.isEmpty()) { return VIEW_TYPE_EMPTY } return VIEW_TYPE_ITEM }
重写 getItemCount 方法:kotlin override fun getItemCount(): Int { if (dataList.isEmpty()) { return 1 // 返回1表示显示空视图 } return dataList.size }
在 onCreateViewHolder 中处理不同类型的视图:kotlin override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { if (viewType == VIEW_TYPE_EMPTY) { val emptyView: ItemNoMoreBinding = DataBindingUtil.inflate( (parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)!!, R.layout.item_no_more, parent, false ) return ViewHolderEmpty(emptyView) } else { val binding = ItemListBinding.inflate( parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater, parent, false ) return ItemViewHolder(binding.root) } }
重写 onBindViewHolder 方法:
```kotlin
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolderEmpty) {
// 空视图不需要绑定数据
return
}
// 绑定列表项数据
if (holder is ItemViewHolder) {
val item = dataList[position]
holder.bind(item)
}
}
```
空视图的布局文件示例(item_no_more.xml):
```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_empty" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="暂无数据"
android:textColor="@color/text_gray"
android:textSize="14sp" />
这种实现方式的优点:
1. 统一的空视图处理逻辑
2. 支持自定义空视图样式
3. 不影响列表正常数据的显示
4. 便于维护和扩展
@Database(entities = [PassWordCardBean::class, CardData::class, ProjectDataBean::class], version = 1)
主要实体:
- PassWordCardBean: 密码卡数据
- CardData: 卡片数据
- ProjectDataBean: 项目数据
项目中所有的数据类(Data Class)都应该统一放在bean
包下的相应子目录中,按照功能和用途进行分类:
generallibrary/src/main/java/com/dayu/general/bean/
├── net/ # 网络接口相关数据类
├── card/ # 卡片相关数据类
├── db/ # 数据库实体类
└── ... # 其他功能模块数据类
所有API接口的请求和响应数据类都放在net
目录下:
// 请求数据类示例
data class RechargeRequest(
val rechargeType: Int,
val cardNum: String,
val money: String,
// ... 其他字段
)
// 响应数据类示例
data class CardCancelResult(
val projectNo: Int,
val cardNum: String,
val orderNo: String
)
所有卡片操作相关的数据类放在card
目录下:
data class UserCard(
var cardType: String = "",
var balance: Int = 0,
var userCode: String = "",
// ... 其他字段
)
所有Room数据库的实体类放在db
目录下:
@Entity(tableName = "card_data")
data class CardData(
@PrimaryKey val id: Long,
val cardNumber: String,
// ... 其他字段
)
Result
结尾CardCancelResult
- 销卡接口响应RechargeResult
- 充值接口响应WaterPriceResult
- 水价接口响应Request
结尾RechargeRequest
- 充值接口请求SearchUserBeanRequest
- 用户搜索请求UserCard
- 用户卡片数据ClearCard
- 清零卡数据CardData
- 卡片数据表ProjectDataBean
- 项目数据表package com.dayu.general.bean.net
/**
* 销卡结果数据类
* @author: zuo
* @date: 2025/01/17
* @description: 销卡接口返回数据
*/
data class CardCancelResult(
val projectNo: Int, // 项目编号
val cardNum: String, // 卡号
val orderNo: String // 订单号
)
在Activity或其他类中使用数据类时,应该导入具体的数据类:
// 正确的导入方式
import com.dayu.general.bean.net.CardCancelResult
import com.dayu.general.bean.net.RechargeResult
// 在代码中使用
private fun handleCancelResult(result: CardCancelResult) {
// 处理销卡结果
}
禁止在Activity或其他类中内联定义数据类:
// ❌ 错误做法 - 不要在Activity中内联定义数据类
class CardCancelActivity : BaseNfcActivity() {
// ❌ 禁止这样做
data class CardCancelResult(
val projectNo: Int,
val cardNum: String,
val orderNo: String
)
}
// ✅ 正确做法 - 在bean包中定义数据类
// 文件: generallibrary/src/main/java/com/dayu/general/bean/net/CardCancelResult.kt
data class CardCancelResult(
val projectNo: Int,
val cardNum: String,
val orderNo: String
)
对于已经存在的内联数据类,应该按照以下步骤进行迁移:
通过统一的数据类管理规范,可以提高代码的可维护性和可读性,使项目结构更加清晰规范。
克隆项目:bash git clone [项目地址]
在Android Studio中打开项目
同步Gradle文件
构建项目:bash ./gradlew build
运行应用:
./gradlew installDebug
基础功能库,提供:
- 基础Activity
- 通用工具类
- 基础UI组件
通用功能模块,包含:
- Room数据库实现
- 数据访问对象(DAO)
- 实体类定义
TitleBar是一个自定义的标题栏组件,提供了左中右三个位置的文本和图片设置功能。
<com.dayu.baselibrary.view.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="@dimen/dimen_title_height"
app:leftText="返回"
app:leftImage="@drawable/ic_back"
app:centerText="标题"
app:centerImage="@drawable/ic_logo"
app:rightText="更多"
app:rightImage="@drawable/ic_more"/>
设置文本和图片kotlin titleBar.apply { setLeftText("返回") setLeftImage(R.drawable.ic_back) setCenterText("标题") setRightText("更多") setRightImage(R.drawable.ic_more) }
点击事件监听
```kotlin
// 方式1:使用类型常量(推荐)
titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) {
finish()
}
titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) {
showMenu()
}
// 或者使用完整的OnClickListener
titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE, View.OnClickListener {
finish()
})
// 方式2:使用位置和类型常量(已废弃)
titleBar.setOnItemclickListner(TitleBar.IMAGE, TitleBar.LEFT) { finish() }
```
ClickType_LEFT_TEXT
: 左侧文本点击ClickType_LEFT_IMAGE
: 左侧图片点击ClickType_CENTER_TEXT
: 中间文本点击ClickType_CENTER_IMAGE
: 中间图片点击ClickType_RIGHT_TEXT
: 右侧文本点击ClickType_RIGHT_IMAGE
: 右侧图片点击// 设置右侧按钮状态
titleBar.setRightStatus(false) // 禁用右侧按钮
// 设置右侧图片可见性
titleBar.setRightIMGVisibility(View.GONE)
// 获取中间的TextView
val titleTextView = titleBar.getTitleTextView()
// 获取右侧布局
val rightLayout = titleBar.getLlRight()
// 在Activity的initView方法中设置
private fun initView() {
// 设置返回按钮点击事件
binding.titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) {
finish()
}
// 设置右侧文本按钮点击事件
binding.titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) {
// 处理右侧按钮点击逻辑
handleRightButtonClick()
}
}
项目中实现了支付方式的动态获取和显示功能,支持从服务器获取支付方式列表并动态创建RadioButton。
// 支付方式数据类
data class PaymentMethod(
val id: Long,
val name: String,
val remarks: String,
val deleted: Int
)
// 支付方式接口返回数据类
data class PaymentMethodResponse(
val itemTotal: Any?,
val obj: List<PaymentMethod>,
val pageCurr: Any?,
val pageSize: Any?,
val pageTotal: Any?
)
class YourActivity : AppCompatActivity() {
// 支付方式相关属性
private var paymentMethod: String = "现金"
private var paymentId: Long = 0
private var paymentMethodList: List<PaymentMethod> = listOf()
// ... 其他代码
}
/**
* 获取支付方式列表
*/
private fun getPaymentMethods() {
ApiManager.getInstance().requestGetLoading(
this,
"sell/paymentmethod/get",
PaymentMethodResponse::class.java,
null,
object : SubscriberListener<BaseResponse<PaymentMethodResponse>>() {
override fun onNext(response: BaseResponse<PaymentMethodResponse>) {
if (response.success) {
val paymentMethods = response.content?.obj ?: listOf()
if (paymentMethods.isNotEmpty()) {
paymentMethodList = paymentMethods
updatePaymentMethodRadioGroup()
}
} else {
Toast.makeText(this@YourActivity, "获取支付方式失败: ${response.msg}", Toast.LENGTH_SHORT).show()
}
}
override fun onError(e: Throwable?) {
super.onError(e)
Toast.makeText(this@YourActivity, "获取支付方式失败: ${e?.message ?: "网络异常"}", Toast.LENGTH_SHORT).show()
}
}
)
}
/**
* 更新支付方式RadioGroup
*/
private fun updatePaymentMethodRadioGroup() {
// 清空原有RadioButton
binding.paymentMethodRadioGroup.removeAllViews()
// 动态添加RadioButton
paymentMethodList.forEachIndexed { index, method ->
val radioButton = RadioButton(this)
radioButton.id = View.generateViewId()
radioButton.layoutParams = LinearLayout.LayoutParams(0, resources.getDimensionPixelSize(R.dimen.dimen_40), 1.0f)
// 设置样式
radioButton.text = method.name
radioButton.background = resources.getDrawable(R.drawable.radio_selector)
radioButton.buttonDrawable = null
radioButton.gravity = Gravity.CENTER
radioButton.setTextColor(resources.getColorStateList(R.color.radio_button_text_color))
radioButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
// 添加到RadioGroup
binding.paymentMethodRadioGroup.addView(radioButton)
// 默认选中第一个
if (index == 0) {
radioButton.isChecked = true
paymentMethod = method.name
paymentId = method.id
}
}
// 设置选择监听
binding.paymentMethodRadioGroup.setOnCheckedChangeListener { group, checkedId ->
for (i in 0 until group.childCount) {
val radioButton = group.getChildAt(i) as RadioButton
if (radioButton.id == checkedId) {
paymentMethod = radioButton.text.toString()
paymentId = paymentMethodList[i].id
break
}
}
}
}
<RadioGroup
android:id="@+id/paymentMethodRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 动态添加RadioButton,不需要预定义 -->
</RadioGroup>
getPaymentMethods()
前已经初始化了相关的UI组件View.generateViewId()
CrashReport.postCatchedException(e)
上报异常// 在API回调中处理不同类型的异常
object : SubscriberListener<BaseResponse<YourDataType>>() {
override fun onNext(response: BaseResponse<YourDataType>) {
if (response.success) {
// 处理成功情况
handleSuccess(response.content)
} else {
// 处理业务异常
handleBusinessError(response.code, response.msg)
}
}
override fun onError(e: Throwable?) {
super.onError(e)
// 处理网络异常
handleNetworkError(e)
// 重置UI状态
resetViewState()
}
}
// 业务异常处理方法
private fun handleBusinessError(code: String?, msg: String?) {
when (code) {
"1081" -> ToastUtil.show("该卡片未在系统中注册,请先进行开卡操作")
"1001" -> ToastUtil.show("权限不足,请联系管理员")
"1002" -> ToastUtil.show("账户余额不足")
else -> {
val errorMsg = when {
msg.isNullOrBlank() -> "操作失败,请重试"
msg.contains("数据不存在") -> "数据不存在,请检查输入信息"
msg.contains("网络") -> "网络连接异常,请检查网络后重试"
msg.contains("超时") -> "请求超时,请重试"
else -> "操作失败: $msg"
}
ToastUtil.show(errorMsg)
}
}
// 重置界面状态
resetViewState()
}
// 网络异常处理方法
private fun handleNetworkError(e: Throwable?) {
val errorMsg = when {
e?.message?.contains("timeout") == true -> "网络请求超时,请检查网络连接"
e?.message?.contains("network") == true -> "网络连接失败,请检查网络设置"
e?.message?.contains("host") == true -> "服务器连接失败,请稍后重试"
else -> "网络异常: ${e?.message ?: "未知错误"}"
}
ToastUtil.show(errorMsg)
}
### Dialog弹窗使用最佳实践
项目中提供了多种Dialog组件,用于不同的交互场景。推荐使用项目已有的Dialog组件来保持UI风格的一致性。
#### 常用Dialog组件
1. **ConfirmDialog**: 确认对话框,用于重要操作的二次确认
2. **TipDialog**: 提示对话框,用于显示提示信息
3. **EdtDialog**: 输入对话框,用于获取用户输入
4. **自定义Dialog**: 继承Dialog类实现特定功能
#### ConfirmDialog使用示例
// 基本用法 - 只显示消息
val dialog = ConfirmDialog(context, "操作成功")
dialog.show()
// 带标题的用法
val dialog = ConfirmDialog(context, "提示", "确认要删除这条记录吗?") {
// 点击确认按钮的回调
deleteRecord()
dialog.dismiss()
}
dialog.show()
// 在异常处理中使用
private fun handleError(title: String, message: String) {
activity?.let { activity ->
val confirmDialog = ConfirmDialog(activity, title, message) {
// 点击确认后的操作
resetViewState()
}
confirmDialog.show()
}
}
```
// 简单提示
val tipDialog = TipDialog(context, "操作完成")
tipDialog.show()
// 带回调的提示
val tipDialog = TipDialog(context, "确认退出应用?", object : TipUtil.TipListener {
override fun onCancle() {
// 取消操作
}
})
tipDialog.show()
class CustomDialog(context: Context) : Dialog(context, R.style.ws_pay_showSelfDialog) {
private lateinit var binding: DialogCustomBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogCustomBinding.inflate(layoutInflater)
setContentView(binding.root)
// 设置对话框属性
setupDialog()
// 初始化视图
initViews()
}
private fun setupDialog() {
// 设置对话框宽度为屏幕宽度的85%
window?.apply {
val params = attributes
params.width = (context.resources.displayMetrics.widthPixels * 0.85).toInt()
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
params.gravity = Gravity.CENTER
attributes = params
setBackgroundDrawableResource(android.R.color.transparent)
}
// 设置点击外部不取消
setCanceledOnTouchOutside(false)
}
private fun initViews() {
binding.btnConfirm.setOnClickListener {
// 处理确认逻辑
dismiss()
}
binding.btnCancel.setOnClickListener {
dismiss()
}
}
}
内存泄漏防护: 确保在Activity销毁时关闭Dialogkotlin override fun onDestroy() { super.onDestroy() dialog?.dismiss() }
生命周期管理: 在Fragment中使用Dialog时注意生命周期kotlin // 在Fragment中安全显示Dialog activity?.let { activity -> if (!activity.isFinishing && !activity.isDestroyed) { dialog.show() } }
样式一致性: 使用项目统一的Dialog样式kotlin // 使用项目定义的Dialog样式 super(context, R.style.ws_pay_showSelfDialog)
用户体验优化:
在处理金额相关的数据时,应该统一使用两位小数格式,确保显示的一致性和准确性。
// 使用String.format格式化金额为两位小数
val amount = 123.4
val formattedAmount = String.format("%.2f", amount)
// 结果: "123.40"
// 在显示时添加货币单位
binding.balanceText.text = "${formattedAmount} 元"
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
}
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
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来限制用户只能输入最多两位小数:
/**
* 设置金额输入限制,最多保留两位小数
*/
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)
项目中实现了完整的充值功能,包括充值接口调用和NFC写卡操作。
接口地址: /terminal/card/termRecharge
请求方式: POST
接口描述: 终端充值接口,用于创建充值订单并返回写卡所需信息
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
)
{
"rechargeType": 2,
"cardNum": "53232810100600001",
"money": "300.0",
"amount": "50",
"gift": "5",
"paymentId": "1838466162264350722",
"price": "0.90",
"remarks": "充值",
"operator": 2024090516595200300
}
data class RechargeResult(
val projectNo: Int, // 项目编号
val cardNum: String, // 卡号
val orderNo: String, // 订单号
val waterPrice: Double, // 水价
val time: String // 时间
)
{
"code": "0001",
"content": {
"projectNo": 10,
"cardNum": "53232810100600001",
"orderNo": "2506041414250065",
"waterPrice": 0.9,
"time": "2025-05-08 17:31:02"
},
"msg": "请求成功",
"success": true
}
/**
* 调用充值接口
*/
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 ?: "网络异常"}")
}
}
)
}
/**
* 启动写卡界面
*/
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)
}
在NfcWreatActivity
中,充值操作会被自动处理:
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 ?: "未知错误"}")
}
}
}
})
}
/terminal/card/termRecharge
接口创建充值订单// 充值请求数据类
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
接口描述: 获取当前系统水价信息
data class WaterPriceResult(
val price: Double // 水价
)
{
"code": "0001",
"content": {
"price": 0.9
},
"msg": "请求成功",
"success": true
}
class BaseApplication {
companion object {
// 水价信息
var waterPrice: Double = 0.0
}
}
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)
}
}
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), // 使用获取到的水价
// ... 其他参数 ...
)
// ... 接口调用代码 ...
}
}
// 水价结果数据类
data class WaterPriceResult(
val price: Double // 水价
)
[添加许可证信息]
[添加联系方式]