Przeglądaj źródła

feat: Implement material exchange and type management features

- Added material exchange functionality, including UI for borrowing and returning materials, cabinet door control, and material list display.
- Implemented material type management, allowing users to add, update, and delete material types with associated icons and pictures.
- Added data layer support for materials, material types, and related operations, including database DAOs, repositories, and business logic.
- Enhanced `CustomNavBar` to support clearing selection and improved item selection logic.
- Updated `FormLayout` to support label alignment options (center, top, baseline).
- Added new icons and drawable resources for material management UI.
- Improved `CanHelper` with new methods for synchronous read and write operations and added `controlDoorOpen` command.
- Integrated Coil for image loading with SVG support.
- Refactored login UI layout for better consistency across orientations.
- Updated minimum SDK version to 26.
- Added various i18n strings for new features.
- Miscellaneous bug fixes and UI improvements.
周文健 2 miesięcy temu
rodzic
commit
1447995683
72 zmienionych plików z 2504 dodań i 383 usunięć
  1. 1 1
      app/build.gradle.kts
  2. 106 46
      app/src/main/assets/i18n/zh-CN.json
  3. 2 0
      app/src/main/assets/themes/Default/icons/border-all.svg
  4. 10 0
      app/src/main/assets/themes/Default/icons/icon_placeholder_add.svg
  5. 21 2
      app/src/main/java/com/grkj/iscs_mc/ISCSMCApplication.kt
  6. 3 1
      app/src/main/java/com/grkj/iscs_mc/features/login/fragment/LoginFragment.kt
  7. 11 2
      app/src/main/java/com/grkj/iscs_mc/features/main/activity/MainActivity.kt
  8. 113 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/material_manage/AddMaterialsTypeDialog.kt
  9. 145 0
      app/src/main/java/com/grkj/iscs_mc/features/main/dialog/material_manage/UpdateMaterialsTypeDialog.kt
  10. 9 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/IsMaterialsTypeEntity.kt
  11. 1 1
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/MaterialExchangeHeaderEntity.kt
  12. 33 0
      app/src/main/java/com/grkj/iscs_mc/features/main/entity/QuickEntranceMenuItemEntity.kt
  13. 6 1
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/home/HomeFragment.kt
  14. 0 119
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialExchangeFragment.kt
  15. 202 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsExchangeFragment.kt
  16. 4 2
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsHomeManageFragment.kt
  17. 20 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsManageFragment.kt
  18. 165 0
      app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsTypeManageFragment.kt
  19. 52 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/material_manage/MaterialExchangeViewModel.kt
  20. 78 0
      app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/material_manage/MaterialsTypeManageViewModel.kt
  21. 13 1
      app/src/main/java/com/grkj/iscs_mc/features/splash/activity/SplashActivity.kt
  22. 5 0
      app/src/main/res/drawable/bg_badge.xml
  23. 9 0
      app/src/main/res/drawable/bg_tab_container_normal.xml
  24. 6 0
      app/src/main/res/drawable/bg_tab_container_selected.xml
  25. 5 0
      app/src/main/res/drawable/bg_tab_container_selector.xml
  26. 5 0
      app/src/main/res/drawable/flex_divider.xml
  27. 3 3
      app/src/main/res/layout-land/activity_main.xml
  28. 18 11
      app/src/main/res/layout-land/fragment_login.xml
  29. 2 2
      app/src/main/res/layout/activity_main.xml
  30. 206 0
      app/src/main/res/layout/dialog_materials_type.xml
  31. 2 3
      app/src/main/res/layout/fragment_home.xml
  32. 18 11
      app/src/main/res/layout/fragment_login.xml
  33. 130 58
      app/src/main/res/layout/fragment_material_exchange.xml
  34. 183 0
      app/src/main/res/layout/fragment_materials_manage.xml
  35. 152 0
      app/src/main/res/layout/fragment_materials_type_manage.xml
  36. 37 18
      app/src/main/res/layout/item_material_exchange_header.xml
  37. 33 0
      app/src/main/res/layout/item_materials.xml
  38. 56 0
      app/src/main/res/layout/item_materials_type.xml
  39. 17 3
      app/src/main/res/navigation/nav_material_manage.xml
  40. 0 3
      app/src/main/res/navigation/nav_user_info.xml
  41. 1 1
      data/build.gradle.kts
  42. 5 0
      data/src/main/java/com/grkj/data/common/EventConstants.kt
  43. 6 0
      data/src/main/java/com/grkj/data/di/AppEntryPoint.kt
  44. 7 0
      data/src/main/java/com/grkj/data/di/DatabaseModule.kt
  45. 7 0
      data/src/main/java/com/grkj/data/di/LogicManager.kt
  46. 9 0
      data/src/main/java/com/grkj/data/di/LogicModule.kt
  47. 14 0
      data/src/main/java/com/grkj/data/di/RepositoryModule.kt
  48. 33 0
      data/src/main/java/com/grkj/data/domain/logic/IMaterialsLogic.kt
  49. 36 0
      data/src/main/java/com/grkj/data/domain/logic/impl/MaterialsLogic.kt
  50. 4 0
      data/src/main/java/com/grkj/data/hardware/IHardwareHelper.kt
  51. 14 0
      data/src/main/java/com/grkj/data/hardware/can/CanCommand.kt
  52. 12 0
      data/src/main/java/com/grkj/data/hardware/can/CanHardwareHelper.kt
  53. 30 0
      data/src/main/java/com/grkj/data/hardware/can/CanHelper.kt
  54. 50 0
      data/src/main/java/com/grkj/data/local/dao/MaterialsDao.kt
  55. 24 1
      data/src/main/java/com/grkj/data/local/database/ISCSDatabase.kt
  56. 13 8
      data/src/main/java/com/grkj/data/local/database/ISCSMigrations.kt
  57. 33 0
      data/src/main/java/com/grkj/data/repository/IMaterialsRepository.kt
  58. 33 0
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkMaterialsRepository.kt
  59. 36 0
      data/src/main/java/com/grkj/data/repository/impl/standard/StandardMaterialsRepository.kt
  60. 28 0
      data/src/main/java/com/grkj/data/utils/event/ToastEvent.kt
  61. 2 2
      shared/build.gradle.kts
  62. 11 0
      shared/src/main/java/com/grkj/shared/utils/FilePickerUtils.kt
  63. 1 1
      ui-base/build.gradle.kts
  64. 14 8
      ui-base/src/main/java/com/grkj/ui_base/base/BaseActivity.kt
  65. 1 11
      ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt
  66. 2 2
      ui-base/src/main/java/com/grkj/ui_base/config/ISCSConfig.kt
  67. 18 0
      ui-base/src/main/java/com/grkj/ui_base/utils/extension/View.kt
  68. 58 41
      ui-base/src/main/java/com/grkj/ui_base/widget/CustomNavBar.kt
  69. 93 9
      ui-base/src/main/java/com/grkj/ui_base/widget/FormLayout.kt
  70. 6 0
      ui-base/src/main/res/drawable/bg_underline.xml
  71. 16 10
      ui-base/src/main/res/values/attrs.xml
  72. 5 1
      ui-base/src/main/res/values/dimens.xml

+ 1 - 1
app/build.gradle.kts

