Browse Source

refactor(更新)
- 角色管理完成

周文健 5 months ago
parent
commit
7a0396a5fc
49 changed files with 1848 additions and 48 deletions
  1. 4 2
      app/build.gradle.kts
  2. 7 1
      app/proguard-rules.pro
  3. BIN
      app/src/main/assets/data.db
  4. 1 1
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  5. 16 3
      app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt
  6. 209 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddRoleDialog.kt
  7. 232 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateRoleDialog.kt
  8. 93 3
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UserDetailDialog.kt
  9. 11 0
      app/src/main/java/com/grkj/iscs/features/main/entity/AddRoleDataEntity.kt
  10. 73 0
      app/src/main/java/com/grkj/iscs/features/main/entity/RoleManageFunctionalPermissionsEntity.kt
  11. 12 0
      app/src/main/java/com/grkj/iscs/features/main/entity/UpdateRoleDataEntity.kt
  12. 81 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/RoleManageFragment.kt
  13. 43 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/UserManageFragment.kt
  14. 65 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/RoleManageViewModel.kt
  15. 230 0
      app/src/main/res/layout/dialog_add_role.xml
  16. 3 1
      app/src/main/res/layout/dialog_add_user.xml
  17. 3 1
      app/src/main/res/layout/dialog_filter_role.xml
  18. 3 1
      app/src/main/res/layout/dialog_filter_user.xml
  19. 230 0
      app/src/main/res/layout/dialog_update_role.xml
  20. 9 7
      app/src/main/res/layout/dialog_user_detail.xml
  21. 2 2
      app/src/main/res/layout/fragment_home.xml
  22. 1 1
      app/src/main/res/layout/fragment_user_manage.xml
  23. 30 0
      app/src/main/res/layout/item_role.xml
  24. 1 2
      app/src/main/res/layout/item_user_manage_user.xml
  25. 4 3
      app/src/main/res/layout/item_workstation.xml
  26. 8 2
      app/src/main/res/values-en/strings.xml
  27. 8 2
      app/src/main/res/values-zh/strings.xml
  28. 8 2
      app/src/main/res/values/strings.xml
  29. 13 0
      data/src/main/java/com/grkj/data/dao/RoleDao.kt
  30. 60 0
      data/src/main/java/com/grkj/data/dao/SysMenuDao.kt
  31. 14 0
      data/src/main/java/com/grkj/data/data/MainDomainData.kt
  32. 6 1
      data/src/main/java/com/grkj/data/database/ISCSDatabase.kt
  33. 2 0
      data/src/main/java/com/grkj/data/di/AppEntryPoint.kt
  34. 5 0
      data/src/main/java/com/grkj/data/di/DatabaseModule.kt
  35. 4 0
      data/src/main/java/com/grkj/data/di/RepositoryManager.kt
  36. 8 0
      data/src/main/java/com/grkj/data/di/RepositoryModule.kt
  37. 65 0
      data/src/main/java/com/grkj/data/enums/RoleFunctionalPermissionsEnum.kt
  38. 53 0
      data/src/main/java/com/grkj/data/model/dos/SysMenu.kt
  39. 14 0
      data/src/main/java/com/grkj/data/model/dos/SysRoleMenu.kt
  40. 13 0
      data/src/main/java/com/grkj/data/model/vo/AddRoleDo.kt
  41. 14 0
      data/src/main/java/com/grkj/data/model/vo/UpdateRoleDo.kt
  42. 12 0
      data/src/main/java/com/grkj/data/repository/IRoleRepository.kt
  43. 23 0
      data/src/main/java/com/grkj/data/repository/ISysMenuRepository.kt
  44. 44 2
      data/src/main/java/com/grkj/data/repository/impl/RoleRepository.kt
  45. 87 0
      data/src/main/java/com/grkj/data/repository/impl/SysMenuRepository.kt
  46. 17 2
      data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt
  47. 2 2
      gradle/libs.versions.toml
  48. 1 1
      settings.gradle.kts
  49. 4 3
      ui-base/src/main/java/com/grkj/ui_base/widget/ShadowTextView.kt

+ 4 - 2
app/build.gradle.kts

