左晓为主开发手持机充值管理机
README.md
@@ -317,8 +317,17 @@
2. 点击事件监听
```kotlin
// 方式1:使用类型常量(推荐)
titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) { finish() }
titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) { showMenu() }
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() }
@@ -352,6 +361,173 @@
2. 设置文本或图片时,如果传入null或0,对应的视图将被隐藏
3. 组件默认使用垂直线性布局,确保在布局文件中设置合适的高度
#### 常见使用示例
```kotlin
// 在Activity的initView方法中设置
private fun initView() {
    // 设置返回按钮点击事件
    binding.titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) {
        finish()
    }
    // 设置右侧文本按钮点击事件
    binding.titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) {
        // 处理右侧按钮点击逻辑
        handleRightButtonClick()
    }
}
```
## 支付方式动态获取功能
项目中实现了支付方式的动态获取和显示功能,支持从服务器获取支付方式列表并动态创建RadioButton。
### 数据结构
```kotlin
// 支付方式数据类
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?
)
```
### 使用方式
#### 1. 在Activity中添加支付方式相关属性
```kotlin
class YourActivity : AppCompatActivity() {
    // 支付方式相关属性
    private var paymentMethod: String = "现金"
    private var paymentId: Long = 0
    private var paymentMethodList: List<PaymentMethod> = listOf()
    // ... 其他代码
}
```
#### 2. 获取支付方式列表
```kotlin
/**
 * 获取支付方式列表
 */
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()
            }
        }
    )
}
```
#### 3. 动态创建RadioButton
```kotlin
/**
 * 更新支付方式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
            }
        }
    }
}
```
#### 4. 布局文件配置
```xml
<RadioGroup
    android:id="@+id/paymentMethodRadioGroup"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <!-- 动态添加RadioButton,不需要预定义 -->
</RadioGroup>
```
### 功能特点
1. **动态获取**: 从服务器动态获取支付方式列表,支持后台配置
2. **自动布局**: 根据支付方式数量自动调整RadioButton布局
3. **样式统一**: 所有动态创建的RadioButton使用统一的样式
4. **默认选择**: 自动选中第一个支付方式作为默认选项
5. **事件处理**: 支持选择变化监听,实时更新当前选中的支付方式
### 注意事项
1. 确保在调用`getPaymentMethods()`前已经初始化了相关的UI组件
2. 动态创建的RadioButton需要设置唯一的ID,使用`View.generateViewId()`
3. 在Activity销毁时注意清理相关资源,避免内存泄漏
4. 网络请求失败时要有相应的错误处理机制
## 注意事项
1. 数据库迁移
@@ -366,6 +542,205 @@
   - 敏感数据需要加密存储
   - 注意用户数据的安全处理
4. 异常处理
   - 所有try catch块中必须使用`CrashReport.postCatchedException(e)`上报异常
   - 确保异常信息被正确记录和上报
   - 避免异常信息泄露敏感数据
5. API异常处理最佳实践
   - 针对特定错误码提供友好的用户提示
   - 区分网络异常、业务异常和系统异常
   - 异常发生后及时重置UI状态,允许用户重试
   - 提供明确的操作指引,如"请先进行开卡操作"
### API异常处理示例
```kotlin
// 在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使用示例
```kotlin
// 基本用法 - 只显示消息
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()
    }
}
```
#### TipDialog使用示例
```kotlin
// 简单提示
val tipDialog = TipDialog(context, "操作完成")
tipDialog.show()
// 带回调的提示
val tipDialog = TipDialog(context, "确认退出应用?", object : TipUtil.TipListener {
    override fun onCancle() {
        // 取消操作
    }
})
tipDialog.show()
```
#### 自定义Dialog最佳实践
```kotlin
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()
        }
    }
}
```
#### Dialog使用注意事项
1. **内存泄漏防护**: 确保在Activity销毁时关闭Dialog
```kotlin
override fun onDestroy() {
    super.onDestroy()
    dialog?.dismiss()
}
```
2. **生命周期管理**: 在Fragment中使用Dialog时注意生命周期
```kotlin
// 在Fragment中安全显示Dialog
activity?.let { activity ->
    if (!activity.isFinishing && !activity.isDestroyed) {
        dialog.show()
    }
}
```
3. **样式一致性**: 使用项目统一的Dialog样式
```kotlin
// 使用项目定义的Dialog样式
super(context, R.style.ws_pay_showSelfDialog)
```
4. **用户体验优化**:
   - 重要操作使用ConfirmDialog进行二次确认
   - 错误信息使用带标题的Dialog,提供清晰的错误分类
   - 长时间操作显示加载Dialog,避免用户误操作
## 贡献指南
1. Fork 项目