@@ -12,7 +12,7 @@ android {
 
     defaultConfig {
         applicationId = "com.grkj.iscs_mc"
-        minSdk = 24
+        minSdk = 26
         targetSdk = 36
         versionCode = 1
         versionName = "v1.0.0"

+ 106 - 46
app/src/main/assets/i18n/zh-CN.json

@@ -209,11 +209,6 @@
     "type": "text",
     "value": "请输入用户名"
   },
-  "please_input_password": {
-    "key": "please_input_password",
-    "type": "text",
-    "value": "请输入密码"
-  },
   "please_scan_face": {
     "key": "please_scan_face",
     "type": "text",
@@ -429,6 +424,11 @@
     "type": "text",
     "value": "删除成功"
   },
+  "delete_failed": {
+    "key": "delete_failed",
+    "type": "text",
+    "value": "删除失败"
+  },
   "detail": {
     "key": "detail",
     "type": "text",
@@ -794,6 +794,11 @@
     "type": "text",
     "value": "保存成功!"
   },
+  "save_failed": {
+    "key": "save_failed",
+    "type": "text",
+    "value": "保存失败!"
+  },
   "select": {
     "key": "select",
     "type": "text",
@@ -1169,46 +1174,6 @@
     "type": "text",
     "value": "卡片管理"
   },
-  "user_info": {
-    "key": "user_info",
-    "type": "text",
-    "value": "个人信息"
-  },
-  "reset_password": {
-    "key": "reset_password",
-    "type": "text",
-    "value": "重置密码"
-  },
-  "fingerprint_setting": {
-    "key": "fingerprint_setting",
-    "type": "text",
-    "value": "设置指纹"
-  },
-  "face_setting": {
-    "key": "face_setting",
-    "type": "text",
-    "value": "设置人脸"
-  },
-  "card_setting": {
-    "key": "card_setting",
-    "type": "text",
-    "value": "设置工卡"
-  },
-  "logout": {
-    "key": "logout",
-    "type": "text",
-    "value": "退出登录"
-  },
-  "data_manage": {
-    "key": "data_manage",
-    "type": "text",
-    "value": "数据管理"
-  },
-  "home": {
-    "key": "home",
-    "type": "text",
-    "value": "主页"
-  },
   "year": {
     "key": "year",
     "type": "text",
@@ -1427,7 +1392,7 @@
   "confirm": {
     "key": "confirm",
     "type": "text",
-    "value": "执行确认"
+    "value": "确认"
   },
   "exception_manage": {
     "key": "exception_manage",
@@ -1533,5 +1498,100 @@
     "key": "security_compliance_analysis",
     "type": "text",
     "value": "安全合规分析"
+  },
+  "material_exchange_tip": {
+    "key": "material_exchange_tip",
+    "type": "text",
+    "value": "请领取或归还物资,然后关闭柜⻔,系统将⾃动结算。"
+  },
+  "material_exchange_settlement_tip": {
+    "key": "material_exchange_settlement_tip",
+    "type": "text",
+    "value": "请等待物资盘点结束后,确认物资领取和归还的记录。"
+  },
+  "material_exchange_complete_tip": {
+    "key": "material_exchange_settlement_tip",
+    "type": "text",
+    "value": "请确认本次物资取还记录,确认后您的账号将⾃动退出。"
+  },
+  "open_door": {
+    "key": "open_door",
+    "type": "text",
+    "value": "开柜"
+  },
+  "borrowable": {
+    "key": "borrowable",
+    "type": "text",
+    "value": "可借"
+  },
+  "wait_back": {
+    "key": "wait_back",
+    "type": "text",
+    "value": "待还"
+  },
+  "no_data": {
+    "key": "no_data",
+    "type": "text",
+    "value": "暂无数据"
+  },
+  "in_open_door": {
+    "key": "in_open_door",
+    "type": "text",
+    "value": "正在打开柜门……"
+  },
+  "open_door_success": {
+    "key": "open_door_success",
+    "type": "text",
+    "value": "柜门打开成功"
+  },
+  "open_door_failed": {
+    "key": "open_door_failed",
+    "type": "text",
+    "value": "柜门打开失败"
+  },
+  "materials_type_name": {
+    "key": "materials_type_name",
+    "type": "text",
+    "value": "物资类型名称"
+  },
+  "materials_type_icon": {
+    "key": "materials_type_icon",
+    "type": "text",
+    "value": "物资类型图标"
+  },
+  "materials_type_picture": {
+    "key": "materials_type_picture",
+    "type": "text",
+    "value": "物资类型缩略图"
+  },
+  "has_materials_type_in_use": {
+    "key": "has_materials_type_in_use",
+    "type": "text",
+    "value": "存在物资类型正在使用中,无法删除"
+  },
+  "materials_type_manage_add_title": {
+    "key": "materials_type_manage_add_title",
+    "type": "text",
+    "value": "新增物资类型"
+  },
+  "materials_type_manage_update_title": {
+    "key": "materials_type_manage_update_title",
+    "type": "text",
+    "value": "修改物资类型"
+  },
+  "please_input_materials_type_name": {
+    "key": "please_input_materials_type_name",
+    "type": "text",
+    "value": "请输入物资类型名称"
+  },
+  "check_standard": {
+    "key": "check_standard",
+    "type": "text",
+    "value": "检查标准"
+  },
+  "materials_manage_title": {
+    "key": "materials_manage_title",
+    "type": "text",
+    "value": "物资管理"
   }
 }

+ 2 - 0
app/src/main/assets/themes/Default/icons/border-all.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M19,0H5C2.24,0,0,2.24,0,5v14c0,2.76,2.24,5,5,5h14c2.76,0,5-2.24,5-5V5c0-2.76-2.24-5-5-5Zm3,5v6H13V2h6c1.65,0,3,1.35,3,3ZM5,2h6V11H2V5c0-1.65,1.35-3,3-3ZM2,19v-6H11v9H5c-1.65,0-3-1.35-3-3Zm17,3h-6V13h9v6c0,1.65-1.35,3-3,3Z"/></svg>

+ 10 - 0
app/src/main/assets/themes/Default/icons/icon_placeholder_add.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Add image placeholder">
+  <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
+    <!-- 虚线边框 -->
+    <rect x="8" y="8" width="100" height="100" rx="12"
+          stroke-width="2" stroke-dasharray="6 6"/>
+    <!-- 加号 -->
+    <path d="M60 40 v40 M40 60 h40" stroke-width="3"/>
+  </g>
+</svg>

+ 21 - 2
app/src/main/java/com/grkj/iscs_mc/ISCSMCApplication.kt

@@ -6,6 +6,10 @@ import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import ch.qos.logback.classic.Level
+import coil.Coil
+import coil.ImageLoader
+import coil.decode.SvgDecoder
+import coil.memory.MemoryCache
 import com.drake.statelayout.StateConfig
 import com.grkj.data.common.EventConstants
 import com.grkj.data.di.LogicManager
@@ -69,7 +73,7 @@ class ISCSMCApplication : Application() {
 
         // 这些通常很轻,可以保留在主线程(如需极致,可推迟到首 Activity onCreate)
         StateConfig.emptyLayout = com.grkj.ui_base.R.layout.layout_empty
-        AutoSizeConfig.getInstance().isCustomFragment = true
+        AutoSizeConfig.getInstance().apply { setLog(true) }.isCustomFragment = false
 
         // —— 把可疑重活统统后台化 ——
         ThreadUtils.runOnIO {
@@ -101,7 +105,7 @@ class ISCSMCApplication : Application() {
                 ArcSoftUtil.checkActiveStatus(this@ISCSMCApplication)
                 ArcSoftUtil.initEngine(this@ISCSMCApplication)
             }.onFailure { logger.error("ArcSoft 引擎初始化失败", it) }
-
+            initImageLoader()
             // 5) 数据库/业务/硬件连接(你原来就放 IO,很好)
             DbReadyGate.await()
             LogicManager.init(this@ISCSMCApplication)
@@ -112,6 +116,21 @@ class ISCSMCApplication : Application() {
         }
     }
 
+    private fun initImageLoader(){
+        val loader = ImageLoader.Builder(this)
+            .components { add(SvgDecoder.Factory()) }  // 支持 SVG
+            .memoryCache {
+                MemoryCache.Builder(this)
+                    .maxSizePercent(0.25)             // 视项目情况调
+                    .build()
+            }
+            .respectCacheHeaders(false)
+            .crossfade(false)
+            .build()
+
+        Coil.setImageLoader(loader)
+    }
+
     @Subscribe(threadMode = ThreadMode.MAIN)
     open fun onEvent(event: EventBean<*>) {
         when (event.code) {

+ 3 - 1
app/src/main/java/com/grkj/iscs_mc/features/login/fragment/LoginFragment.kt

@@ -161,6 +161,7 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
             viewModel.loginWithAccount(
                 binding.etAccount.text.toString(), binding.etPassword.text.toString()
             ).observe(this) {
+                hideLoading()
                 checkLoginResult(it)
             }
         }
@@ -335,7 +336,8 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>() {
 
     override fun initData() {
         super.initData()
-        binding.version.text = "${requireContext().getAppVersionName()}-${requireContext().serialNo()}"
+        binding.version.text =
+            "${requireContext().getAppVersionName()}-${requireContext().serialNo()}"
         loginMenuList.apply {
             add(
                 LoginMenuEntity(

+ 11 - 2
app/src/main/java/com/grkj/iscs_mc/features/main/activity/MainActivity.kt

@@ -28,6 +28,7 @@ import com.grkj.shared.utils.extension.toByteArrays
 import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.ui_base.base.BaseNavActivity
+import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.utils.event.BottomTipEvent
 import com.grkj.ui_base.utils.event.RFIDReadEvent
 import com.grkj.ui_base.utils.extension.removeTint
@@ -36,6 +37,7 @@ import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.extension.setDebouncedClickListener
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
+import me.jessyan.autosize.AutoSize
 import org.checkerframework.common.subtyping.qual.Bottom
 
 /**
@@ -143,6 +145,7 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
                 binding.navBar.selectedItemId = firstId
                 MainDomainData.fromQuickEntry = false
             }
+            binding.navBar.clearSelected()
             replaceNavGraph(R.navigation.nav_user_info)
         }
         navController.addOnDestinationChangedListener { _, destination, _ ->
@@ -153,6 +156,9 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
                 MainDomainData.fromQuickEntry = false
             }
             binding.navBar.isVisible = bottomNavDestinations.contains(destination.id)
+            if (bottomNavDestinations.contains(destination.id)) {
+                binding.bottomTip.isVisible = false
+            }
         }
     }
 
@@ -165,8 +171,11 @@ class MainActivity() : BaseNavActivity<ActivityMainBinding>() {
 
             EventConstants.EVENT_BOTTOM_TIP -> {
                 (event.data as BottomTipEvent).let {
-                    binding.bottomTip.isVisible = !it.msg.isNullOrEmpty()
-                    binding.bottomTip.text = it.msg.toString()
+                    binding.bottomTip.post {
+                        AutoSize.autoConvertDensity(this, ISCSConfig.DESIGN_SIZE, isPortrait())
+                        binding.bottomTip.isVisible = !it.msg.isNullOrEmpty()
+                        binding.bottomTip.text = it.msg.toString()
+                    }
                 }
             }
         }

+ 113 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/material_manage/AddMaterialsTypeDialog.kt

@@ -0,0 +1,113 @@
+package com.grkj.iscs_mc.features.main.dialog.material_manage
+
+import android.view.View
+import coil.load
+import com.grkj.data.utils.event.ToastEvent
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogMaterialsTypeBinding
+import com.grkj.iscs_mc.features.main.entity.IsMaterialsTypeEntity
+import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.shared.utils.SAFHelper.uriToFile
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.clearSrcTint
+import com.grkj.ui_base.utils.extension.setSrcTint
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.SIKCore
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 新增物资类型
+ */
+class AddMaterialsTypeDialog(
+    val filePickerUtils: FilePickerUtils,
+    val onConfirm: (CustomDialog, IsMaterialsTypeEntity) -> Unit
+) :
+    OnBindView<CustomDialog>(R.layout.dialog_materials_type) {
+    private lateinit var binding: DialogMaterialsTypeBinding
+    private val isMaterialsTypeEntity = IsMaterialsTypeEntity()
+
+    override fun onBind(dialog: CustomDialog, contentView: View) {
+        binding = DialogMaterialsTypeBinding.bind(contentView)
+        dialog.isCancelable = false
+        dialog.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+        binding.title.text = CommonUtils.getStr("materials_type_manage_add_title")
+        binding.closeIv.setDebouncedClickListener { dialog.dismiss() }
+        binding.cancel.setDebouncedClickListener { dialog.dismiss() }
+        binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+        binding.materialsTypeIcon.loadSkinIcon("icon_placeholder_add.svg")
+        binding.materialsTypePicture.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+        binding.materialsTypePicture.loadSkinIcon("icon_placeholder_add.svg")
+        binding.materialsTypeIcon.setDebouncedClickListener {
+            filePickerUtils.pickFile(FilePickerUtils.imageMimeTypes) {
+                it?.let {
+                    isMaterialsTypeEntity.materialsTypeIcon =
+                        SIKCore.getApplication().uriToFile(it)?.absolutePath
+                    if (isMaterialsTypeEntity.materialsTypeIcon?.endsWith("png")==true||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpg")==true||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpeg")==true||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("webp")==true){
+                        binding.materialsTypeIcon.clearSrcTint()
+                    }else{
+                        binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+                    }
+                    binding.materialsTypeIcon.load(isMaterialsTypeEntity.materialsTypeIcon)
+                }
+            }
+        }
+        binding.materialsTypePicture.setDebouncedClickListener {
+            filePickerUtils.pickFile(FilePickerUtils.imageMimeTypes) {
+                it?.let {
+                    isMaterialsTypeEntity.materialsTypePicture =
+                        SIKCore.getApplication().uriToFile(it)?.absolutePath
+                    if (isMaterialsTypeEntity.materialsTypePicture?.endsWith("png")==true||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpg")==true||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpeg")==true||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("webp")==true){
+                        binding.materialsTypePicture.clearSrcTint()
+                    }else{
+                        binding.materialsTypePicture.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+                    }
+                    binding.materialsTypePicture.load(isMaterialsTypeEntity.materialsTypePicture)
+                }
+            }
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                isMaterialsTypeEntity.materialsTypeName =
+                    binding.materialsTypeNameEt.text.toString()
+                isMaterialsTypeEntity.checkStandard = binding.checkStandard.text.toString()
+                isMaterialsTypeEntity.remark = binding.remark.text.toString()
+                onConfirm(dialog, isMaterialsTypeEntity)
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.materialsTypeNameEt.text.toString().isEmpty()) {
+            ToastEvent.sendToastEvent(CommonUtils.getStr("please_input_materials_type_name"))
+            return false
+        }
+        return true
+    }
+
+    companion object {
+        /**
+         * 显示对话框并设置确认回调
+         */
+        @JvmStatic
+        fun show(
+            filePickerUtils: FilePickerUtils,
+            onConfirm: (CustomDialog, IsMaterialsTypeEntity) -> Unit
+        ) {
+            CustomDialog.show(
+                AddMaterialsTypeDialog(filePickerUtils, onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

+ 145 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/dialog/material_manage/UpdateMaterialsTypeDialog.kt

@@ -0,0 +1,145 @@
+package com.grkj.iscs_mc.features.main.dialog.material_manage
+
+import android.view.View
+import coil.load
+import com.grkj.data.utils.event.ToastEvent
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogMaterialsTypeBinding
+import com.grkj.iscs_mc.features.main.entity.IsMaterialsTypeEntity
+import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.shared.utils.SAFHelper.uriToFile
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.clearSrcTint
+import com.grkj.ui_base.utils.extension.setSrcTint
+import com.kongzue.dialogx.dialogs.CustomDialog
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.SIKCore
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 新增物资类型
+ */
+class UpdateMaterialsTypeDialog(
+    val filePickerUtils: FilePickerUtils,
+    val isMaterialsTypeEntity: IsMaterialsTypeEntity,
+    val onConfirm: (CustomDialog, IsMaterialsTypeEntity) -> Unit
+) :
+    OnBindView<CustomDialog>(R.layout.dialog_materials_type) {
+    private lateinit var binding: DialogMaterialsTypeBinding
+
+    override fun onBind(dialog: CustomDialog, contentView: View) {
+        binding = DialogMaterialsTypeBinding.bind(contentView)
+        binding.title.text = CommonUtils.getStr("materials_type_manage_update_title")
+        dialog.isCancelable = false
+        dialog.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.attr.scrim))
+        binding.closeIv.setDebouncedClickListener { dialog.dismiss() }
+        binding.cancel.setDebouncedClickListener { dialog.dismiss() }
+        isMaterialsTypeEntity.materialsTypeIcon?.let {
+            if (isMaterialsTypeEntity.materialsTypeIcon?.endsWith("png") == true ||
+                isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpg") == true ||
+                isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpeg") == true ||
+                isMaterialsTypeEntity.materialsTypeIcon?.endsWith("webp") == true
+            ) {
+                binding.materialsTypeIcon.clearSrcTint()
+            } else {
+                binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+            }
+            binding.materialsTypeIcon.load(it)
+        } ?: run {
+            binding.materialsTypeIcon.loadSkinIcon("icon_placeholder_add.svg")
+            binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+        }
+        isMaterialsTypeEntity.materialsTypePicture?.let {
+            if (isMaterialsTypeEntity.materialsTypePicture?.endsWith("png") == true ||
+                isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpg") == true ||
+                isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpeg") == true ||
+                isMaterialsTypeEntity.materialsTypePicture?.endsWith("webp") == true
+            ) {
+                binding.materialsTypePicture.clearSrcTint()
+            } else {
+                binding.materialsTypePicture.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+            }
+            binding.materialsTypePicture.load(it)
+        } ?: run {
+            binding.materialsTypePicture.loadSkinIcon("icon_placeholder_add.svg")
+            binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+        }
+        binding.materialsTypeNameEt.setText(isMaterialsTypeEntity.materialsTypeName)
+        binding.checkStandard.setText(isMaterialsTypeEntity.checkStandard)
+        binding.remark.setText(isMaterialsTypeEntity.remark)
+        binding.materialsTypeIcon.setDebouncedClickListener {
+            filePickerUtils.pickFile(FilePickerUtils.imageMimeTypes) {
+                it?.let {
+                    isMaterialsTypeEntity.materialsTypeIcon =
+                        SIKCore.getApplication().uriToFile(it)?.absolutePath
+                    if (isMaterialsTypeEntity.materialsTypeIcon?.endsWith("png") == true ||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpg") == true ||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("jpeg") == true ||
+                        isMaterialsTypeEntity.materialsTypeIcon?.endsWith("webp") == true
+                    ) {
+                        binding.materialsTypeIcon.clearSrcTint()
+                    } else {
+                        binding.materialsTypeIcon.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+                    }
+                    binding.materialsTypeIcon.load(isMaterialsTypeEntity.materialsTypeIcon)
+                }
+            }
+        }
+        binding.materialsTypePicture.setDebouncedClickListener {
+            filePickerUtils.pickFile(FilePickerUtils.imageMimeTypes) {
+                it?.let {
+                    isMaterialsTypeEntity.materialsTypePicture =
+                        SIKCore.getApplication().uriToFile(it)?.absolutePath
+                    if (isMaterialsTypeEntity.materialsTypePicture?.endsWith("png") == true ||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpg") == true ||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("jpeg") == true ||
+                        isMaterialsTypeEntity.materialsTypePicture?.endsWith("webp") == true
+                    ) {
+                        binding.materialsTypePicture.clearSrcTint()
+                    } else {
+                        binding.materialsTypePicture.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+                    }
+                    binding.materialsTypePicture.load(isMaterialsTypeEntity.materialsTypePicture)
+                }
+            }
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                isMaterialsTypeEntity.materialsTypeName =
+                    binding.materialsTypeNameEt.text.toString()
+                isMaterialsTypeEntity.checkStandard = binding.checkStandard.text.toString()
+                isMaterialsTypeEntity.remark = binding.remark.text.toString()
+                onConfirm(dialog, isMaterialsTypeEntity)
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.materialsTypeNameEt.text.toString().isEmpty()) {
+            ToastEvent.sendToastEvent(CommonUtils.getStr("please_input_materials_type_name"))
+            return false
+        }
+        return true
+    }
+
+    companion object {
+        /**
+         * 显示对话框并设置确认回调
+         */
+        @JvmStatic
+        fun show(
+            filePickerUtils: FilePickerUtils,
+            isMaterialsTypeEntity: IsMaterialsTypeEntity,
+            onConfirm: (CustomDialog, IsMaterialsTypeEntity) -> Unit
+        ) {
+            CustomDialog.show(
+                UpdateMaterialsTypeDialog(filePickerUtils, isMaterialsTypeEntity, onConfirm),
+                CustomDialog.ALIGN.CENTER
+            )
+        }
+    }
+}

+ 9 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/IsMaterialsTypeEntity.kt

@@ -0,0 +1,9 @@
+package com.grkj.iscs_mc.features.main.entity
+
+import com.grkj.data.local.dos.IsMaterialsType
+
+/**
+ * 物资类型实体
+ */
+class IsMaterialsTypeEntity : IsMaterialsType() {
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs_mc/features/main/entity/MaterialExchangeHeaderEntity.kt

@@ -7,7 +7,7 @@ data class MaterialExchangeHeaderEntity(
     /**
      * 头部图标
      */
-    val headerIcon: String,
+    val headerIcon: String?,
     /**
      * 头部标题 key
      */

+ 33 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/entity/QuickEntranceMenuItemEntity.kt

@@ -112,6 +112,39 @@ data class QuickEntranceMenuItemEntity(
         @JvmStatic
         fun getDestId(permission: RoleFunctionalPermissionsEnum): Int {
             return when (permission) {
+
+                RoleFunctionalPermissionsEnum.USER_MANAGE -> R.id.action_dataManageHomeFragment_to_userManageFragment
+                RoleFunctionalPermissionsEnum.ROLE_MANAGE -> R.id.action_dataManageHomeFragment_to_roleManageFragment
+                RoleFunctionalPermissionsEnum.DEPT_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.BLACK_LIST_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.LOGIN_LOG_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.EXCHANGE_LOG_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.DATA_EXPORT -> R.id.action_dataManageHomeFragment_to_dataExportFragment
+                RoleFunctionalPermissionsEnum.BACKUP_AND_RESTORE -> R.id.action_dataManageHomeFragment_to_backupAndRestoreFragment
+
+                RoleFunctionalPermissionsEnum.MATERIAL_EXCHANGE -> R.id.action_materialHomeManageFragment_to_materialsExchangeFragment
+                RoleFunctionalPermissionsEnum.MATERIAL_TYPE -> 0
+                RoleFunctionalPermissionsEnum.MATERIAL_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.MATERIAL_CHECK -> 0
+                RoleFunctionalPermissionsEnum.MATERIAL_REPAIR_AND_REPLACEMENT -> 0
+                RoleFunctionalPermissionsEnum.MATERIAL_INSTRUCTIONS -> 0
+
+                RoleFunctionalPermissionsEnum.EXCEPTION_REPORT -> 0
+                RoleFunctionalPermissionsEnum.EXCEPTION_MANAGE -> 0
+                RoleFunctionalPermissionsEnum.WARNING_MANAGE -> 0
+
+                RoleFunctionalPermissionsEnum.USER_DEPT_ANALYSIS -> 0
+                RoleFunctionalPermissionsEnum.USE_BEHAVIOR_ANALYSIS -> 0
+                RoleFunctionalPermissionsEnum.OPERATION_AND_MAINTENANCE_ANALYSIS -> 0
+                RoleFunctionalPermissionsEnum.INVENTORY_TURNOVER_ANALYSIS -> 0
+                RoleFunctionalPermissionsEnum.COST_BENEFIT_ANALYSIS -> 0
+                RoleFunctionalPermissionsEnum.SECURITY_COMPLIANCE_ANALYSIS -> 0
+
+                RoleFunctionalPermissionsEnum.USER_INFO -> R.id.action_userInfoHomeFragment_to_userInfoFragment
+                RoleFunctionalPermissionsEnum.RESET_PASSWORD -> R.id.action_userInfoHomeFragment_to_resetPasswordFragment
+                RoleFunctionalPermissionsEnum.FINGERPRINT_SETTING -> R.id.action_userInfoHomeFragment_to_setFingerprintFragment
+                RoleFunctionalPermissionsEnum.FACE_SETTING -> R.id.action_userInfoHomeFragment_to_setFaceFragment
+                RoleFunctionalPermissionsEnum.CARD_SETTING -> R.id.action_userInfoHomeFragment_to_setJobCardFragment
                 else -> 0
             }
         }

+ 6 - 1
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/home/HomeFragment.kt

@@ -17,6 +17,7 @@ import com.grkj.iscs_mc.features.main.viewmodel.home.HomeViewModel
 import com.grkj.shared.utils.i18n.I18nManager
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.JumpViewEvent
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
@@ -36,7 +37,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
                 mutableListOf<QuickEntranceMenuItemEntity>(
                     QuickEntranceMenuItemEntity(
                         0,
-                        "document.svg",
+                        "box-loading.svg",
                         I18nManager.t(RoleFunctionalPermissionsEnum.MATERIAL_EXCHANGE.description),
                         RoleFunctionalPermissionsEnum.MATERIAL_EXCHANGE,
                         QuickEntranceMenuItemEntity.getNavGraphId(RoleFunctionalPermissionsEnum.MATERIAL_EXCHANGE),
@@ -94,6 +95,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
 
     private fun getHomeData() {
         viewModel.getHomeData().observe(this) {
+            binding.borrowedMaterialTitle.text = CommonUtils.getStr(
+                "you_have_borrowed_materials",
+                viewModel.borrowMaterial.toString()
+            )
             binding.totalMaterialNum.text = viewModel.allMaterial.toString()
             binding.borrowMaterialNum.text = viewModel.borrowMaterial.toString()
             binding.unborrowedMaterialNum.text = viewModel.unborrowedMaterial.toString()

+ 0 - 119
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialExchangeFragment.kt

@@ -1,119 +0,0 @@
-package com.grkj.iscs_mc.features.main.fragment.material_manage
-
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.DefaultItemAnimator
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.drake.brv.BindingAdapter
-import com.drake.brv.utils.linear
-import com.drake.brv.utils.setup
-import com.google.android.flexbox.AlignItems
-import com.google.android.flexbox.FlexDirection
-import com.google.android.flexbox.FlexWrap
-import com.google.android.flexbox.FlexboxLayoutManager
-import com.google.android.flexbox.JustifyContent
-import com.grkj.iscs_mc.R
-import com.grkj.iscs_mc.databinding.FragmentMaterialExchangeBinding
-import com.grkj.iscs_mc.databinding.ItemMaterialExchangeHeaderBinding
-import com.grkj.iscs_mc.features.main.entity.MaterialExchangeHeaderEntity
-import com.grkj.ui_base.base.BaseFragment
-import com.grkj.ui_base.skin.loadSkinIcon
-import com.grkj.ui_base.utils.CommonUtils
-import com.sik.sikcore.extension.setDebouncedClickListener
-import dagger.hilt.android.AndroidEntryPoint
-
-/**
- * 物资取还
- */
-@AndroidEntryPoint
-class MaterialExchangeFragment : BaseFragment<FragmentMaterialExchangeBinding>() {
-    override fun getLayoutId(): Int {
-        return R.layout.fragment_material_exchange
-    }
-
-    override fun initView() {
-        binding.headerRvList.linear(LinearLayoutManager.HORIZONTAL).setup {
-            addType<MaterialExchangeHeaderEntity>(R.layout.item_material_exchange_header)
-            onBind {
-                onHeaderRVListBinding()
-            }
-        }
-    }
-
-    private fun BindingAdapter.BindingViewHolder.onHeaderRVListBinding() {
-        val item = getModel<MaterialExchangeHeaderEntity>()
-        val itemBinding = getBinding<ItemMaterialExchangeHeaderBinding>()
-        itemBinding.headerIcons.loadSkinIcon(item.headerIcon)
-        itemBinding.headerTitle.text = CommonUtils.getStr(item.headerTitle, item.headerNumber)
-        itemBinding.root.setDebouncedClickListener {
-            changeMaterialData(item.materialsTypeId)
-        }
-    }
-
-    /**
-     * 变更物资数据
-     */
-    private fun changeMaterialData(materialsTypeId: Long) {
-
-    }
-
-    fun toggleHeader(expanded: Boolean) {
-        val rv = binding.headerRvList
-
-        // 切换前压一下布局 & 动画,避免闪烁
-        rv.itemAnimator = null
-        rv.suppressLayout(true)
-
-        if (expanded) {
-            // 展开:Flexbox 多行换行
-            val flex = FlexboxLayoutManager(rv.context).apply {
-                flexDirection = FlexDirection.ROW
-                flexWrap = FlexWrap.WRAP
-                justifyContent = JustifyContent.FLEX_START
-                alignItems = AlignItems.FLEX_START
-            }
-            // 如果之前加过 DividerItemDecoration(Linear 用),先移除
-            removeLinearDividerIfAny(rv)
-
-            rv.layoutManager = flex
-            // 展开时通常需要自适应高度
-            rv.layoutParams = rv.layoutParams.apply {
-                height = ViewGroup.LayoutParams.WRAP_CONTENT
-            }
-            // Flex 场景一般不需要过度回弹
-            rv.overScrollMode = View.OVER_SCROLL_NEVER
-        } else {
-            // 收起:单行横向
-            val linear = LinearLayoutManager(rv.context, RecyclerView.HORIZONTAL, false)
-            rv.layoutManager = linear
-            // 收起时限制高度为一行(按你项目的 chip 高度 + padding 来)
-            rv.layoutParams = rv.layoutParams.apply {
-                height =
-                    rv.resources.getDimensionPixelSize(com.grkj.ui_base.R.dimen.header_chip_row_height)
-            }
-            // 如需单行分隔/间距可恢复 DividerItemDecoration(HORIZONTAL)
-            addLinearDividerIfNeeded(rv)
-        }
-
-        // 触发行高/换行的重新测量
-        rv.requestLayout()
-        rv.suppressLayout(false)
-        rv.itemAnimator = DefaultItemAnimator() // 需要再开动画就恢复
-    }
-
-    private fun removeLinearDividerIfAny(rv: RecyclerView) {
-        val count = rv.itemDecorationCount
-        for (i in count - 1 downTo 0) {
-            val d = rv.getItemDecorationAt(i)
-            if (d is DividerItemDecoration) rv.removeItemDecoration(d)
-        }
-    }
-
-    private fun addLinearDividerIfNeeded(rv: RecyclerView) {
-        // 视需求添加;如果你用的是 item 自带 margin,那就不用 Divider
-        // rv.addItemDecoration(DividerItemDecoration(rv.context, RecyclerView.HORIZONTAL))
-    }
-
-}

+ 202 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsExchangeFragment.kt

@@ -0,0 +1,202 @@
+package com.grkj.iscs_mc.features.main.fragment.material_manage
+
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import coil.load
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.dividerSpace
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.google.android.flexbox.AlignItems
+import com.google.android.flexbox.FlexDirection
+import com.google.android.flexbox.FlexWrap
+import com.google.android.flexbox.FlexboxItemDecoration
+import com.google.android.flexbox.FlexboxLayoutManager
+import com.google.android.flexbox.JustifyContent
+import com.grkj.data.local.dos.IsMaterials
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentMaterialExchangeBinding
+import com.grkj.iscs_mc.databinding.ItemMaterialExchangeHeaderBinding
+import com.grkj.iscs_mc.databinding.ItemMaterialsBinding
+import com.grkj.iscs_mc.features.main.entity.MaterialExchangeHeaderEntity
+import com.grkj.iscs_mc.features.main.viewmodel.material_manage.MaterialExchangeViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.skin.loadSkinIcon
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.event.BottomTipEvent
+import com.grkj.ui_base.utils.extension.clearSrcTint
+import com.grkj.ui_base.utils.extension.setSrcTint
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 物资取还
+ */
+@AndroidEntryPoint
+class MaterialsExchangeFragment : BaseFragment<FragmentMaterialExchangeBinding>() {
+    private val viewModel: MaterialExchangeViewModel by viewModels()
+    private var expanded: Boolean = false
+    private var currentMaterialsTypeId: Long = -1L
+    private var currentTabPosition: Int = 0
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_material_exchange
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.borrowableLayout.setDebouncedClickListener {
+            changeTab(0)
+        }
+        binding.waitBackLayout.setDebouncedClickListener {
+            changeTab(1)
+        }
+        binding.headerRvList.linear(LinearLayoutManager.HORIZONTAL).setup {
+            addType<MaterialExchangeHeaderEntity>(R.layout.item_material_exchange_header)
+            onBind {
+                onHeaderRVListBinding()
+            }
+        }
+        binding.headerRvListAll.apply {
+            layoutManager = FlexboxLayoutManager(requireContext())
+        }.setup {
+            addType<MaterialExchangeHeaderEntity>(R.layout.item_material_exchange_header)
+            onBind {
+                onHeaderRVListBinding()
+            }
+        }
+        binding.listRv.apply {
+            layoutManager = FlexboxLayoutManager(requireContext())
+        }.setup {
+            addType<IsMaterials>(R.layout.item_materials)
+            onBind {
+                onMaterialsRVListBinding()
+            }
+        }
+        binding.headerExpandIv.setDebouncedClickListener {
+            expanded = !expanded
+            binding.headerExpandIv.rotation = if (expanded) 180f else 0f
+            binding.headerRvListAll.isVisible = expanded
+            binding.headerRvList.isVisible = !expanded
+        }
+        binding.openDoor.setDebouncedClickListener {
+            showLoading(CommonUtils.getStr("in_open_door"))
+            viewModel.openDoor().observe(this) {
+                hideLoading()
+                if (it) {
+                    showToast(CommonUtils.getStr("open_door_success"))
+                } else {
+                    showToast(CommonUtils.getStr("open_door_failed"))
+                }
+            }
+        }
+        BottomTipEvent.sendBottomTipEvent(CommonUtils.getStr("material_exchange_tip"))
+        changeTab(currentTabPosition, false)
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onMaterialsRVListBinding() {
+        val item = getModel<IsMaterials>()
+        val itemBinding = getBinding<ItemMaterialsBinding>()
+        itemBinding.materialsPicture.load(viewModel.materialsType.find { it.materialsTypeId == item.materialsTypeId }?.materialsTypePicture)
+        itemBinding.materialsName.text = item.materialsName
+        itemBinding.materialsRfid.text = item.materialsRfid
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onHeaderRVListBinding() {
+        val item = getModel<MaterialExchangeHeaderEntity>()
+        val itemBinding = getBinding<ItemMaterialExchangeHeaderBinding>()
+        item.headerIcon?.let {
+            if (item.materialsTypeId == -1L) {
+                itemBinding.headerIcons.loadSkinIcon(item.headerIcon)
+            } else {
+                if (it.endsWith("png") || it.endsWith("jpg") || it.endsWith("jpeg") || it.endsWith("webp")) {
+                    itemBinding.headerIcons.clearSrcTint()
+                } else {
+                    itemBinding.headerIcons.setSrcTint(CommonUtils.getColor(com.grkj.ui_base.R.attr.colorPrimary))
+                }
+                itemBinding.headerIcons.load(item.headerIcon)
+            }
+        } ?: run {
+            itemBinding.headerIcons.loadSkinIcon("boxes.svg")
+        }
+        itemBinding.headerTitle.text = "${item.headerTitle}(${item.headerNumber})"
+        itemBinding.underLine.isVisible = item.materialsTypeId == currentMaterialsTypeId
+        itemBinding.root.setDebouncedClickListener {
+            changeMaterialData(currentTabPosition, item.materialsTypeId)
+            adapter.notifyDataSetChanged()
+        }
+    }
+
+    private fun changeTab(tabPosition: Int, loadData: Boolean = true) {
+        binding.borrowableLayout.isSelected = tabPosition == 0
+        binding.waitBackLayout.isSelected = tabPosition == 1
+        currentTabPosition = tabPosition
+        if (loadData) {
+            changeMaterialData(currentTabPosition, currentMaterialsTypeId)
+        }
+    }
+
+    /**
+     * 变更物资数据
+     */
+    private fun changeMaterialData(tabPosition: Int, materialsTypeId: Long) {
+        currentMaterialsTypeId = materialsTypeId
+        binding.listRv.models = viewModel.materialsData.filter {
+            if (tabPosition == 0) {
+                it.materialsId !in viewModel.materialsLoan.map { it.materialsId }
+            } else {
+                it.materialsId in viewModel.materialsLoan.map { it.materialsId }
+            } && (materialsTypeId == -1L || it.materialsTypeId == materialsTypeId)
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.getMaterialsData().observe(this) {
+            if (viewModel.materialsData.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.waitBackNum.isVisible = !viewModel.materialsLoan.isEmpty()
+            binding.waitBackNum.text = viewModel.materialsLoan.size.toString()
+            binding.headerRvList.models = listOf(
+                MaterialExchangeHeaderEntity(
+                    "border-all.svg",
+                    CommonUtils.getStr("all"),
+                    viewModel.materialsData.size,
+                    -1L
+                )
+            ) + viewModel.materialsType.map {
+                MaterialExchangeHeaderEntity(
+                    it.materialsTypeIcon,
+                    it.materialsTypeName,
+                    viewModel.materialsData.filter { type -> type.materialsTypeId == it.materialsTypeId }.size,
+                    it.materialsTypeId
+                )
+            }
+            binding.headerRvListAll.models = listOf(
+                MaterialExchangeHeaderEntity(
+                    "border-all.svg",
+                    CommonUtils.getStr("all"),
+                    viewModel.materialsData.size,
+                    -1L
+                )
+            ) + viewModel.materialsType.map {
+                MaterialExchangeHeaderEntity(
+                    it.materialsTypeIcon,
+                    it.materialsTypeName,
+                    viewModel.materialsData.filter { type -> type.materialsTypeId == it.materialsTypeId }.size,
+                    it.materialsTypeId
+                )
+            }
+            changeMaterialData(currentTabPosition, currentMaterialsTypeId)
+        }
+    }
+
+}

+ 4 - 2
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialHomeManageFragment.kt → app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsHomeManageFragment.kt

@@ -29,7 +29,7 @@ import dagger.hilt.android.AndroidEntryPoint
  * 物资管理
  */
 @AndroidEntryPoint
-class MaterialHomeManageFragment : BaseFragment<FragmentMaterialManageHomeBinding>() {
+class MaterialsHomeManageFragment : BaseFragment<FragmentMaterialManageHomeBinding>() {
 
     private var menuData: MutableList<MenuItemEntity> = mutableListOf(
         MenuItemEntity(
@@ -111,7 +111,9 @@ class MaterialHomeManageFragment : BaseFragment<FragmentMaterialManageHomeBindin
 
     private fun onMenuClick(menuType: Int) {
         when (menuType) {
-            0 -> navController.navigate(R.id.action_materialHomeManageFragment_to_materialExchangeFragment)
+            0 -> navController.navigate(R.id.action_materialHomeManageFragment_to_materialsExchangeFragment)
+            1 -> navController.navigate(R.id.action_materialHomeManageFragment_to_materialsTypeManageFragment)
+            2 -> navController.navigate(R.id.action_materialHomeManageFragment_to_materialsManageFragment)
             else -> {
                 showToast(CommonUtils.getStr("feature_under_development"))
             }

+ 20 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsManageFragment.kt

@@ -0,0 +1,20 @@
+package com.grkj.iscs_mc.features.main.fragment.material_manage
+
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentMaterialsManageBinding
+import com.grkj.ui_base.base.BaseFragment
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 物资管理
+ */
+@AndroidEntryPoint
+class MaterialsManageFragment : BaseFragment<FragmentMaterialsManageBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_materials_manage
+    }
+
+    override fun initView() {
+
+    }
+}

+ 165 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/fragment/material_manage/MaterialsTypeManageFragment.kt

@@ -0,0 +1,165 @@
+package com.grkj.iscs_mc.features.main.fragment.material_manage
+
+import androidx.fragment.app.viewModels
+import coil.load
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.dividerSpace
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.FragmentMaterialsTypeManageBinding
+import com.grkj.iscs_mc.databinding.ItemMaterialsTypeBinding
+import com.grkj.iscs_mc.features.main.dialog.material_manage.AddMaterialsTypeDialog
+import com.grkj.iscs_mc.features.main.dialog.material_manage.UpdateMaterialsTypeDialog
+import com.grkj.iscs_mc.features.main.entity.IsMaterialsTypeEntity
+import com.grkj.iscs_mc.features.main.viewmodel.material_manage.MaterialsTypeManageViewModel
+import com.grkj.shared.utils.FilePickerUtils
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.sik.sikcore.data.BeanUtils
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 物资类型管理
+ */
+@AndroidEntryPoint
+class MaterialsTypeManageFragment : BaseFragment<FragmentMaterialsTypeManageBinding>() {
+    private val viewModel: MaterialsTypeManageViewModel by viewModels()
+    private lateinit var filePickerUtils: FilePickerUtils
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_materials_type_manage
+    }
+
+    override fun initView() {
+        filePickerUtils = FilePickerUtils(this)
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.delete.setDebouncedClickListener {
+            deleteSelect()
+        }
+        binding.add.setDebouncedClickListener {
+            AddMaterialsTypeDialog.show(filePickerUtils) { dialog, isMaterialsTypeEntity ->
+                viewModel.addMaterialsType(isMaterialsTypeEntity).observe(this) {
+                    dialog.dismiss()
+                    if (it) {
+                        TipDialog.showSuccess(CommonUtils.getStr("save_success"), onConfirmClick = {
+                            getMaterialsType()
+                        }, onCancelClick = {
+                            getMaterialsType()
+                        })
+                    } else {
+                        TipDialog.showError(CommonUtils.getStr("save_failed"))
+                    }
+                }
+            }
+        }
+        binding.listRv.linear().dividerSpace(10, DividerOrientation.GRID).setup {
+            addType<IsMaterialsType>(R.layout.item_materials_type)
+            onBind {
+                onRVListBinding()
+            }
+        }
+        checkSelectAllStatus()
+    }
+
+    /**
+     * 删除选中
+     */
+    private fun deleteSelect() {
+        viewModel.checkMaterialsInBorrowed().observe(this) {
+            if (it) {
+                showToast(CommonUtils.getStr("has_materials_type_in_use"))
+                return@observe
+            }
+            viewModel.deleteSelect().observe(this) {
+                if (it) {
+                    TipDialog.showSuccess(CommonUtils.getStr("delete_success"), onConfirmClick = {
+                        getMaterialsType()
+                    }, onCancelClick = {
+                        getMaterialsType()
+                    })
+                } else {
+                    TipDialog.showError(CommonUtils.getStr("delete_failed"))
+                }
+            }
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onRVListBinding() {
+        val item = getModel<IsMaterialsType>()
+        val itemBinding = getBinding<ItemMaterialsTypeBinding>()
+        itemBinding.materialsTypeName.text = item.materialsTypeName
+        itemBinding.materialsTypeIcon.load(item.materialsTypeIcon)
+        itemBinding.materialsTypePicture.load(item.materialsTypePicture)
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { v, checked ->
+            item.isSelected = checked
+            checkSelectAllStatus()
+        }
+        itemBinding.root.setDebouncedClickListener {
+            BeanUtils.copyProperties(
+                item,
+                IsMaterialsTypeEntity::class.java
+            )?.let {
+                UpdateMaterialsTypeDialog.show(
+                    filePickerUtils,
+                    it,
+                ) { dialog, isMaterialsTypeEntity ->
+                    viewModel.updateMaterialsType(isMaterialsTypeEntity)
+                        .observe(this@MaterialsTypeManageFragment) {
+                            dialog.dismiss()
+                            if (it) {
+                                TipDialog.showSuccess(
+                                    CommonUtils.getStr("save_success"),
+                                    onConfirmClick = {
+                                        getMaterialsType()
+                                    },
+                                    onCancelClick = {
+                                        getMaterialsType()
+                                    })
+                            } else {
+                                TipDialog.showError(CommonUtils.getStr("save_failed"))
+                            }
+                        }
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查全选状态
+     */
+    private fun checkSelectAllStatus() {
+        binding.selectAll.setOnCheckedChangeListener(null)
+        binding.selectAll.isChecked =
+            viewModel.materialsTypeData.all { it.isSelected } && viewModel.materialsTypeData.isNotEmpty()
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.materialsTypeData.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getMaterialsType()
+    }
+
+    private fun getMaterialsType() {
+        viewModel.getMaterialsType().observe(this) {
+            if (viewModel.materialsTypeData.isEmpty()) {
+                binding.state.showEmpty()
+            } else {
+                binding.state.showContent()
+            }
+            binding.listRv.models = viewModel.materialsTypeData
+            checkSelectAllStatus()
+        }
+    }
+}

+ 52 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/material_manage/MaterialExchangeViewModel.kt

@@ -0,0 +1,52 @@
+package com.grkj.iscs_mc.features.main.viewmodel.material_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.domain.logic.IMaterialsLogic
+import com.grkj.data.enums.HardwareMode
+import com.grkj.data.local.dos.IsMaterials
+import com.grkj.data.local.dos.IsMaterialsLoan
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.ui_base.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+@HiltViewModel
+class MaterialExchangeViewModel @Inject constructor(
+    val materialsLogic: IMaterialsLogic
+) : BaseViewModel() {
+    /**
+     * 物资数据
+     */
+    var materialsData: List<IsMaterials> = listOf()
+
+    /**
+     * 物资借出数据
+     */
+    var materialsLoan: List<IsMaterialsLoan> = listOf()
+
+    /**
+     * 物资类型
+     */
+    var materialsType: List<IsMaterialsType> = listOf()
+
+    /**
+     * 获取物资数据
+     */
+    fun getMaterialsData(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            materialsType = materialsLogic.getMaterialsType()
+            emit(true)
+        }
+    }
+
+    /**
+     * 开柜
+     */
+    fun openDoor(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(HardwareMode.getCurrentHardwareMode().openDoor())
+        }
+    }
+}

+ 78 - 0
app/src/main/java/com/grkj/iscs_mc/features/main/viewmodel/material_manage/MaterialsTypeManageViewModel.kt

@@ -0,0 +1,78 @@
+package com.grkj.iscs_mc.features.main.viewmodel.material_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.domain.logic.IMaterialsLogic
+import com.grkj.data.domain.logic.impl.MaterialsLogic
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.iscs_mc.features.main.entity.IsMaterialsTypeEntity
+import com.grkj.ui_base.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 物资类型管理
+ */
+@HiltViewModel
+class MaterialsTypeManageViewModel @Inject constructor(
+    val materialsLogic: IMaterialsLogic
+) : BaseViewModel() {
+    /**
+     * 物资类型数据
+     */
+    var materialsTypeData: List<IsMaterialsType> = listOf()
+
+    /**
+     * 获取物资类型
+     */
+    fun getMaterialsType(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            materialsTypeData = materialsLogic.getMaterialsType()
+            emit(true)
+        }
+    }
+
+    /**
+     * 检查是否有正在使用的物资类型
+     */
+    fun checkMaterialsInBorrowed(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val hasMaterialsInBorrowed =
+                materialsLogic.checkMaterialsInBorrowed(materialsTypeData.filter { it.isSelected }
+                    .map { it.materialsTypeId })
+            emit(hasMaterialsInBorrowed)
+        }
+    }
+
+    /**
+     * 删除选中
+     */
+    fun deleteSelect(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            materialsLogic.deleteMaterialsType(materialsTypeData.filter { it.isSelected }
+                .map { it.materialsTypeId })
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加物资类型
+     */
+    fun addMaterialsType(materialsTypeEntity: IsMaterialsTypeEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            materialsLogic.insertMaterialsType(materialsTypeEntity)
+            emit(true)
+        }
+    }
+
+    /**
+     * 更新物资类型
+     */
+    fun updateMaterialsType(materialsTypeEntity: IsMaterialsTypeEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO){
+            materialsLogic.updateMaterialsType(materialsTypeEntity)
+            emit(true)
+        }
+    }
+}

+ 13 - 1
app/src/main/java/com/grkj/iscs_mc/features/splash/activity/SplashActivity.kt

@@ -12,6 +12,8 @@ import com.grkj.iscs_mc.databinding.ActivitySplashBinding
 import com.grkj.iscs_mc.features.login.activity.LoginActivity
 import com.grkj.iscs_mc.features.splash.viewmodel.SplashViewModel
 import com.grkj.shared.config.Constants
+import com.grkj.shared.utils.i18n.LanguageRegistry
+import com.grkj.shared.utils.i18n.LanguageStore
 import com.grkj.ui_base.base.BaseActivity
 import com.kongzue.dialogx.DialogX
 import com.kongzue.dialogx.util.TextInfo
@@ -22,6 +24,7 @@ import dagger.hilt.android.HiltAndroidApp
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import java.util.Locale
 
 @AndroidEntryPoint
 class SplashActivity : BaseActivity<ActivitySplashBinding>() {
@@ -32,6 +35,15 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
 
     override fun initView() {
         // 应用启动时按已存配置安排下一次(默认=每天 00:00)
+        if (LanguageStore.currentMode() == LanguageStore.Mode.FOLLOW_SYSTEM) {
+            val targetRegion = "US" // 自己决定用什么;也可以是 "CN" / 配置项 / 服务器下发
+            val entries = LanguageRegistry.entriesFromSources(targetRegion)
+            LanguageStore.setExplicit(
+                Locale.forLanguageTag(
+                    entries.find { it.region == targetRegion }?.tag ?: entries[0].tag
+                )
+            )
+        }
         val dialogXTextInfo = TextInfo()
         val dialogXTitleTextInfo = TextInfo().apply {
             isBold = true
@@ -64,7 +76,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
         lifecycleScope.launch {
             BackupScheduler.applySaved(this@SplashActivity)
             DbReadyGate.await()
-            viewModel.checkPresetData().observe(this@SplashActivity){
+            viewModel.checkPresetData().observe(this@SplashActivity) {
                 viewModel.checkSysMenuAndRole().observe(this@SplashActivity) {
                     startActivity(Intent(this@SplashActivity, LoginActivity::class.java))
                     finish()

+ 5 - 0
app/src/main/res/drawable/bg_badge.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="?attr/colorStatusRed" />
+</shape>

+ 9 - 0
app/src/main/res/drawable/bg_tab_container_normal.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="?attr/colorContainerBg" />
+    <stroke
+        android:width="@dimen/iscs_stroke_sm"
+        android:color="?attr/colorBgStroke" />
+    <corners android:radius="@dimen/iscs_radius_sm" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/bg_tab_container_selected.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="?attr/colorInfo" />
+    <corners android:radius="@dimen/iscs_radius_sm" />
+</shape>

+ 5 - 0
app/src/main/res/drawable/bg_tab_container_selector.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/bg_tab_container_selected" android:state_selected="true" />
+    <item android:drawable="@drawable/bg_tab_container_normal" />
+</selector>

+ 5 - 0
app/src/main/res/drawable/flex_divider.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size android:width="@dimen/iscs_space_4" android:height="@dimen/iscs_space_4"/>
+    <solid android:color="@android:color/transparent"/>
+</shape>

+ 3 - 3
app/src/main/res/layout-land/activity_main.xml

@@ -87,7 +87,7 @@
             android:name="androidx.navigation.fragment.NavHostFragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_above="@+id/login_tip"
+            android:layout_above="@+id/bottom_tip"
             android:layout_below="@+id/header_layout"
             android:layout_toRightOf="@+id/nav_bar"
             app:defaultNavHost="true"
@@ -99,14 +99,14 @@
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
             android:layout_gravity="center_vertical"
-            android:layout_marginLeft="@dimen/iscs_space_4"
             android:layout_marginVertical="@dimen/iscs_space_2"
+            android:layout_marginLeft="@dimen/iscs_space_4"
             android:layout_toRightOf="@+id/nav_bar"
             android:drawableLeft="@mipmap/tip"
             android:drawablePadding="@dimen/iscs_space_2"
             android:textColor="?attr/colorTextPrimary"
             android:textSize="@dimen/iscs_text_md"
-            android:visibility="gone" />
+            android:visibility="visible" />
 
         <com.grkj.ui_base.widget.CustomNavBar
             android:id="@+id/nav_bar"

+ 18 - 11
app/src/main/res/layout-land/fragment_login.xml

@@ -109,12 +109,11 @@
 
                 </LinearLayout>
 
-                <LinearLayout
+                <RelativeLayout
                     android:id="@+id/login_layout"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_horizontal"
-                    android:gravity="center_horizontal"
                     android:orientation="vertical"
                     android:padding="@dimen/iscs_space_2"
                     android:visibility="gone">
@@ -123,17 +122,20 @@
                         android:id="@+id/login_tip"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_marginBottom="@dimen/iscs_space_2"
+                        android:layout_marginVertical="@dimen/iscs_space_4"
                         android:drawableLeft="@mipmap/tip"
                         android:drawablePadding="@dimen/iscs_space_2"
+                        android:layout_centerHorizontal="true"
                         android:textColor="?attr/colorTextPrimary"
                         android:textSize="@dimen/iscs_text_md" />
 
                     <LinearLayout
                         android:id="@+id/login_account"
                         android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:layout_weight="1"
+                        android:layout_height="wrap_content"
+                        android:layout_below="@+id/login_tip"
+                        android:layout_above="@+id/tv_cancel"
+                        android:layout_centerHorizontal="true"
                         android:orientation="vertical">
 
                         <EditText
@@ -141,7 +143,7 @@
                             style="@style/CommonEdit"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_input_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:minWidth="@dimen/iscs_input_min_width"
                             android:textSize="@dimen/iscs_text_sm"
                             app:i18nHint='@{"please_input_account"}' />
@@ -151,7 +153,7 @@
                             style="@style/CommonEdit"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_input_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:inputType="textPassword"
                             android:minWidth="@dimen/iscs_input_min_width"
                             android:textSize="@dimen/iscs_text_sm"
@@ -162,7 +164,7 @@
                             style="@style/CommonBtn"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_btn_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:textSize="@dimen/iscs_text_sm"
                             app:i18nKey='@{"login"}' />
                     </LinearLayout>
@@ -171,7 +173,9 @@
                         android:id="@+id/login_face"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_weight="1"
+                        android:layout_below="@+id/login_tip"
+                        android:layout_above="@+id/tv_cancel"
+                        android:layout_centerHorizontal="true"
                         android:visibility="gone">
 
                         <FrameLayout
@@ -190,8 +194,8 @@
                             android:layout_width="@dimen/dialog_common_root_height_login_and_check"
                             android:layout_height="@dimen/dialog_common_root_height_login_and_check"
                             android:layout_centerHorizontal="true"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:layout_gravity="center_horizontal"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
                             android:tint="?attr/colorPrimary" />
                     </RelativeLayout>
 
@@ -200,10 +204,13 @@
                         style="@style/CommonBtn"
                         android:layout_width="@dimen/iscs_input_min_width"
                         android:layout_height="@dimen/login_dialog_btn_height"
+                        android:layout_marginBottom="@dimen/iscs_space_4"
+                        android:layout_alignParentBottom="true"
                         android:background="@drawable/white_stroke_bg"
                         android:textSize="@dimen/iscs_text_sm"
+                        android:layout_centerHorizontal="true"
                         app:i18nKey='@{"back"}' />
-                </LinearLayout>
+                </RelativeLayout>
             </FrameLayout>
         </LinearLayout>
 

+ 2 - 2
app/src/main/res/layout/activity_main.xml

@@ -107,8 +107,8 @@
                 android:drawableLeft="@mipmap/tip"
                 android:drawablePadding="@dimen/iscs_space_2"
                 android:textColor="?attr/colorTextPrimary"
-                android:visibility="gone"
-                android:textSize="@dimen/iscs_text_md" />
+                android:textSize="@dimen/iscs_text_md"
+                android:visibility="gone" />
 
             <com.grkj.ui_base.widget.CustomNavBar
                 android:id="@+id/nav_bar"

+ 206 - 0
app/src/main/res/layout/dialog_materials_type.xml

@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/dialog_common_root_height_large"
+        android:layout_marginHorizontal="@dimen/iscs_space_5"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_title_normal_padding_vertical">
+
+            <TextView
+                android:id="@+id/title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"materials_type_manage_add_title"}' />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:src="@drawable/icon_close"
+                android:tint="?attr/colorPrimary" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorDivider" />
+
+        <com.grkj.ui_base.widget.FormLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:clipToPadding="false"
+            android:minWidth="0dp"
+            android:orientation="vertical"
+            android:padding="@dimen/dialog_content_normal_padding_horizontal"
+            app:columnSpacing="@dimen/iscs_space_2"
+            app:rowSpacing="@dimen/iscs_space_2">
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/materials_type_name_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"materials_type_name"}'
+                app:markPosition="start"
+                app:required="true" />
+
+            <EditText
+                android:id="@+id/materials_type_name_et"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_marginRight="@dimen/dialog_content_normal_padding_horizontal"
+                android:background="@drawable/bg_common_input"
+                android:maxLines="1"
+                android:minWidth="@dimen/iscs_input_min_width"
+                android:paddingHorizontal="@dimen/iscs_space_2"
+                android:paddingVertical="@dimen/iscs_input_padding_vertical"
+                android:singleLine="true"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="field"
+                app:i18nHint='@{"please_input_materials_type_name"}' />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/materials_type_icon_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"materials_type_icon"}'
+                app:markPosition="start"
+                app:required="false" />
+
+
+            <ImageView
+                android:id="@+id/materials_type_icon"
+                android:layout_width="@dimen/iscs_icon_size_xl"
+                android:layout_height="@dimen/iscs_icon_size_xl"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:scaleType="fitCenter"
+                android:tint="?attr/colorPrimary"
+                app:formRole="field" />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/materials_type_picture_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"materials_type_picture"}'
+                app:markPosition="start"
+                app:required="false" />
+
+            <ImageView
+                android:id="@+id/materials_type_picture"
+                android:layout_width="@dimen/iscs_icon_size_xl"
+                android:layout_height="@dimen/iscs_icon_size_xl"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:scaleType="fitCenter"
+                app:formRole="field" />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/check_standard_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"check_standard"}'
+                app:labelAlignment="top"
+                app:markPosition="start"
+                app:required="false" />
+
+            <EditText
+                android:id="@+id/check_standard"
+                android:layout_width="@dimen/iscs_mutil_input_min_height"
+                android:layout_height="@dimen/iscs_mutil_input_min_height"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                android:gravity="top"
+                android:padding="@dimen/iscs_space_2"
+                android:textSize="@dimen/iscs_text_xs"
+                app:formRole="field" />
+
+            <com.grkj.ui_base.widget.RequiredTextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:formRole="label"
+                app:i18nKey='@{"remark"}'
+                app:labelAlignment="top"
+                app:markPosition="start"
+                app:required="false" />
+
+            <EditText
+                android:id="@+id/remark"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_common_input"
+                app:formRole="field" />
+        </com.grkj.ui_base.widget.FormLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/iscs_space_2">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_confirm"
+                android:drawableLeft="@mipmap/icon_confirm"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"confirm"}' />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_cancel"
+                android:drawableLeft="@mipmap/icon_cancel"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"cancel"}' />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 2 - 3
app/src/main/res/layout/fragment_home.xml

@@ -209,8 +209,7 @@
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="@dimen/iscs_space_2"
                     android:textColor="?attr/colorTextPrimary"
-                    android:textSize="@dimen/iscs_text_md"
-                    app:i18nKey='@{"you_have_borrowed_materials"}' />
+                    android:textSize="@dimen/iscs_text_md" />
             </LinearLayout>
 
 
@@ -219,7 +218,7 @@
                 android:layout_height="wrap_content"
                 android:background="@drawable/home_card_bg">
                 <com.drake.statelayout.StateLayout
-                    android:id="@+id/state_layout"
+                    android:id="@+id/state"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
 

+ 18 - 11
app/src/main/res/layout/fragment_login.xml

@@ -107,13 +107,12 @@
                     </LinearLayout>
                 </LinearLayout>
 
-                <LinearLayout
+                <RelativeLayout
                     android:id="@+id/login_layout"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_horizontal"
                     android:orientation="vertical"
-                    android:gravity="center_horizontal"
                     android:padding="@dimen/iscs_space_2"
                     android:visibility="gone">
 
@@ -121,17 +120,20 @@
                         android:id="@+id/login_tip"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_marginBottom="@dimen/iscs_space_2"
+                        android:layout_marginVertical="@dimen/iscs_space_4"
                         android:drawableLeft="@mipmap/tip"
                         android:drawablePadding="@dimen/iscs_space_2"
+                        android:layout_centerHorizontal="true"
                         android:textColor="?attr/colorTextPrimary"
                         android:textSize="@dimen/iscs_text_md" />
 
                     <LinearLayout
                         android:id="@+id/login_account"
                         android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        android:layout_weight="1"
+                        android:layout_height="wrap_content"
+                        android:layout_below="@+id/login_tip"
+                        android:layout_above="@+id/tv_cancel"
+                        android:layout_centerHorizontal="true"
                         android:orientation="vertical">
 
                         <EditText
@@ -139,7 +141,7 @@
                             style="@style/CommonEdit"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_input_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:minWidth="@dimen/iscs_input_min_width"
                             android:textSize="@dimen/iscs_text_sm"
                             app:i18nHint='@{"please_input_account"}' />
@@ -149,7 +151,7 @@
                             style="@style/CommonEdit"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_input_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:inputType="textPassword"
                             android:minWidth="@dimen/iscs_input_min_width"
                             android:textSize="@dimen/iscs_text_sm"
@@ -160,7 +162,7 @@
                             style="@style/CommonBtn"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/login_dialog_btn_height"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:textSize="@dimen/iscs_text_sm"
                             app:i18nKey='@{"login"}' />
                     </LinearLayout>
@@ -169,7 +171,9 @@
                         android:id="@+id/login_face"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
-                        android:layout_weight="1"
+                        android:layout_below="@+id/login_tip"
+                        android:layout_above="@+id/tv_cancel"
+                        android:layout_centerHorizontal="true"
                         android:visibility="gone">
 
                         <FrameLayout
@@ -188,7 +192,7 @@
                             android:layout_width="@dimen/dialog_common_root_height_login_and_check"
                             android:layout_height="@dimen/dialog_common_root_height_login_and_check"
                             android:layout_centerHorizontal="true"
-                            android:layout_marginBottom="@dimen/iscs_space_1"
+                            android:layout_marginTop="@dimen/iscs_space_4"
                             android:layout_gravity="center_horizontal"
                             android:tint="?attr/colorPrimary" />
                     </RelativeLayout>
@@ -198,10 +202,13 @@
                         style="@style/CommonBtn"
                         android:layout_width="@dimen/iscs_input_min_width"
                         android:layout_height="@dimen/login_dialog_btn_height"
+                        android:layout_marginBottom="@dimen/iscs_space_4"
+                        android:layout_alignParentBottom="true"
                         android:background="@drawable/white_stroke_bg"
                         android:textSize="@dimen/iscs_text_sm"
+                        android:layout_centerHorizontal="true"
                         app:i18nKey='@{"back"}' />
-                </LinearLayout>
+                </RelativeLayout>
             </FrameLayout>
         </LinearLayout>
 

+ 130 - 58
app/src/main/res/layout/fragment_material_exchange.xml

@@ -6,7 +6,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/iscs_space_2"
-        android:background="@drawable/home_card_bg"
         android:orientation="vertical">
 
         <LinearLayout
@@ -33,88 +32,161 @@
                 android:textStyle="bold"
                 app:i18nKey='@{"material_exchange"}' />
 
-            <TextView
-                android:id="@+id/back"
+            <LinearLayout
+                android:id="@+id/borrowableLayout"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginVertical="5dp"
                 android:layout_marginLeft="@dimen/iscs_space_2"
-                android:background="@drawable/common_btn_secondary"
-                android:drawableLeft="@mipmap/icon_back"
-                android:drawablePadding="@dimen/iscs_space_2"
-                android:drawableTint="?attr/colorPrimary"
+                android:background="@drawable/bg_tab_container_selector"
                 android:gravity="center"
-                android:minHeight="@dimen/common_btn_height"
-                android:paddingHorizontal="@dimen/iscs_space_4"
-                android:textColor="?attr/colorTextPrimary"
-                android:textSize="@dimen/iscs_text_md"
-                app:i18nKey='@{"back"}' />
+                android:orientation="horizontal"
+                android:paddingHorizontal="@dimen/iscs_space_4">
+
+                <TextView
+                    android:id="@+id/borrowableBtn"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"borrowable"}' />
+
+            </LinearLayout>
+
+
+            <LinearLayout
+                android:id="@+id/waitBackLayout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/bg_tab_container_selector"
+                android:orientation="horizontal"
+                android:paddingHorizontal="@dimen/iscs_space_4">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_md"
+                    app:i18nKey='@{"wait_back"}' />
+
+                <TextView
+                    android:id="@+id/waitBackNum"
+                    android:layout_width="@dimen/title_icon_size"
+                    android:layout_height="@dimen/title_icon_size"
+                    android:layout_marginLeft="@dimen/iscs_space_1"
+                    android:background="@drawable/bg_badge"
+                    android:gravity="center"
+                    android:textColor="?attr/colorTextPrimary"
+                    android:textSize="@dimen/iscs_text_sm"
+                    android:visibility="gone" />
+            </LinearLayout>
+
+
         </LinearLayout>
 
-        <View
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="@dimen/divider_line_space"
-            android:background="?attr/colorBlack" />
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/iscs_space_2"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:layout_weight="1"
+            android:background="@drawable/home_card_bg"
+            android:divider="@drawable/divider_table"
+            android:orientation="vertical"
+            android:showDividers="middle">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_card_header_bg"
+                android:orientation="horizontal"
+                android:showDividers="middle">
+
+                <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:minHeight="@dimen/header_chip_row_height">
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/header_rv_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:padding="@dimen/iscs_space_2" />
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/header_rv_list_all"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:padding="@dimen/iscs_space_2"
+                        android:visibility="gone"/>
+                </FrameLayout>
+
+
+                <ImageView
+                    android:id="@+id/header_expand_iv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="@dimen/iscs_space_1"
+                    android:layout_marginTop="@dimen/iscs_space_1"
+                    android:tint="?attr/colorPrimary"
+                    app:skinSrc='@{"angle-circle-down.svg"}' />
+            </LinearLayout>
+
+            <com.drake.statelayout.StateLayout
+                android:id="@+id/state"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg">
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/list_rv"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:padding="@dimen/iscs_space_2" />
+            </com.drake.statelayout.StateLayout>
+
+        </LinearLayout>
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/iscs_space_2"
+            android:layout_marginVertical="@dimen/iscs_space_2"
+            android:divider="@drawable/common_divider_normal_space_horizontal"
+            android:gravity="center"
             android:orientation="horizontal"
-            android:paddingHorizontal="@dimen/iscs_space_2"
-            android:paddingVertical="@dimen/iscs_space_2">
+            android:showDividers="middle">
 
             <TextView
-                android:layout_width="wrap_content"
+                android:id="@+id/open_door"
+                android:layout_width="@dimen/iscs_button_min_width"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/iscs_space_2"
-                android:layout_weight="1"
+                android:background="@drawable/common_btn_confirm"
+                android:drawableLeft="@mipmap/icon_confirm"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
                 android:textColor="?attr/colorTextPrimary"
                 android:textSize="@dimen/iscs_text_md"
-                app:i18nKey='@{"data_export_tip"}' />
+                app:i18nKey='@{"open_door"}' />
 
             <TextView
-                android:id="@+id/export"
-                android:layout_width="wrap_content"
+                android:id="@+id/back"
+                android:layout_width="@dimen/iscs_button_min_width"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/iscs_space_2"
-                android:layout_marginRight="@dimen/iscs_space_2"
-                android:background="@drawable/common_btn_secondary"
+                android:background="@drawable/common_btn_cancel"
+                android:drawableLeft="@mipmap/icon_cancel"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
                 android:paddingHorizontal="@dimen/iscs_space_4"
                 android:textColor="?attr/colorTextPrimary"
                 android:textSize="@dimen/iscs_text_md"
-                app:i18nKey='@{"common_export"}' />
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginHorizontal="@dimen/iscs_space_4"
-            android:layout_marginTop="@dimen/iscs_space_2"
-            android:background="@drawable/common_card_header_bg"
-            android:orientation="horizontal"
-            android:showDividers="middle">
-
-            <androidx.recyclerview.widget.RecyclerView
-                android:id="@+id/header_rv_list"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_weight="1" />
-
-            <ImageView
-                android:id="@+id/header_expand_iv"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginHorizontal="@dimen/iscs_space_1"
-                android:layout_marginTop="@dimen/iscs_space_1"
-                app:skinSrc='@{"angle-circle-down.svg"}' />
+                app:i18nKey='@{"back"}' />
         </LinearLayout>
-
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/list_rv"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_marginHorizontal="@dimen/iscs_space_4"
-            android:layout_marginBottom="@dimen/iscs_space_2"
-            android:background="@drawable/common_card_bg" />
     </LinearLayout>
 </layout>

+ 183 - 0
app/src/main/res/layout/fragment_materials_manage.xml

@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:tint="?attr/colorPrimary"
+                app:skinSrc='@{"boxes.svg"}' />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:textStyle="bold"
+                app:i18nKey='@{"materials_manage_title"}' />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:drawableTint="?attr/colorPrimary"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"back"}' />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_space_2">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"insert"}' />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"delete"}' />
+
+            <TextView
+                android:id="@+id/reset"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"reset"}' />
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/filter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"filter"}' />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:background="@drawable/common_card_header_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <com.google.android.material.checkbox.MaterialCheckBox
+                android:id="@+id/select_all"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                app:useMaterialThemeColors="true" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"role_manage_role_num"}' />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"role_manage_role_name"}' />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"role_manage_permission_string"}' />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginBottom="@dimen/iscs_space_2">
+
+            <com.drake.statelayout.StateLayout
+                android:id="@+id/state"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg">
+
+                <androidx.recyclerview.widget.RecyclerView
+                    android:id="@+id/role_list_rv"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:background="@drawable/common_card_bg" />
+            </com.drake.statelayout.StateLayout>
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 152 - 0
app/src/main/res/layout/fragment_materials_type_manage.xml

@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/iscs_space_2"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2">
+
+            <ImageView
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:tint="?attr/colorPrimary"
+                app:skinSrc='@{"tags.svg"}' />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:layout_weight="1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                android:textStyle="bold"
+                app:i18nKey='@{"material_type"}' />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="5dp"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:drawableLeft="@mipmap/icon_back"
+                android:drawablePadding="@dimen/iscs_space_2"
+                android:drawableTint="?attr/colorPrimary"
+                android:gravity="center"
+                android:minHeight="@dimen/common_btn_height"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"back"}' />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="?attr/colorBlack" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_space_2">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"insert"}' />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_2"
+                android:background="@drawable/common_btn_secondary"
+                android:paddingHorizontal="@dimen/iscs_space_4"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"delete"}' />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:background="@drawable/common_card_header_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <com.google.android.material.checkbox.MaterialCheckBox
+                android:id="@+id/select_all"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                app:useMaterialThemeColors="true" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"materials_type_name"}' />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"materials_type_icon"}' />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_md"
+                app:i18nKey='@{"materials_type_picture"}' />
+        </LinearLayout>
+
+
+        <com.drake.statelayout.StateLayout
+            android:id="@+id/state"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/iscs_space_4"
+            android:layout_marginBottom="@dimen/iscs_space_2"
+            android:background="@drawable/common_card_bg">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.drake.statelayout.StateLayout>
+    </LinearLayout>
+</layout>

+ 37 - 18
app/src/main/res/layout/item_material_exchange_header.xml

@@ -1,26 +1,45 @@
 <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <LinearLayout
+    <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:orientation="horizontal">
+        android:layout_height="wrap_content">
 
-        <ImageView
-            android:id="@+id/header_icons"
-            android:layout_width="@dimen/title_icon_size"
-            android:layout_height="@dimen/title_icon_size"
-            android:adjustViewBounds="true"
-            android:scaleType="fitCenter"
-            android:tint="?attr/colorPrimary" />
-
-        <TextView
-            android:id="@+id/header_title"
+        <LinearLayout
+            android:id="@+id/header_layout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginLeft="@dimen/iscs_space_1"
-            android:textColor="?attr/colorTextPrimary"
-            android:textSize="@dimen/iscs_text_sm" />
-    </LinearLayout>
+            android:gravity="center"
+            android:layout_marginLeft="@dimen/iscs_space_4"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/header_icons"
+                android:layout_width="@dimen/title_icon_size"
+                android:layout_height="@dimen/title_icon_size"
+                android:adjustViewBounds="true"
+                android:scaleType="fitCenter"
+                android:tint="?attr/colorPrimary" />
+
+            <TextView
+                android:id="@+id/header_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/iscs_space_1"
+                android:textColor="?attr/colorTextPrimary"
+                android:textSize="@dimen/iscs_text_sm" />
+        </LinearLayout>
+
+        <View
+            android:id="@+id/under_line"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/divider_underline_space"
+            android:layout_below="@+id/header_layout"
+            android:layout_alignLeft="@+id/header_layout"
+            android:layout_alignRight="@+id/header_layout"
+            android:layout_marginTop="@dimen/divider_underline_space"
+            android:visibility="gone"
+            android:background="@drawable/bg_underline" />
+    </RelativeLayout>
+
 </layout>

+ 33 - 0
app/src/main/res/layout/item_materials.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/iscs_space_4"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/materials_picture"
+            android:layout_width="120dp"
+            android:layout_height="120dp"
+            android:scaleType="fitCenter" />
+
+        <TextView
+            android:id="@+id/materials_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_md" />
+
+        <TextView
+            android:id="@+id/materials_rfid"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/iscs_space_2"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_xs" />
+    </LinearLayout>
+</layout>

+ 56 - 0
app/src/main/res/layout/item_materials_type.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:divider="@drawable/divider_table"
+        android:showDividers="middle">
+
+        <com.google.android.material.checkbox.MaterialCheckBox
+            android:id="@+id/select"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            app:useMaterialThemeColors="true" />
+
+        <TextView
+            android:id="@+id/materials_type_name"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_md" />
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/materials_type_icon"
+                android:layout_width="@dimen/iscs_icon_size_xl"
+                android:layout_height="@dimen/iscs_icon_size_xl"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/iscs_space_1"
+                android:scaleType="fitCenter"
+                android:tint="?attr/colorPrimary" />
+        </FrameLayout>
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/materials_type_picture"
+                android:layout_width="@dimen/iscs_icon_size_xl"
+                android:layout_height="@dimen/iscs_icon_size_xl"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/iscs_space_1"
+                android:scaleType="fitCenter" />
+        </FrameLayout>
+    </LinearLayout>
+</layout>

+ 17 - 3
app/src/main/res/navigation/nav_material_manage.xml

@@ -6,14 +6,28 @@
 
     <fragment
         android:id="@+id/materialHomeManageFragment"
-        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialHomeManageFragment"
+        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialsHomeManageFragment"
         android:label="MaterialHomeManageFragment" >
         <action
-            android:id="@+id/action_materialHomeManageFragment_to_materialExchangeFragment"
+            android:id="@+id/action_materialHomeManageFragment_to_materialsExchangeFragment"
             app:destination="@id/materialExchangeFragment" />
+        <action
+            android:id="@+id/action_materialHomeManageFragment_to_materialsTypeManageFragment"
+            app:destination="@id/materialsTypeManageFragment" />
+        <action
+            android:id="@+id/action_materialHomeManageFragment_to_materialsManageFragment"
+            app:destination="@id/materialsManageFragment" />
     </fragment>
     <fragment
         android:id="@+id/materialExchangeFragment"
-        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialExchangeFragment"
+        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialsExchangeFragment"
         android:label="MaterialExchangeFragment" />
+    <fragment
+        android:id="@+id/materialsTypeManageFragment"
+        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialsTypeManageFragment"
+        android:label="MaterialsTypeManageFragment" />
+    <fragment
+        android:id="@+id/materialsManageFragment"
+        android:name="com.grkj.iscs_mc.features.main.fragment.material_manage.MaterialsManageFragment"
+        android:label="MaterialsManageFragment" />
 </navigation>

+ 0 - 3
app/src/main/res/navigation/nav_user_info.xml

@@ -17,9 +17,6 @@
         <action
             android:id="@+id/action_userInfoHomeFragment_to_setJobCardFragment"
             app:destination="@id/setJobCardFragment" />
-        <action
-            android:id="@+id/action_userInfoHomeFragment_to_resetPasswordFragment2"
-            app:destination="@id/resetPasswordFragment" />
         <action
             android:id="@+id/action_userInfoHomeFragment_to_setFaceFragment"
             app:destination="@id/setFaceFragment" />

+ 1 - 1
data/build.gradle.kts

@@ -10,7 +10,7 @@ android {
     compileSdk = 36
 
     defaultConfig {
-        minSdk = 24
+        minSdk = 26
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")

+ 5 - 0
data/src/main/java/com/grkj/data/common/EventConstants.kt

@@ -32,6 +32,11 @@ object EventConstants {
      * 底部提示事件
      */
     const val EVENT_BOTTOM_TIP: Int = 100_000_006
+
+    /**
+     * 弹窗事件
+     */
+    const val EVENT_TOAST_CODE: Int = 100_000_007
     //---------------------------硬件通知------------------------
     /**
      * RFID读卡事件

+ 6 - 0
data/src/main/java/com/grkj/data/di/AppEntryPoint.kt

@@ -2,6 +2,7 @@ package com.grkj.data.di
 
 import com.grkj.data.domain.logic.IDataExportLogic
 import com.grkj.data.domain.logic.IHardwareLogic
+import com.grkj.data.domain.logic.IMaterialsLogic
 import com.grkj.data.domain.logic.ISysLogic
 import com.grkj.data.domain.logic.IUserLogic
 import dagger.hilt.EntryPoint
@@ -30,4 +31,9 @@ interface AppEntryPoint {
      * 导出
      */
     fun dataExportLogic(): IDataExportLogic
+
+    /**
+     * 物资
+     */
+    fun materialsLogic(): IMaterialsLogic
 }

+ 7 - 0
data/src/main/java/com/grkj/data/di/DatabaseModule.kt

@@ -1,6 +1,7 @@
 package com.grkj.data.di
 
 import com.grkj.data.local.dao.HardwareDao
+import com.grkj.data.local.dao.MaterialsDao
 import com.grkj.data.local.dao.SysDao
 import com.grkj.data.local.dao.UserDao
 import com.grkj.data.local.database.ISCSDatabase
@@ -44,4 +45,10 @@ object DatabaseModule {
     @Provides
     fun provideHardwareDao(db: ISCSDatabase): HardwareDao = db.hardwareDao()
 
+    /**
+     * 物资表提供
+     */
+    @Provides
+    fun provideMaterialsDao(db: ISCSDatabase): MaterialsDao = db.materialsDao()
+
 }

+ 7 - 0
data/src/main/java/com/grkj/data/di/LogicManager.kt

@@ -3,6 +3,7 @@ package com.grkj.data.di
 import android.app.Application
 import com.grkj.data.domain.logic.IDataExportLogic
 import com.grkj.data.domain.logic.IHardwareLogic
+import com.grkj.data.domain.logic.IMaterialsLogic
 import com.grkj.data.domain.logic.ISysLogic
 import com.grkj.data.domain.logic.IUserLogic
 import com.grkj.data.domain.logic.impl.DataExportLogic
@@ -32,11 +33,17 @@ object LogicManager {
      * 数据导出
      */
     lateinit var dataExportLogic: IDataExportLogic
+
+    /**
+     * 物资
+     */
+    lateinit var materialsLogic: IMaterialsLogic
     fun init(app: Application) {
         val ep = EntryPointAccessors.fromApplication(app, AppEntryPoint::class.java)
         userLogic = ep.userLogic()
         sysLogic = ep.sysLogic()
         hardwareLogic = ep.hardwareLogic()
         dataExportLogic = ep.dataExportLogic()
+        materialsLogic = ep.materialsLogic()
     }
 }

+ 9 - 0
data/src/main/java/com/grkj/data/di/LogicModule.kt

@@ -2,10 +2,12 @@ package com.grkj.data.di
 
 import com.grkj.data.domain.logic.IDataExportLogic
 import com.grkj.data.domain.logic.IHardwareLogic
+import com.grkj.data.domain.logic.IMaterialsLogic
 import com.grkj.data.domain.logic.ISysLogic
 import com.grkj.data.domain.logic.IUserLogic
 import com.grkj.data.domain.logic.impl.DataExportLogic
 import com.grkj.data.domain.logic.impl.HardwareLogic
+import com.grkj.data.domain.logic.impl.MaterialsLogic
 import com.grkj.data.domain.logic.impl.SysLogic
 import com.grkj.data.domain.logic.impl.UserLogic
 import dagger.Module
@@ -57,4 +59,11 @@ object LogicModule {
     @Provides
     @Singleton
     fun provideDataExportLogic(dataExportLogic: DataExportLogic): IDataExportLogic = dataExportLogic
+
+    /**
+     * 物资业务逻辑提供
+     */
+    @Provides
+    @Singleton
+    fun provideMaterialLogic(materialsLogic: MaterialsLogic): IMaterialsLogic = materialsLogic
 }

+ 14 - 0
data/src/main/java/com/grkj/data/di/RepositoryModule.kt

@@ -2,12 +2,15 @@ package com.grkj.data.di
 
 import com.grkj.data.common.MMKVConstants
 import com.grkj.data.repository.IHardwareRepository
+import com.grkj.data.repository.IMaterialsRepository
 import com.grkj.data.repository.ISysRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.data.repository.impl.network.NetworkHardwareRepository
+import com.grkj.data.repository.impl.network.NetworkMaterialsRepository
 import com.grkj.data.repository.impl.network.NetworkSysRepository
 import com.grkj.data.repository.impl.network.NetworkUserRepository
 import com.grkj.data.repository.impl.standard.StandardHardwareRepository
+import com.grkj.data.repository.impl.standard.StandardMaterialsRepository
 import com.grkj.data.repository.impl.standard.StandardSysRepository
 import com.grkj.data.repository.impl.standard.StandardUserRepository
 import com.sik.sikcore.extension.getMMKVData
@@ -61,4 +64,15 @@ object RepositoryModule {
         network: NetworkSysRepository,
     ): ISysRepository =
         if (MMKVConstants.SERVER_ADDRESS.getMMKVData("").isNotEmpty()) network else standard
+
+    /**
+     * 物资仓储注入
+     */
+    @Provides
+    @Singleton
+    fun provideMaterialsRepository(
+        standard: StandardMaterialsRepository,
+        network: NetworkMaterialsRepository,
+    ): IMaterialsRepository =
+        if (MMKVConstants.SERVER_ADDRESS.getMMKVData("").isNotEmpty()) network else standard
 }

+ 33 - 0
data/src/main/java/com/grkj/data/domain/logic/IMaterialsLogic.kt

@@ -0,0 +1,33 @@
+package com.grkj.data.domain.logic
+
+import com.grkj.data.local.dos.IsMaterialsType
+
+/**
+ * 物资相关业务
+ */
+interface IMaterialsLogic {
+    /**
+     * 获取物资类型数据
+     */
+    fun getMaterialsType(): List<IsMaterialsType>
+
+    /**
+     * 新增物资类型
+     */
+    fun insertMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 更新物资类型
+     */
+    fun updateMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 删除物资类型
+     */
+    fun deleteMaterialsType(materialTypeIds: List<Long>)
+
+    /**
+     * 检查是否有正在使用的物资类型
+     */
+    fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Boolean
+}

+ 36 - 0
data/src/main/java/com/grkj/data/domain/logic/impl/MaterialsLogic.kt

@@ -0,0 +1,36 @@
+package com.grkj.data.domain.logic.impl
+
+import com.grkj.data.domain.logic.BaseLogic
+import com.grkj.data.domain.logic.IMaterialsLogic
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.data.repository.IMaterialsRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * 物资相关业务
+ */
+@Singleton
+class MaterialsLogic @Inject constructor(
+    val materialsRepository: IMaterialsRepository
+) : BaseLogic(), IMaterialsLogic {
+    override fun getMaterialsType(): List<IsMaterialsType> {
+        return materialsRepository.getMaterialsType()
+    }
+
+    override fun insertMaterialsType(materialsType: IsMaterialsType) {
+        materialsRepository.insertMaterialsType(materialsType)
+    }
+
+    override fun updateMaterialsType(materialsType: IsMaterialsType) {
+        materialsRepository.updateMaterialsType(materialsType)
+    }
+
+    override fun deleteMaterialsType(materialTypeIds: List<Long>) {
+        materialsRepository.deleteMaterialsType(materialTypeIds)
+    }
+
+    override fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Boolean {
+        return materialsRepository.checkMaterialsInBorrowed(materialsTypeId)
+    }
+}

+ 4 - 0
data/src/main/java/com/grkj/data/hardware/IHardwareHelper.kt

@@ -4,4 +4,8 @@ package com.grkj.data.hardware
  * 硬件帮助抽象
  */
 interface IHardwareHelper {
+    /**
+     * 开柜门
+     */
+    suspend fun openDoor(left: Boolean? = null, right: Boolean? = null): Boolean
 }

+ 14 - 0
data/src/main/java/com/grkj/data/hardware/can/CanCommand.kt

@@ -183,6 +183,20 @@ object CanCommands {
             )
         }
 
+        /** 开门控制 */
+        fun controlDoorOpen(left: Boolean?, right: Boolean?): SdoRequest.Write {
+            var v = 0
+            if (left != null) v = v or (1 shl 0)
+            if (right != null) v = v or (1 shl 4)
+            return SdoRequest.Write(
+                nodeId,
+                Command.CONTROL_REG,
+                0x00,
+                shortLE(v, v),
+                2
+            )
+        }
+
         /** 1..5 位 RFID 常见映射:0x6020..0x6024 */
         fun getSlotRfid_1to5(slotIndex1to5: Int): SdoRequest.Read {
             require(slotIndex1to5 in 1..5) { "slotIndex must be 1..5" }

+ 12 - 0
data/src/main/java/com/grkj/data/hardware/can/CanHardwareHelper.kt

@@ -1,9 +1,21 @@
 package com.grkj.data.hardware.can
 
 import com.grkj.data.hardware.IHardwareHelper
+import com.sik.comm.impl_can.SdoOp
 
 /**
  * Can 硬件帮助接口实现
  */
 class CanHardwareHelper : IHardwareHelper {
+    override suspend fun openDoor(left: Boolean?, right: Boolean?): Boolean {
+        val materialCabinets =
+            CanHelper.getDeviceByDeviceType(CanDeviceConst.DEVICE_MATERIAL_CABINET_CONTROL_BOARD)
+        return if (materialCabinets.isNotEmpty()) {
+            val req = CanCommands.forDevice(materialCabinets.map { it.key }[0])
+                .controlDoorOpen(left, right)
+            CanHelper.writeTo(req)?.op != SdoOp.ERROR
+        } else {
+            false
+        }
+    }
 }

+ 30 - 0
data/src/main/java/com/grkj/data/hardware/can/CanHelper.kt

@@ -168,6 +168,21 @@ object CanHelper {
         }
     }
 
+    /**
+     * 读取
+     */
+    suspend fun readFrom(req: SdoRequest.Read): SdoResponse.ReadData? {
+        return runCatching {
+            ProtocolManager.getProtocol(CustomCanConfig.instance.deviceId)
+                .send(CustomCanConfig.instance.deviceId, req.toCommMessage())
+        }.onSuccess { rsp ->
+            rsp
+        }.onFailure {
+            logger.info("读取失败:${it}")
+            null
+        }.getOrNull()?.toSdoResponse() as? SdoResponse.ReadData
+    }
+
     /**
      * 写入到
      */
@@ -183,4 +198,19 @@ object CanHelper {
             }
         }
     }
+
+    /**
+     * 写入同步
+     */
+    suspend fun writeTo(req: SdoRequest.Write): SdoResponse.WriteAck? {
+        return runCatching {
+            ProtocolManager.getProtocol(CustomCanConfig.instance.deviceId)
+                .send(CustomCanConfig.instance.deviceId, req.toCommMessage())
+        }.onSuccess { rsp ->
+            rsp
+        }.onFailure {
+            logger.info("写入失败:${it}")
+            null
+        }.getOrNull()?.toSdoResponse() as? SdoResponse.WriteAck
+    }
 }

+ 50 - 0
data/src/main/java/com/grkj/data/local/dao/MaterialsDao.kt

@@ -0,0 +1,50 @@
+package com.grkj.data.local.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.grkj.data.local.dos.IsMaterialsType
+
+/**
+ * 物资表
+ */
+@Dao
+interface MaterialsDao {
+
+    /**
+     * 新增物资类型
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 更新物资类型q
+     */
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    fun updateMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 获取物资类型
+     */
+    @Query("select * from is_materials_type where del_flag = 0")
+    fun getMaterialsType(): List<IsMaterialsType>
+
+    /**
+     * 删除物资类型
+     */
+    @Query("update is_materials_type set del_flag = 1 where materials_type_id in (:materialTypeIds)")
+    fun deleteMaterialsType(materialTypeIds: List<Long>)
+
+    /**
+     * 检查是否有正在使用的物资类型
+     */
+    @Query("""
+        select count(*) from is_materials_loan iml
+        left join is_materials im on iml.materials_id = im.materials_id
+        left join is_materials_type imt on im.materials_type_id = imt.materials_type_id
+        where iml.status = 0 and iml.del_flag = 0 and imt.materials_type_id in (:materialsTypeId)
+    """)
+    fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Int
+}

+ 24 - 1
data/src/main/java/com/grkj/data/local/database/ISCSDatabase.kt

@@ -10,9 +10,23 @@ import androidx.room.TypeConverters
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.grkj.data.converters.Converters
 import com.grkj.data.local.dao.HardwareDao
+import com.grkj.data.local.dao.MaterialsDao
 import com.grkj.data.local.dao.SysDao
 import com.grkj.data.local.dao.UserDao
 import com.grkj.data.local.dos.IsJobCardDo
+import com.grkj.data.local.dos.IsMaterials
+import com.grkj.data.local.dos.IsMaterialsCabinet
+import com.grkj.data.local.dos.IsMaterialsChangeRecord
+import com.grkj.data.local.dos.IsMaterialsCheckPlan
+import com.grkj.data.local.dos.IsMaterialsCheckRecord
+import com.grkj.data.local.dos.IsMaterialsInstructions
+import com.grkj.data.local.dos.IsMaterialsLoan
+import com.grkj.data.local.dos.IsMaterialsPlanCabinet
+import com.grkj.data.local.dos.IsMaterialsProperty
+import com.grkj.data.local.dos.IsMaterialsPropertyValue
+import com.grkj.data.local.dos.IsMaterialsReminder
+import com.grkj.data.local.dos.IsMaterialsRestitutionRules
+import com.grkj.data.local.dos.IsMaterialsType
 import com.grkj.data.local.dos.SysMenu
 import com.grkj.data.local.dos.SysRole
 import com.grkj.data.local.dos.SysRoleMenu
@@ -36,7 +50,11 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase as CipherDB
  */
 @Database(
     entities = [SysUserDo::class, IsJobCardDo::class, SysUserCharacteristicDo::class,
-        SysMenu::class, SysRoleMenu::class, SysRole::class, SysUserRole::class],
+        SysMenu::class, SysRoleMenu::class, SysRole::class, SysUserRole::class, IsMaterials::class,
+        IsMaterialsCabinet::class, IsMaterialsChangeRecord::class, IsMaterialsCheckPlan::class,
+        IsMaterialsCheckRecord::class, IsMaterialsInstructions::class, IsMaterialsLoan::class,
+        IsMaterialsPlanCabinet::class, IsMaterialsProperty::class, IsMaterialsPropertyValue::class,
+        IsMaterialsReminder::class, IsMaterialsRestitutionRules::class, IsMaterialsType::class],
     version = ISCSMigrations.VERSION,
     exportSchema = true
 )
@@ -57,6 +75,11 @@ abstract class ISCSDatabase : RoomDatabase() {
      */
     abstract fun sysDao(): SysDao
 
+    /**
+     * 物资表
+     */
+    abstract fun materialsDao(): MaterialsDao
+
     companion object {
         const val DB_NAME = "iscs_mc_database.db"
         private val logger: Logger = LoggerFactory.getLogger(ISCSDatabase::class.java)

+ 13 - 8
data/src/main/java/com/grkj/data/local/database/ISCSMigrations.kt

@@ -61,14 +61,19 @@ object ISCSMigrations {
      */
     private val MIGRATION_3_4 = object : Migration(3, 4) {
         override fun migrate(database: SupportSQLiteDatabase) {
-            database.execSQL("DROP TABLE IF EXISTS `is_workstation`")
-            database.execSQL("""
-                CREATE TABLE IF NOT EXISTS sys_user_role (
-                    user_id INTEGER NOT NULL,
-                    role_id INTEGER NOT NULL,
-                    PRIMARY KEY (user_id, role_id)
-                );
-            """.trimIndent())
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials` (`materials_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `materials_code` TEXT, `materials_name` TEXT, `materials_type_id` INTEGER, `workarea_id` INTEGER, `materials_cabinet_id` INTEGER, `service_life` TEXT, `available_life` TEXT, `service_times` INTEGER, `available_times` INTEGER, `expiration_date` TEXT, `materials_rfid` TEXT, `supplier` TEXT, `start_time` TEXT, `properties` TEXT, `loan_state` TEXT NOT NULL DEFAULT '1', `status` TEXT NOT NULL DEFAULT '0', `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_cabinet` (`cabinet_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cabinet_code` TEXT NOT NULL, `cabinet_name` TEXT NOT NULL, `hardware_id` INTEGER, `workarea_id` INTEGER, `workstation_id` INTEGER, `cabinet_icon` TEXT, `cabinet_picture` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `status` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_change_record` (`change_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `check_record_id` INTEGER, `old_materials_id` INTEGER, `old_materials_rfid` TEXT, `new_materials_id` INTEGER, `new_materials_rfid` TEXT, `change_user_id` INTEGER, `change_date` TEXT, `change_type` TEXT, `operate_type` TEXT, `status` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_check_plan` (`plan_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `plan_name` TEXT, `workstation_id` INTEGER, `plan_date` TEXT NOT NULL, `check_user_id` INTEGER, `status` TEXT NOT NULL DEFAULT '0', `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_check_record` (`record_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `plan_id` INTEGER NOT NULL, `workstation_id` INTEGER, `cabinet_id` INTEGER, `materials_id` INTEGER NOT NULL, `check_user_id` INTEGER, `check_date` TEXT, `status` TEXT, `reason` TEXT, `measure` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_instructions` (`instructions_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `instructions_title` TEXT, `materials_type_id` INTEGER, `file_url` TEXT, `file_url_img` TEXT, `file_path_img` TEXT, `file_type` TEXT, `order_num` INTEGER NOT NULL DEFAULT 1, `status` TEXT NOT NULL DEFAULT '0', `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_loan` (`materials_loan_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `materials_id` INTEGER NOT NULL, `loan_user_id` INTEGER NOT NULL, `loan_from_id` INTEGER, `loan_time` TEXT, `reminder_time` TEXT, `restitution_user_id` INTEGER, `restitution_to_id` INTEGER, `restitution_time` TEXT, `actual_restitution_time` TEXT, `timeout_alarm` TEXT, `restitution_required` INTEGER NOT NULL DEFAULT 1, `del_flag` TEXT NOT NULL DEFAULT '0', `status` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_plan_cabinet` (`plan_id` INTEGER NOT NULL, `cabinet_id` INTEGER NOT NULL, `check_user_id` INTEGER, `signature_img` TEXT, `signature_time` TEXT, `submit` TEXT NOT NULL DEFAULT '0', `status` TEXT NOT NULL DEFAULT '0', PRIMARY KEY(`plan_id`, `cabinet_id`))")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_property` (`property_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `property_name` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `status` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_property_value` (`record_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `property_id` INTEGER NOT NULL, `value_name` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `status` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_reminder` (`record_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `materials_loan_id` INTEGER NOT NULL, `reminder_type` INTEGER, `read_flag` INTEGER NOT NULL DEFAULT 0, `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_restitution_rules` (`rule_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `materials_type_id` INTEGER NOT NULL, `restitution_required` INTEGER NOT NULL DEFAULT 1, `restoration_required` INTEGER, `loan_duration` INTEGER, `reminder_time` INTEGER, `timeout_alarm` INTEGER, `del_flag` TEXT NOT NULL DEFAULT '0', `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
+            database.execSQL("CREATE TABLE IF NOT EXISTS `is_materials_type` (`materials_type_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `materials_type_code` TEXT, `materials_type_name` TEXT NOT NULL, `parent_id` INTEGER NOT NULL DEFAULT 0, `ancestors` TEXT NOT NULL, `enable_flag` TEXT NOT NULL DEFAULT 'Y', `service_life` TEXT, `available_life` TEXT, `service_times` INTEGER, `available_times` INTEGER, `materials_type_icon` TEXT, `materials_type_picture` TEXT, `check_standard` TEXT, `del_flag` TEXT NOT NULL DEFAULT '0', `status` TEXT, `property_ids` TEXT, `create_by` TEXT, `create_time` TEXT, `update_by` TEXT, `update_time` TEXT, `remark` TEXT)")
         }
     }
 }

+ 33 - 0
data/src/main/java/com/grkj/data/repository/IMaterialsRepository.kt

@@ -0,0 +1,33 @@
+package com.grkj.data.repository
+
+import com.grkj.data.local.dos.IsMaterialsType
+
+/**
+ * 物资仓储
+ */
+interface IMaterialsRepository {
+    /**
+     * 获取物资类型
+     */
+    fun getMaterialsType(): List<IsMaterialsType>
+
+    /**
+     * 新增物资类型
+     */
+    fun insertMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 更新物资类型
+     */
+    fun updateMaterialsType(materialsType: IsMaterialsType)
+
+    /**
+     * 删除物资类型
+     */
+    fun deleteMaterialsType(materialTypeIds: List<Long>)
+
+    /**
+     * 检查是否有正在使用的物资类型
+     */
+    fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Boolean
+}

+ 33 - 0
data/src/main/java/com/grkj/data/repository/impl/network/NetworkMaterialsRepository.kt

@@ -0,0 +1,33 @@
+package com.grkj.data.repository.impl.network
+
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.data.repository.BaseRepository
+import com.grkj.data.repository.IMaterialsRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * 物资联网仓储
+ */
+@Singleton
+class NetworkMaterialsRepository @Inject constructor(): BaseRepository(), IMaterialsRepository {
+    override fun getMaterialsType(): List<IsMaterialsType> {
+        TODO("Not yet implemented")
+    }
+
+    override fun insertMaterialsType(materialsType: IsMaterialsType) {
+        TODO("Not yet implemented")
+    }
+
+    override fun updateMaterialsType(materialsType: IsMaterialsType) {
+        TODO("Not yet implemented")
+    }
+
+    override fun deleteMaterialsType(materialTypeIds: List<Long>) {
+        TODO("Not yet implemented")
+    }
+
+    override fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Boolean {
+        TODO("Not yet implemented")
+    }
+}

+ 36 - 0
data/src/main/java/com/grkj/data/repository/impl/standard/StandardMaterialsRepository.kt

@@ -0,0 +1,36 @@
+package com.grkj.data.repository.impl.standard
+
+import com.grkj.data.local.dao.MaterialsDao
+import com.grkj.data.local.dos.IsMaterialsType
+import com.grkj.data.repository.BaseRepository
+import com.grkj.data.repository.IMaterialsRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * 物资本地仓储
+ */
+@Singleton
+class StandardMaterialsRepository @Inject constructor(
+    val materialsDao: MaterialsDao
+) : BaseRepository(), IMaterialsRepository {
+    override fun getMaterialsType(): List<IsMaterialsType> {
+        return materialsDao.getMaterialsType()
+    }
+
+    override fun checkMaterialsInBorrowed(materialsTypeId: List<Long>): Boolean {
+        return materialsDao.checkMaterialsInBorrowed(materialsTypeId) > 0
+    }
+
+    override fun insertMaterialsType(materialsType: IsMaterialsType) {
+        materialsDao.insertMaterialsType(materialsType)
+    }
+
+    override fun updateMaterialsType(materialsType: IsMaterialsType) {
+        materialsDao.updateMaterialsType(materialsType)
+    }
+
+    override fun deleteMaterialsType(materialTypeIds: List<Long>) {
+        materialsDao.deleteMaterialsType(materialTypeIds)
+    }
+}

+ 28 - 0
data/src/main/java/com/grkj/data/utils/event/ToastEvent.kt

@@ -0,0 +1,28 @@
+package com.grkj.data.utils.event
+
+import com.grkj.data.common.EventConstants
+import com.grkj.shared.model.EventBean
+import com.grkj.shared.utils.event.EventHelper
+
+/**
+ * 弹窗事件
+ */
+class ToastEvent(
+    val msg: String? = null
+) {
+    companion object {
+        /**
+         * 发送加载通知
+         */
+        @JvmStatic
+        fun sendToastEvent(
+            msg: String? = null
+        ) {
+            val toastEventBean = EventBean<ToastEvent>(
+                EventConstants.EVENT_TOAST_CODE,
+                ToastEvent(msg)
+            )
+            EventHelper.sendEvent(toastEventBean)
+        }
+    }
+}

+ 2 - 2
shared/build.gradle.kts

@@ -11,7 +11,7 @@ android {
     compileSdk = 36
 
     defaultConfig {
-        minSdk = 24
+        minSdk = 26
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")
@@ -58,7 +58,7 @@ dependencies {
     api(libs.sik.extension.encrypt)
     api(libs.sik.extension.image)
     api("org.mindrot:jbcrypt:0.4")
-//    api("com.github.SilverIceKey:AndroidPdfViewer:3.2.1")
+    api("com.github.SilverIceKey:AndroidPdfViewer:3.2.2")
     implementation("com.machinezoo.sourceafis:sourceafis:3.15.0")
     implementation("com.google.dagger:hilt-android:2.56.2")
     ksp("com.google.dagger:hilt-android-compiler:2.56.2")

+ 11 - 0
shared/src/main/java/com/grkj/shared/utils/FilePickerUtils.kt

@@ -35,6 +35,9 @@ class FilePickerUtils(caller: ActivityResultCaller) {
     /**
      * 打开文件选择器
      * @param mimeTypes 文件类型,例如 arrayOf("application/pdf", "image")
+     * PNG → "image/png"
+     * JPG / JPEG → "image/jpeg"
+     * SVG → "image/svg+xml"
      *
      */
     fun pickFile(mimeTypes: Array<String> = arrayOf("*/*"), callback: (Uri?) -> Unit) {
@@ -50,4 +53,12 @@ class FilePickerUtils(caller: ActivityResultCaller) {
         onDirPicked = callback
         dirPickerLauncher.launch(null)
     }
+
+    companion object {
+        /**
+         * 图片格式
+         */
+        @JvmStatic
+        val imageMimeTypes = arrayOf("image/*")
+    }
 }

+ 1 - 1
ui-base/build.gradle.kts

@@ -11,7 +11,7 @@ android {
     compileSdk = 36
 
     defaultConfig {
-        minSdk = 24
+        minSdk = 26
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")

+ 14 - 8
ui-base/src/main/java/com/grkj/ui_base/base/BaseActivity.kt

@@ -9,19 +9,18 @@ import androidx.activity.enableEdgeToEdge
 import androidx.appcompat.app.AppCompatActivity
 import androidx.databinding.DataBindingUtil
 import androidx.databinding.ViewDataBinding
-import androidx.navigation.NavController
-import androidx.navigation.fragment.NavHostFragment
 import com.grkj.data.common.EventConstants
+import com.grkj.data.utils.event.LoadingEvent
+import com.grkj.data.utils.event.ToastEvent
 import com.grkj.shared.model.EventBean
 import com.grkj.shared.utils.KeyboardUtils
 import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.dialog.LoadingDialog
-import com.grkj.data.utils.event.LoadingEvent
-import com.grkj.ui_base.utils.event.JumpViewEvent
 import com.grkj.ui_base.utils.extension.checkPermissions
 import com.grkj.ui_base.utils.extension.tip
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikandroid.permission.PermissionUtils
+import me.jessyan.autosize.AutoSizeCompat
 import me.jessyan.autosize.internal.CustomAdapt
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
@@ -29,6 +28,7 @@ import org.greenrobot.eventbus.ThreadMode
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
+
 /**
  * BaseActivity: 支持 ViewBinding, EventBus, 权限管理, 串口 & 蓝牙, 以及 Navigation 多 Graph 切换 & BottomNav
  */
@@ -99,6 +99,14 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity(), CustomAd
                     }
                 }
             }
+
+            EventConstants.EVENT_TOAST_CODE -> {
+                (event.data as ToastEvent).apply {
+                    msg?.let {
+                        showToast(it)
+                    }
+                }
+            }
         }
     }
 
@@ -146,16 +154,14 @@ abstract class BaseActivity<V : ViewDataBinding> : AppCompatActivity(), CustomAd
         logger.info("横竖屏切换:orientation = ${newConfig.orientation}")
     }
 
-
     // 是否以“宽度”为基准适配,false 则按“高度”来算缩放比例
     override fun isBaseOnWidth(): Boolean {
-        logger.info("应用宽度为基准:${isPortrait()}")
         return isPortrait()
     }
 
-    // 设计图的尺寸 dp。竖屏时用 600dp 作为基准宽度;横屏时用 1024dp 作为基准高度
+    // 设计图的尺寸 dp。竖屏时用 600dp 作为基准宽度;横屏时用 600dp 作为基准高度
     override fun getSizeInDp(): Float {
-        return ISCSConfig.designSize
+        return ISCSConfig.DESIGN_SIZE
     }
 
     /** 子类必须实现:布局资源 ID */

+ 1 - 11
ui-base/src/main/java/com/grkj/ui_base/base/BaseFragment.kt

@@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory
 /**
  * BaseFragment: 支持 ViewBinding, EventBus, 权限管理, 串口 & 蓝牙, 以及 Navigation 切换
  */
-abstract class BaseFragment<V : ViewDataBinding> : Fragment(), CustomAdapt {
+abstract class BaseFragment<V : ViewDataBinding> : Fragment() {
     protected val logger: Logger = LoggerFactory.getLogger(this::class.java)
     protected lateinit var binding: V
     private var isFirstLoad: Boolean = true
@@ -137,16 +137,6 @@ abstract class BaseFragment<V : ViewDataBinding> : Fragment(), CustomAdapt {
         return resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
     }
 
-    // 是否以“宽度”为基准适配,false 则按“高度”来算缩放比例
-    override fun isBaseOnWidth(): Boolean {
-        return isPortrait()
-    }
-
-    // 设计图的尺寸 dp。竖屏时用 600dp 作为基准宽度;横屏时用 1024dp 作为基准高度
-    override fun getSizeInDp(): Float {
-        return ISCSConfig.designSize
-    }
-
     /** 获取布局资源 ID */
     @LayoutRes
     protected abstract fun getLayoutId(): Int

+ 2 - 2
ui-base/src/main/java/com/grkj/ui_base/config/ISCSConfig.kt

@@ -10,7 +10,7 @@ object ISCSConfig {
     /**
      * 是否是DEBUG模式
      */
-    val DEBUG: Boolean = true
+    val DEBUG: Boolean = false
 
     /**
      * 是否是联网版
@@ -20,5 +20,5 @@ object ISCSConfig {
     /**
      * 设计图尺寸,这边以短边为主
      */
-    val designSize = 600f
+    const val DESIGN_SIZE = 600f
 }

+ 18 - 0
ui-base/src/main/java/com/grkj/ui_base/utils/extension/View.kt

@@ -2,12 +2,15 @@ package com.grkj.ui_base.utils.extension
 
 import android.content.Context
 import android.content.res.Resources
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
 import android.graphics.Rect
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.ImageView
+import androidx.annotation.ColorInt
 import androidx.core.widget.ImageViewCompat
 import androidx.fragment.app.Fragment
 import androidx.recyclerview.widget.LinearLayoutManager
@@ -174,3 +177,18 @@ fun Float.dpToPx(context: Context): Float =
 fun ImageView.removeTint() {
     ImageViewCompat.setImageTintList(this, null)
 }
+
+/**
+ * 给 ImageView 的 src 着色
+ */
+fun ImageView.setSrcTint(@ColorInt color: Int, mode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN) {
+    this.colorFilter = PorterDuffColorFilter(color, mode)
+}
+
+/**
+ * 清除 tint
+ */
+fun ImageView.clearSrcTint() {
+    this.colorFilter = null
+}
+

+ 58 - 41
ui-base/src/main/java/com/grkj/ui_base/widget/CustomNavBar.kt

@@ -1,10 +1,8 @@
 package com.grkj.ui_base.widget
 
 import android.annotation.SuppressLint
-import android.app.Activity
 import android.content.Context
 import android.content.res.ColorStateList
-import android.content.res.Configuration
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.util.AttributeSet
@@ -23,10 +21,8 @@ import androidx.annotation.MenuRes
 import androidx.appcompat.view.menu.MenuBuilder
 import androidx.core.view.isEmpty
 import com.grkj.ui_base.R
-import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.skin.loadSkinIcon
 import com.sik.sikcore.extension.setDebouncedClickListener
-import me.jessyan.autosize.AutoSize
 import me.jessyan.autosize.utils.AutoSizeUtils
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
@@ -43,6 +39,10 @@ class CustomNavBar @JvmOverloads constructor(
 ) : LinearLayout(context, attrs, defStyle) {
     private val logger: Logger = LoggerFactory.getLogger(this::class.java)
 
+    private companion object {
+        const val NO_SELECTION = -1
+    }
+
     @SuppressLint("RestrictedApi")
     private val menuBuilder = MenuBuilder(context)
     val menu: Menu get() = menuBuilder
@@ -78,8 +78,8 @@ class CustomNavBar @JvmOverloads constructor(
         setWillNotDraw(false) // ★ 需要绘制分割线/选中态
         isClickable = true
         isFocusable = true
-        val isLand = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
-        AutoSize.autoConvertDensity(context as Activity, ISCSConfig.designSize, !isLand)
+//        val isLand = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+//        AutoSize.autoConvertDensity(context as Activity, ISCSConfig.designSize, !isLand)
         context.obtainStyledAttributes(attrs, R.styleable.CustomNavBar, defStyle, 0).apply {
             navOrientation = getInt(R.styleable.CustomNavBar_navOrientation, 0)
             iconSize =
@@ -197,51 +197,49 @@ class CustomNavBar @JvmOverloads constructor(
                 container.getChildAt(c).refreshDrawableState() // ★ 子也刷一下
             }
         }
-        if (length > 0) selectIndex(selectedIdx.coerceIn(0, length - 1))
+        if (length > 0) {
+            if (selectedIdx in 0 until length) {
+                selectIndex(selectedIdx)
+            } else {
+                selectIndex(NO_SELECTION) // 全部不选
+            }
+        }
     }
 
     private fun selectIndex(idx: Int) {
-        if (idx == selectedIdx) {
-            invalidate(); return
-        }
         selectedIdx = idx
+
         for (i in 0 until childCount) {
-            val v = getChildAt(i)
-            v.isSelected = (i == selectedIdx)
-            v.refreshDrawableState()                   // ★
-            for (c in 0 until (v as ViewGroup).childCount) {
-                val childView = v.getChildAt(c)
-                when (childView) {
+            val container = getChildAt(i) as ViewGroup
+            val selected = (selectedIdx >= 0 && i == selectedIdx)
+
+            container.isSelected = selected
+            container.refreshDrawableState()
+
+            for (c in 0 until container.childCount) {
+                val child = container.getChildAt(c)
+                when (child) {
                     is ImageView -> {
-                        if (v.isSelected) {
-                            childView.layoutParams = childView.layoutParams.apply {
-                                width = iconSelectedSize.toInt()
-                                height = iconSelectedSize.toInt()
-                            }
-                        } else {
-                            childView.layoutParams = childView.layoutParams.apply {
-                                width = iconSize.toInt()
-                                height = iconSize.toInt()
-                            }
+                        child.layoutParams = child.layoutParams.apply {
+                            val s = if (selected) iconSelectedSize else iconSize
+                            width = s; height = s
                         }
                     }
 
                     is TextView -> {
-                        if (v.isSelected) {
-                            childView.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX, textSelectedSizePx.toFloat()
-                            )
-                        } else {
-                            childView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx.toFloat())
-                        }
+                        child.setTextSize(
+                            TypedValue.COMPLEX_UNIT_PX,
+                            (if (selected) textSelectedSizePx else textSizePx).toFloat()
+                        )
                     }
                 }
-                childView.refreshDrawableState() // ★
+                child.refreshDrawableState()
             }
         }
         invalidate()
     }
 
+
     @SuppressLint("RestrictedApi")
     override fun onDraw(canvas: Canvas) {
         // 菜单发生变化(比如运行时 add/remove)时自动重建
@@ -256,17 +254,29 @@ class CustomNavBar @JvmOverloads constructor(
     }
 
     var selectedItemId: Int
-        @SuppressLint("RestrictedApi") get() = menuBuilder.getItemOrNull(selectedIdx)?.itemId
-            ?: View.NO_ID
-        @SuppressLint("RestrictedApi") set(id) {
-            if (isEmpty() && menuBuilder.size() > 0) buildItems()  // ★
-            val idx =
-                (0 until menuBuilder.size()).firstOrNull { menuBuilder.getItem(it).itemId == id }
-                    ?: return
+        @SuppressLint("RestrictedApi")
+        get() = if (selectedIdx in 0 until menuBuilder.size())
+            menuBuilder.getItem(selectedIdx).itemId
+        else View.NO_ID
+        @SuppressLint("RestrictedApi")
+        set(id) {
+            if (isEmpty() && menuBuilder.size() > 0) buildItems()
+
+            // ★ 支持“清空选中”
+            if (id == View.NO_ID) {
+                selectIndex(NO_SELECTION)
+                return
+            }
+
+            val idx = (0 until menuBuilder.size())
+                .firstOrNull { menuBuilder.getItem(it).itemId == id }
+                ?: return
+
             selectIndex(idx)
             onItemSelected?.invoke(menuBuilder.getItem(idx))
         }
 
+
     // —— 工具 —— //
     private fun dp(dp: Float): Int = AutoSizeUtils.dp2px(context, dp)
     private fun sp(sp: Float): Int = AutoSizeUtils.sp2px(context, sp)
@@ -286,4 +296,11 @@ class CustomNavBar @JvmOverloads constructor(
     @SuppressLint("RestrictedApi")
     private fun MenuBuilder.getItemOrNull(i: Int): MenuItem? =
         if (i in 0 until size()) getItem(i) else null
+
+    /**
+     * 取消选择
+     */
+    fun clearSelected() {
+        selectIndex(NO_SELECTION)  // 直接调 index 逻辑,别再走 selectedItemId
+    }
 }

+ 93 - 9
ui-base/src/main/java/com/grkj/ui_base/widget/FormLayout.kt

@@ -4,6 +4,7 @@ import android.content.Context
 import android.util.AttributeSet
 import android.view.View
 import android.view.ViewGroup
+import androidx.annotation.IntDef
 import androidx.core.view.MarginLayoutParamsCompat
 import com.grkj.ui_base.R
 import kotlin.math.max
@@ -23,27 +24,54 @@ class FormLayout @JvmOverloads constructor(
     private var maxLabelWidth = 0
     private val rows = ArrayList<Pair<View?, View?>>()
 
+    private var labelAlignment = ALIGN_CENTER  // 新增
+
+
     init {
         context.obtainStyledAttributes(attrs, R.styleable.FormLayout).apply {
             rowSpacingPx = getDimensionPixelSize(R.styleable.FormLayout_rowSpacing, rowSpacingPx)
             colSpacingPx = getDimensionPixelSize(R.styleable.FormLayout_columnSpacing, colSpacingPx)
             labelMinPx = getDimensionPixelSize(R.styleable.FormLayout_labelMinWidth, labelMinPx)
             labelMaxPx = getDimensionPixelSize(R.styleable.FormLayout_labelMaxWidth, labelMaxPx)
+            // 读取新增属性
+            labelAlignment = getInt(R.styleable.FormLayout_labelAlignment, ALIGN_CENTER)
             recycle()
         }
     }
 
     class LayoutParams : MarginLayoutParams {
         var formRole: Int = ROLE_FIELD
+
+        // 可选:逐行覆盖父布局的对齐策略
+        var labelAlignmentOverride: Int? = null
+
         constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
             val a = c.obtainStyledAttributes(attrs, R.styleable.FormLayout_Layout)
             formRole = a.getInt(R.styleable.FormLayout_Layout_formRole, ROLE_FIELD)
+            if (a.hasValue(R.styleable.FormLayout_Layout_labelAlignment)) {
+                labelAlignmentOverride =
+                    a.getInt(R.styleable.FormLayout_Layout_labelAlignment, ALIGN_CENTER)
+            }
             a.recycle()
         }
+
         constructor(width: Int, height: Int) : super(width, height)
         constructor(source: ViewGroup.LayoutParams) : super(source)
     }
 
+    // 便捷方法:代码动态修改
+    fun setLabelAlignment(@Alignment mode: Int) {
+        if (labelAlignment != mode) {
+            labelAlignment = mode
+            requestLayout()
+        }
+    }
+
+    @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
+    @IntDef(ALIGN_CENTER, ALIGN_TOP, ALIGN_BASELINE)
+    @Retention(AnnotationRetention.SOURCE)
+    annotation class Alignment
+
     override fun generateLayoutParams(attrs: AttributeSet): ViewGroup.LayoutParams =
         LayoutParams(context, attrs)
 
@@ -58,6 +86,11 @@ class FormLayout @JvmOverloads constructor(
     companion object {
         const val ROLE_LABEL = 0
         const val ROLE_FIELD = 1
+
+        // 新增:对齐常量
+        const val ALIGN_CENTER = 0
+        const val ALIGN_TOP = 1
+        const val ALIGN_BASELINE = 2
     }
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -113,8 +146,10 @@ class FormLayout @JvmOverloads constructor(
             val childWSpec = when (lp.width) {
                 ViewGroup.LayoutParams.MATCH_PARENT ->
                     MeasureSpec.makeMeasureSpec(avail, MeasureSpec.EXACTLY)
+
                 ViewGroup.LayoutParams.WRAP_CONTENT ->
                     MeasureSpec.makeMeasureSpec(avail, MeasureSpec.AT_MOST)
+
                 else -> {
                     val target = min(avail, max(0, lp.width))
                     MeasureSpec.makeMeasureSpec(target, MeasureSpec.EXACTLY)
@@ -156,8 +191,10 @@ class FormLayout @JvmOverloads constructor(
                 val childWSpec = when (lp.width) {
                     ViewGroup.LayoutParams.MATCH_PARENT ->
                         MeasureSpec.makeMeasureSpec(avail, MeasureSpec.EXACTLY)
+
                     ViewGroup.LayoutParams.WRAP_CONTENT ->
                         MeasureSpec.makeMeasureSpec(maxWrapFieldWidth, MeasureSpec.EXACTLY)
+
                     else -> {
                         val target = min(avail, max(0, lp.width))
                         MeasureSpec.makeMeasureSpec(target, MeasureSpec.EXACTLY)
@@ -172,7 +209,8 @@ class FormLayout @JvmOverloads constructor(
                 field.measure(childWSpec, childHSpec)
 
                 rowH = max(rowH, field.measuredHeight + lp.topMargin + lp.bottomMargin)
-                maxFieldMeasuredWithMargins = max(maxFieldMeasuredWithMargins, field.measuredWidth + startM + endM)
+                maxFieldMeasuredWithMargins =
+                    max(maxFieldMeasuredWithMargins, field.measuredWidth + startM + endM)
             }
 
             if (!firstRow) totalH += rowSpacingPx
@@ -204,16 +242,62 @@ class FormLayout @JvmOverloads constructor(
             if (!firstRow) y += rowSpacingPx
             firstRow = false
 
-            val lpL = label?.layoutParams as? MarginLayoutParams
-            val lpF = field?.layoutParams as? MarginLayoutParams
-            val labelTotalH = label?.let { (lpL?.topMargin ?: 0) + it.measuredHeight + (lpL?.bottomMargin ?: 0) } ?: 0
-            val fieldTotalH = field?.let { (lpF?.topMargin ?: 0) + it.measuredHeight + (lpF?.bottomMargin ?: 0) } ?: 0
+            val lpL = label?.layoutParams as? LayoutParams
+            val lpF = field?.layoutParams as? LayoutParams
+
+            val labelTotalH =
+                label?.let { (lpL?.topMargin ?: 0) + it.measuredHeight + (lpL?.bottomMargin ?: 0) }
+                    ?: 0
+            val fieldTotalH =
+                field?.let { (lpF?.topMargin ?: 0) + it.measuredHeight + (lpF?.bottomMargin ?: 0) }
+                    ?: 0
             val rowH = max(labelTotalH, fieldTotalH)
 
-            val topL = label?.let { y + ((rowH - (it.measuredHeight + (lpL!!.topMargin + lpL.bottomMargin))) / 2) + lpL.topMargin }
-            val topF = field?.let { y + ((rowH - (it.measuredHeight + (lpF!!.topMargin + lpF.bottomMargin))) / 2) + lpF.topMargin }
+            // 逐行对齐策略:优先 LP 覆盖,否则用全局
+            val rowAlign = lpL?.labelAlignmentOverride ?: labelAlignment
+
+            // 先算 field 的 top,后续对齐 label 用
+            val topF = field?.let {
+                val lp = lpF!!
+                y + ((rowH - (it.measuredHeight + (lp.topMargin + lp.bottomMargin))) / 2) + lp.topMargin
+            }
+
+            // 根据 rowAlign 计算 label 的 top
+            val topL = label?.let {
+                val lp = lpL!!
+                when (rowAlign) {
+                    ALIGN_TOP -> {
+                        // 与 field 可绘制区顶对齐(考虑 margin)
+                        if (field != null) {
+                            topF!!
+                        } else {
+                            // 没有 field 就居中回退
+                            y + ((rowH - (it.measuredHeight + (lp.topMargin + lp.bottomMargin))) / 2) + lp.topMargin
+                        }
+                    }
+
+                    ALIGN_BASELINE -> {
+                        // 若两边都有 baseline,基线对齐;否则回退到 ALIGN_TOP
+                        val labelBase = it.baseline
+                        val fieldBase = field?.baseline ?: -1
+                        if (labelBase >= 0 && fieldBase >= 0 && field != null) {
+                            val fieldTop = topF!!
+                            val targetBaselineY = fieldTop + fieldBase
+                            targetBaselineY - labelBase
+                        } else {
+                            if (field != null) topF!! else
+                                y + ((rowH - (it.measuredHeight + (lp.topMargin + lp.bottomMargin))) / 2) + lp.topMargin
+                        }
+                    }
+
+                    else -> {
+                        // ALIGN_CENTER(保持你原行为)
+                        y + ((rowH - (it.measuredHeight + (lp.topMargin + lp.bottomMargin))) / 2) + lp.topMargin
+                    }
+                }
+            }
 
-            // label:右对齐到 barrier
+            // --- 布局 label(与原逻辑一致)
             label?.let {
                 val lp = lpL!!
                 val startM = MarginLayoutParamsCompat.getMarginStart(lp)
@@ -231,7 +315,7 @@ class FormLayout @JvmOverloads constructor(
                 }
             }
 
-            // field:左对齐到 barrier;MATCH_PARENT 铺满,其它用自身 measuredWidth(wrap 已被统一
+            // --- 布局 field(原逻辑
             field?.let {
                 val lp = lpF!!
                 val startM = MarginLayoutParamsCompat.getMarginStart(lp)

+ 6 - 0
ui-base/src/main/res/drawable/bg_underline.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/iscs_space_5" />
+    <solid android:color="?attr/colorPrimary" />
+</shape>

+ 16 - 10
ui-base/src/main/res/values/attrs.xml

@@ -78,12 +78,12 @@
     <attr name="colorHomeBlockOngoing" format="color" />
     <attr name="colorHomeBlockLocked" format="color" />
     <attr name="colorHomeBlockUseHardware" format="color" />
-    <attr name="colorSwitchOn" format="color"/>
-    <attr name="colorSwitchOff" format="color"/>
-    <attr name="colorMapBase" format="color"/>
-    <attr name="colorJobExecuteStepDone" format="color"/>
-    <attr name="colorJobExecuteStepDoing" format="color"/>
-    <attr name="colorJobExecuteStepPending" format="color"/>
+    <attr name="colorSwitchOn" format="color" />
+    <attr name="colorSwitchOff" format="color" />
+    <attr name="colorMapBase" format="color" />
+    <attr name="colorJobExecuteStepDone" format="color" />
+    <attr name="colorJobExecuteStepDoing" format="color" />
+    <attr name="colorJobExecuteStepPending" format="color" />
 
     <!-- 辅助 -->
     <attr name="colorHint" format="color" />
@@ -140,7 +140,7 @@
         <!-- 显式标注类型为 color,避免弱类型 -->
         <attr name="navBarBackground" />
         <attr name="navDivider" />
-        <attr name="navIconTint"/>
+        <attr name="navIconTint" />
         <attr name="navIconTintSelected" />
         <attr name="navTextColor" />
         <attr name="navTextColorSelected" />
@@ -166,15 +166,21 @@
         <!-- 可选:限制左列宽度范围 -->
         <attr name="labelMinWidth" format="dimension" />
         <attr name="labelMaxWidth" format="dimension" />
+        <attr name="labelAlignment" format="enum">
+            <enum name="center" value="0" />
+            <enum name="top" value="1" />
+            <enum name="baseline" value="2" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="FormLayout_Layout">
         <attr name="formRole" format="enum">
-            <enum name="label" value="0"/>
-            <enum name="field" value="1"/>
+            <enum name="label" value="0" />
+            <enum name="field" value="1" />
         </attr>
+        <attr name="labelAlignment" />
     </declare-styleable>
     <declare-styleable name="MaxHeightRecyclerView">
-        <attr name="maxHeight" format="dimension"/>
+        <attr name="maxHeight" format="dimension" />
     </declare-styleable>
 </resources>

+ 5 - 1
ui-base/src/main/res/values/dimens.xml

@@ -21,7 +21,7 @@
     <dimen name="common_text_padding">5dp</dimen>
 
     <dimen name="common_btn_width">150dp</dimen>
-    <dimen name="common_btn_height">50dp</dimen>
+    <dimen name="common_btn_height">40dp</dimen>
 
     <dimen name="tip_dialog_header_height">44dp</dimen>
     <dimen name="tip_dialog_width">500dp</dimen>
@@ -29,6 +29,7 @@
     <dimen name="loading_size">152dp</dimen>
     <dimen name="title_icon_size">20dp</dimen>
     <dimen name="divider_line_space">1dp</dimen>
+    <dimen name="divider_underline_space">3dp</dimen>
     <dimen name="dialog_content_normal_padding_horizontal">16dp</dimen>
     <dimen name="dialog_common_root_height_md">450dp</dimen>
     <dimen name="dialog_common_root_height_large">600dp</dimen>
@@ -56,6 +57,7 @@
     <dimen name="iscs_radius_xl">20dp</dimen>
 
     <dimen name="iscs_icon_size_md">30dp</dimen>
+    <dimen name="iscs_icon_size_xl">50dp</dimen>
     <dimen name="iscs_icon_size_sm">15dp</dimen>
 
     <!-- 间距(dp,按4dp栅格:space_2 = 8dp) -->
@@ -67,6 +69,8 @@
     <dimen name="iscs_title_normal_padding_vertical">5dp</dimen>
     <dimen name="iscs_input_padding_vertical">2dp</dimen>
     <dimen name="iscs_input_min_width">200dp</dimen>
+    <dimen name="iscs_mutil_input_min_height">200dp</dimen>
+    <dimen name="iscs_button_min_width">134dp</dimen>
     <!-- 文本(sp) -->
     <dimen name="iscs_text_xsm">10sp</dimen>
     <dimen name="iscs_text_xs">13sp</dimen>