@@ -33,14 +33,16 @@ android {
             // 如果你确实想用 release 签名
             signingConfig = signingConfigs.getByName("release")
             // Debug 不混淆、不裁剪
+            isDebuggable = true
             isMinifyEnabled = false
             isShrinkResources = false
         }
         getByName("release") {
             signingConfig = signingConfigs.getByName("release")
             // 混淆 & 去掉无用资源
-            isMinifyEnabled = true
-            isShrinkResources = true
+            isDebuggable = false
+            isMinifyEnabled = false
+            isShrinkResources = false
 
             proguardFiles(
                 getDefaultProguardFile("proguard-android-optimize.txt"),

+ 7 - 1
app/proguard-rules.pro

@@ -28,4 +28,10 @@
 
 # 若启用模糊效果,请增加如下配置:
 -dontwarn androidx.renderscript.**
--keep public class androidx.renderscript.** { *; }
+-keep public class androidx.renderscript.** { *; }
+
+# 忽略 BlockHound 集成类缺失的警告
+-dontwarn reactor.blockhound.integration.BlockHoundIntegration
+
+# 也可以一并忽略 netty Hidden 里引用的 BlockHoundIntegration
+-dontwarn io.netty.util.internal.Hidden$NettyBlockHoundIntegration

BIN
app/src/main/assets/data.db


+ 1 - 1
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -142,7 +142,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
     override fun onResume() {
         super.onResume()
         MainDomainData.clear()
-
+        viewModel.checkSysMenu().observe(this){}
         FingerprintUtil.init(this)
         FingerprintUtil.start()
         FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {

+ 16 - 3
app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt

@@ -2,18 +2,21 @@ package com.grkj.iscs.features.login.viewmodel
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
-import com.grkj.data.repository.impl.UserRepository
+import com.grkj.data.repository.ISysMenuRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
 import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
 
 /**
  * 登录界面模型
  */
 @HiltViewModel
-class LoginViewModel @Inject constructor(val userRepository: IUserRepository) : BaseViewModel() {
+class LoginViewModel @Inject constructor(
+    val userRepository: IUserRepository,
+    val sysMenuRepository: ISysMenuRepository
+) : BaseViewModel() {
 
     /**
      * 用户名登录
@@ -67,4 +70,14 @@ class LoginViewModel @Inject constructor(val userRepository: IUserRepository) :
             emit(logoutSuccess)
         }
     }
+
+    /**
+     * 检查菜单
+     */
+    fun checkSysMenu(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            sysMenuRepository.checkSysMenu()
+            emit(true)
+        }
+    }
 }

+ 209 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddRoleDialog.kt

@@ -0,0 +1,209 @@
+package com.grkj.iscs.features.main.dialog.data_manage
+
+import android.content.Context
+import android.view.View
+import androidx.core.view.isVisible
+import com.drake.brv.BindingAdapter
+import com.drake.brv.item.ItemDepth
+import com.drake.brv.item.ItemPosition
+import com.drake.brv.utils.bindingAdapter
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.model.vo.RoleManageFilterVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddRoleBinding
+import com.grkj.iscs.databinding.ItemRoleBinding
+import com.grkj.iscs.features.main.entity.AddRoleDataEntity
+import com.grkj.iscs.features.main.entity.RoleManageFunctionalPermissionsEntity
+import me.jessyan.autosize.utils.AutoSizeUtils
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 添加角色
+ */
+class AddRoleDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (AddRoleDataEntity) -> Unit = {}
+    private lateinit var binding: DialogAddRoleBinding
+    private val indentPx: Int by lazy { AutoSizeUtils.dp2px(context, 20f) }
+    private var roleManageFunctionalPermissionsData =
+        RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+    private var selectedPermission: MutableList<String> = mutableListOf()
+
+    init {
+        setContentView(R.layout.dialog_add_role)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogAddRoleBinding.bind(contentView)
+        binding.closeIv.setOnClickListener {
+            dismiss()
+            clearData()
+        }
+        binding.confirm.setOnClickListener {
+            val addRoleDataEntity = AddRoleDataEntity()
+            addRoleDataEntity.roleName = binding.roleNameEt.text.toString()
+            addRoleDataEntity.roleKeys = binding.roleKeyEt.text.toString()
+            addRoleDataEntity.status = if (binding.statusRg.checkedRadioButtonId == -1) {
+                null
+            } else {
+                binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+            }
+            addRoleDataEntity.functionalPermissions = getSelectedFunctionalPermissions()
+            onConfirm(addRoleDataEntity)
+            dismiss()
+            clearData()
+        }
+        binding.cancel.setOnClickListener {
+            dismiss()
+            clearData()
+        }
+        binding.roleListRv.linear().setup {
+            addType<RoleManageFunctionalPermissionsEntity>(R.layout.item_role)
+            onBind {
+                onRoleRVListBinding(this)
+            }
+        }.models = roleManageFunctionalPermissionsData
+        binding.expandCollapse.setOnCheckedChangeListener { _, isChecked ->
+            roleManageFunctionalPermissionsData =
+                RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+            expandOrCollapseAll(isChecked, roleManageFunctionalPermissionsData)
+            if (binding.allSelected.isChecked) {
+                selectAllOrNone(true, roleManageFunctionalPermissionsData)
+            } else {
+                setSelectedData(roleManageFunctionalPermissionsData)
+            }
+            binding.roleListRv.models = roleManageFunctionalPermissionsData
+        }
+        setSelectAllListener()
+    }
+
+    private fun clearData() {
+        binding.roleNameEt.setText("")
+        binding.roleKeyEt.setText("")
+        binding.statusRg.clearCheck()
+        binding.expandCollapse.isChecked = false
+        binding.allSelected.isChecked = false
+        roleManageFunctionalPermissionsData =
+            RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+        binding.roleListRv.models = roleManageFunctionalPermissionsData
+    }
+
+    private fun setSelectedData(dataList: List<RoleManageFunctionalPermissionsEntity>) {
+        dataList.forEach {
+            it.isSelected = selectedPermission.contains(it.functionalPermission)
+            setSelectedData(it.children)
+        }
+    }
+
+    private fun setSelectAllListener() {
+        binding.allSelected.setOnCheckedChangeListener { _, isChecked ->
+            selectAllOrNone(isChecked, roleManageFunctionalPermissionsData)
+            binding.roleListRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    /**
+     * 判断:以当前节点为根,自己和所有子孙节点是否都选中(isSelected == true)。
+     *
+     * @return 如果:当前节点本身 isSelected == true 且 所有 children 递归判断也是 true,则返回 true;否则返回 false。
+     */
+    fun RoleManageFunctionalPermissionsEntity.allSelectedRecursively(): Boolean {
+        // 如果自己没选中,直接返回 false
+        if (!isSelected) return false
+
+        // 递归检查所有子节点
+        children.forEach { child ->
+            if (!child.allSelectedRecursively()) return false
+        }
+        // 自己选中且所有子节点都选中
+        return true
+    }
+
+    private fun expandOrCollapseAll(
+        isExpand: Boolean,
+        roleFunctionalPermissionsData: List<RoleManageFunctionalPermissionsEntity>
+    ) {
+        roleFunctionalPermissionsData.forEach {
+            it.itemExpand = isExpand
+            expandOrCollapseAll(isExpand, it.children)
+        }
+    }
+
+    private fun selectAllOrNone(
+        isSelected: Boolean,
+        roleFunctionalPermissionsData: List<RoleManageFunctionalPermissionsEntity>
+    ) {
+        roleFunctionalPermissionsData.forEach {
+            if (isSelected) {
+                selectedPermission.add(it.functionalPermission)
+            } else {
+                selectedPermission.removeIf { selectedData -> selectedData == it.functionalPermission }
+            }
+            it.isSelected = isSelected
+            selectAllOrNone(isSelected, it.children)
+        }
+        selectedPermission.distinct()
+    }
+
+    private fun getSelectedFunctionalPermissions(dataList: List<RoleManageFunctionalPermissionsEntity> = roleManageFunctionalPermissionsData): MutableList<RoleManageFunctionalPermissionsEntity> {
+        return mutableListOf<RoleManageFunctionalPermissionsEntity>().apply {
+            addAll(dataList.filter { it.isSelected })
+            dataList.filter { it.isSelected }.forEach {
+                addAll(getSelectedFunctionalPermissions(it.children))
+            }
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onRoleRVListBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemRoleBinding>()
+        val item = holder.getModel<RoleManageFunctionalPermissionsEntity>()
+        // ① 动态缩进
+        itemBinding.rootLayout.setPadding(indentPx * item.level, 0, 0, 0)
+        itemBinding.rootLayout.setOnClickListener {
+            if (item.itemExpand) {
+                itemBinding.arrowIv.rotation = 0f
+                holder.collapse()
+            } else {
+                itemBinding.arrowIv.rotation = 90f
+                holder.expand()
+            }
+        }
+        if (item.itemExpand) {
+            itemBinding.arrowIv.rotation = 90f
+        } else {
+            itemBinding.arrowIv.rotation = 0f
+        }
+        itemBinding.roleTv.text = item.description
+        itemBinding.roleCb.setOnCheckedChangeListener(null)
+        itemBinding.roleCb.isChecked = item.isSelected
+        itemBinding.roleCb.setOnCheckedChangeListener { _, isChecked ->
+            item.isSelected = isChecked
+            if (item.children.isNotEmpty()) {
+                selectAllOrNone(isChecked, item.children)
+            }
+            if (isChecked) {
+                selectedPermission.add(item.functionalPermission)
+            } else {
+                selectedPermission.removeIf { selectedData -> selectedData == item.functionalPermission }
+            }
+            checkAllSelect()
+            adapter.notifyDataSetChanged()
+        }
+    }
+
+    private fun checkAllSelect() {
+        if (roleManageFunctionalPermissionsData.all { it.allSelectedRecursively() }) {
+            binding.allSelected.setOnCheckedChangeListener(null)
+            setSelectAllListener()
+        }
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(onConfirm: (AddRoleDataEntity) -> Unit) {
+        this.onConfirm = onConfirm
+    }
+}

+ 232 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateRoleDialog.kt

@@ -0,0 +1,232 @@
+package com.grkj.iscs.features.main.dialog.data_manage
+
+import android.content.Context
+import android.view.View
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.dao.RoleDao
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddRoleBinding
+import com.grkj.iscs.databinding.DialogUpdateRoleBinding
+import com.grkj.iscs.databinding.ItemRoleBinding
+import com.grkj.iscs.features.main.entity.AddRoleDataEntity
+import com.grkj.iscs.features.main.entity.RoleManageFunctionalPermissionsEntity
+import com.grkj.iscs.features.main.entity.UpdateRoleDataEntity
+import me.jessyan.autosize.utils.AutoSizeUtils
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 更新角色
+ */
+class UpdateRoleDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (UpdateRoleDataEntity) -> Unit = {}
+    private lateinit var binding: DialogUpdateRoleBinding
+    private val indentPx: Int by lazy { AutoSizeUtils.dp2px(context, 20f) }
+    private var roleManageFunctionalPermissionsData =
+        RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+    private var selectedPermission: MutableList<String> = mutableListOf()
+    private lateinit var updateRoleDataEntity: UpdateRoleDataEntity
+
+    init {
+        setContentView(R.layout.dialog_update_role)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogUpdateRoleBinding.bind(contentView)
+        binding.closeIv.setOnClickListener {
+            dismiss()
+            clearData()
+        }
+        binding.confirm.setOnClickListener {
+            updateRoleDataEntity.roleName = binding.roleNameEt.text.toString()
+            updateRoleDataEntity.roleKeys = binding.roleKeyEt.text.toString()
+            updateRoleDataEntity.status = if (binding.statusRg.checkedRadioButtonId == -1) {
+                null
+            } else {
+                binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+            }
+            updateRoleDataEntity.functionalPermissions = getSelectedFunctionalPermissions()
+            onConfirm(updateRoleDataEntity)
+            dismiss()
+            clearData()
+        }
+        binding.cancel.setOnClickListener {
+            dismiss()
+            clearData()
+        }
+        binding.roleListRv.linear().setup {
+            addType<RoleManageFunctionalPermissionsEntity>(R.layout.item_role)
+            onBind {
+                onRoleRVListBinding(this)
+            }
+        }.models = roleManageFunctionalPermissionsData
+        binding.expandCollapse.setOnCheckedChangeListener { _, isChecked ->
+            roleManageFunctionalPermissionsData =
+                RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+            expandOrCollapseAll(isChecked, roleManageFunctionalPermissionsData)
+            if (binding.allSelected.isChecked) {
+                selectAllOrNone(true, roleManageFunctionalPermissionsData)
+            } else {
+
+                setSelectedData(roleManageFunctionalPermissionsData)
+            }
+            binding.roleListRv.models = roleManageFunctionalPermissionsData
+        }
+        setSelectAllListener()
+    }
+
+    private fun setSelectedData(dataList: List<RoleManageFunctionalPermissionsEntity>) {
+        dataList.forEach {
+            it.isSelected = selectedPermission.contains(it.functionalPermission)
+            setSelectedData(it.children)
+        }
+    }
+
+    private fun clearData() {
+        binding.roleNameEt.setText("")
+        binding.roleKeyEt.setText("")
+        binding.statusRg.clearCheck()
+        binding.expandCollapse.isChecked = false
+        binding.allSelected.isChecked = false
+        binding.roleListRv.models = null
+    }
+
+    private fun getSelectedData(
+        dataList: List<RoleManageFunctionalPermissionsEntity>,
+        selectedPermission: MutableList<String> = mutableListOf()
+    ): MutableList<String> {
+        dataList.forEach {
+            if (it.isSelected) {
+                selectedPermission.add(it.functionalPermission)
+            }
+            getSelectedData(it.children, selectedPermission)
+        }
+        return selectedPermission
+    }
+
+    private fun setSelectAllListener() {
+        binding.allSelected.setOnCheckedChangeListener { _, isChecked ->
+            selectAllOrNone(isChecked, roleManageFunctionalPermissionsData)
+            binding.roleListRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    /**
+     * 判断:以当前节点为根,自己和所有子孙节点是否都选中(isSelected == true)。
+     *
+     * @return 如果:当前节点本身 isSelected == true 且 所有 children 递归判断也是 true,则返回 true;否则返回 false。
+     */
+    fun RoleManageFunctionalPermissionsEntity.allSelectedRecursively(): Boolean {
+        // 如果自己没选中,直接返回 false
+        if (!isSelected) return false
+
+        // 递归检查所有子节点
+        children.forEach { child ->
+            if (!child.allSelectedRecursively()) return false
+        }
+        // 自己选中且所有子节点都选中
+        return true
+    }
+
+    private fun expandOrCollapseAll(
+        isExpand: Boolean,
+        roleFunctionalPermissionsData: List<RoleManageFunctionalPermissionsEntity>
+    ) {
+        roleFunctionalPermissionsData.forEach {
+            it.itemExpand = isExpand
+            expandOrCollapseAll(isExpand, it.children)
+        }
+    }
+
+    private fun selectAllOrNone(
+        isSelected: Boolean,
+        roleFunctionalPermissionsData: List<RoleManageFunctionalPermissionsEntity>
+    ) {
+        roleFunctionalPermissionsData.forEach {
+            if (isSelected) {
+                selectedPermission.add(it.functionalPermission)
+            } else {
+                selectedPermission.removeIf { selectedData -> selectedData == it.functionalPermission }
+            }
+            it.isSelected = isSelected
+            selectAllOrNone(isSelected, it.children)
+        }
+        selectedPermission.distinct()
+    }
+
+    private fun getSelectedFunctionalPermissions(dataList: List<RoleManageFunctionalPermissionsEntity> = roleManageFunctionalPermissionsData): MutableList<RoleManageFunctionalPermissionsEntity> {
+        return mutableListOf<RoleManageFunctionalPermissionsEntity>().apply {
+            addAll(dataList.filter { it.isSelected })
+            dataList.filter { it.isSelected }.forEach {
+                addAll(getSelectedFunctionalPermissions(it.children))
+            }
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onRoleRVListBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemRoleBinding>()
+        val item = holder.getModel<RoleManageFunctionalPermissionsEntity>()
+        // ① 动态缩进
+        itemBinding.rootLayout.setPadding(indentPx * item.level, 0, 0, 0)
+        itemBinding.rootLayout.setOnClickListener {
+            if (item.itemExpand) {
+                itemBinding.arrowIv.rotation = 0f
+                holder.collapse()
+            } else {
+                itemBinding.arrowIv.rotation = 90f
+                holder.expand()
+            }
+        }
+        if (item.itemExpand) {
+            itemBinding.arrowIv.rotation = 90f
+        } else {
+            itemBinding.arrowIv.rotation = 0f
+        }
+        itemBinding.roleTv.text = item.description
+        itemBinding.roleCb.setOnCheckedChangeListener(null)
+        itemBinding.roleCb.isChecked = item.isSelected
+        itemBinding.roleCb.setOnCheckedChangeListener { _, isChecked ->
+            item.isSelected = isChecked
+            if (item.children.isNotEmpty()) {
+                selectAllOrNone(isChecked, item.children)
+            }
+            if (isChecked) {
+                selectedPermission.add(item.functionalPermission)
+            } else {
+                selectedPermission.removeIf { selectedData -> selectedData == item.functionalPermission }
+            }
+            checkAllSelect()
+            adapter.notifyDataSetChanged()
+        }
+    }
+
+    private fun checkAllSelect() {
+        if (roleManageFunctionalPermissionsData.all { it.allSelectedRecursively() }) {
+            binding.allSelected.setOnCheckedChangeListener(null)
+            setSelectAllListener()
+        }
+    }
+
+    /**
+     * 设置角色数据
+     */
+    fun setRoleData(updateRoleDataEntity: UpdateRoleDataEntity) {
+        this.updateRoleDataEntity = updateRoleDataEntity
+        binding.roleNameEt.setText(updateRoleDataEntity.roleName)
+        binding.roleKeyEt.setText(updateRoleDataEntity.roleKeys)
+        binding.activateRb.isChecked = updateRoleDataEntity.status == true
+        this.selectedPermission.addAll(getSelectedData(updateRoleDataEntity.functionalPermissions))
+        setSelectedData(roleManageFunctionalPermissionsData)
+        binding.roleListRv.models = roleManageFunctionalPermissionsData
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(onConfirm: (UpdateRoleDataEntity) -> Unit) {
+        this.onConfirm = onConfirm
+    }
+}

+ 93 - 3
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UserDetailDialog.kt

@@ -6,6 +6,9 @@ import com.grkj.data.model.vo.AddUserDataVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.DialogUserDetailBinding
+import com.grkj.iscs.features.main.dialog.TextDropDownDialog
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
 import razerdp.basepopup.BasePopupWindow
 
 /**
@@ -13,28 +16,115 @@ import razerdp.basepopup.BasePopupWindow
  */
 class UserDetailDialog(context: Context) : BasePopupWindow(context) {
     private lateinit var binding: DialogUserDetailBinding
+    private var textDropDownDialog: TextDropDownDialog
+    private var roleData: List<TextDropDownDialog.TextDropDownEntity> = listOf()
+    private var workstationData: List<TextDropDownDialog.TextDropDownEntity> = listOf()
+    private var selectedRoleData: TextDropDownDialog.TextDropDownEntity? = null
+    private var selectedWorkstationData: TextDropDownDialog.TextDropDownEntity? = null
     private var onConfirm: (AddUserDataVo) -> Unit = {}
 
     init {
         setContentView(R.layout.dialog_user_detail)
+        textDropDownDialog = TextDropDownDialog(context)
+        textDropDownDialog.setWidthAsAnchorView(true)
     }
 
     override fun onViewCreated(contentView: View) {
         super.onViewCreated(contentView)
         binding = DialogUserDetailBinding.bind(contentView)
+        binding.roleTv.setOnClickListener {
+            textDropDownDialog.setData(roleData)
+            textDropDownDialog.setOnItemSelectListener {
+                selectedRoleData = it
+                binding.roleTv.text = it.getShowText()
+            }
+            textDropDownDialog.showPopupWindow(binding.roleTv)
+        }
+        binding.workstationNameTv.setOnClickListener {
+            textDropDownDialog.setData(workstationData)
+            textDropDownDialog.setOnItemSelectListener {
+                selectedWorkstationData = it
+                binding.workstationNameTv.text = it.getShowText()
+            }
+            textDropDownDialog.showPopupWindow(binding.workstationNameTv)
+        }
         binding.cancel.setOnClickListener { dismiss() }
         binding.closeIv.setOnClickListener { dismiss() }
         binding.confirm.setOnClickListener {
-            dismiss()
+            if (checkData()) {
+                val addUserData = AddUserDataVo(
+                    binding.nicknameEt.text.toString(),
+                    binding.cardcodeEt.text.toString(),
+                    selectedRoleData?.getId() ?: 0,
+                    selectedWorkstationData?.getId() ?: 0,
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+                )
+                onConfirm(addUserData)
+                binding.nicknameEt.setText("")
+                binding.cardcodeEt.setText("")
+                binding.roleTv.text = ""
+                binding.workstationNameTv.text = ""
+                binding.activateRb.isChecked = false
+                binding.deactivateRb.isChecked = false
+                dismiss()
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.nicknameEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_nickname)
+            return false
+        }
+        if (binding.cardcodeEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_card_code)
+            return false
+        }
+        if (binding.roleTv.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_select_role)
+            return false
         }
+        if (binding.workstationNameTv.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_select_area)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    /**
+     * 设置角色数据
+     */
+    fun setRoleData(roleData: List<TextDropDownDialog.TextDropDownEntity>) {
+        this.roleData = roleData
+    }
+
+    /**
+     * 设置岗位数据
+     */
+    fun setWorkstationData(workstationData: List<TextDropDownDialog.TextDropDownEntity>) {
+        this.workstationData = workstationData
+    }
+
+    /**
+     * 设置确认监听
+     */
+    fun setOnConfirmListener(onConfirm: (AddUserDataVo) -> Unit) {
+        this.onConfirm = onConfirm
     }
 
     /**
      * 设置用户数据
      */
     fun setUserData(userData: UserManageVo) {
-        binding.nicknameTv.text = userData.nickName
-        binding.cardcodeTv.text = userData.cardCodes.joinToString(",")
+        binding.nicknameEt.setText(userData.nickName)
+        binding.cardcodeEt.setText(userData.cardCodes.joinToString(","))
         binding.roleTv.text = userData.roleNames.filter { it != null }.joinToString(",")
         binding.workstationNameTv.text =
             userData.workstationNames.filter { it != null }.joinToString(",")

+ 11 - 0
app/src/main/java/com/grkj/iscs/features/main/entity/AddRoleDataEntity.kt

@@ -0,0 +1,11 @@
+package com.grkj.iscs.features.main.entity
+
+/**
+ * 添加角色数据实体
+ */
+class AddRoleDataEntity {
+    var roleName: String = ""
+    var roleKeys: String = ""
+    var status: Boolean? = null
+    var functionalPermissions: List<RoleManageFunctionalPermissionsEntity> = listOf()
+}

+ 73 - 0
app/src/main/java/com/grkj/iscs/features/main/entity/RoleManageFunctionalPermissionsEntity.kt

@@ -0,0 +1,73 @@
+package com.grkj.iscs.features.main.entity
+
+import com.drake.brv.item.ItemExpand
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+
+/**
+ *  角色功能权限实体
+ */
+class RoleManageFunctionalPermissionsEntity(
+    override var itemExpand: Boolean = false,
+    override var itemGroupPosition: Int
+) : ItemExpand {
+
+    var functionalPermission: String = ""
+    var description: String = ""
+    var level: Int = 0
+    var children: MutableList<RoleManageFunctionalPermissionsEntity> = mutableListOf()
+
+    var isSelected: Boolean = false
+
+    override fun getItemSublist(): List<Any?>? {
+        return children
+    }
+
+    companion object {
+        /**
+         * 获取功能权限数据
+         */
+        @JvmStatic
+        fun getFunctionalPermissions(level: Int = 0): List<RoleManageFunctionalPermissionsEntity> {
+            return mutableListOf<RoleManageFunctionalPermissionsEntity>().apply {
+                RoleFunctionalPermissionsEnum.values().filter { it.level == level }
+                    .forEachIndexed { index, item ->
+                        val roleManageFunctionalPermissionsEntity =
+                            RoleManageFunctionalPermissionsEntity(false, index)
+                        roleManageFunctionalPermissionsEntity.functionalPermission =
+                            item.functionalPermission
+                        roleManageFunctionalPermissionsEntity.description = item.description
+                        roleManageFunctionalPermissionsEntity.level = item.level
+                        item.children.forEachIndexed {index,child->
+                            roleManageFunctionalPermissionsEntity.children.add(
+                                getFunctionPermission(
+                                    index,child
+                                )
+                            )
+                        }
+                        add(roleManageFunctionalPermissionsEntity)
+                    }
+            }
+        }
+
+        /**
+         * 根据权限获取
+         */
+        @JvmStatic
+        private fun getFunctionPermission(index:Int,item: RoleFunctionalPermissionsEnum): RoleManageFunctionalPermissionsEntity {
+            val roleManageFunctionalPermissionsEntity =
+                RoleManageFunctionalPermissionsEntity(false, index)
+            roleManageFunctionalPermissionsEntity.functionalPermission =
+                item.functionalPermission
+            roleManageFunctionalPermissionsEntity.description = item.description
+            roleManageFunctionalPermissionsEntity.level = item.level
+            item.children.forEachIndexed {index,child->
+                roleManageFunctionalPermissionsEntity.children.add(
+                    getFunctionPermission(
+                        index,child
+                    )
+                )
+            }
+            return roleManageFunctionalPermissionsEntity;
+        }
+    }
+}

+ 12 - 0
app/src/main/java/com/grkj/iscs/features/main/entity/UpdateRoleDataEntity.kt

@@ -0,0 +1,12 @@
+package com.grkj.iscs.features.main.entity
+
+/**
+ * 添加角色数据实体
+ */
+class UpdateRoleDataEntity {
+    var roleId: Long = 0
+    var roleName: String = ""
+    var roleKeys: String = ""
+    var status: Boolean? = null
+    var functionalPermissions: List<RoleManageFunctionalPermissionsEntity> = listOf()
+}

+ 81 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/RoleManageFragment.kt

@@ -14,7 +14,10 @@ import com.grkj.data.model.vo.RoleManageVo
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentRoleManageBinding
 import com.grkj.iscs.databinding.ItemRoleManageRoleBinding
+import com.grkj.iscs.features.main.dialog.data_manage.AddRoleDialog
 import com.grkj.iscs.features.main.dialog.data_manage.FilterRoleDialog
+import com.grkj.iscs.features.main.dialog.data_manage.UpdateRoleDialog
+import com.grkj.iscs.features.main.entity.UpdateRoleDataEntity
 import com.grkj.iscs.features.main.viewmodel.data_manage.RoleManageViewModel
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.dialog.TipDialog
@@ -30,6 +33,8 @@ import kotlin.getValue
 @AndroidEntryPoint
 class RoleManageFragment : BaseFragment<FragmentRoleManageBinding>() {
     private val viewModel: RoleManageViewModel by viewModels()
+    private lateinit var addRoleDialog: AddRoleDialog
+    private lateinit var updateRoleDialog: UpdateRoleDialog
     private lateinit var filterRoleDialog: FilterRoleDialog
     override fun getLayoutId(): Int {
         return R.layout.fragment_role_manage
@@ -38,11 +43,73 @@ class RoleManageFragment : BaseFragment<FragmentRoleManageBinding>() {
     override fun initView() {
         filterRoleDialog = FilterRoleDialog(requireContext())
         filterRoleDialog.popupGravity = Gravity.CENTER
+        addRoleDialog = AddRoleDialog(requireContext())
+        addRoleDialog.popupGravity = Gravity.CENTER
+        updateRoleDialog = UpdateRoleDialog(requireContext())
+        updateRoleDialog.popupGravity = Gravity.CENTER
         binding.back.setOnClickListener { navController.popBackStack() }
         filterRoleDialog.setOnConfirmListener {
             viewModel.roleFilterData = it
             getRoleData(nextPage = false)
         }
+        addRoleDialog.setOnConfirmListener {
+            viewModel.addRoleData(it).observe(this) {
+                if (it) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.SUCCESS,
+                        msg = CommonUtils.getStr(R.string.add_role_succeed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getRoleData(nextPage = false)
+                        }
+                    )
+                } else {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr(R.string.add_role_failed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getRoleData(nextPage = false)
+                        }
+                    )
+                }
+            }
+        }
+        updateRoleDialog.setOnConfirmListener {
+            viewModel.updateRoleData(it).observe(this) {
+                if (it) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.SUCCESS,
+                        msg = CommonUtils.getStr(R.string.update_role_succeed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getRoleData(nextPage = false)
+                        }
+                    )
+                } else {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr(R.string.update_role_failed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getRoleData(nextPage = false)
+                        }
+                    )
+                }
+            }
+        }
         binding.back.setDebouncedClickListener {
             navController.popBackStack()
         }
@@ -50,7 +117,7 @@ class RoleManageFragment : BaseFragment<FragmentRoleManageBinding>() {
             deleteSelectUser()
         }
         binding.addUser.setDebouncedClickListener {
-            PopTip.tip("功能开发中")
+            addRoleDialog.showPopupWindow()
         }
         binding.filterUser.setDebouncedClickListener {
             filterRoleDialog.showPopupWindow()
@@ -97,6 +164,19 @@ class RoleManageFragment : BaseFragment<FragmentRoleManageBinding>() {
             binding.selectAll.isChecked = viewModel.roleManageDataList.all { it.isSelected }
             setSelectAllListener()
         }
+        itemBinding.root.setOnClickListener {
+            viewModel.getFunctionalPermissionsByRoleId(item.roleId)
+                .observe(this@RoleManageFragment) {
+                    updateRoleDialog.setRoleData(UpdateRoleDataEntity().apply {
+                        roleId = item.roleId
+                        roleName = item.roleName
+                        roleKeys = item.roleKey.toString()
+                        status = item.status == "0"
+                        functionalPermissions = it
+                    })
+                    updateRoleDialog.showPopupWindow()
+                }
+        }
     }
 
     override fun initData() {

+ 43 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/UserManageFragment.kt

@@ -80,6 +80,35 @@ class UserManageFragment : BaseFragment<FragmentUserManageBinding>() {
                 }
             }
         }
+        userDetailDialog.setOnConfirmListener {
+            viewModel.addUser(it).observe(this) {
+                if (it) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.SUCCESS,
+                        msg = CommonUtils.getStr(R.string.add_user_succeed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getUserData(false)
+                        }
+                    )
+                } else {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr(R.string.add_user_failed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getUserData(false)
+                        }
+                    )
+                }
+            }
+        }
         binding.back.setDebouncedClickListener {
             navController.popBackStack()
         }
@@ -139,6 +168,7 @@ class UserManageFragment : BaseFragment<FragmentUserManageBinding>() {
         val item = holder.getModel<UserManageVo>()
         itemBinding.nickname.text = item.nickName
         itemBinding.cardCode.text = item.cardCodes.joinToString(",")
+        itemBinding.role.text = item.roleNames.joinToString(",")
         itemBinding.select.setOnCheckedChangeListener(null)
         itemBinding.select.isChecked = item.isSelected
         itemBinding.select.setOnCheckedChangeListener { _, checked ->
@@ -147,7 +177,19 @@ class UserManageFragment : BaseFragment<FragmentUserManageBinding>() {
             binding.selectAll.isChecked = viewModel.userManageDataList.all { it.isSelected }
             setSelectAllListener()
         }
-        itemBinding.view.setOnClickListener {
+        itemBinding.root.setOnClickListener {
+            userDetailDialog.setRoleData(viewModel.roleData.map {
+                TextDropDownDialog.SimpleTextDropDownEntity(
+                    dataId = it.roleId,
+                    dataText = it.roleName
+                )
+            })
+            userDetailDialog.setWorkstationData(viewModel.workstationData.map {
+                TextDropDownDialog.SimpleTextDropDownEntity(
+                    dataId = it.workstationId,
+                    dataText = it.workstationName
+                )
+            })
             userDetailDialog.setUserData(item)
             userDetailDialog.showPopupWindow()
         }

+ 65 - 1
app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/RoleManageViewModel.kt

@@ -2,9 +2,15 @@ package com.grkj.iscs.features.main.viewmodel.data_manage
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.grkj.data.model.vo.AddRoleDo
 import com.grkj.data.model.vo.RoleManageFilterVo
 import com.grkj.data.model.vo.RoleManageVo
+import com.grkj.data.model.vo.UpdateRoleDo
 import com.grkj.data.repository.IRoleRepository
+import com.grkj.data.repository.impl.SysMenuRepository
+import com.grkj.iscs.features.main.entity.AddRoleDataEntity
+import com.grkj.iscs.features.main.entity.RoleManageFunctionalPermissionsEntity
+import com.grkj.iscs.features.main.entity.UpdateRoleDataEntity
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
@@ -14,7 +20,10 @@ import javax.inject.Inject
  * 角色管理界面模型
  */
 @HiltViewModel
-class RoleManageViewModel @Inject constructor(val roleRepository: IRoleRepository) :
+class RoleManageViewModel @Inject constructor(
+    val roleRepository: IRoleRepository,
+    val sysMenuRepository: SysMenuRepository
+) :
     BaseViewModel() {
     private var current: Int = 0
     private var size: Int = 50
@@ -50,4 +59,59 @@ class RoleManageViewModel @Inject constructor(val roleRepository: IRoleRepositor
             emit(true)
         }
     }
+
+    /**
+     * 添加角色
+     */
+    fun addRoleData(roleDataEntity: AddRoleDataEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val sysMenus = sysMenuRepository.getSysMenus()
+            roleRepository.addRoleData(AddRoleDo().apply {
+                roleName = roleDataEntity.roleName
+                roleKey = roleDataEntity.roleKeys
+                status = roleDataEntity.status
+                menuData =
+                    sysMenus.filter { it.menuName in roleDataEntity.functionalPermissions.map { it.description } }
+            })
+            emit(true)
+        }
+    }
+
+    /**
+     * 更新角色
+     */
+    fun updateRoleData(roleDataEntity: UpdateRoleDataEntity): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val sysMenus = sysMenuRepository.getSysMenus()
+            roleRepository.updateRoleData(UpdateRoleDo().apply {
+                roleId = roleDataEntity.roleId
+                roleName = roleDataEntity.roleName
+                roleKey = roleDataEntity.roleKeys
+                status = roleDataEntity.status
+                menuData =
+                    sysMenus.filter { it.menuName in roleDataEntity.functionalPermissions.map { it.description } }
+            })
+            emit(true)
+        }
+    }
+
+    /**
+     * 根据角色id获取功能权限列表
+     */
+    fun getFunctionalPermissionsByRoleId(roleId: Long): LiveData<List<RoleManageFunctionalPermissionsEntity>> {
+        return liveData(Dispatchers.IO) {
+            val functionalPermissions =
+                RoleManageFunctionalPermissionsEntity.getFunctionalPermissions().reversed()
+            var selectedMenuNames =
+                sysMenuRepository.getSysMenusByRoleId(roleId).map { it.menuName }
+            fun setSelectedFunctionalPermissions(dataList: List<RoleManageFunctionalPermissionsEntity>) {
+                dataList.forEach {
+                    it.isSelected = it.description in selectedMenuNames
+                    setSelectedFunctionalPermissions(it.children)
+                }
+            }
+            setSelectedFunctionalPermissions(functionalPermissions)
+            emit(functionalPermissions)
+        }
+    }
 }

+ 230 - 0
app/src/main/res/layout/dialog_add_role.xml

@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="400dp"
+        android:layout_height="600dp"
+        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="10dp"
+            android:paddingVertical="5dp">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="10dp"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/role_manage_role_name"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <EditText
+                    android:id="@+id/role_name_et"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:hint="@string/please_input_role_name"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/role_manage_permission_string"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <EditText
+                    android:id="@+id/role_key_et"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:hint="@string/please_input_permission_characters"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="56dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/manage_filter_status"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <RadioGroup
+                    android:id="@+id/status_rg"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <RadioButton
+                        android:id="@+id/activate_rb"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
+                        android:text="@string/user_manage_filter_activate" />
+
+                    <RadioButton
+                        android:id="@+id/deactivate_rb"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
+                        android:text="@string/user_manage_filter_deactivate" />
+                </RadioGroup>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:text="@string/manage_role_function_permission"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:orientation="vertical">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal">
+
+                        <CheckBox
+                            android:id="@+id/expand_collapse"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/expand_collapse"
+                            android:textColor="@color/black"
+                            android:textSize="18sp" />
+
+                        <CheckBox
+                            android:id="@+id/all_selected"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginLeft="10dp"
+                            android:text="@string/all_select_not_all_select"
+                            android:textColor="@color/black"
+                            android:textSize="18sp" />
+                    </LinearLayout>
+
+                    <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"
+                        android:paddingHorizontal="10dp"
+                        android:paddingBottom="10dp" />
+                </LinearLayout>
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="10dp">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 3 - 1
app/src/main/res/layout/dialog_add_user.xml

@@ -172,7 +172,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/user_manage_filter_status"
+                    android:text="@string/manage_filter_status"
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
@@ -187,6 +187,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_activate" />
 
                     <RadioButton
@@ -194,6 +195,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_deactivate" />
                 </RadioGroup>
             </LinearLayout>

+ 3 - 1
app/src/main/res/layout/dialog_filter_role.xml

@@ -114,7 +114,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/user_manage_filter_status"
+                    android:text="@string/manage_filter_status"
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
@@ -129,6 +129,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_activate" />
 
                     <RadioButton
@@ -136,6 +137,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_deactivate" />
                 </RadioGroup>
             </LinearLayout>

+ 3 - 1
app/src/main/res/layout/dialog_filter_user.xml

@@ -144,7 +144,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/user_manage_filter_status"
+                    android:text="@string/manage_filter_status"
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
@@ -159,6 +159,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_activate" />
 
                     <RadioButton
@@ -166,6 +167,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_deactivate" />
                 </RadioGroup>
             </LinearLayout>

+ 230 - 0
app/src/main/res/layout/dialog_update_role.xml

@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="400dp"
+        android:layout_height="600dp"
+        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="10dp"
+            android:paddingVertical="5dp">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="10dp"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/role_manage_role_name"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <EditText
+                    android:id="@+id/role_name_et"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:hint="@string/please_input_role_name"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/role_manage_permission_string"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <EditText
+                    android:id="@+id/role_key_et"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:hint="@string/please_input_permission_characters"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="56dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/manage_filter_status"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <RadioGroup
+                    android:id="@+id/status_rg"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+
+                    <RadioButton
+                        android:id="@+id/activate_rb"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
+                        android:text="@string/user_manage_filter_activate" />
+
+                    <RadioButton
+                        android:id="@+id/deactivate_rb"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="10dp"
+                        android:textSize="18sp"
+                        android:text="@string/user_manage_filter_deactivate" />
+                </RadioGroup>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="10dp"
+                    android:text="@string/manage_role_function_permission"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:orientation="vertical">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal">
+
+                        <CheckBox
+                            android:id="@+id/expand_collapse"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/expand_collapse"
+                            android:textColor="@color/black"
+                            android:textSize="18sp" />
+
+                        <CheckBox
+                            android:id="@+id/all_selected"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginLeft="10dp"
+                            android:text="@string/all_select_not_all_select"
+                            android:textColor="@color/black"
+                            android:textSize="18sp" />
+                    </LinearLayout>
+
+                    <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"
+                        android:paddingHorizontal="10dp"
+                        android:paddingBottom="10dp" />
+                </LinearLayout>
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="10dp">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 9 - 7
app/src/main/res/layout/dialog_user_detail.xml

@@ -58,8 +58,8 @@
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
-                <TextView
-                    android:id="@+id/nickname_tv"
+                <EditText
+                    android:id="@+id/nickname_et"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="10dp"
@@ -88,8 +88,8 @@
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
-                <TextView
-                    android:id="@+id/cardcode_tv"
+                <EditText
+                    android:id="@+id/cardcode_et"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="10dp"
@@ -124,6 +124,7 @@
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="10dp"
                     android:background="@drawable/bg_common_input"
+                    android:drawableRight="@drawable/icon_drop_down"
                     android:hint="@string/please_select_role"
                     android:paddingHorizontal="10dp"
                     android:paddingVertical="2dp"
@@ -152,6 +153,7 @@
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="10dp"
                     android:background="@drawable/bg_common_input"
+                    android:drawableRight="@drawable/icon_drop_down"
                     android:hint="@string/please_select_area"
                     android:paddingHorizontal="10dp"
                     android:paddingVertical="2dp"
@@ -170,7 +172,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:text="@string/user_manage_filter_status"
+                    android:text="@string/manage_filter_status"
                     android:textColor="@color/black"
                     android:textSize="18sp" />
 
@@ -185,7 +187,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
-                        android:enabled="false"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_activate" />
 
                     <RadioButton
@@ -193,7 +195,7 @@
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"
                         android:layout_marginLeft="10dp"
-                        android:enabled="false"
+                        android:textSize="18sp"
                         android:text="@string/user_manage_filter_deactivate" />
                 </RadioGroup>
             </LinearLayout>

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

@@ -15,14 +15,14 @@
             android:layout_marginTop="40dp"
             android:text="@string/loto"
             android:textColor="@color/white"
-            android:textSize="40sp"
+            android:textSize="50sp"
             android:textStyle="bold" />
 
         <FrameLayout
             android:layout_width="match_parent"
             android:layout_height="180dp"
             android:layout_marginHorizontal="20dp"
-            android:layout_marginTop="40dp"
+            android:layout_marginTop="10dp"
             android:background="@drawable/home_card_bg">
 
             <androidx.recyclerview.widget.RecyclerView

+ 1 - 1
app/src/main/res/layout/fragment_user_manage.xml

@@ -134,7 +134,7 @@
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
-                android:text="@string/user_manage_detail"
+                android:text="@string/user_manage_role"
                 android:textSize="18sp" />
         </LinearLayout>
 

+ 30 - 0
app/src/main/res/layout/item_role.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:id="@+id/root_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingHorizontal="10dp">
+
+        <ImageView
+            android:id="@+id/arrow_iv"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/icon_arrow_right" />
+
+        <CheckBox
+            android:id="@+id/role_cb"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/role_tv"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="@color/black"
+            android:textSize="18sp" />
+    </LinearLayout>
+</layout>

+ 1 - 2
app/src/main/res/layout/item_user_manage_user.xml

@@ -30,12 +30,11 @@
             android:textSize="18sp" />
 
         <TextView
-            android:id="@+id/view"
+            android:id="@+id/role"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:gravity="center"
-            android:text="@string/user_manage_view"
             android:textSize="18sp" />
     </LinearLayout>
 </layout>

+ 4 - 3
app/src/main/res/layout/item_workstation.xml

@@ -19,11 +19,12 @@
         <TextView
             android:id="@+id/tvName"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
+            android:layout_height="wrap_content"
             android:background="@drawable/bg_workstation_item"
-            android:padding="10dp"
+            android:paddingVertical="10dp"
+            android:paddingHorizontal="30dp"
             android:text="@{node.workstationName}"
             android:textColor="@color/text_color_workstation_item"
-            android:textSize="28sp" />
+            android:textSize="20sp" />
     </LinearLayout>
 </layout>

+ 8 - 2
app/src/main/res/values-en/strings.xml

@@ -32,7 +32,7 @@
     <string name="user_manage_card_code">Card code</string>
     <string name="user_manage_area">Area</string>
     <string name="please_input_area">please input area</string>
-    <string name="user_manage_filter_status">Status</string>
+    <string name="manage_filter_status">Status</string>
     <string name="user_manage_filter_activate">activate</string>
     <string name="user_manage_filter_deactivate">deactivate</string>
     <string name="user_manage_new_user_title">New user</string>
@@ -42,7 +42,6 @@
     <string name="please_select_status">please select status</string>
     <string name="add_user_succeed">New user succeed</string>
     <string name="add_user_failed">New user failed</string>
-    <string name="user_manage_detail">Detail</string>
     <string name="user_manage_view">View</string>
     <string name="role_manage_title">Role manage</string>
     <string name="user_manage_user_detail_title">User detail</string>
@@ -152,4 +151,11 @@
     <string name="point_name_tv">Point name</string>
     <string name="please_select_start_time">please select start time first</string>
     <string name="start_time_must_large_then_end_time">start time must large then end time</string>
+    <string name="manage_role_function_permission">Function permissions</string>
+    <string name="expand_collapse">Expand/Collapse</string>
+    <string name="all_select_not_all_select">Select All/Select None</string>
+    <string name="add_role_succeed">New role succeed</string>
+    <string name="add_role_failed">New role failed</string>
+    <string name="update_role_succeed">Update role succeed</string>
+    <string name="update_role_failed">Update role failed</string>
 </resources>

+ 8 - 2
app/src/main/res/values-zh/strings.xml

@@ -32,7 +32,7 @@
     <string name="user_manage_card_code">工卡</string>
     <string name="user_manage_area">区域</string>
     <string name="please_input_area">请输入区域</string>
-    <string name="user_manage_filter_status">状态</string>
+    <string name="manage_filter_status">状态</string>
     <string name="user_manage_filter_activate">正常</string>
     <string name="user_manage_filter_deactivate">停用</string>
     <string name="user_manage_new_user_title">新增用户</string>
@@ -42,7 +42,6 @@
     <string name="please_select_status">请选择状态</string>
     <string name="add_user_succeed">新增用户成功</string>
     <string name="add_user_failed">新增用户失败</string>
-    <string name="user_manage_detail">详情</string>
     <string name="user_manage_view">查看</string>
     <string name="role_manage_title">角色管理</string>
     <string name="user_manage_user_detail_title">用户详情</string>
@@ -152,4 +151,11 @@
     <string name="point_name_tv">隔离点</string>
     <string name="please_select_start_time">请先选择开始时间</string>
     <string name="start_time_must_large_then_end_time">开始时间必须大于结束时间</string>
+    <string name="manage_role_function_permission">功能权限</string>
+    <string name="expand_collapse">展开/折叠</string>
+    <string name="all_select_not_all_select">全选/全不选</string>
+    <string name="add_role_succeed">新增角色成功</string>
+    <string name="add_role_failed">新增角色失败</string>
+    <string name="update_role_succeed">角色更新成功</string>
+    <string name="update_role_failed">角色更新试下</string>
 </resources>

+ 8 - 2
app/src/main/res/values/strings.xml

@@ -31,7 +31,7 @@
     <string name="user_manage_card_code">工卡</string>
     <string name="user_manage_area">区域</string>
     <string name="please_input_area">请输入区域</string>
-    <string name="user_manage_filter_status">状态</string>
+    <string name="manage_filter_status">状态</string>
     <string name="user_manage_filter_activate">正常</string>
     <string name="user_manage_filter_deactivate">停用</string>
     <string name="user_manage_new_user_title">新增用户</string>
@@ -41,7 +41,6 @@
     <string name="please_select_status">请选择状态</string>
     <string name="add_user_succeed">新增用户成功</string>
     <string name="add_user_failed">新增用户失败</string>
-    <string name="user_manage_detail">详情</string>
     <string name="user_manage_view">查看</string>
     <string name="role_manage_title">角色管理</string>
     <string name="user_manage_user_detail_title">用户详情</string>
@@ -151,4 +150,11 @@
     <string name="point_name_tv">隔离点</string>
     <string name="please_select_start_time">请先选择开始时间</string>
     <string name="start_time_must_large_then_end_time">开始时间必须大于结束时间</string>
+    <string name="manage_role_function_permission">功能权限</string>
+    <string name="expand_collapse">展开/折叠</string>
+    <string name="all_select_not_all_select">全选/全不选</string>
+    <string name="add_role_succeed">新增角色成功</string>
+    <string name="add_role_failed">新增角色失败</string>
+    <string name="update_role_succeed">角色更新成功</string>
+    <string name="update_role_failed">角色更新试下</string>
 </resources>

+ 13 - 0
data/src/main/java/com/grkj/data/dao/RoleDao.kt

@@ -2,6 +2,7 @@ package com.grkj.data.dao
 
 import androidx.room.Dao
 import androidx.room.Insert
+import androidx.room.OnConflictStrategy
 import androidx.room.Query
 import ch.qos.logback.core.status.Status
 import com.grkj.data.model.dos.SysRole
@@ -67,4 +68,16 @@ interface RoleDao {
         size: Int,
         offset: Int
     ): List<RoleManageVo>
+
+    /**
+     * 添加角色
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertRole(role: SysRole): Long
+
+    /**
+     * 根据用户id获取角色数据
+     */
+    @Query("select * from sys_role sr left join sys_user_role sur on sr.role_id = sur.role_id where sur.user_id = :userId")
+    fun getRoleDataByUserId(userId: Long): SysRole
 }

+ 60 - 0
data/src/main/java/com/grkj/data/dao/SysMenuDao.kt

@@ -0,0 +1,60 @@
+package com.grkj.data.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.model.dos.SysMenu
+import com.grkj.data.model.dos.SysRoleMenu
+
+@Dao
+interface SysMenuDao {
+    /** 根据 perms 查找对应的菜单记录(假定 perms 字段在数据库中唯一) */
+    @Query("SELECT * FROM sys_menu WHERE perms = :perms LIMIT 1")
+    fun findByPerms(perms: String): SysMenu?
+
+    /** 插入一条新菜单,如果成功返回新产生的主键 ID(Long),失败或冲突可返回 0L */
+    @Insert(onConflict = OnConflictStrategy.IGNORE)
+    fun insert(menu: SysMenu): Long
+
+    /** 如果需要还可以加个更新方法,比如当发现同 perms 但其他字段变动时更新它 */
+    @Update
+    fun update(menu: SysMenu)
+
+    /** 查询所有数据(可选,只是方便测试) */
+    @Query("SELECT * FROM sys_menu")
+    fun getAll(): List<SysMenu>
+
+    /**
+     * 添加角色菜单关联
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertRoleMenus(sysRoleMenus: MutableList<SysRoleMenu>)
+
+    /**
+     * 根据角色获取权限关键字
+     */
+    @Query(
+        """
+        SELECT GROUP_CONCAT(sm.perms, ',') AS roleKeys
+        FROM sys_role_menu AS srm
+        LEFT JOIN sys_menu AS sm
+            ON srm.menu_id = sm.menu_id
+        WHERE srm.role_id = :roleId
+    """
+    )
+    fun getPermissionsByRoleId(roleId: Long): MutableList<String?>
+
+    /**
+     * 根据角色id删除
+     */
+    @Query("delete from sys_role_menu where role_id = :roleId")
+    fun deleteByRoleId(roleId: Long)
+
+    /**
+     * 根据角色id获取菜单
+     */
+    @Query("select sm.* from sys_menu sm left join sys_role_menu srm on sm.menu_id = srm.menu_id where role_id = :roleId")
+    fun getSysMenusByRoleId(roleId: Long): List<SysMenu>
+}

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

@@ -12,10 +12,24 @@ object MainDomainData {
     @Volatile
     var userInfo: SysUserDo? = null
 
+    /**
+     * 角色关键字
+     */
+    @Volatile
+    var roleKeys: String? = ""
+
+    /**
+     * 权限列表
+     */
+    @Volatile
+    var permissions: MutableList<String?> = mutableListOf()
+
     /**
      * 清除数据
      */
     fun clear() {
         userInfo = null
+        roleKeys = null
+        permissions.clear()
     }
 }

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

@@ -9,6 +9,7 @@ import com.grkj.data.dao.IsolationPointDao
 import com.grkj.data.dao.JobTicketDao
 import com.grkj.data.dao.RfidTokenDao
 import com.grkj.data.dao.RoleDao
+import com.grkj.data.dao.SysMenuDao
 import com.grkj.data.dao.UserDao
 import com.grkj.data.dao.WorkstationDao
 import com.grkj.data.model.dos.IsIsolationPoint
@@ -32,7 +33,9 @@ import com.grkj.data.model.dos.IsSopPoints
 import com.grkj.data.model.dos.IsSopUser
 import com.grkj.data.model.dos.IsUserWorkstation
 import com.grkj.data.model.dos.IsWorkstation
+import com.grkj.data.model.dos.SysMenu
 import com.grkj.data.model.dos.SysRole
+import com.grkj.data.model.dos.SysRoleMenu
 import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.dos.SysUserDo
 import com.grkj.data.model.dos.SysUserRole
@@ -47,7 +50,7 @@ import com.sik.sikcore.SIKCore
         SysRole::class, SysUserRole::class, IsUserWorkstation::class, IsWorkstation::class, IsIsolationPoint::class, IsRfidToken::class,
         IsSop::class, IsSopUser::class, IsSopPoints::class, IsJobTicket::class, IsJobTicketKey::class, IsJobTicketLock::class,
         IsJobTicketPoints::class, IsJobTicketStep::class, IsJobTicketUser::class,
-        IsKey::class, IsLock::class, IsLockCabinet::class, IsLockCabinetSlots::class, IsLocksetType::class, IsLockset::class,
+        IsKey::class, IsLock::class, IsLockCabinet::class, IsLockCabinetSlots::class, IsLocksetType::class, IsLockset::class, SysMenu::class, SysRoleMenu::class
     ],
     version = ISCSMigrations.VERSION
 )
@@ -83,4 +86,6 @@ abstract class ISCSDatabase : RoomDatabase() {
     abstract fun isSopDao(): IsSopDao
 
     abstract fun jobTicketDao(): JobTicketDao
+
+    abstract fun sysMenuDao(): SysMenuDao
 }

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

@@ -6,6 +6,7 @@ import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IRfidTokenRepository
 import com.grkj.data.repository.IRoleRepository
 import com.grkj.data.repository.ISopRepository
+import com.grkj.data.repository.ISysMenuRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.data.repository.IWorkstationRepository
 import dagger.hilt.EntryPoint
@@ -23,4 +24,5 @@ interface AppEntryPoint {
     fun sopRepo(): ISopRepository
     fun userRepo(): IUserRepository
     fun workstationRepo(): IWorkstationRepository
+    fun sysMenuRepo(): ISysMenuRepository
 }

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

@@ -8,6 +8,7 @@ import com.grkj.data.dao.IsolationPointDao
 import com.grkj.data.dao.JobTicketDao
 import com.grkj.data.dao.RfidTokenDao
 import com.grkj.data.dao.RoleDao
+import com.grkj.data.dao.SysMenuDao
 import com.grkj.data.dao.UserDao
 import com.grkj.data.dao.WorkstationDao
 import com.grkj.data.database.ISCSDatabase
@@ -67,4 +68,8 @@ object DatabaseModule {
     @Provides
     fun provideJobTicketDao(db: ISCSDatabase): JobTicketDao =
         db.jobTicketDao()
+
+    @Provides
+    fun provideSysMenuDao(db: ISCSDatabase): SysMenuDao =
+        db.sysMenuDao()
 }

+ 4 - 0
data/src/main/java/com/grkj/data/di/RepositoryManager.kt

@@ -7,8 +7,10 @@ import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IRfidTokenRepository
 import com.grkj.data.repository.IRoleRepository
 import com.grkj.data.repository.ISopRepository
+import com.grkj.data.repository.ISysMenuRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.data.repository.IWorkstationRepository
+import com.grkj.data.repository.impl.SysMenuRepository
 import dagger.hilt.android.EntryPointAccessors
 
 /**
@@ -23,6 +25,7 @@ object RepositoryManager {
     lateinit var sopRepo: ISopRepository
     lateinit var userRepo: IUserRepository
     lateinit var workstationRepo: IWorkstationRepository
+    lateinit var sysMenuRepository: ISysMenuRepository
 
     fun init(app: Application) {
         val ep = EntryPointAccessors.fromApplication(app, AppEntryPoint::class.java)
@@ -34,5 +37,6 @@ object RepositoryManager {
         sopRepo = ep.sopRepo()
         userRepo = ep.userRepo()
         workstationRepo = ep.workstationRepo()
+        sysMenuRepository = ep.sysMenuRepo()
     }
 }

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

@@ -6,6 +6,7 @@ import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IRfidTokenRepository
 import com.grkj.data.repository.IRoleRepository
 import com.grkj.data.repository.ISopRepository
+import com.grkj.data.repository.ISysMenuRepository
 import com.grkj.data.repository.IUserRepository
 import com.grkj.data.repository.IWorkstationRepository
 import com.grkj.data.repository.impl.HardwareRepository
@@ -14,6 +15,7 @@ import com.grkj.data.repository.impl.JobTicketRepository
 import com.grkj.data.repository.impl.RfidTokenRepository
 import com.grkj.data.repository.impl.RoleRepository
 import com.grkj.data.repository.impl.SopRepository
+import com.grkj.data.repository.impl.SysMenuRepository
 import com.grkj.data.repository.impl.UserRepository
 import com.grkj.data.repository.impl.WorkstationRepository
 import dagger.Binds
@@ -72,4 +74,10 @@ abstract class RepositoryModule {
     abstract fun bindWorkstationRepository(
         impl: WorkstationRepository
     ): IWorkstationRepository
+
+    @Binds
+    @Singleton
+    abstract fun bindSysMenuRepository(
+        impl: SysMenuRepository
+    ): ISysMenuRepository
 }

+ 65 - 0
data/src/main/java/com/grkj/data/enums/RoleFunctionalPermissionsEnum.kt

@@ -0,0 +1,65 @@
+package com.grkj.data.enums
+
+import com.sik.sikcore.data.BeanUtils
+import kotlin.collections.mutableListOf
+
+/**
+ * 功能权限(菜单)
+ */
+enum class RoleFunctionalPermissionsEnum(
+    val functionalPermission: String,
+    val description: String,
+    val level: Int,
+    val children: List<RoleFunctionalPermissionsEnum>
+) {
+    USER_MANAGE("data_manage:user_manage", "用户管理", 1, listOf()),
+    ROLE_MANAGE("data_manage:role_manage", "角色管理", 1, listOf()),
+    WORKSTATION_MANAGE("data_manage:workstation_manage", "区域管理", 1, listOf()),
+    POINT_MANAGE("data_manage:point_manage", "点位管理", 1, listOf()),
+    IN_PROGRESS_JOB("job_ticket_manage:in_progress_job", "进行中的作业", 1, listOf()),
+    CREATE_SOP("job_ticket_manage:create_sop", "新建SOP", 1, listOf()),
+    SOP_MANAGE("job_ticket_manage:sop_manage", "SOP管理", 1, listOf()),
+    EXCEPTION_JOB("exception_job", "异常作业", 1, listOf()),
+    CREATE_JOB("job_ticket_manage:create_job", "新建作业", 1, listOf()),
+    JOB_MANAGE("job_ticket_manage:job_manage", "作业管理", 1, listOf()),
+    CREATE_SOP_JOB("job_ticket_manage:create_sop_job", "新建SOP作业", 1, listOf()),
+    LOCKED_POINT("job_ticket_manage:locked_point", "锁定中的点位", 1, listOf()),
+    SLOT_MANAGE("hardware_manage:slot_manage", "仓位管理", 1, listOf()),
+    KEY_MANAGE("hardware_manage:key_manage", "钥匙管理", 1, listOf()),
+    LOCK_MANAGE("hardware_manage:lock_manage", "挂锁管理", 1, listOf()),
+    CARD_MANAGE("hardware_manage:card_manage", "卡片管理", 1, listOf()),
+    RFID_MANAGE("hardware_manage:rfid_manage", "RFID管理", 1, listOf()),
+    EXCEPTION_REPORT("exception_manage:exception_report", "异常上报", 1, listOf()),
+    EXCEPTION_MANAGE("exception_manage:exception_manage", "异常管理", 1, listOf()),
+    EXCEPTION_HOME_MANAGE(
+        "exception_manage",
+        "异常管理", 0,
+        listOf(EXCEPTION_REPORT, EXCEPTION_JOB, EXCEPTION_MANAGE)
+    ),
+    HARDWARE_HOME_MANAGE(
+        "hardware_manage",
+        "硬件管理", 0,
+        listOf(SLOT_MANAGE, KEY_MANAGE, LOCK_MANAGE, CARD_MANAGE, RFID_MANAGE)
+    ),
+    JOB_TICKET_HOME_MANAGE(
+        "job_ticket_manage",
+        "作业管理", 0,
+        listOf(
+            IN_PROGRESS_JOB,
+            CREATE_SOP,
+            SOP_MANAGE,
+            EXCEPTION_JOB,
+            CREATE_JOB,
+            JOB_MANAGE,
+            CREATE_SOP_JOB,
+            LOCKED_POINT
+        )
+    ),
+    DATA_HOME_MANAGE(
+        "data_manage",
+        "数据管理", 0,
+        listOf(USER_MANAGE, ROLE_MANAGE, WORKSTATION_MANAGE, POINT_MANAGE)
+    ),
+    HOME("Home", "主页", 0, listOf()),
+    ;
+}

+ 53 - 0
data/src/main/java/com/grkj/data/model/dos/SysMenu.kt

@@ -0,0 +1,53 @@
+package com.grkj.data.model.dos
+
+import androidx.room.*
+
+@Entity(tableName = "sys_menu")
+class SysMenu : BaseBean() {
+
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = "menu_id")
+    var menuId: Long = 0
+
+    @ColumnInfo(name = "menu_name")
+    var menuName: String = ""
+
+    @ColumnInfo(name = "parent_id")
+    var parentId: Long? = null
+
+    @ColumnInfo(name = "order_num")
+    var orderNum: Int? = null
+
+    @ColumnInfo(name = "path")
+    var path: String? = null
+
+    @ColumnInfo(name = "component")
+    var component: String? = null
+
+    @ColumnInfo(name = "query")
+    var query: String? = null
+
+    @JvmField
+    @ColumnInfo(name = "is_frame")
+    var isFrame: Int? = null
+
+    @JvmField
+    @ColumnInfo(name = "is_cache")
+    var isCache: Int? = null
+
+    @ColumnInfo(name = "menu_type")
+    var menuType: String? = null
+
+    @ColumnInfo(name = "visible")
+    var visible: String? = null
+
+    @ColumnInfo(name = "status")
+    var status: String? = null
+
+    @ColumnInfo(name = "perms")
+    var perms: String? = null
+
+    @ColumnInfo(name = "icon")
+    var icon: String? = null
+
+}

