diff --git a/CLAUDE.md b/CLAUDE.md index d95b3d3..cca4eeb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,104 +98,2295 @@ Repository (数据层: Retrofit API) ``` **关键基类**: -- `BaseBindingActivity`: 提供DataBinding和ViewModel绑定 -- `BaseViewModel`: 提供Loading管理、Lifecycle感知 -- `BasePageViewModel`: 扩展分页列表功能 +- `BaseActivity`: 协程支持、Loading管理、扫码功能、键盘控制 +- `BaseBindingActivity`: 提供DataBinding和ViewModel自动绑定 +- `BaseViewModel`: 提供Loading管理、Lifecycle感知、Activity结果处理 +- `BasePageViewModel`: 扩展分页列表功能、PageModel集成 - `CommonAdapter + BaseViewHolder`: 列表适配器统一封装 -### 网络请求架构 +## 基类架构详解 -**核心组件**: -- `ServiceCreator`: Retrofit实例创建,管理BaseURL和拦截器 -- `NetApply.api`: 统一的API调用入口 -- `RequestKtx.kt`: 协程+Flow的扩展函数封装 +### BaseActivity -**标准请求模式**: +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/BaseActivity.kt` +**核心能力**: +- **协程支持**: 实现`CoroutineScope`,自动管理协程生命周期 +- **Loading管理**: 内置LoadingDialog,支持30秒超时自动关闭 +- **扫码功能**: 封装ZXing扫码,自动处理相机权限申请 +- **键盘控制**: 点击空白区域自动隐藏软键盘 +- **Activity管理**: 通过ActivityCollector统一管理生命周期 +- **字体锁定**: 强制字体大小不随系统设置变化 +- **屏幕适配**: 集成AutoSize自动适配横屏1152dp × 720dp + +**关键方法**: ```kotlin -// 使用launchCollect扩展函数(无Loading) -launchCollect({ - NetApply.api.simplePost("endpoint", params.toRequestBody()) -}) { - onSuccess = { result: BaseResultBean -> - // 成功处理,result.data为返回数据 - } - onFailed = { code, message -> - // 失败处理,默认会showToast(message) - } - onComplete = { - // 请求完成(无论成功失败) +// 显示/隐藏Loading +fun loading() +fun loadingCancel() + +// 扫码功能 +fun scanCode(requestCode: Int) + +// 设置标题栏 +open fun setBackArrow(title: String) +``` + +### BaseBindingActivity + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/base/BaseBindingActivity.kt` + +**设计特点**: +- 使用**ViewBinding/DataBinding**自动绑定视图 +- 自动创建和管理ViewModel +- 生命周期自动绑定到lifecycleOwner +- 简化Activity样板代码 + +**标准开发模板**: +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_XXX) +class XxxActivity : BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_xxx + + override fun viewModelClass() = XxxViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("页面标题") + + // 绑定ViewModel到布局 + binding.viewModel = viewModel + + // 初始化其他UI组件 + initRecyclerView() + initListeners() } } +``` -// 使用launchLoadingCollect(带Loading弹窗) -launchLoadingCollect({ - NetApply.api.simplePost("endpoint", params.toRequestBody()) -}) { - onSuccess = { result -> } - onFailed = { code, message -> } +### BaseViewModel + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/base/BaseViewModel.kt` + +**核心能力**: +- **Loading管理**: 提供`showLoading()`/`dismissLoading()` +- **Activity结果处理**: `onActivityResult()`回调 +- **顶层Activity获取**: `getTopActivity()` +- **生命周期感知**: 继承自AndroidX ViewModel + +**核心方法**: +```kotlin +abstract class BaseViewModel : ViewModel(), ILoading { + // Loading管理 + override fun showLoading() + override fun dismissLoading() + + // Activity结果处理 + open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) + + // 获取顶层Activity + fun getTopActivity(): Activity } ``` -**API接口定义** (在 `module_base/http/user/interface/CommonService.kt`): +### BasePageViewModel +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/base/BasePageViewModel.kt` + +**核心特性**: +- 继承自`BaseViewModel` +- 集成**PageModel**自动处理分页逻辑 +- 实现**IGetData**接口,统一`getData()`方法 +- 实现**IOnItemClickListener**接口,处理列表点击 + +**标准使用模板**: ```kotlin -@POST("{url}") -suspend fun simplePost( - @Path("url", encoded = true) url: String, - @Body data: RequestBody -): BaseResultBean -``` +class XxxListViewModel : BasePageViewModel() { -### 路由系统 + // LiveData定义 + val searchText = MutableLiveData() + val dataList = MutableLiveData>() -使用**ARouter**进行模块间页面跳转: + // 适配器配置(在布局中使用) + val itemLayoutId = R.layout.item_xxx + val itemViewHolder = XxxViewHolder::class.java -```kotlin -// 路由常量定义在 ARouterConstants -ARouter.getInstance() - .build(ARouterConstants.ACTIVITY_URL_XXX) - .withString("key", value) - .navigation() + // 实现数据加载 + override fun getData() { + val requestBody = mapOf( + "page" to pageModel.page, + "limit" to pageModel.limit, + "searchText" to searchText.value + ).toRequestBody() -// 获取参数 -@Autowired(name = "key") -@JvmField -var param: String? = null + launchLoadingCollect({ + NetApply.api.getXxxList(requestBody) + }) { + onSuccess = { + pageModel.handleListBean(it) // 自动处理分页数据 + } + } + } -// 在onCreate中调用 -ARouter.getInstance().inject(this) -``` - -### 事件通信 - -**FlowBus** (module_base/impl/FlowBus.kt) - 基于Kotlin Flow的事件总线: - -```kotlin -// 发送事件 -FlowBus.post(ConstantEvent.EVENT_NAME, data) - -// 接收事件(在ViewModel中) -FlowBus.on(ConstantEvent.EVENT_NAME).collect { data -> - // 处理事件 + // 实现列表点击 + override fun onItemClick(position: Int, type: Int) { + val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as XxxBean + // 跳转详情页 + XxxDetailsActivity.start(getTopActivity(), bean.id) + } } ``` -常用事件常量定义在 `ConstantEvent.kt`。 +### PageModel - 分页工具类 -### 数据模型规范 +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/model/PageModel.kt` -所有API返回数据统一使用 `BaseResultBean`: +**核心功能**: +```kotlin +class PageModel { + var page: Int = 1 // 当前页码 + var limit: Int = 20 // 每页数量 + var rv: RecyclerView? // 列表引用 + + // 绑定SmartRefreshLayout(在Activity中调用) + fun bindSmartRefreshLayout( + srl: SmartRefreshLayout, + rv: RecyclerView, + getData: IGetData, + lifecycleOwner: LifecycleOwner + ) + + // 处理列表数据(在ViewModel中调用) + fun handleListBean(listBean: BaseListBean<*>?) +} +``` + +**在Activity中使用**: +```kotlin +override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("列表页面") + binding.viewModel = viewModel + + // 绑定分页逻辑 + viewModel.pageModel.bindSmartRefreshLayout( + binding.srl, // SmartRefreshLayout + binding.recyclerView, // RecyclerView + viewModel, // 实现了IGetData的ViewModel + this // LifecycleOwner + ) + + // 绑定列表点击 + binding.recyclerView.addOnItemClickListener(viewModel) +} +``` + +### CommonAdapter - 通用列表适配器 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/base/CommonAdapter.kt` + +**核心方法**: +```kotlin +class CommonAdapter( + val context: Context, + private val layoutId: Int, + private val viewHolderClass: Class> +) : RecyclerView.Adapter>() + +// 数据管理 +fun refresh(list: List?) // 刷新数据(清空后重新加载) +fun loadMore(list: List?) // 加载更多(追加数据) +fun addItem(item: Any?) // 添加单个项 +fun removeItem(position: Int) // 删除项 +fun getItem(position: Int): Any? // 获取项 + +// 事件监听 +fun addOnItemClickListener(listener: IOnItemClickListener) +``` + +**使用示例**: +```kotlin +// 在Activity中创建适配器 +val adapter = CommonAdapter( + context = this, + layoutId = R.layout.item_xxx, + viewHolderClass = XxxViewHolder::class.java +) +binding.recyclerView.adapter = adapter + +// 刷新数据 +adapter.refresh(dataList) + +// 添加点击事件 +adapter.addOnItemClickListener(viewModel) +``` + +### BaseViewHolder - ViewHolder基类 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/base/BaseViewHolder.kt` + +**标准实现模板**: +```kotlin +class XxxViewHolder(view: View) : + BaseViewHolder(view) { + + override fun onBind(item: Any?, position: Int) { + // 获取数据Bean + val bean = getItemBean(item) ?: return + + // 使用DataBinding绑定数据 + binding.apply { + this.bean = bean + tvName.text = bean.name + tvDate.text = bean.date + + // 设置整个条目可点击 + notifyItemClick(position, root) + + // 或设置特定按钮可点击 + notifyItemClick(position, btnEdit) + notifyItemClick(position, btnDelete) + } + } +} +``` + +## 网络请求架构详解 + +### ServiceCreator - Retrofit创建器 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/http/user/ServiceCreator.kt` + +**核心配置**: +```kotlin +object ServiceCreator { + // 动态获取IP地址(可在运行时修改) + var ipAddress = SharedPreferenceUtil.getString(Constant.Share.ipAddress) + .noNull(MyApplication.context.getString(R.string.system_url_inner)) + + fun createRetrofit(): Retrofit { + return Retrofit.Builder() + .baseUrl(ipAddress) + .client(httpClientBuilder(SelfLoginInterceptor).build()) + .addConverterFactory(FastJsonConverterFactory()) + .build() + } +} +``` + +**拦截器功能**: +- 自动添加`Authorization: Bearer {token}` +- 添加`timestamp`时间戳 +- 统一处理401(未登录)自动跳转登录页 +- 统一处理500错误并Toast提示 +- 打印请求和响应日志(Debug模式) + +### NetApply - API统一入口 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/http/net/NetApply.kt` ```kotlin -data class BaseResultBean( - val status: Int, // 状态码 - val msg: String, // 消息 - val data: T // 实际数据 +object NetApply { + private const val DEFAULT_TIMEOUT: Long = 30000L + var api: Api by Delegates.notNull() + + // Gson实例(支持日期格式化、空值处理) + val gson: Gson = GsonBuilder() + .setDateFormat(DevFinal.TIME.yyyyMMddHHmmss_HYPHEN) + .registerTypeAdapterFactory(NullStringAdapterFactory()) + .serializeNulls() + .create() +} +``` + +### Api接口定义 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt` + +**标准接口模式**: +```kotlin +interface Api { + companion object { + var BASE_URL = ServiceCreator.ipAddress + } + + // 通用POST请求 + @POST + suspend fun simplePost( + @Url url: String, + @Body body: RequestBody = mapOf("" to "").toRequestBody() + ): BaseResultBean + + // 具体业务接口示例 + + // 列表查询(返回分页数据) + @POST("DomExpCheckIn/search") + suspend fun getGncShouYunList(@Body data: RequestBody): BaseListBean + + // 详情查询(返回单个对象) + @POST("DomExpCheckIn/queryWbByNo") + suspend fun getGncShouYunDetails(@Query("wbNo") wbNo: String): BaseResultBean + + // 新增/编辑/删除(返回成功标志) + @POST("DomExpCheckIn/save") + suspend fun saveGncShouYun(@Body data: RequestBody): BaseResultBean + + // 文件上传 + @Multipart + @POST("api/upload") + suspend fun uploadFile(@Part file: MultipartBody.Part): BaseResultBean +} +``` + +### RequestKtx - 请求扩展函数 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/ktx/RequestKtx.kt` + +#### (1) launchCollect - 无Loading请求 + +适用场景:后台刷新、非关键操作、不需要阻塞用户操作的请求 + +```kotlin +fun Any.launchCollect( + block: suspend () -> T, + resultBuilder: ResultBuilder.() -> Unit ) ``` -业务Bean统一定义在 `module_base/bean/` 目录下,使用Kotlin data class。 +**使用示例**: +```kotlin +launchCollect({ + NetApply.api.getXxxList(params.toRequestBody()) +}) { + onSuccess = { result -> + // 成功处理 + dataList.value = result.data + } + onFailed = { code, message -> + // 失败处理(默认已showToast) + Log.e("TAG", "请求失败: $message") + } + onComplete = { + // 请求完成(无论成功失败) + isRefreshing.value = false + } +} +``` + +#### (2) launchLoadingCollect - 带Loading请求 + +适用场景:关键操作、提交表单、需要用户等待的请求 + +```kotlin +fun ILoading.launchLoadingCollect( + block: suspend () -> T, + resultBuilder: ResultBuilder.() -> Unit +) +``` + +**使用示例**: +```kotlin +launchLoadingCollect({ + NetApply.api.saveXxx(params.toRequestBody()) +}) { + onSuccess = { result -> + showToast("保存成功") + finish() // 关闭当前页面 + } + onFailed = { code, message -> + // 失败处理(已自动显示Toast和关闭Loading) + } +} +``` + +#### (3) toRequestBody - 数据转换 + +```kotlin +fun Any.toRequestBody(removeEmptyOrNull: Boolean = false): RequestBody +``` + +**功能**: +- 将Map或Bean自动转换为JSON格式的RequestBody +- `removeEmptyOrNull = true`: 移除空字符串和null值字段 + +**使用示例**: +```kotlin +val params = mapOf( + "page" to 1, + "limit" to 20, + "waybillNo" to "", // 空字符串 + "status" to null // null值 +) + +// 不移除空值 +params.toRequestBody() +// 生成: {"page":1,"limit":20,"waybillNo":"","status":null} + +// 移除空值 +params.toRequestBody(removeEmptyOrNull = true) +// 生成: {"page":1,"limit":20} +``` + +### BaseResultBean - 统一返回格式 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/bean/BaseResultBean.kt` + +```kotlin +open class BaseResultBean { + var msg: String? = null // 消息提示 + var status: String = "" // 状态码("1"=成功,其他=失败) + var data: T? = null // 实际数据 + + fun verifySuccess(): Boolean { + return status == "1" // 判断是否成功 + } +} +``` + +**BaseListBean - 分页返回**: +```kotlin +class BaseListBean { + var pages = 1 // 总页数 + var total = 0 // 总数量 + var list: ArrayList? = null // 数据列表 +} +``` + +## 统一UI组件规范 + +### 自定义控件 + +#### 1. PadSearchLayout - 搜索输入框组合控件 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/ui/weight/search/layout/PadSearchLayout.kt` + +**功能**: 集成多种输入类型的搜索框组件 + +**支持的类型**: +- `SearchLayoutType.INPUT` - 文本输入 +- `SearchLayoutType.INTEGER` - 数字输入 +- `SearchLayoutType.SPINNER` - 下拉选择 +- `SearchLayoutType.DATE` - 日期选择 + +**核心属性**: +```kotlin +type: SearchLayoutType // 控件类型 +value: String // 绑定的值(支持双向绑定@={}) +hint: String // 提示文字 +list: List // 下拉选项列表 +required: Boolean // 是否必填(显示*号) +icon: Int // 右侧图标资源ID +enable: Boolean // 是否可编辑 +refreshCallBack: () -> Unit // 刷新回调 +onIconClickListener: (View) -> Unit // 图标点击回调 +``` + +**使用示例**: +```xml + + + + + + + + +``` + +#### 2. PadDataLayout - 数据展示/编辑组合控件 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/ui/weight/data/layout/PadDataLayout.kt` + +**功能**: 带标题的数据展示/编辑控件(标题+输入框/下拉/日期) + +**支持的类型**: +- `DataLayoutType.INPUT` - 文本输入 +- `DataLayoutType.SPINNER` - 下拉选择 +- `DataLayoutType.DATE` - 日期选择 + +**核心属性**: +```kotlin +type: DataLayoutType // 控件类型 +title: String // 左侧标题 +titleLength: Int // 标题长度(用于对齐,单位:汉字个数) +value: String // 绑定的值(支持双向绑定@={}) +hint: String // 提示文字 +list: List // 下拉选项列表 +required: Boolean // 是否必填(显示*号) +icon: Int // 右侧图标 +enable: Boolean // 是否可编辑 +inputHeight: Int // 多行输入高度 +maxLength: Int // 最大长度 +``` + +**使用示例**: +```xml + + + + + + + + + + + + + + +``` + +#### 3. StatusView - 状态栏占位View + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/ui/weight/StatusView.kt` + +**功能**: 自动适配状态栏高度的占位View(用于沉浸式状态栏) + +**使用方式**: +```xml + +``` + +### 通用样式定义 + +#### 文本样式 (module_base/res/values/styles.xml) + +```xml + + + + + + + + + + + + + + +``` + +#### 按钮样式 + +```xml + + + + + + + + +``` + +#### 布局样式 + +```xml + + + + + + + + +``` + +### 颜色规范 (module_base/res/values/colors.xml) + +```xml + +#FF1C8CF5 +#0A80FC + + +#1E1E1E +#333333 +#666666 +#999999 +#FF999999 +#3CB5F3 + + +#FFFFFFFF +#FF000000 +#d9001b + + +#FFEDEDED +#FFEDEDED +#FFF6FBFF +#FFEDEDED +#F1F1F1 +#5c6890 +``` + +### Drawable背景规范 + +#### 输入框背景 + +```xml + + + + + + + + + + + + + + +``` + +**使用说明**: +- `bg_search_layout`: 搜索区域输入框专用(8dp圆角) +- `bg_data_layout`: 数据展示区域输入框专用(4dp圆角) +- `bg_input`: 通用输入框背景(白色+灰色边框+8dp圆角) + +#### 按钮背景 + +```xml + + + + + + + + +``` + +**常用按钮背景**: +- `@drawable/bg_btn_bottom` - 底部按钮(蓝色/灰色选择器) +- `@drawable/submit_shape` - 提交按钮(蓝色+5dp圆角) +- `@drawable/bg_red_radius_5` - 红色按钮(5dp圆角) +- `@drawable/bg_primary_radius_4` - 主色按钮(4dp圆角) + +#### 通用形状背景 + +- `@drawable/bg_white_radius_8` - 白色圆角背景(8dp) +- `@drawable/bg_white_circle` - 白色圆形背景 +- `@drawable/bg_dialog_round` - 对话框圆角背景 +- `@drawable/bg_confirm_dialog` - 确认对话框背景 + +### 通用布局组件 + +#### 标题栏 (title_tool_bar.xml) + +**使用方式**: +```xml + +``` + +在Activity中设置标题: +```kotlin +setBackArrow("页面标题") +``` + +## DataBinding适配器 + +项目中提供了丰富的BindingAdapter,简化DataBinding使用。 + +### 图片加载 (BindingAdapter.kt) + +```xml + +``` + +**支持的属性**: +- `loadImage`: 图片地址(URL或本地路径) +- `loadError`: 错误图片 +- `loadPlaceholder`: 占位图 +- `loadCircle`: 是否圆形(true/false) +- `loadRadius`: 圆角(dp) +- `loadWidth`/`loadHeight`: 指定宽高 + +### View可见性 (ViewAdapter.kt) + +```xml + + + + + + + + +``` + +### TextView文本对齐 (TextViewAdapter.kt) + +```xml + + + + + + + + +``` + +### Spinner下拉框 (SpinnerAdapter.kt) + +```xml + +``` + +### Shape动态背景 (ShapeBindingAdapter.kt) + +```xml + + + + + + + + +``` + +### EditText扩展 (EditTextKtx.kt) + +```xml + + + + + +``` + +## Kotlin扩展函数 + +### Toast扩展 (ToastKtx.kt) + +```kotlin +// 显示Toast(默认长时间) +showToast("操作成功") + +// 短时间Toast +showToast("提示信息", isShort = true) +``` + +### Dialog扩展 (DialogKtx.kt) + +```kotlin +// 确认对话框 +showConfirmDialog( + message = "确定要删除吗?", + title = "提示", + confirmText = "确定", + cancelText = "取消" +) { + // 确认操作 + deleteItem() +} +``` + +### 字符串处理 (CommonKtx.kt) + +```kotlin +// 空处理 +val text = nullableString.noNull("默认值") +val text2 = nullableString.noNull() // 空字符串 + +// 验证非空 +if (waybillNo.verifyNullOrEmpty("请输入运单号")) { + return // 验证失败会自动Toast +} +``` + +### 布尔转换 + +```kotlin +// Boolean -> Int +val intValue = booleanValue.toInt() // true=1, false=0 + +// String -> Boolean +val bool = "1".toBoolean() // "1"=true, 其他=false + +// Int -> Boolean +val bool = 1.toBoolean() // 1=true, 其他=false +``` + +### 日期格式化 + +```kotlin +// 格式化日期 +val dateStr = Date().formatDate() // "2025-11-12" +val dateStr2 = Date().formatDate("yyyy/MM/dd") // "2025/11/12" + +// 格式化日期时间 +val dateTimeStr = Date().formatDateTime() // "2025-11-12 14:30:00" +``` + +### JSON处理 + +```kotlin +// 对象转JSON +val json = userBean.toJson() +val jsonFormatted = userBean.toJson(format = true) // 格式化输出 + +// 对象转Map +val map = userBean.toMap() + +// Map去除空值 +val cleanMap = map.trim(deep = true) +``` + +### 权限申请 + +```kotlin +// 申请单个权限 +permission(Manifest.permission.CAMERA) { + // 获取权限后操作 + openCamera() +} + +// 申请多个权限 +permission( + Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE +) { + // 所有权限获取后操作 + takePicture() +} + +// 处理拒绝情况 +permission( + Manifest.permission.CAMERA, + onDenied = { + showToast("需要相机权限才能扫码") + }, + onGranted = { + openCamera() + } +) +``` + +## 路由系统 + +### ARouter路由注册 + +**路由常量定义**: `module_base/src/main/java/com/lukouguoji/module_base/router/ARouterConstants.kt` + +```kotlin +object ARouterConstants { + // 国内出港 + const val ACTIVITY_URL_GNC_SHOUYUN_LIST = "/gnc/GncShouYunListActivity" + const val ACTIVITY_URL_GNC_STASH_DETAILS = "/gnc/GncStashDetailsActivity" + + // 国际进港 + const val ACTIVITY_URL_GJJ_TALLY_LIST = "/gjj/GjjTallyListActivity" + const val ACTIVITY_URL_GJJ_GOODS_LIST = "/gjj/GjjGoodsListActivity" + + // 通用功能 + const val ACTIVITY_URL_SCAN = "/common/ScanActivity" +} +``` + +### 使用方式 + +```kotlin +// 1. 在Activity上添加注解 +@Route(path = ARouterConstants.ACTIVITY_URL_GNC_SHOUYUN_LIST) +class GncShouYunListActivity : BaseBindingActivity<...>() { + // ... +} + +// 2. 跳转页面 +ARouter.getInstance() + .build(ARouterConstants.ACTIVITY_URL_GNC_SHOUYUN_LIST) + .withString("waybillNo", "12345678901") + .withInt("type", 1) + .navigation() + +// 3. 接收参数 +@Autowired(name = "waybillNo") +@JvmField +var waybillNo: String? = null + +@Autowired(name = "type") +@JvmField +var type: Int = 0 + +override fun initOnCreate(savedInstanceState: Bundle?) { + ARouter.getInstance().inject(this) // 注入参数 + // 使用参数 + Log.d("TAG", "waybillNo: $waybillNo, type: $type") +} +``` + +## 事件通信 + +### FlowBus事件总线 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/impl/FlowBus.kt` + +```kotlin +// 定义事件常量(在ConstantEvent.kt中) +object ConstantEvent { + const val EVENT_REFRESH_LIST = "event_refresh_list" + const val EVENT_UPDATE_STATUS = "event_update_status" +} + +// 发送事件 +FlowBus.with(ConstantEvent.EVENT_REFRESH_LIST).emit("refresh") + +// 接收事件(在ViewModel中) +FlowBus.with(ConstantEvent.EVENT_REFRESH_LIST).observe(this) { data -> + // 处理事件 + loadData() +} + +// 粘性事件(先发送后接收也能收到) +FlowBus.withSticky("user_info").emit(userBean) +FlowBus.withSticky("user_info").observe(this) { user -> + // 处理用户信息 +} +``` + +## 开发规范 + +### 代码组织规范 + +#### 目录结构标准 + +``` +module_xxx/ +└── src/main/java/com/lukouguoji/xxx/ + ├── page/ # 页面目录(推荐新模式) + │ └── feature/ # 功能模块 + │ ├── list/ # 列表页 + │ │ ├── XxxListActivity.kt + │ │ ├── XxxListViewModel.kt + │ │ └── XxxListViewHolder.kt + │ ├── details/ # 详情页 + │ │ ├── XxxDetailsActivity.kt + │ │ └── XxxDetailsViewModel.kt + │ └── add/ # 新增/编辑页 + │ ├── XxxAddActivity.kt + │ └── XxxAddViewModel.kt + ├── activity/ # Activity目录(旧模式) + ├── viewModel/ # ViewModel目录(旧模式) + └── holder/ # ViewHolder目录 +``` + +#### 命名规范 + +**Activity命名**: +- 列表页: `XxxListActivity` +- 详情页: `XxxDetailsActivity` +- 新增页: `XxxAddActivity` +- 编辑页: `XxxEditActivity` + +**ViewModel命名**: +- 列表: `XxxListViewModel` +- 详情: `XxxDetailsViewModel` +- 新增: `XxxAddViewModel` + +**ViewHolder命名**: +- 格式: `XxxViewHolder` 或 `XxxListViewHolder` + +**Layout命名**: +- Activity: `activity_xxx_list.xml` +- Fragment: `fragment_xxx.xml` +- Item: `item_xxx.xml` + +### Bean定义规范 + +**文件位置**: `module_base/src/main/java/com/lukouguoji/module_base/bean/` + +```kotlin +// 使用Kotlin data class +data class XxxBean( + var id: String = "", + var name: String = "", + var date: String = "", + var status: String = "" +) + +// 如果需要DataBinding双向绑定,使用BaseObservable +class XxxBean : BaseObservable() { + + @Bindable + var name: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.name) + } + + @Bindable + var weight: String = "" + set(value) { + field = value + notifyPropertyChanged(BR.weight) + } + + // 使用ObservableBoolean/ObservableInt等 + val checked = ObservableBoolean(false) + val count = ObservableInt(0) + + // 计算属性 + val displayName: String + get() = "【$name】" +} +``` + +## 实际业务开发示例 + +### 列表页完整示例 + +#### Activity层 + +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_GNC_SHOUYUN_LIST) +class GncShouYunListActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_gnc_shouyun_list + + override fun viewModelClass() = GncShouYunListViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("国内出港收运记录") + + // 绑定ViewModel + binding.viewModel = viewModel + + // 绑定分页逻辑 + viewModel.pageModel.bindSmartRefreshLayout( + binding.srl, + binding.recyclerView, + viewModel, + this + ) + + // 绑定列表点击 + binding.recyclerView.addOnItemClickListener(viewModel) + + // 初始加载 + viewModel.refresh() + } +} +``` + +#### ViewModel层 + +```kotlin +class GncShouYunListViewModel : BasePageViewModel() { + + // LiveData定义 + val date = MutableLiveData(DateUtils.getCurrentTime().formatDate()) + val dest = MutableLiveData("") + val waybillNo = MutableLiveData("") + val count = MutableLiveData("0") + + // 适配器配置(在布局中使用) + val itemLayoutId = R.layout.item_gnc_shouyun + val itemViewHolder = GncShouYunListViewHolder::class.java + + // 数据加载 + override fun getData() { + val requestBody = mapOf( + "page" to pageModel.page, + "limit" to pageModel.limit, + "opDate" to date.value, + "dest" to dest.value, + "wbNo" to waybillNo.value + ).toRequestBody() + + launchLoadingCollect({ + NetApply.api.getGncShouYunList(requestBody) + }) { + onSuccess = { + pageModel.handleListBean(it) + count.value = it.total.toString() + } + } + } + + // 条目点击 + override fun onItemClick(position: Int, type: Int) { + val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as GncShouYunBean + GncStashDetailsActivity.start(getTopActivity(), bean.whId) + } + + // 搜索 + fun search() { + refresh() + } + + // 扫码 + fun scanWaybill() { + ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL) + } + + // 处理扫码结果 + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK && data != null) { + when (requestCode) { + Constant.RequestCode.WAYBILL -> { + waybillNo.value = data.getStringExtra(Constant.Result.CODED_CONTENT) + search() + } + } + } + } +} +``` + +#### ViewHolder层 + +```kotlin +class GncShouYunListViewHolder(view: View) : + BaseViewHolder(view) { + + override fun onBind(item: Any?, position: Int) { + val bean = getItemBean(item) ?: return + + // DataBinding绑定数据 + binding.bean = bean + + // 整个条目可点击 + notifyItemClick(position, binding.shouyunItem) + + // 特定控件点击 + binding.ivIcon.setOnClickListener { + bean.checked.set(!bean.checked.get()) + } + } +} +``` + +#### Layout层 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 详情页完整示例 + +#### Activity层 + +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_GNC_STASH_DETAILS) +class GncStashDetailsActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_gnc_stash_details + + override fun viewModelClass() = GncStashDetailsViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("国内出港运单详情") + + // 初始化ViewModel(传递参数) + viewModel.initOnCreated(intent) + + // 绑定ViewModel + binding.viewModel = viewModel + } + + companion object { + /** + * 静态启动方法(推荐方式) + */ + @JvmStatic + fun start(context: Context, whId: String) { + val starter = Intent(context, GncStashDetailsActivity::class.java) + .putExtra(Constant.Key.ID, whId) + context.startActivity(starter) + } + } +} +``` + +#### ViewModel层 + +```kotlin +class GncStashDetailsViewModel : BaseViewModel() { + + var id = "" + val dataBean = MutableLiveData() + + fun initOnCreated(intent: Intent) { + id = intent.getStringExtra(Constant.Key.ID) ?: "" + getData() + } + + private fun getData() { + launchLoadingCollect({ + NetApply.api.getGncStashDetails(id) + }) { + onSuccess = { + dataBean.value = it.data ?: GncStashBean() + } + } + } + + // 打印标签 + fun printLabel() { + dataBean.value?.let { bean -> + PrinterModel.printWaybillLabel(bean) + } + } +} +``` + +#### Layout层 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 新增/编辑页完整示例 + +#### Activity层 + +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_CAR_ADD) +class CarAddActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_car_add + + override fun viewModelClass() = CarAddViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + viewModel.initOnCreated(intent) + + // 根据页面类型设置标题 + when (viewModel.pageType) { + DetailsPageType.Add -> setBackArrow("新增车辆") + DetailsPageType.Edit -> setBackArrow("编辑车辆") + DetailsPageType.Details -> setBackArrow("车辆详情") + } + + binding.viewModel = viewModel + } + + companion object { + @JvmStatic + fun startForAdd(context: Context) { + val starter = Intent(context, CarAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Add.name) + context.startActivity(starter) + } + + @JvmStatic + fun startForEdit(context: Context, carId: String) { + val starter = Intent(context, CarAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Edit.name) + .putExtra(Constant.Key.ID, carId) + context.startActivity(starter) + } + + @JvmStatic + fun startForDetails(context: Context, carId: String) { + val starter = Intent(context, CarAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Details.name) + .putExtra(Constant.Key.ID, carId) + context.startActivity(starter) + } + } +} +``` + +#### ViewModel层 + +```kotlin +class CarAddViewModel : BaseViewModel() { + + var pageType: DetailsPageType = DetailsPageType.Add + var carId = "" + + val carBean = MutableLiveData(CarBean()) + val statusList = MutableLiveData>() + + fun initOnCreated(intent: Intent) { + // 获取页面类型 + pageType = DetailsPageType.valueOf( + intent.getStringExtra(Constant.Key.PAGE_TYPE) ?: DetailsPageType.Add.name + ) + + // 加载下拉列表 + loadStatusList() + + // 如果是编辑或详情,加载数据 + if (pageType != DetailsPageType.Add) { + carId = intent.getStringExtra(Constant.Key.ID) ?: "" + loadData() + } + } + + private fun loadStatusList() { + statusList.value = listOf( + KeyValue("1", "正常"), + KeyValue("2", "维修中"), + KeyValue("3", "停用") + ) + } + + private fun loadData() { + launchLoadingCollect({ + NetApply.api.getCarDetails(carId) + }) { + onSuccess = { + carBean.value = it.data ?: CarBean() + } + } + } + + fun submit() { + val bean = carBean.value ?: return + + // 验证 + if (bean.carId.verifyNullOrEmpty("请输入车辆编号")) return + if (bean.status.verifyNullOrEmpty("请选择状态")) return + + // 提交 + launchLoadingCollect({ + val params = mapOf( + "id" to carId, + "carId" to bean.carId, + "status" to bean.status, + "remark" to bean.remark + ).toRequestBody(removeEmptyOrNull = true) + + NetApply.api.saveCar(params) + }) { + onSuccess = { + showToast(if (pageType == DetailsPageType.Add) "新增成功" else "编辑成功") + + // 发送刷新事件 + FlowBus.with(ConstantEvent.EVENT_REFRESH_CAR_LIST).emit("refresh") + + // 关闭页面 + getTopActivity().finish() + } + } + } +} +``` + +#### Layout层 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## 开发检查清单 + +### 列表页开发清单(7步) + +**步骤1: 创建Bean** +```kotlin +// 位置: module_base/src/main/java/com/lukouguoji/module_base/bean/ +data class XxxBean( + var id: String = "", + var name: String = "", + var date: String = "", + var status: String = "" +) +``` + +**步骤2: 在Api中添加接口** +```kotlin +// 位置: module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt +@POST("api/xxx/list") +suspend fun getXxxList(@Body data: RequestBody): BaseListBean +``` + +**步骤3: 创建ViewHolder** +```kotlin +// 位置: module_xxx/src/main/java/.../holder/ +class XxxViewHolder(view: View) : + BaseViewHolder(view) { + + override fun onBind(item: Any?, position: Int) { + val bean = getItemBean(item) ?: return + binding.bean = bean + notifyItemClick(position, binding.root) + } +} +``` + +**步骤4: 创建ViewModel** +```kotlin +// 继承BasePageViewModel +class XxxListViewModel : BasePageViewModel() { + + val searchText = MutableLiveData() + val itemLayoutId = R.layout.item_xxx + val itemViewHolder = XxxViewHolder::class.java + + override fun getData() { + val requestBody = mapOf( + "page" to pageModel.page, + "limit" to pageModel.limit, + "searchText" to searchText.value + ).toRequestBody() + + launchLoadingCollect({ + NetApply.api.getXxxList(requestBody) + }) { + onSuccess = { + pageModel.handleListBean(it) + } + } + } + + override fun onItemClick(position: Int, type: Int) { + val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as XxxBean + // 跳转详情页 + } +} +``` + +**步骤5: 创建Activity** +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_XXX_LIST) +class XxxListActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_xxx_list + override fun viewModelClass() = XxxListViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("列表页面") + binding.viewModel = viewModel + + viewModel.pageModel.bindSmartRefreshLayout( + binding.srl, + binding.recyclerView, + viewModel, + this + ) + + binding.recyclerView.addOnItemClickListener(viewModel) + } +} +``` + +**步骤6: 创建Layout** +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**步骤7: 注册路由** +```kotlin +// 位置: module_base/.../router/ARouterConstants.kt +const val ACTIVITY_URL_XXX_LIST = "/xxx/XxxListActivity" +``` + +### 详情页开发清单(4步) + +**步骤1: 在Api中添加接口** +```kotlin +@POST("api/xxx/details") +suspend fun getXxxDetails(@Query("id") id: String): BaseResultBean +``` + +**步骤2: 创建ViewModel** +```kotlin +class XxxDetailsViewModel : BaseViewModel() { + + var id = "" + val dataBean = MutableLiveData() + + fun initOnCreated(intent: Intent) { + id = intent.getStringExtra(Constant.Key.ID) ?: "" + getData() + } + + private fun getData() { + launchLoadingCollect({ + NetApply.api.getXxxDetails(id) + }) { + onSuccess = { + dataBean.value = it.data ?: XxxBean() + } + } + } +} +``` + +**步骤3: 创建Activity(含静态start方法)** +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_XXX_DETAILS) +class XxxDetailsActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_xxx_details + override fun viewModelClass() = XxxDetailsViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("详情页面") + viewModel.initOnCreated(intent) + binding.viewModel = viewModel + } + + companion object { + @JvmStatic + fun start(context: Context, id: String) { + val starter = Intent(context, XxxDetailsActivity::class.java) + .putExtra(Constant.Key.ID, id) + context.startActivity(starter) + } + } +} +``` + +**步骤4: 创建Layout** +```xml + + + + + + + + + + + + + + + + + +``` + +### 新增/编辑页开发清单(5步) + +**步骤1: 在Api中添加接口** +```kotlin +@POST("api/xxx/save") +suspend fun saveXxx(@Body data: RequestBody): BaseResultBean + +@POST("api/xxx/details") +suspend fun getXxxDetails(@Query("id") id: String): BaseResultBean +``` + +**步骤2: 创建ViewModel** +```kotlin +class XxxAddViewModel : BaseViewModel() { + + var pageType: DetailsPageType = DetailsPageType.Add + var id = "" + + val dataBean = MutableLiveData(XxxBean()) + val optionList = MutableLiveData>() + + fun initOnCreated(intent: Intent) { + pageType = DetailsPageType.valueOf( + intent.getStringExtra(Constant.Key.PAGE_TYPE) ?: DetailsPageType.Add.name + ) + + loadOptions() + + if (pageType != DetailsPageType.Add) { + id = intent.getStringExtra(Constant.Key.ID) ?: "" + loadData() + } + } + + private fun loadData() { + launchLoadingCollect({ + NetApply.api.getXxxDetails(id) + }) { + onSuccess = { + dataBean.value = it.data ?: XxxBean() + } + } + } + + fun submit() { + val bean = dataBean.value ?: return + + // 验证 + if (bean.name.verifyNullOrEmpty("请输入名称")) return + + // 提交 + launchLoadingCollect({ + val params = mapOf( + "id" to id, + "name" to bean.name, + // 其他字段... + ).toRequestBody(removeEmptyOrNull = true) + + NetApply.api.saveXxx(params) + }) { + onSuccess = { + showToast(if (pageType == DetailsPageType.Add) "新增成功" else "保存成功") + FlowBus.with(ConstantEvent.EVENT_REFRESH_LIST).emit("refresh") + getTopActivity().finish() + } + } + } +} +``` + +**步骤3: 创建Activity(含多个静态start方法)** +```kotlin +@Route(path = ARouterConstants.ACTIVITY_URL_XXX_ADD) +class XxxAddActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_xxx_add + override fun viewModelClass() = XxxAddViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + viewModel.initOnCreated(intent) + + when (viewModel.pageType) { + DetailsPageType.Add -> setBackArrow("新增") + DetailsPageType.Edit -> setBackArrow("编辑") + DetailsPageType.Details -> setBackArrow("详情") + } + + binding.viewModel = viewModel + } + + companion object { + @JvmStatic + fun startForAdd(context: Context) { + val starter = Intent(context, XxxAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Add.name) + context.startActivity(starter) + } + + @JvmStatic + fun startForEdit(context: Context, id: String) { + val starter = Intent(context, XxxAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Edit.name) + .putExtra(Constant.Key.ID, id) + context.startActivity(starter) + } + + @JvmStatic + fun startForDetails(context: Context, id: String) { + val starter = Intent(context, XxxAddActivity::class.java) + .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Details.name) + .putExtra(Constant.Key.ID, id) + context.startActivity(starter) + } + } +} +``` + +**步骤4: 创建Layout** +```xml + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**步骤5: 注册路由并发送刷新事件** +```kotlin +// 注册路由 +const val ACTIVITY_URL_XXX_ADD = "/xxx/XxxAddActivity" + +// 在列表页接收刷新事件 +FlowBus.with(ConstantEvent.EVENT_REFRESH_LIST).observe(this) { + viewModel.refresh() +} +``` + +## 常见业务场景 + +### 扫码后查询 + +```kotlin +// 在ViewModel中 +fun scanWaybill() { + ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL) +} + +override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) { + val code = data?.getStringExtra(Constant.Result.CODED_CONTENT) + waybillNo.value = code + search() // 自动搜索 + } +} +``` + +### 打印标签 + +```kotlin +// 绑定打印服务 +bindService(Intent(this, PrinterService::class.java), serviceConnection, BIND_AUTO_CREATE) + +// 打印 +val printData = DataForSendToPrinter() +printData.addText("运单号: ${waybillNo}") +printData.addText("重量: ${weight}kg") +printData.addBarcode(waybillNo, 2, 100) +printerService?.sendPrintData(printData) +``` + +### 图片上传 + +```kotlin +// 选择图片 +PictureSelector.create(this) + .openGallery(SelectMimeType.ofImage()) + .setMaxSelectNum(1) + .setImageEngine(GlideEngine.createGlideEngine()) + .forResult { result -> + val path = result[0].realPath + uploadImage(path) + } + +// 上传接口 +private fun uploadImage(path: String) { + val file = File(path) + val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) + val part = MultipartBody.Part.createFormData("file", file.name, requestFile) + + launchLoadingCollect({ + NetApply.api.uploadFile(part) + }) { + onSuccess = { result -> + imageUrl.value = result.data?.url + showToast("上传成功") + } + } +} +``` ## 关键技术点 @@ -228,9 +2419,15 @@ data class BaseResultBean( ### 权限管理 -使用 AndPermission 2.0.2: +使用 AndPermission 2.0.2 或扩展函数: ```kotlin +// 方式1: 使用扩展函数(推荐) +permission(Manifest.permission.CAMERA) { + openCamera() +} + +// 方式2: 使用AndPermission AndPermission.with(this) .runtime() .permission(Permission.CAMERA) @@ -246,166 +2443,11 @@ AndPermission.with(this) ```kotlin PictureSelector.create(this) .openGallery(SelectMimeType.ofImage()) + .setMaxSelectNum(9) .setImageEngine(GlideEngine.createGlideEngine()) - .forResult { result -> } -``` - -## 开发规范 - -### 新增功能页面 - -1. **创建页面结构** (按 list/details/add 组织): - ``` - page/ - └── feature/ - ├── list/ - │ ├── FeatureListActivity.kt - │ ├── FeatureListViewModel.kt - │ └── FeatureListViewHolder.kt - ├── details/ - │ ├── FeatureDetailsActivity.kt - │ └── FeatureDetailsViewModel.kt - └── add/ - ├── FeatureAddActivity.kt - └── FeatureAddViewModel.kt - ``` - -2. **在ARouterConstants中注册路由**: - ```kotlin - const val ACTIVITY_URL_FEATURE_LIST = "/feature/list" - ``` - -3. **在CommonService中添加API接口**: - ```kotlin - @POST("api/feature/list") - suspend fun getFeatureList(@Body data: RequestBody): BaseResultBean> - ``` - -4. **创建数据Bean** (在module_base/bean/): - ```kotlin - data class FeatureBean( - val id: String, - val name: String - // 其他字段... - ) - ``` - -### Activity开发模板 - -```kotlin -@Route(path = ARouterConstants.ACTIVITY_URL_XXX) -class XxxActivity : BaseBindingActivity() { - - override fun getViewBinding() = ActivityXxxBinding.inflate(layoutInflater) - - override fun getViewModel() = ViewModelProvider(this)[XxxViewModel::class.java] - - override fun initView() { - // 初始化UI - binding.apply { - viewModel = this@XxxActivity.viewModel - } - } - - override fun initData() { - // 加载数据 - viewModel.loadData() - } - - override fun addListener() { - // 添加监听器 - } - - override fun addObserver() { - // 观察LiveData/Flow - } -} -``` - -### ViewModel开发模板 - -```kotlin -class XxxViewModel : BasePageViewModel() { - - // LiveData定义 - val dataList = MutableLiveData>() - - fun loadData() { - launchLoadingCollect({ - NetApply.api.simplePost("endpoint", params.toRequestBody()) - }) { - onSuccess = { result -> - dataList.value = result.data - } - } - } -} -``` - -### 列表开发模板 - -```kotlin -// ViewHolder -class XxxViewHolder(itemView: View) : BaseViewHolder(itemView) { - override fun onBindViewHolder(item: XxxBean, position: Int) { - // 绑定数据到视图 - } -} - -// Activity中使用 -val adapter = CommonAdapter( - R.layout.item_xxx, - dataList -) { XxxViewHolder(it) } -binding.recyclerView.adapter = adapter -``` - -## 常见业务场景 - -### 扫码后查询 - -```kotlin -// 启动扫码 -ARouter.getInstance() - .build(ARouterConstants.ACTIVITY_URL_SCAN) - .navigation(this, REQUEST_CODE_SCAN) - -// 处理扫码结果 -override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) { - val code = data?.getStringExtra("code") - // 调用查询接口 - } -} -``` - -### 打印标签 - -```kotlin -// 绑定打印服务 -bindService(Intent(this, PrinterService::class.java), serviceConnection, BIND_AUTO_CREATE) - -// 打印 -val printData = DataForSendToPrinter() -// 构建打印内容... -printerService?.sendPrintData(printData) -``` - -### 图片上传 - -```kotlin -// 选择图片 -PictureSelector.create(this) - .openGallery(SelectMimeType.ofImage()) .forResult { result -> - val path = result[0].realPath - uploadImage(path) + // 处理选择结果 } - -// 上传接口 -@Multipart -@POST("api/upload") -suspend fun uploadImage(@Part file: MultipartBody.Part): BaseResultBean ``` ## 重要配置文件 @@ -446,3 +2488,24 @@ suspend fun uploadImage(@Part file: MultipartBody.Part): BaseResultBean