+ 14 - 0
data/src/main/java/com/grkj/data/model/dos/SysRoleMenu.kt

@@ -0,0 +1,14 @@
+package com.grkj.data.model.dos
+
+import androidx.room.*
+
+@Entity(tableName = "sys_role_menu", primaryKeys = ["role_id", "menu_id"])
+class SysRoleMenu {
+
+    @ColumnInfo(name = "role_id")
+    var roleId: Long = 0
+
+    @ColumnInfo(name = "menu_id")
+    var menuId: Long = 0
+
+}

+ 13 - 0
data/src/main/java/com/grkj/data/model/vo/AddRoleDo.kt

@@ -0,0 +1,13 @@
+package com.grkj.data.model.vo
+
+import com.grkj.data.model.dos.SysMenu
+
+/**
+ * 添加角色实体
+ */
+class AddRoleDo {
+    var roleName: String = ""
+    var roleKey: String = ""
+    var status: Boolean? = true
+    var menuData: List<SysMenu> = listOf()
+}

+ 14 - 0
data/src/main/java/com/grkj/data/model/vo/UpdateRoleDo.kt

@@ -0,0 +1,14 @@
+package com.grkj.data.model.vo
+
+import com.grkj.data.model.dos.SysMenu
+
+/**
+ * 添加角色实体
+ */
+class UpdateRoleDo {
+    var roleId: Long = 0
+    var roleName: String = ""
+    var roleKey: String = ""
+    var status: Boolean? = true
+    var menuData: List<SysMenu> = listOf()
+}

+ 12 - 0
data/src/main/java/com/grkj/data/repository/IRoleRepository.kt

@@ -4,8 +4,10 @@ import android.health.connect.ReadRecordsRequestUsingIds
 import androidx.work.impl.model.WorkGenerationalId
 import com.grkj.data.model.dos.SysRole
 import com.grkj.data.model.dos.SysUserRole
+import com.grkj.data.model.vo.AddRoleDo
 import com.grkj.data.model.vo.RoleManageFilterVo
 import com.grkj.data.model.vo.RoleManageVo
+import com.grkj.data.model.vo.UpdateRoleDo
 
 /**
  * 角色仓储
@@ -35,4 +37,14 @@ interface IRoleRepository {
      * 获取角色数据
      */
     fun getRoleManagerData(roleManageFilterVo: RoleManageFilterVo?, current: Int, size: Int): List<RoleManageVo>
+
+    /**
+     * 添加角色数据
+     */
+    fun addRoleData(addRoleDo: AddRoleDo)
+
+    /**
+     * 更新角色数据
+     */
+    fun updateRoleData(updateRoleDo: UpdateRoleDo)
 }

+ 23 - 0
data/src/main/java/com/grkj/data/repository/ISysMenuRepository.kt

@@ -0,0 +1,23 @@
+package com.grkj.data.repository
+
+import com.grkj.data.model.dos.SysMenu
+
+/**
+ * 菜单仓储
+ */
+interface ISysMenuRepository {
+    /**
+     * 检查系统菜单
+     */
+    fun checkSysMenu()
+
+    /**
+     * 获取菜单数据
+     */
+    fun getSysMenus(): List<SysMenu>
+
+    /**
+     * 根据角色id获取菜单数据
+     */
+    fun getSysMenusByRoleId(roleId: Long): List<SysMenu>
+}

+ 44 - 2
data/src/main/java/com/grkj/data/repository/impl/RoleRepository.kt

@@ -1,11 +1,14 @@
 package com.grkj.data.repository.impl
 
 import com.grkj.data.dao.RoleDao
-import com.grkj.data.database.ISCSDatabase
+import com.grkj.data.dao.SysMenuDao
 import com.grkj.data.model.dos.SysRole
+import com.grkj.data.model.dos.SysRoleMenu
 import com.grkj.data.model.dos.SysUserRole
+import com.grkj.data.model.vo.AddRoleDo
 import com.grkj.data.model.vo.RoleManageFilterVo
 import com.grkj.data.model.vo.RoleManageVo
+import com.grkj.data.model.vo.UpdateRoleDo
 import com.grkj.data.repository.IRoleRepository
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -14,7 +17,8 @@ import javax.inject.Singleton
  * 岗位仓储
  */
 @Singleton
-class RoleRepository @Inject constructor(val roleDao: RoleDao): IRoleRepository {
+class RoleRepository @Inject constructor(val roleDao: RoleDao,val sysMenuDao: SysMenuDao) :
+    IRoleRepository {
 
     override fun getRoleData(): List<SysRole> {
         return roleDao.getRoleData()
@@ -48,4 +52,42 @@ class RoleRepository @Inject constructor(val roleDao: RoleDao): IRoleRepository
             current * size
         )
     }
+
+    override fun addRoleData(addRoleDo: AddRoleDo) {
+        val sysRole = SysRole()
+        sysRole.roleName = addRoleDo.roleName
+        sysRole.roleKey = addRoleDo.roleKey
+        sysRole.status = if (addRoleDo.status == true) "0" else "2"
+        sysRole.dataScope = "1"
+        val roleId = roleDao.insertRole(sysRole)
+        val sysRoleMenu = mutableListOf<SysRoleMenu>().apply {
+            addRoleDo.menuData.forEach {data->
+                val sysRoleMenu = SysRoleMenu()
+                sysRoleMenu.roleId = roleId
+                sysRoleMenu.menuId = data.menuId
+                add(sysRoleMenu)
+            }
+        }
+        sysMenuDao.insertRoleMenus(sysRoleMenu)
+    }
+
+    override fun updateRoleData(updateRoleDo: UpdateRoleDo) {
+        val sysRole = SysRole()
+        sysRole.roleId = updateRoleDo.roleId
+        sysRole.roleName = updateRoleDo.roleName
+        sysRole.roleKey = updateRoleDo.roleKey
+        sysRole.status = if (updateRoleDo.status == true) "0" else "2"
+        sysRole.dataScope = "1"
+        val roleId = roleDao.insertRole(sysRole)
+        sysMenuDao.deleteByRoleId(updateRoleDo.roleId)
+        val sysRoleMenu = mutableListOf<SysRoleMenu>().apply {
+            updateRoleDo.menuData.forEach {data->
+                val sysRoleMenu = SysRoleMenu()
+                sysRoleMenu.roleId = roleId
+                sysRoleMenu.menuId = data.menuId
+                add(sysRoleMenu)
+            }
+        }
+        sysMenuDao.insertRoleMenus(sysRoleMenu)
+    }
 }

+ 87 - 0
data/src/main/java/com/grkj/data/repository/impl/SysMenuRepository.kt

@@ -0,0 +1,87 @@
+package com.grkj.data.repository.impl
+
+import com.grkj.data.dao.SysMenuDao
+import com.grkj.data.enums.RoleFunctionalPermissionsEnum
+import com.grkj.data.model.dos.SysMenu
+import com.grkj.data.repository.ISysMenuRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * 菜单仓储实现
+ */
+@Singleton
+class SysMenuRepository @Inject constructor(val sysMenuDao: SysMenuDao) : ISysMenuRepository {
+    override fun checkSysMenu() {
+        // 找出所有顶层菜单(level == 0),依次递归插入/更新
+        RoleFunctionalPermissionsEnum.values()
+            .filter { it.level == 0 }
+            .forEach { topEnum ->
+                processMenuEnumRecursive(topEnum, parentId = null)
+            }
+    }
+
+    override fun getSysMenus(): List<SysMenu> {
+        return sysMenuDao.getAll()
+    }
+
+    override fun getSysMenusByRoleId(roleId: Long): List<SysMenu> {
+        return sysMenuDao.getSysMenusByRoleId(roleId)
+    }
+
+    /**
+     * 递归“插入或更新”一个枚举项,以及它的子节点。
+     *
+     * @param menuEnum 当前要处理的枚举项
+     * @param parentId 父菜单在数据库里的 menuId (顶层传 null)
+     * @return 返回在数据库里最终存在的 SysMenu 对象
+     */
+    private fun processMenuEnumRecursive(
+        menuEnum: RoleFunctionalPermissionsEnum,
+        parentId: Long?
+    ): SysMenu {
+        // 1. 查找数据库里是否已存在相同 perms 的菜单
+        val existing: SysMenu? = sysMenuDao.findByPerms(menuEnum.functionalPermission)
+
+        val currentMenu: SysMenu = if (existing == null) {
+            // 不存在,构造一个新的实体并插入
+            val newMenu = SysMenu().apply {
+                menuName = menuEnum.description
+                perms = menuEnum.functionalPermission
+                // 这里用 level 做 orderNum(仅示意),可根据业务自由调整
+                orderNum = menuEnum.level
+                this.parentId = parentId
+                // 其它字段(path、component、isFrame、icon 等)可在此处赋值
+                // 例如:path = "/${menuEnum.functionalPermission.replace(":", "/")}"
+            }
+            val newId: Long = sysMenuDao.insert(newMenu)
+            if (newId <= 0) {
+                // 如果 onConflict = IGNORE 导致插入被忽略(并发、重复等),则重新查询一次
+                sysMenuDao.findByPerms(menuEnum.functionalPermission)
+                    ?: throw IllegalStateException(
+                        "插入 SysMenu(${menuEnum.functionalPermission}) 失败,且数据库中没有已存在记录"
+                    )
+            } else {
+                // 插入成功,Room 会自动把自增主键分配给 newMenu.menuId
+                newMenu.menuId = newId
+                newMenu
+            }
+        } else {
+            // 已经存在,检查 parentId 是否需要更新
+            if (existing.parentId != parentId) {
+                existing.parentId = parentId
+                sysMenuDao.update(existing)
+            }
+            existing
+        }
+
+        // 2. 递归处理它的子节点
+        if (menuEnum.children.isNotEmpty()) {
+            menuEnum.children.forEach { childEnum ->
+                processMenuEnumRecursive(childEnum, parentId = currentMenu.menuId)
+            }
+        }
+
+        return currentMenu
+    }
+}

+ 17 - 2
data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt

@@ -1,9 +1,10 @@
 package com.grkj.data.repository.impl
 
 import com.grkj.data.dao.HardwareDao
+import com.grkj.data.dao.RoleDao
+import com.grkj.data.dao.SysMenuDao
 import com.grkj.data.dao.UserDao
 import com.grkj.data.data.MainDomainData
-import com.grkj.data.database.ISCSDatabase
 import com.grkj.data.model.dos.SysUserDo
 import com.grkj.data.model.vo.AddUserDataVo
 import com.grkj.data.model.vo.UserManageVo
@@ -21,7 +22,9 @@ import javax.inject.Singleton
 @Singleton
 class UserRepository @Inject constructor(
     private val hardwareDao: HardwareDao,
-    private val userDao: UserDao
+    private val userDao: UserDao,
+    private val roleDao: RoleDao,
+    private val sysMenuDao: SysMenuDao
 )  : IUserRepository {
 
     override fun loginWithAccount(
@@ -38,6 +41,9 @@ class UserRepository @Inject constructor(
         val matched = BCryptUtils.matchPassword(password, sysUserDo?.password ?: "")
         if (matched) {
             MainDomainData.userInfo = sysUserDo
+            val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
+            MainDomainData.roleKeys = roleData.roleKey
+            MainDomainData.permissions = sysMenuDao.getPermissionsByRoleId(roleData.roleId)
         }
         return matched
     }
@@ -48,6 +54,9 @@ class UserRepository @Inject constructor(
             val sysUserDo = userDao.getUserInfoByUserId(it)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
+                val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                MainDomainData.roleKeys = roleData.roleKey
+                MainDomainData.permissions = sysMenuDao.getPermissionsByRoleId(roleData.roleId)
             }
             return userDao.getUserInfoByUserId(it) != null
         } ?: false
@@ -73,6 +82,9 @@ class UserRepository @Inject constructor(
             val sysUserDo = userDao.getUserInfoByUserId(userId)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
+                val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                MainDomainData.roleKeys = roleData.roleKey
+                MainDomainData.permissions = sysMenuDao.getPermissionsByRoleId(roleData.roleId)
                 hasFingerprint = true
             } else {
                 hasFingerprint = false
@@ -103,6 +115,9 @@ class UserRepository @Inject constructor(
             val sysUserDo = userDao.getUserInfoByUserId(userId)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
+                val roleData = roleDao.getRoleDataByUserId(sysUserDo.userId)
+                MainDomainData.roleKeys = roleData.roleKey
+                MainDomainData.permissions = sysMenuDao.getPermissionsByRoleId(roleData.roleId)
                 hasFace = true
             } else {
                 hasFace = false

+ 2 - 2
gradle/libs.versions.toml

@@ -6,7 +6,7 @@ junit = "4.13.2"
 junitVersion = "1.1.5"
 espressoCore = "3.5.1"
 appcompat = "1.6.1"
-avi_library = "2.1.3"
+avi_library = "2.1.5"
 material = "1.10.0"
 activity = "1.8.0"
 constraintlayout = "2.1.4"
@@ -31,7 +31,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
 androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
 androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
-avi-library = { module = "com.wang.avi:library", version.ref = "avi_library" }
+avi-library = { module = "io.github.maitrungduc1410:AVLoadingIndicatorView", version.ref = "avi_library" }
 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
 androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }

+ 1 - 1
settings.gradle.kts

@@ -18,7 +18,7 @@ dependencyResolutionManagement {
         google()
         mavenCentral()
         maven("https://jitpack.io")
-        maven("https://maven.aliyun.com/repository/public")
+//        maven("https://maven.aliyun.com/repository/public")
     }
 }
 

+ 4 - 3
ui-base/src/main/java/com/grkj/ui_base/widget/ShadowTextView.kt

@@ -12,9 +12,10 @@ class ShadowTextView @JvmOverloads constructor(
     defStyleAttr: Int = android.R.attr.textViewStyle
 ) : AppCompatTextView(context, attrs, defStyleAttr) {
 
-    private var shadowColor: Int = 0x55000000  // default semi-transparent black
-    private var shadowDx: Float = 2f          // default 2px
-    private var shadowDy: Float = 2f          // default 2px
+    private var shadowColor: Int =
+        context.getColor(R.color.black)  // default semi-transparent black
+    private var shadowDx: Float = 4f          // default 2px
+    private var shadowDy: Float = 4f          // default 2px
 
     // 保存原文字颜色
     private var originalTextColor: Int = currentTextColor