Bladeren bron

迁入ISCS部分代码

Frankensteinly 9 maanden geleden
bovenliggende
commit
4d3c60b444
80 gewijzigde bestanden met toevoegingen van 4914 en 7 verwijderingen
  1. 5 0
      app/build.gradle
  2. 12 1
      app/src/main/AndroidManifest.xml
  3. 18 0
      app/src/main/java/com/grkj/iscs_mc/MyApplication.kt
  4. 97 0
      app/src/main/java/com/grkj/iscs_mc/extentions/ByteArray.kt
  5. 46 0
      app/src/main/java/com/grkj/iscs_mc/extentions/Context.kt
  6. 18 0
      app/src/main/java/com/grkj/iscs_mc/extentions/Int.kt
  7. 20 0
      app/src/main/java/com/grkj/iscs_mc/extentions/Long.kt
  8. 27 0
      app/src/main/java/com/grkj/iscs_mc/extentions/String.kt
  9. 17 0
      app/src/main/java/com/grkj/iscs_mc/extentions/View.kt
  10. 12 0
      app/src/main/java/com/grkj/iscs_mc/model/Constants.kt
  11. 62 0
      app/src/main/java/com/grkj/iscs_mc/model/Token.kt
  12. 199 0
      app/src/main/java/com/grkj/iscs_mc/model/UrlConsts.kt
  13. 7 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/BaseVO.kt
  14. 14 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/card/CardInfoRespVO.kt
  15. 28 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/dept/DeptListRespVO.kt
  16. 11 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/key/KeyInfoRespVO.kt
  17. 11 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/lock/LockInfoRespVO.kt
  18. 7 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/lock/LockTakeUpdateReqVO.kt
  19. 36 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/machinery/MachineryDetailRespVO.kt
  20. 56 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/machinery/MachineryPageRespVO.kt
  21. 32 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/sop/SopInfoRespVO.kt
  22. 37 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/sop/SopPageRespVO.kt
  23. 22 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/CreateTicketReqVO.kt
  24. 10 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/LockPointUpdateReqVO.kt
  25. 31 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/LotoMapRespVO.kt
  26. 21 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/StepDetailRespVO.kt
  27. 61 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketDetailMonitorRespVO.kt
  28. 62 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketDetailRespVO.kt
  29. 58 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketEquipDetailRespVO.kt
  30. 34 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketPageRespVO.kt
  31. 18 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketTypeRespVO.kt
  32. 8 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketUserReqVO.kt
  33. 43 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/WorkstationTicketListRespVO.kt
  34. 182 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/user/RoleListRespVO.kt
  35. 127 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/user/UserInfoRespVO.kt
  36. 122 0
      app/src/main/java/com/grkj/iscs_mc/model/vo/user/UserListRespVO.kt
  37. 43 0
      app/src/main/java/com/grkj/iscs_mc/util/AppUtils.kt
  38. 122 0
      app/src/main/java/com/grkj/iscs_mc/util/CRC16.java
  39. 80 0
      app/src/main/java/com/grkj/iscs_mc/util/CommonUtils.kt
  40. 138 0
      app/src/main/java/com/grkj/iscs_mc/util/CrashUtil.kt
  41. 16 0
      app/src/main/java/com/grkj/iscs_mc/util/DownloadCallBack.kt
  42. 140 0
      app/src/main/java/com/grkj/iscs_mc/util/Executor.kt
  43. 141 0
      app/src/main/java/com/grkj/iscs_mc/util/FileUtil.kt
  44. 93 0
      app/src/main/java/com/grkj/iscs_mc/util/Funcs.kt
  45. 689 0
      app/src/main/java/com/grkj/iscs_mc/util/NetApi.kt
  46. 294 0
      app/src/main/java/com/grkj/iscs_mc/util/NetHttpManager.kt
  47. 101 0
      app/src/main/java/com/grkj/iscs_mc/util/NetManager.kt
  48. 105 0
      app/src/main/java/com/grkj/iscs_mc/util/SPUtils.kt
  49. 69 0
      app/src/main/java/com/grkj/iscs_mc/util/StompApi.kt
  50. 116 0
      app/src/main/java/com/grkj/iscs_mc/util/StompManager.kt
  51. 26 0
      app/src/main/java/com/grkj/iscs_mc/util/ToastUtils.kt
  52. 41 0
      app/src/main/java/com/grkj/iscs_mc/util/log/ILog.kt
  53. 16 0
      app/src/main/java/com/grkj/iscs_mc/util/log/LogDiskStrategy.kt
  54. 64 0
      app/src/main/java/com/grkj/iscs_mc/util/log/LogHandle.kt
  55. 178 0
      app/src/main/java/com/grkj/iscs_mc/util/log/LogUtil.kt
  56. 81 0
      app/src/main/java/com/grkj/iscs_mc/util/log/LogWriteHandler.kt
  57. 147 0
      app/src/main/java/com/grkj/iscs_mc/util/log/MyCsvFormatStrategy.kt
  58. 105 0
      app/src/main/java/com/grkj/iscs_mc/util/log/MyDiskLogStrategy.kt
  59. 75 0
      app/src/main/java/com/grkj/iscs_mc/view/activity/LoginActivity.kt
  60. 102 0
      app/src/main/java/com/grkj/iscs_mc/view/dialog/LoginDialog.kt
  61. 5 0
      app/src/main/java/com/grkj/iscs_mc/view/iview/ILoginView.kt
  62. 60 0
      app/src/main/java/com/grkj/iscs_mc/view/presenter/LoginPresenter.kt
  63. 6 0
      app/src/main/res/drawable/common_btn_bg.xml
  64. 6 0
      app/src/main/res/drawable/common_btn_blue_bg.xml
  65. 6 0
      app/src/main/res/drawable/common_btn_red_bg.xml
  66. 6 0
      app/src/main/res/drawable/item_rv_login_bg.xml
  67. 8 0
      app/src/main/res/drawable/white_stroke_bg.xml
  68. 31 0
      app/src/main/res/layout/activity_login.xml
  69. 70 0
      app/src/main/res/layout/dialog_login.xml
  70. 21 0
      app/src/main/res/layout/item_rv_login.xml
  71. BIN
      app/src/main/res/mipmap/login_account.png
  72. BIN
      app/src/main/res/mipmap/login_bg.png
  73. BIN
      app/src/main/res/mipmap/login_card.png
  74. BIN
      app/src/main/res/mipmap/login_face.png
  75. BIN
      app/src/main/res/mipmap/login_fingerprint.png
  76. 32 5
      app/src/main/res/values/colors.xml
  77. 35 0
      app/src/main/res/values/dimens.xml
  78. 19 0
      app/src/main/res/values/strings.xml
  79. 54 0
      app/src/main/res/values/styles.xml
  80. 2 1
      gradle.properties

+ 5 - 0
app/build.gradle

@@ -74,4 +74,9 @@ dependencies {
     implementation 'pub.devrel:easypermissions:3.0.0'
 
     implementation 'com.wang.avi:library:2.1.3'
+
+    // RV通用Adapter  https://github.com/hongyangAndroid/base-adapter
+    implementation 'com.zhy:base-rvadapter:3.0.3'
+    // 日志工具 https://github.com/orhanobut/logger
+    implementation 'com.orhanobut:logger:2.2.0'
 }

+ 12 - 1
app/src/main/AndroidManifest.xml

@@ -13,8 +13,16 @@
         android:supportsRtl="true"
         android:theme="@style/Theme.ISCS_MC"
         tools:targetApi="31">
+
+        <meta-data
+            android:name="design_width_in_dp"
+            android:value="640" />
+        <meta-data
+            android:name="design_height_in_dp"
+            android:value="360" />
+
         <activity
-            android:name=".view.activity.HomeActivity"
+            android:name=".view.activity.LoginActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -22,5 +30,8 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity
+            android:name=".view.activity.HomeActivity"
+            android:exported="true" />
     </application>
 </manifest>

+ 18 - 0
app/src/main/java/com/grkj/iscs_mc/MyApplication.kt

@@ -3,6 +3,13 @@ package com.grkj.iscs_mc
 import android.app.Application
 import android.content.Context
 import androidx.multidex.MultiDex
+import com.grkj.iscs_mc.model.Token
+import com.grkj.iscs_mc.util.FileUtil
+import com.grkj.iscs_mc.util.FileUtil.LOG_DIR
+import com.grkj.iscs_mc.util.NetApi
+import com.grkj.iscs_mc.util.NetHttpManager
+import com.grkj.iscs_mc.util.SPUtils
+import com.grkj.iscs_mc.util.log.LogUtil
 
 class MyApplication : Application() {
 
@@ -13,6 +20,17 @@ class MyApplication : Application() {
     override fun onCreate() {
         super.onCreate()
         instance = this
+//        LogUtil.init(instance!!, FileUtil.ROOT_APP + FileUtil.LOG_DIR)
+        // 路径:sdcard/Android/data/com.grkj.iscs/files/iscs/log
+        LogUtil.init(this, "${FileUtil.getRootFolder(this)?.absolutePath}$LOG_DIR")
+
+        NetHttpManager.getInstance().initCtx(this)
+
+        NetApi.logout()
+        SPUtils.clearLoginUser(this)
+        Token.clear(this)
+
+        LogUtil.i("App start")
     }
 
     override fun attachBaseContext(base: Context?) {

+ 97 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/ByteArray.kt

@@ -0,0 +1,97 @@
+package com.grkj.iscs_mc.extentions
+
+import android.util.Base64
+import com.grkj.iscs_mc.util.CRC16
+import java.io.ByteArrayOutputStream
+import kotlin.experimental.xor
+
+fun ByteArray.startsWith(prefix: ByteArray): Boolean {
+    require(this.size >= prefix.size) { "ByteArray is smaller than the prefix." }
+
+    for (i in prefix.indices) {
+        if (this[i] != prefix[i]) {
+            return false
+        }
+    }
+    return true
+}
+
+fun ByteArray.base64() : String {
+    return Base64.encodeToString(this, Base64.NO_WRAP)
+}
+
+private val hexEncodingTable = byteArrayOf(
+    '0'.toByte(), '1'.toByte(), '2'.toByte(), '3'.toByte(), '4'.toByte(), '5'.toByte(), '6'.toByte(), '7'.toByte(),
+    '8'.toByte(), '9'.toByte(), 'A'.toByte(), 'B'.toByte(), 'C'.toByte(), 'D'.toByte(), 'E'.toByte(), 'F'.toByte()
+)
+
+fun ByteArray.toHexStrings(space: Boolean = true) : String {
+    val out = ByteArrayOutputStream()
+    try {
+        for (i in 0 until size) {
+            val v: Int = this[i].toInt() and 0xff
+            out.write(hexEncodingTable[v ushr 4].toInt())
+            out.write(hexEncodingTable[v and 0xf].toInt())
+            if (space) {
+                out.write(' '.toInt())
+            }
+        }
+    } catch (e: java.lang.Exception) {
+        throw IllegalStateException("exception encoding Hex string: " + e.message, e)
+    }
+    val bytes = out.toByteArray()
+    return String(bytes)
+}
+
+const val RADIX_62 = 62
+
+/**
+ * 把[from, to) 部分的子数组,转换为以 0-9 a-z A-Z 共 62个字符组成的数字
+ *
+ * @param from 起始
+ * @param to 结束(不包含)
+ */
+fun ByteArray.to62Num(from: Int, to: Int) : Int {
+    var sum = 0
+    var scale = 1
+    for (i in to - 1 downTo from) {
+        sum += from62(this[i]) * scale
+        scale *= RADIX_62
+    }
+    return sum
+}
+
+/**
+ * 把[from, to) 部分的字节做异或
+ */
+fun ByteArray.xor(from: Int, to: Int) : Byte {
+    var res = this[from]
+    for (i in (from + 1) until to) {
+        res = res xor this[i]
+    }
+    return res
+}
+
+private fun from62(b: Byte): Int {
+    if (b in 0x30..0x39) {
+        return b - 0x30
+    }
+    if (b in 0x61..0x7a) {
+        return b - 0x57
+    }
+    if (b in 0x41..0x5a) {
+        return b - 0x1d
+    }
+    throw IllegalArgumentException("$b is not a num char")
+}
+
+/**
+ * 计算 [from, to) 部分的字节做 的 CRC16 校验值
+ * @return 两字节的校验值
+ */
+fun ByteArray.crc16(from: Int = 0, to: Int = size) : ByteArray {
+    val value = CRC16.crc16(this, from.coerceAtLeast(0), to.coerceAtMost(size))
+    val c1 = (0xff00 and value shr 8).toByte()
+    val c2 = (0xff and value).toByte()
+    return byteArrayOf(c1, c2)
+}

+ 46 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/Context.kt

@@ -0,0 +1,46 @@
+package com.grkj.iscs_mc.extentions
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Observer
+import com.grkj.iscs_mc.util.NetManager
+import java.util.Locale
+
+/**
+ * 网络管理器
+ */
+fun Context.netManager() = NetManager.getInstance(applicationContext)
+
+fun Context.addNetObserver(observer: Observer<Boolean>) {
+    netManager().liveData.observeForever(observer)
+}
+
+fun Context.removeNetObserver(observer: Observer<Boolean>) {
+    netManager().liveData.removeObserver(observer)
+}
+
+fun Context.serialNo() : String {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+        && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
+        == PackageManager.PERMISSION_GRANTED) {
+        return Build.getSerial().toUpperCase(Locale.ROOT)
+    }
+    return Build.SERIAL.toUpperCase(Locale.ROOT)
+}
+
+/**
+ * 检查权限
+ */
+fun Context.checkPermissions(permissions: Array<String>): Boolean {
+    for (permission in permissions) {
+        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
+            return false
+        }
+    }
+    return true
+}

+ 18 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/Int.kt

@@ -0,0 +1,18 @@
+package com.grkj.iscs_mc.extentions
+
+//fun Int.toByteArray(capability: Int = 2): ByteArray {
+//    return ByteBuffer.allocate(capability)
+//        .order(ByteOrder.BIG_ENDIAN) // 可以根据需要改为 ByteOrder.LITTLE_ENDIAN
+//        .putShort(this.toShort()) // 只取低16位
+//        .array()
+//}
+
+fun Int.toByteArray(capability: Int = 2): ByteArray {
+//    require(capability in 1..4) { "Length must be between 1 and 4" }
+    val bytes = ByteArray(capability)
+    for (i in 0 until capability) {
+//        bytes[capability - i - 1] = ((this ushr (i * 8)) and 0xFF).toByte() // 大端模式
+        bytes[i] = ((this ushr (i * 8)) and 0xFF).toByte()  // 小端模式
+    }
+    return bytes
+}

+ 20 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/Long.kt

@@ -0,0 +1,20 @@
+package com.grkj.iscs_mc.extentions
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+fun Long.toByteArray(): ByteArray {
+    return ByteBuffer.allocate(java.lang.Long.BYTES)
+        .order(ByteOrder.LITTLE_ENDIAN)
+        .putLong(this).array()
+}
+
+fun Long.toByteArrays(capability: Int = 4): ByteArray {
+//    require(capability in 1..4) { "Length must be between 1 and 4" }
+    val bytes = ByteArray(capability)
+    for (i in 0 until capability) {
+//        bytes[capability - i - 1] = ((this ushr (i * 8)) and 0xFF).toByte() // 大端模式
+        bytes[i] = ((this ushr (i * 8)) and 0xFF).toByte()  // 小端模式
+    }
+    return bytes
+}

+ 27 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/String.kt

@@ -0,0 +1,27 @@
+package com.grkj.iscs_mc.extentions
+
+/**
+ * 长度不足默认补0
+ */
+fun String.toByteArray(length: Int, paddingByte: Byte = 0): ByteArray {
+    val bytes = this.toByteArray(Charsets.UTF_8)
+    return if (bytes.size >= length) {
+        bytes.copyOf(length)
+    } else {
+        ByteArray(length) { index ->
+            if (index < bytes.size) {
+                bytes[index]
+            } else {
+                paddingByte
+            }
+        }
+    }
+}
+
+/**
+ * 移除开头的补0,长度不足8位补0
+ */
+fun String.removeLeadingZeros(): String {
+    val trimmed = this.dropWhile { it == '0' }.takeIf { it.isNotEmpty() } ?: "0"
+    return trimmed.padStart(8, '0')
+}

+ 17 - 0
app/src/main/java/com/grkj/iscs_mc/extentions/View.kt

@@ -0,0 +1,17 @@
+package com.grkj.iscs_mc.extentions
+
+import android.view.View
+
+fun View.debounce(
+    debounceTime: Long = 1500L,
+    onClick: () -> Unit
+) {
+    var lastClickTime = 0L
+    setOnClickListener {
+        val currentTime = System.currentTimeMillis()
+        if (currentTime - lastClickTime >= debounceTime) {
+            onClick()
+        }
+        lastClickTime = currentTime
+    }
+}

+ 12 - 0
app/src/main/java/com/grkj/iscs_mc/model/Constants.kt

@@ -0,0 +1,12 @@
+package com.grkj.iscs_mc.model
+
+object Constants {
+    const val DEVICE_TYPE = 1 // 1.机柜 2.物料柜 3.手提柜 4.混合柜
+    const val DEVICE_TYPE_NORMAL = "Android_Normal"     // 机柜
+    const val DEVICE_TYPE_MATERIAL = "Android_Material" // 物资柜
+    const val DEVICE_TYPE_PORTABLE = "Android_Portable" // 手提柜
+    const val DEVICE_TYPE_HYBRID = "Android_Hybrid"     // 混合柜
+
+    const val PERMISSION_REQUEST_CODE = 1
+    const val BLE_LOCAL_NAME = "keyLock"
+}

+ 62 - 0
app/src/main/java/com/grkj/iscs_mc/model/Token.kt

@@ -0,0 +1,62 @@
+package com.grkj.iscs_mc.model
+
+import android.content.Context
+import android.content.SharedPreferences
+import java.util.concurrent.TimeUnit
+
+class Token(
+    val token: String,
+    private val expiresAt: Long
+) {
+
+
+    fun saveToSp(context:Context): Boolean {
+        val now = System.currentTimeMillis() / 1000
+        return tokenSp(context).edit()
+            .putString("token", token)
+            .putLong("expiresAt", now + 60 * 60 * 2)
+            .commit()
+    }
+
+    fun isValid(): Boolean {
+        return expiresAt < 0 || expiresAt > System.currentTimeMillis() / 1000
+    }
+
+    override fun toString(): String {
+        return "Token(token='$token', expiresAt=$expiresAt)"
+    }
+
+    companion object {
+
+        fun fromSp(context:Context): Token? {
+            val sp = tokenSp(context)
+            val token = sp.getString("token", null)
+            if (token == null) {
+                return null
+            }
+            val expiresAt = sp.getLong("expiresAt", 0)
+            val now = System.currentTimeMillis() / 1000
+            if (expiresAt < now) {
+                return null
+            }
+            return Token(token, expiresAt)
+        }
+
+        fun clear(context: Context) : Boolean {
+            return tokenSp(context).edit()
+                .putString("token", null)
+                .putLong("expiresAt", 0)
+                .commit()
+        }
+
+        private fun tokenSp(context: Context): SharedPreferences {
+            return context.getSharedPreferences("token", Context.MODE_PRIVATE)
+        }
+
+        fun refresh(context: Context) {
+            val now = System.currentTimeMillis() / 1000
+            val sp = tokenSp(context)
+            sp.edit().putLong("expiresAt", now + 60 * 60 * 2).apply()
+        }
+    }
+}

+ 199 - 0
app/src/main/java/com/grkj/iscs_mc/model/UrlConsts.kt

@@ -0,0 +1,199 @@
+package com.grkj.iscs_mc.model
+
+object UrlConsts {
+    const val BASE_URL = "http://192.168.28.82:9190"
+//    const val BASE_URL = "http://192.168.28.97:9190"
+    const val WEB_SOCKET = "ws://192.168.1.127:9090/websocket/iot/127"
+
+    const val AUTOCODE_TICKET_NUMBER = "JOB_TICKET_CODE"
+
+    /**
+     * 登录
+     */
+    const val SIGN_IN = "/login"
+
+    /**
+     * 退出登录
+     */
+    const val LOGOUT = "/mobile/login/logout"
+
+    /**
+     * 查询SOP信息-分页
+     */
+    const val SOP_PAGE = "/iscs/mars/sop/getIsMarsSopPage"
+
+    /**
+     * 查询字典 - 工作票类型
+     */
+    const val TICKET_TYPE = "/system/dict/data/type/ticket_type"
+
+    /**
+     * 获取一个自动生成的编码
+     */
+    const val AUTO_CODE = "/system/autocode/get"
+
+    /**
+     * 获取SOP详细信息
+     */
+    const val SOP_INFO = "/iscs/sop/selectIsSopById"
+
+    /**
+     * 获取用户列表 - 上锁人
+     */
+    const val USER_LIST = "/system/user/list"
+
+    /**
+     * 获取部门列表
+     */
+    const val DEPT_LIST = "/system/dept/list"
+
+    /**
+     * 创建工作票
+     */
+    const val CREATE_TICKET = "/iscs/ticket/insertIsJobTicket"
+
+    /**
+     * 查询作业票-分页
+     */
+    const val PAGE_TICKET = "/iscs/ticket/getIsJobTicketPage"
+
+    /**
+     * 获取作业票详细信息
+     */
+    const val DETAIL_TICKET = "/iscs/ticket/selectIsJobTicketById"
+
+    /**
+     * 刷卡登录
+     */
+    const val LOGIN_CARD = "/iscs/card/login"
+
+    /**
+     * 获取刷卡信息
+     */
+    const val CARD_INFO_BY_LOGIN_USER = "/iscs/card/selectIsJobCardByLoginUser"
+
+    /**
+     * 通过NFC编号获取刷卡信息
+     */
+    const val CARD_INFO_BY_CARD_NFC = "/iscs/card/selectIsJobCardByCardNfc"
+
+    /**
+     * 通过NFC编号获取钥匙详细信息
+     */
+    const val KEY_INFO_BY_NFC = "/iscs/key/selectIsKeyByNfc"
+
+    /**
+     * 通过NFC编号获取钥匙详细信息 - 免验证
+     */
+    const val KEY_INFO_BY_NFC_WITHOUT_AUTH = "/iscs/key/selectIsKeyByNfcWithoutAuth"
+
+    /**
+     * 通过NFC编号获取锁详细信息
+     */
+    const val LOCK_INFO_BY_NFC = "/iscs/lock/selectIsLockByNfc"
+
+    /**
+     * 归还挂锁时更新数据
+     */
+    const val LOCK_RETURN_UPDATE = "/iscs/hardware-api/updateTicketLockReturn"
+
+    /**
+     * 取出挂锁时更新数据
+     */
+    const val LOCK_TAKE_UPDATE = "/iscs/hardware-api/updateTicketLockTake"
+
+    /**
+     * 挂锁上锁时更新数据,更新挂锁和哪个隔离点进行了绑定
+     */
+    const val LOCK_POINT_UPDATE = "/iscs/hardware-api/updateTicketLockPoint"
+
+    /**
+     * 获取作业票和关联数据
+     */
+    const val TICKET_EQUIP_DETAIL = "/iscs/hardware-api/selectTicketDetailById"
+
+    /**
+     * 取出钥匙
+     */
+    const val KEY_TAKE_UPDATE = "/iscs/hardware-api/updateTakeOutKey"
+
+    /**
+     * 归还钥匙
+     */
+    const val KEY_RETURN_UPDATE = "/iscs/hardware-api/updateReturnKey"
+
+    /**
+     * 更新作业票进度
+     */
+    const val TICKET_UPDATE_PROGRESS = "/iscs/ticket/updateProgress"
+
+    /**
+     * 批量更新作业票下隔离点的上锁解锁状况
+     */
+    const val LOCK_POINT_UPDATE_BATCH = "/iscs/hardware-api/updateLockPointBatch"
+
+    /**
+     * 根据角色获取人员列表
+     */
+    const val ROLE_LIST = "/system/role/authUser/allocatedListByRoleKey"
+
+    /**
+     * 正在进行中的作业票列表
+     */
+    const val WORKSTATION_TICKET_LIST = "/iscs/ticket/getWorkstationTicketList"
+
+    /**
+     * 设备工艺分页
+     */
+    const val MACHINERY_PAGE = "/iscs/machinery/getIsMachineryPage"
+
+    /**
+     * 获取八大步骤详细
+     */
+    const val TICKET_STEP = "/iscs/step/selectStepsByTicketId"
+
+    /**
+     * 获取设备工艺详细信息
+     */
+    const val MACHINERY_DETAIL = "/iscs/machinery/selectIsMachineryById"
+
+    /**
+     * 获取电柜map解析数据
+     */
+    const val LOTO_MAP = "/iscs/station/selectLotoMapById"
+
+    /**
+     * 取消作业票
+     */
+    const val CANCEL_TICKET = "/iscs/ticket/updateJobToCancel"
+
+    /**
+     * 结束作业票
+     */
+    const val FINISH_TICKET = "/iscs/ticket/updateJobToFinish"
+
+    /**
+     * 新增人员
+     */
+    const val UPDATE_TICKET_USER = "/iscs/step/addJobUsers"
+
+    /**
+     * 作业票详情监控
+     */
+    const val TICKET_DETAIL_MONITOR = "/iscs/ticket/selectMonitorJobTicketDetail"
+
+    /**
+     * 八大步骤执行
+     */
+    const val UPDATE_STEP = "/iscs/step/updateJobStep"
+
+    /**
+     * 共锁人上锁/解锁
+     */
+    const val UPDATE_COLOCKER_STATUS = "/iscs/hardware-api/updateColockerStatus"
+
+    /**
+     * 获取用户信息
+     */
+    const val GET_USER_INFO = "/getInfo"
+}

+ 7 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/BaseVO.kt

@@ -0,0 +1,7 @@
+package com.grkj.iscs_mc.model.vo
+
+open class BaseVO<T> {
+    val code = 0
+    val data: T? = null
+    val msg: String? = null
+}

+ 14 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/card/CardInfoRespVO.kt

@@ -0,0 +1,14 @@
+package com.grkj.iscs_mc.model.vo.card
+
+import java.io.Serializable
+
+data class CardInfoRespVO(
+    var cardId: Long?,
+    var cardCode: String?,
+    var hardwareId: Long?,
+    var cardNfc: String?,
+    var cardType: Int?,
+    var userId: Long?,
+    var userName: String?,
+    var roleKeyList: List<String>?
+): Serializable

+ 28 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/dept/DeptListRespVO.kt

@@ -0,0 +1,28 @@
+package com.grkj.iscs_mc.model.vo.dept
+
+data class DeptListRespVO(
+    /** 部门ID */
+    val deptId: Long?,
+    /** 父部门ID  */
+    val parentId: Long?,
+    /** 祖级列表  */
+    val ancestors: String?,
+    /** 部门名称  */
+    val deptName: String?,
+    /** 显示顺序  */
+    val orderNum: String?,
+    /** 负责人  */
+    val leader: String?,
+    /** 联系电话  */
+    val phone: String?,
+    /** 邮箱  */
+    val email: String?,
+    /** 部门状态:0正常,1停用  */
+    val status: String?,
+    /** 删除标志(0代表存在 2代表删除)  */
+    val delFlag: String?,
+    /** 父部门名称  */
+    val parentName: String?,
+    /** 子部门  */
+    val children: List<DeptListRespVO> = mutableListOf()
+)

+ 11 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/key/KeyInfoRespVO.kt

@@ -0,0 +1,11 @@
+package com.grkj.iscs_mc.model.vo.key
+
+data class KeyInfoRespVO(
+    val keyId: Long?,
+    val keyCode: String?,
+    val keyName: String?,
+    val hardwareId: Long?,
+    val keyNfc: String?,
+    val macAddress: String?,
+    val delFlag: String?
+)

+ 11 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/lock/LockInfoRespVO.kt

@@ -0,0 +1,11 @@
+package com.grkj.iscs_mc.model.vo.lock
+
+data class LockInfoRespVO(
+    val lockId: Long?,
+    val lockCode: String?,
+    val lockName: String?,
+    val lockTypeId: Long?,
+    val hardwareId: Long?,
+    val lockNfc: String?,
+    val delFlag: String?
+)

+ 7 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/lock/LockTakeUpdateReqVO.kt

@@ -0,0 +1,7 @@
+package com.grkj.iscs_mc.model.vo.lock
+
+data class LockTakeUpdateReqVO(
+    val ticketId: Long?,
+    val lockNfc: String?,
+    val serialNumber: String?
+)

+ 36 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/machinery/MachineryDetailRespVO.kt

@@ -0,0 +1,36 @@
+package com.grkj.iscs_mc.model.vo.machinery
+
+import com.grkj.iscs_mc.model.vo.machinery.MachineryPageRespVO.Record.SysDictData
+
+
+data class MachineryDetailRespVO(
+    val machineryId: Long?,
+
+    val machineryCode: String?,
+
+    val machineryName: String?,
+
+    val machineryType: String?,
+
+    val workstationId: Long?,
+
+    val workstationName: String?,
+
+    val lotoId: Long?,
+
+    val lotoName: String?,
+
+    val machineryImg: String?,
+
+    val parentId: Long?,
+
+    val ancestors: String?,
+
+    val status: String?,
+
+    val delFlag: String?,
+
+    val pointIdList: List<Long>?,
+
+    val sysDictDatas: List<SysDictData>?,
+)

+ 56 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/machinery/MachineryPageRespVO.kt

@@ -0,0 +1,56 @@
+package com.grkj.iscs_mc.model.vo.machinery
+
+data class MachineryPageRespVO(
+    val countId: Any,
+    val current: Int,
+    val maxLimit: Any,
+    val optimizeCountSql: Boolean,
+    val orders: MutableList<Any>,
+    val pages: Int,
+    val records: MutableList<Record>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+) {
+    data class Record(
+        val machineryId: Long?,
+
+        val machineryCode: String?,
+
+        val machineryName: String?,
+
+        val machineryType: String?,
+
+        val workstationId: Long?,
+
+        val workstationName: String?,
+
+        val lotoId: Long?,
+
+        val lotoName: String?,
+
+        val machineryImg: String?,
+
+        val parentId: Long?,
+
+        val ancestors: String?,
+
+        val status: String?,
+
+        val delFlag: String?,
+
+        val pointIdList: List<Long>?,
+
+        val sysDictDatas: MutableList<SysDictData>?
+    ) {
+        data class SysDictData(
+            val dictLabel: String?,
+
+            /** 字典键值  */
+            val dictValue: String?,
+
+            /** 字典类型  */
+            val dictType: String?
+        )
+    }
+}

+ 32 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/sop/SopInfoRespVO.kt

@@ -0,0 +1,32 @@
+package com.grkj.iscs_mc.model.vo.sop
+
+data class SopInfoRespVO(
+    val sopId: Long?,
+    val sopCode: String?,
+    val sopName: String?,
+    val sopType: String?,
+    val workshopId: Long?,
+    val workareaId: Long?,
+    val sopContent: String?,
+    val sopStatus: String?,
+    val delFlag: String?,
+    val pointDetailVOList: List<PointDetailVO>?
+) {
+    data class PointDetailVO(
+        val pointId: Long?,
+        val pointCode: String?,
+        val pointName: String?,
+        val pointType: String?,
+        val pointTypeName: String?,
+        val workshopId: Long?,
+        val workshopName: String?,
+        val workareaId: Long?,
+        val workareaName: String?,
+        val powerType: String?,
+        val powerTypeName: String?,
+        val isolationMethod: String?,
+        val pointIcon: String?,
+        val pointPicture: String?,
+        val delFlag: String? = null
+    )
+}

+ 37 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/sop/SopPageRespVO.kt

@@ -0,0 +1,37 @@
+package com.grkj.iscs_mc.model.vo.sop
+
+data class SopPageRespVO(
+    val current: Int,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: List<Record>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+) {
+    data class Record(
+        val createBy: String,
+        val createTime: String,
+        val delFlag: String,
+        val pointCount: Int,
+        val sopCode: String,
+        val sopContent: String,
+        val sopId: Long,
+        val sopName: String,
+        val sopStatus: String,
+        val sopType: String,
+        val updateBy: String,
+        val updateTime: String,
+        val workareaId: Long,
+        val workareaName: String,
+        val workshopId: Long,
+        val workshopName: String,
+        val sopIndex: Int,
+        val workstationId: Long,
+        val workstationName: String,
+        val machineryId: Long,
+        val machineryName: String
+    )
+}
+

+ 22 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/CreateTicketReqVO.kt

@@ -0,0 +1,22 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class CreateTicketReqVO(
+    val ticketCode: String?,
+    val ticketName: String?,
+    val workshopId: Long?,
+    val workareaId: Long?,
+    val sopId: Long?,
+    val ticketType: String?,
+    val ticketContent: String?,
+    val ticketStartTime: String?,
+    val ticketEndTime: String?,
+    val pointIds: String?,
+    val ticketUserDTOList: List<TicketUserVO>? = null
+) {
+    data class TicketUserVO(
+        val userId: Long?,
+        val userName: String?,
+        val userType: String,
+        val userRole: String?
+    )
+}

+ 10 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/LockPointUpdateReqVO.kt

@@ -0,0 +1,10 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class LockPointUpdateReqVO(
+    val ticketId: Long?,
+    val lockNfc: String?,
+    val pointNfc: String?,
+    val keyNfc: String?,
+    val target: Int?,
+    val status: Int?
+)

+ 31 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/LotoMapRespVO.kt

@@ -0,0 +1,31 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class LotoMapRespVO(
+    val row: Int?,
+
+    val col: Int?,
+
+    val pointId: Long?,
+
+    val pointName: String?,
+
+    val remark: String?,
+
+    val prePointId: Long?,
+
+    val pointType: String?,
+
+    val pointTypeName: String?,
+
+    val powerType: String?,
+
+    val powerTypeName: String?,
+
+    val state: Boolean?,
+
+    val pointIcon: String?,
+
+    val pointPicture: String?,
+
+    val mapImg: String?,
+)

+ 21 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/StepDetailRespVO.kt

@@ -0,0 +1,21 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class StepDetailRespVO(
+    val stepId: Long?,
+
+    val ticketId: Long?,
+
+    val stepIndex: Int?,
+
+    val stepStatus: String?,
+
+    val stepContent: String?,
+
+    val androidStepContent: String?,
+
+    val lockNum: Int?,
+
+    val userNum: Int?,
+
+    val conflictJobNum: Int?
+)

+ 61 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketDetailMonitorRespVO.kt

@@ -0,0 +1,61 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketDetailMonitorRespVO(
+    val jobTicket: IsJobTicket?,
+    val lockKeyName: String?,
+    val lockTime: String?,
+    val colockKeyName: String?,
+    val colockTime: String?,
+    val lockUserList: List<IsJobTicketUser>?,
+    val colockUserList: List<IsJobTicketUser>?,
+    val ticketPointsList: List<IsJobTicketPointsVO>?
+) {
+    data class IsJobTicket(
+        val ticketId: Long?,
+        val ticketCode: String?,
+        val ticketName: String?,
+        val workshopId: Long?,
+        val workareaId: Long?,
+        val sopId: Long?,
+        val ticketType: String?,
+        val ticketContent: String?,
+        val ticketStatus: String?,
+        val ticketStartTime: String?,
+        val ticketEndTime: String?,
+        val delFlag: String?,
+        val workstationId: Long?,
+        val machineryId: Long?
+    )
+
+    data class IsJobTicketUser(
+        val recordId: Long?,
+        val ticketId: Long?,
+        val userId: Long?,
+        val userName: String?,
+        val userType: String?,
+        val userRole: String?,
+        val jobStatus: Int?,
+        val delFlag: String?
+    )
+
+    data class IsJobTicketPointsVO(
+        val recordId: Long?,
+        val ticketId: Long?,
+        val workshopId: Long?,
+        val workareaId: Long?,
+        val pointId: Long?,
+        val pointName: String?,
+        val pointStatus: String?,
+        val delFlag: String?,
+        val lockId: Long?,
+        val lockName: String?,
+        val lockedByKeyId: Long?,
+        val unlockedByKeyId: Long?,
+        val lockTime: String?,
+        val unlockTime: String?,
+        val prePointId: Long?,
+        val locksetName: String?,
+        val remark: String?,
+        val switchStatus: String?   // 0:开 1:关
+    )
+}

+ 62 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketDetailRespVO.kt

@@ -0,0 +1,62 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketDetailRespVO(
+    val ticketId: Long?,
+    val ticketCode: String?,
+    val ticketName: String?,
+    val workshopId: Long?,
+    val workshopName: String?,
+    val workareaId: Long?,
+    val workareaName: String?,
+    val sopId: Long?,
+    val sopName: String?,
+    val ticketType: String?,
+    val ticketContent: String?,
+    val ticketStatus: String?,
+    val ticketStartTime: String?,
+    val ticketEndTime: String?,
+    val delFlag: String?,
+    val lockId: Long?,
+    val lockedByKeyId: Long?,
+    val unlockedByKeyId: Long?,
+    val lockTime: String?,
+    val unlockTime: String?,
+    val pointDetailVOList: List<PointDetailVO>?,
+    val jobTicketUserList: List<UserVO>?
+) {
+    data class PointDetailVO(
+        val pointId: Long?,
+        val pointCode: String?,
+        val pointName: String?,
+        val pointType: String?,
+        val pointTypeName: String?,
+        val pointNfc: String?,
+        val workshopId: Long?,
+        val workshopName: String?,
+        val workareaId: Long?,
+        val workareaName: String?,
+        val powerType: String?,
+        val powerTypeName: String?,
+        val isolationMethod: String?,
+        val pointIcon: String?,
+        val pointPicture: String?,
+        val delFlag: String?,
+        val lockTypeId: Long?,
+        val lockTypeCode: String?,
+        val lockTypeName: String?,
+        val lockTypeIcon: String?,
+        val lockTypeImg: String?,
+        val lockId: Long?,
+        val lockNfc: String?,
+        val prePointId: Long?)
+
+    data class UserVO(
+        val recordId: Long?,
+        val ticketId: Long?,
+        val userId: Long?,
+        val userName: String?,
+        val userType: String?,
+        val userRole: String?,
+        val jobStatus: Int?,
+        val delFlag: String?)
+}

+ 58 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketEquipDetailRespVO.kt

@@ -0,0 +1,58 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketEquipDetailRespVO(
+    val ticketId: Long?,
+    val ticketCode: String?,
+    val ticketName: String?,
+    val workshopId: Long?,
+    val workareaId: Long?,
+    val sopId: Long?,
+    val ticketType: String?,
+    val ticketContent: String?,
+    val ticketStatus: String?,
+    val ticketStartTime: String?,
+    val ticketEndTime: String?,
+    val delFlag: String?,
+    val ticketKeyVOList: List<JobTicketKeyVO>?,
+    val ticketLockVOList: List<JobTicketLockVO>?,
+    val ticketLocksetVOList: List<JobTicketLocksetVO>?
+) {
+    data class JobTicketKeyVO(
+        val recordId: Long?,
+        val ticketId: Long?,
+        val keyId: Long?,
+        val fromHardwareId: Long?,
+        val toHardwareId: Long?,
+        val collectTime: String?,
+        val giveBackTime: String?,
+        val keyStatus: String?,
+        val delFlag: String?,
+        val ticketType: Int?
+    )
+
+    data class JobTicketLockVO(
+        val recordId: Long?,
+        val ticketId: Long?,
+        val lockId: Long?,
+        val lockNfc: String?,
+        val fromHardwareId: Long?,
+        val toHardwareId: Long?,
+        val isolationPointId: Long?,
+        val lockStatus: String?,
+        val delFlag: String?
+    )
+
+    data class JobTicketLocksetVO(
+        val recordId: Long?,
+        val jobTicketId: Long?,
+        val pointId: Long?,
+        val locksetId: Long?,
+        val fromHardwareId: Long?,
+        val toHardwareId: Long?,
+        val locksetTypeId: Long?,
+        val locksetStatus: String?,
+        val collectTime: String?,
+        val giveBackTime: String?,
+        val delFlag: String?
+    )
+}

+ 34 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketPageRespVO.kt

@@ -0,0 +1,34 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketPageRespVO(
+    val current: Int,
+    val optimizeCountSql: Boolean,
+    val orders: List<Any>,
+    val pages: Int,
+    val records: MutableList<Record>,
+    val searchCount: Boolean,
+    val size: Int,
+    val total: Int
+) {
+    data class Record(
+        val createBy: String,
+        val createTime: String,
+        val delFlag: String,
+        val pointCount: Int,
+        val sopId: String,
+        val ticketCode: String,
+        val ticketContent: String,
+        val ticketEndTime: String,
+        val ticketId: String,
+        val ticketName: String,
+        val ticketStartTime: String,
+        val ticketStatus: String,
+        val ticketType: String,
+        val updateBy: String,
+        val updateTime: String,
+        val workareaId: String,
+        val workareaName: String,
+        val workshopId: String,
+        val workshopName: String
+    )
+}

+ 18 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketTypeRespVO.kt

@@ -0,0 +1,18 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketTypeRespVO(
+    val createBy: String,
+    val createTime: String,
+    val default: Boolean,
+    val dictCode: String,
+    val dictLabel: String,
+    val dictSort: String,
+    val dictType: String,
+    val dictValue: String,
+    val isDefault: String,
+    val listClass: String,
+    val params: Params,
+    val status: String
+) {
+    class Params
+}

+ 8 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/TicketUserReqVO.kt

@@ -0,0 +1,8 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class TicketUserReqVO(
+    val userId: Long,
+    val userName: String,
+    val userType: String,
+    val userRole: String
+)

+ 43 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/ticket/WorkstationTicketListRespVO.kt

@@ -0,0 +1,43 @@
+package com.grkj.iscs_mc.model.vo.ticket
+
+data class WorkstationTicketListRespVO(
+    /** 作业票ID  */
+    val ticketId: Long?,
+
+    /** 作业票编号  */
+    val ticketCode: String?,
+
+    /** 作业票名称  */
+    val ticketName: String?,
+
+    /** 所属车间ID  */
+    val workshopId: Long?,
+
+    /** 所属区域ID  */
+    val workareaId: Long?,
+
+    /** 所属SOPID  */
+    val sopId: Long?,
+
+    /** 作业票类型  */
+    val ticketType: String?,
+
+    /** 作业票详情  */
+    val ticketContent: String?,
+
+    /** 作业票状态  */
+    val ticketStatus: String?,
+
+    /** 作业票开始时间  */
+    val ticketStartTime: String?,
+
+    /** 作业票结束时间  */
+    val ticketEndTime: String?,
+
+    /** 删除标志(0代表存在 2代表删除)  */
+    val delFlag: String?,
+
+    val workstationId: Long?,
+
+    val machineryId: Long?
+)

+ 182 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/user/RoleListRespVO.kt

@@ -0,0 +1,182 @@
+package com.grkj.iscs_mc.model.vo.user
+
+data class RoleListRespVO(
+    val total: Long,
+    
+    /**
+     * 列表数据
+     */
+    val rows: List<SysUserVO>?,
+
+    /**
+     * 消息状态码
+     */
+    val code: Int?,
+
+    /**
+     * 消息内容
+     */
+    val msg: String?
+) {
+    data class SysUserVO(
+        val searchValue: String?,
+        val createBy: String?,
+        val createTime: String?,
+        val updateBy: String?,
+        val updateTime: String?,
+        val remark: String?,
+        val params: Map<String, Any>?,
+
+        val userId: Long?,
+
+        /** 部门ID  */
+        val deptId: Long?,
+
+        /** 用户账号  */
+        val userName: String?,
+
+        /** 用户昵称  */
+        val nickName: String?,
+
+        /** 用户邮箱  */
+        val email: String?,
+
+        /** 手机号码  */
+        val phonenumber: String?,
+
+        /** 用户性别  */
+        val sex: String?,
+
+        /** 用户头像  */
+        val avatar: String?,
+
+        /** 密码  */
+        val password: String?,
+
+        /** 盐加密  */
+        val salt: String?,
+
+        /** 帐号状态(0正常 1停用)  */
+        val status: String?,
+
+        /** 删除标志(0代表存在 2代表删除)  */
+        val delFlag: String?,
+
+        /** 最后登录IP  */
+        val loginIp: String?,
+
+        /** 最后登录时间  */
+        val loginDate: String?,
+
+        /** 部门对象  */
+
+        val dept: SysDept?,
+
+        /** 角色对象  */
+        val roles: List<SysRoleVO>?,
+
+        /** 角色组  */
+        val roleIds: List<Long>?,
+
+        /** 岗位组  */
+        val postIds: List<Long>?,
+
+        /** 角色ID  */
+        val roleId: Long?,
+
+        val roleKey: String?,
+
+        val admin: Boolean?
+    ) {
+        data class SysRoleVO(
+            val searchValue: String?,
+            val createBy: String?,
+            val createTime: String?,
+            val updateBy: String?,
+            val updateTime: String?,
+            val remark: String?,
+            val params: Map<String, Any>?,
+
+            /** 角色ID  */
+            val roleId: Long?,
+
+            /** 角色名称  */
+            val roleName: String?,
+
+            /** 角色权限  */
+            val roleKey: String?,
+
+            /** 角色排序  */
+            val roleSort: String?,
+
+            /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)  */
+            val dataScope: String?,
+
+            /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)  */
+            val menuCheckStrictly: Boolean?,
+
+            /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )  */
+            val deptCheckStrictly: Boolean?,
+
+            /** 角色状态(0正常 1停用)  */
+            val status: String?,
+
+            /** 删除标志(0代表存在 2代表删除)  */
+            val delFlag: String?,
+
+            /** 用户是否存在此角色标识 默认不存在  */
+            val flag: Boolean?,
+
+            /** 菜单组  */
+            val menuIds: List<Long>?,
+
+            /** 部门组(数据权限)  */
+            val deptIds: List<Long>?
+        )
+        
+        data class SysDept(
+            val searchValue: String?,
+            val createBy: String?,
+            val createTime: String?,
+            val updateBy: String?,
+            val updateTime: String?,
+            val remark: String?,
+            val params: Map<String, Any>?,
+
+            val deptId: Long?,
+
+            /** 父部门ID  */
+            val parentId: Long?,
+
+            /** 祖级列表  */
+            val ancestors: String?,
+
+            /** 部门名称  */
+            val deptName: String?,
+
+            /** 显示顺序  */
+            val orderNum: String?,
+
+            /** 负责人  */
+            val leader: String?,
+
+            /** 联系电话  */
+            val phone: String?,
+
+            /** 邮箱  */
+            val email: String?,
+
+            /** 部门状态:0正常,1停用  */
+            val status: String?,
+
+            /** 删除标志(0代表存在 2代表删除)  */
+            val delFlag: String?,
+
+            /** 父部门名称  */
+            val parentName: String?,
+
+            /** 子部门  */
+            val children: List<SysDept>?
+        )
+    }
+}

+ 127 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/user/UserInfoRespVO.kt

@@ -0,0 +1,127 @@
+package com.grkj.iscs_mc.model.vo.user
+
+import java.io.Serializable
+
+data class UserInfoRespVO(
+    val code: Int?,
+
+    val msg: String?,
+
+    val permissions: MutableList<String>?,
+
+    val roles: MutableList<String>?,
+
+    val user: SysUserVO?
+) : Serializable {
+    data class SysUserVO(
+        val userId: Long?,
+
+        val deptId: Long?,
+
+        val userName: String?,
+
+        val nickName: String?,
+
+        val email: String?,
+
+        val phonenumber: String?,
+
+        val sex: String?,
+
+        val avatar: String?,
+
+        val password: String?,
+
+        val salt: String?,
+
+        val status: String?,
+
+        val delFlag: String?,
+
+        val loginIp: String?,
+
+        val loginDate: String?,
+
+        val dept: SysDeptVO?,
+
+        val roles: List<SysRole>?,
+
+        val roleIds: MutableList<Long>?,
+
+        val postIds: MutableList<Long>?,
+
+        val workstationIds: MutableList<Long>?,
+
+        val unitIds: MutableList<Long>?,
+
+        val roleId: Long?,
+
+        val roleKey: String?,
+
+        val unitId: Long?,
+
+        val workstationId: Long?,
+
+        val unitName: String?,
+
+        val roleName: String?,
+
+        val userIds: Set<Long>?,
+
+        val b: Boolean?
+    ) : Serializable {
+        data class SysRole(
+            val roleId: Long?,
+
+            val roleName: String?,
+
+            val roleKey: String?,
+
+            val roleSort: String?,
+
+            val dataScope: String?,
+
+            val marsDataScope: String?,
+
+            val menuCheckStrictly: Boolean?,
+
+            val deptCheckStrictly: Boolean?,
+
+            val status: String?,
+
+            val delFlag: String?,
+
+            val flag: Boolean?,
+
+            val menuIds: List<Long>,
+
+            val deptIds: List<Long>,
+
+            val workstationIds: List<Long>
+        ) : Serializable
+
+        data class SysDeptVO(
+            val deptId: Long?,
+
+            val parentId: Long?,
+
+            val ancestors: String?,
+
+            val deptName: String?,
+
+            val orderNum: String?,
+
+            val leader: String?,
+
+            val phone: String?,
+
+            val email: String?,
+
+            val status: String?,
+
+            val delFlag: String?,
+
+            val parentName: String?
+        ) : Serializable
+    }
+}

+ 122 - 0
app/src/main/java/com/grkj/iscs_mc/model/vo/user/UserListRespVO.kt

@@ -0,0 +1,122 @@
+package com.grkj.iscs_mc.model.vo.user
+
+data class UserListRespVO(
+    val code: Int,
+    val msg: String,
+    val rows: List<Row>,
+    val total: Int
+) {
+    data class Row(
+        val userId: Long?,
+
+        val deptId: Long?,
+
+        val userName: String?,
+
+        val nickName: String?,
+
+        val email: String?,
+
+        val phonenumber: String?,
+
+        val sex: String?,
+
+        val avatar: String?,
+
+        val password: String?,
+
+        val salt: String?,
+
+        val status: String?,
+
+        val delFlag: String?,
+
+        val loginIp: String?,
+
+        val loginString: String?,
+
+        val dept: SysDept?,
+
+        val roles: List<SysRole>?,
+
+        val roleIds: List<Long>,
+
+        val postIds: List<Long>,
+
+        val workstationIds: List<Long>,
+
+        val unitIds: List<Long>,
+
+        val roleId: Long?,
+
+        val roleKey: String?,
+
+        val unitId: Long?,
+
+        val workstationId: Long?,
+
+        val unitName: String?,
+
+        val roleName: String?,
+
+        val userIds: Set<Long>?,
+
+        val b: Boolean?
+    ) {
+        data class SysRole(
+            val roleId: Long?,
+
+            val roleName: String?,
+
+            val roleKey: String?,
+
+            val roleSort: String?,
+
+            val dataScope: String?,
+
+            val marsDataScope: String?,
+
+            val menuCheckStrictly: Boolean?,
+
+            val deptCheckStrictly: Boolean?,
+
+            val status: String?,
+
+            val delFlag: String?,
+
+            val flag: Boolean?,
+
+            val menuIds: List<Long>,
+
+            val deptIds: List<Long>,
+
+            val workstationIds: List<Long>
+        )
+
+        data class SysDept(
+            val deptId: Long?,
+
+            val parentId: Long?,
+
+            val ancestors: String?,
+
+            val deptName: String?,
+
+            val orderNum: String?,
+
+            val leader: String?,
+
+            val phone: String?,
+
+            val email: String?,
+
+            val status: String?,
+
+            val delFlag: String?,
+
+            val parentName: String?,
+
+            val children: MutableList<SysDept>
+        )
+    }
+}

+ 43 - 0
app/src/main/java/com/grkj/iscs_mc/util/AppUtils.kt

@@ -0,0 +1,43 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.content.pm.PackageManager
+
+/**
+ * App工具类
+ */
+object AppUtils {
+
+    /**
+     * 获取版本号
+     *
+     * @return 当前应用的VersionName
+     */
+    fun getPkgVerName(context: Context): String? {
+        return try {
+            val manager: PackageManager = context.packageManager
+            val info =
+                manager.getPackageInfo(context.packageName!!, 0) //PackageManager.GET_CONFIGURATIONS
+            info.versionName
+        } catch (e: Exception) {
+            e.printStackTrace()
+            null
+        }
+    }
+
+    /**
+     * 获取VersionCode
+     *
+     * @return 当前应用的VersionCode
+     */
+    fun getPkgVerCode(context: Context): Int {
+        return try {
+            val manager: PackageManager = context.packageManager
+            val info = manager.getPackageInfo(context.packageName!!, 0)
+            info.versionCode
+        } catch (e: Exception) {
+            e.printStackTrace()
+            -1
+        }
+    }
+}

+ 122 - 0
app/src/main/java/com/grkj/iscs_mc/util/CRC16.java

@@ -0,0 +1,122 @@
+package com.grkj.iscs_mc.util;
+
+public class CRC16 {
+
+    private static final byte[] auchCRCHi = { 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01,
+            (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01,
+            (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,
+            (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01,
+            (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,
+            (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01,
+            (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80,
+            (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1,
+            (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,
+            (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,
+            (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,
+            (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,
+            (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,
+            (byte) 0xC1, (byte) 0x81, (byte) 0x40 };
+
+    private static final byte[] auchCRCLo = { (byte) 0x00, (byte) 0xC0, (byte) 0xC1,
+            (byte) 0x01, (byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2,
+            (byte) 0xC6, (byte) 0x06, (byte) 0x07, (byte) 0xC7, (byte) 0x05,
+            (byte) 0xC5, (byte) 0xC4, (byte) 0x04, (byte) 0xCC, (byte) 0x0C,
+            (byte) 0x0D, (byte) 0xCD, (byte) 0x0F, (byte) 0xCF, (byte) 0xCE,
+            (byte) 0x0E, (byte) 0x0A, (byte) 0xCA, (byte) 0xCB, (byte) 0x0B,
+            (byte) 0xC9, (byte) 0x09, (byte) 0x08, (byte) 0xC8, (byte) 0xD8,
+            (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB,
+            (byte) 0xDA, (byte) 0x1A, (byte) 0x1E, (byte) 0xDE, (byte) 0xDF,
+            (byte) 0x1F, (byte) 0xDD, (byte) 0x1D, (byte) 0x1C, (byte) 0xDC,
+            (byte) 0x14, (byte) 0xD4, (byte) 0xD5, (byte) 0x15, (byte) 0xD7,
+            (byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12,
+            (byte) 0x13, (byte) 0xD3, (byte) 0x11, (byte) 0xD1, (byte) 0xD0,
+            (byte) 0x10, (byte) 0xF0, (byte) 0x30, (byte) 0x31, (byte) 0xF1,
+            (byte) 0x33, (byte) 0xF3, (byte) 0xF2, (byte) 0x32, (byte) 0x36,
+            (byte) 0xF6, (byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35,
+            (byte) 0x34, (byte) 0xF4, (byte) 0x3C, (byte) 0xFC, (byte) 0xFD,
+            (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE,
+            (byte) 0xFA, (byte) 0x3A, (byte) 0x3B, (byte) 0xFB, (byte) 0x39,
+            (byte) 0xF9, (byte) 0xF8, (byte) 0x38, (byte) 0x28, (byte) 0xE8,
+            (byte) 0xE9, (byte) 0x29, (byte) 0xEB, (byte) 0x2B, (byte) 0x2A,
+            (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF,
+            (byte) 0x2D, (byte) 0xED, (byte) 0xEC, (byte) 0x2C, (byte) 0xE4,
+            (byte) 0x24, (byte) 0x25, (byte) 0xE5, (byte) 0x27, (byte) 0xE7,
+            (byte) 0xE6, (byte) 0x26, (byte) 0x22, (byte) 0xE2, (byte) 0xE3,
+            (byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0,
+            (byte) 0xA0, (byte) 0x60, (byte) 0x61, (byte) 0xA1, (byte) 0x63,
+            (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66, (byte) 0xA6,
+            (byte) 0xA7, (byte) 0x67, (byte) 0xA5, (byte) 0x65, (byte) 0x64,
+            (byte) 0xA4, (byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D,
+            (byte) 0xAF, (byte) 0x6F, (byte) 0x6E, (byte) 0xAE, (byte) 0xAA,
+            (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9,
+            (byte) 0xA8, (byte) 0x68, (byte) 0x78, (byte) 0xB8, (byte) 0xB9,
+            (byte) 0x79, (byte) 0xBB, (byte) 0x7B, (byte) 0x7A, (byte) 0xBA,
+            (byte) 0xBE, (byte) 0x7E, (byte) 0x7F, (byte) 0xBF, (byte) 0x7D,
+            (byte) 0xBD, (byte) 0xBC, (byte) 0x7C, (byte) 0xB4, (byte) 0x74,
+            (byte) 0x75, (byte) 0xB5, (byte) 0x77, (byte) 0xB7, (byte) 0xB6,
+            (byte) 0x76, (byte) 0x72, (byte) 0xB2, (byte) 0xB3, (byte) 0x73,
+            (byte) 0xB1, (byte) 0x71, (byte) 0x70, (byte) 0xB0, (byte) 0x50,
+            (byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53,
+            (byte) 0x52, (byte) 0x92, (byte) 0x96, (byte) 0x56, (byte) 0x57,
+            (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94, (byte) 0x54,
+            (byte) 0x9C, (byte) 0x5C, (byte) 0x5D, (byte) 0x9D, (byte) 0x5F,
+            (byte) 0x9F, (byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A,
+            (byte) 0x9B, (byte) 0x5B, (byte) 0x99, (byte) 0x59, (byte) 0x58,
+            (byte) 0x98, (byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89,
+            (byte) 0x4B, (byte) 0x8B, (byte) 0x8A, (byte) 0x4A, (byte) 0x4E,
+            (byte) 0x8E, (byte) 0x8F, (byte) 0x4F, (byte) 0x8D, (byte) 0x4D,
+            (byte) 0x4C, (byte) 0x8C, (byte) 0x44, (byte) 0x84, (byte) 0x85,
+            (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86,
+            (byte) 0x82, (byte) 0x42, (byte) 0x43, (byte) 0x83, (byte) 0x41,
+            (byte) 0x81, (byte) 0x80, (byte) 0x40 };
+
+    public static int crc16(byte[] puchMsg, int from, int to) {
+        byte uchCRCHi = (byte) 0xFF;
+        byte uchCRCLo = (byte) 0xFF;
+        for (int i = from; i < to; i++) {
+            int uIndex = (uchCRCHi ^ puchMsg[i]) & 0xff;
+            uchCRCHi = (byte) (uchCRCLo ^ auchCRCHi[uIndex]);
+            uchCRCLo = auchCRCLo[uIndex];
+        }
+        return ((((int) uchCRCHi) << 8 | (((int) uchCRCLo) & 0xff))) & 0xffff;
+    }
+
+}

+ 80 - 0
app/src/main/java/com/grkj/iscs_mc/util/CommonUtils.kt

@@ -0,0 +1,80 @@
+package com.grkj.iscs_mc.util
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.BLUETOOTH_ADVERTISE
+import android.Manifest.permission.BLUETOOTH_CONNECT
+import android.Manifest.permission.BLUETOOTH_SCAN
+import android.content.Context
+import android.os.Build
+import androidx.appcompat.app.AppCompatActivity
+import com.grkj.iscs_mc.MyApplication
+import com.grkj.iscs_mc.model.Constants.PERMISSION_REQUEST_CODE
+import pub.devrel.easypermissions.EasyPermissions
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+object CommonUtils {
+
+    val hexRegex = "^[0-9A-Fa-f]+$".toRegex()
+
+    fun dip2px(dpValue: Float): Int {
+        val density = MyApplication.instance!!.resources.displayMetrics.density
+        return (dpValue * density + 0.5f).toInt()
+    }
+
+    fun checkPermission(activity: AppCompatActivity, permissions: Array<String>, callBack: () -> Unit) {
+        val isPermission = EasyPermissions.hasPermissions(activity, *permissions)
+        if (isPermission) {
+            callBack.invoke()
+        } else {
+            EasyPermissions.requestPermissions(activity, "", PERMISSION_REQUEST_CODE, *permissions)
+        }
+    }
+
+    fun checkBlePermission(activity: AppCompatActivity, callBack: () -> Unit) {
+        val permissions: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            arrayOf(
+                BLUETOOTH_SCAN,
+                BLUETOOTH_ADVERTISE,
+                BLUETOOTH_CONNECT,
+                ACCESS_FINE_LOCATION,
+                ACCESS_COARSE_LOCATION
+            )
+        } else {
+            arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)
+        }
+        checkPermission(activity, permissions, callBack)
+    }
+
+    fun getStr(textId: Int, ctx: Context? = null): String? {
+        return ctx?.resources?.getString(textId) ?: let {
+            MyApplication.instance?.applicationContext?.resources?.getString(textId)
+        }
+    }
+
+    fun getDiffHours(dateString: String?): Int? {
+        if (dateString.isNullOrEmpty()) {
+            return null
+        }
+        val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+        val date = format.parse(dateString)
+        if (date != null) {
+            // 将 Date 对象转换为时间戳(毫秒)
+            val timestamp = date.time
+            // 获取当前时间的时间戳
+            val currentTimestamp = System.currentTimeMillis()
+            // 计算时间差(毫秒)
+            val timeDifferenceInMillis = currentTimestamp - timestamp
+            // 将时间差转换为小时数,向下取整
+            val hoursDifference = (timeDifferenceInMillis / (1000 * 60 * 60)).toInt()
+
+            return hoursDifference
+        }
+        return null
+    }
+
+    fun isValidHex(hexString: String): Boolean {
+        return hexRegex.matches(hexString)
+    }
+}

+ 138 - 0
app/src/main/java/com/grkj/iscs_mc/util/CrashUtil.kt

@@ -0,0 +1,138 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Looper
+import com.grkj.iscs_mc.util.log.LogUtil
+import java.io.*
+import java.lang.reflect.Field
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+
+class CrashUtil : Thread.UncaughtExceptionHandler {
+    var mContext: Context? = null
+    var mDefaultHandler: Thread.UncaughtExceptionHandler? = null
+
+    // 用来存储设备信息和异常信息
+    private val infos: MutableMap<String, String> = mutableMapOf()
+
+    // 用于格式化日期,作为日志文件名的一部分
+    private val formatter: DateFormat = SimpleDateFormat("yyyyMMdd-HHmmss")
+
+    companion object {
+        val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { CrashUtil() }
+    }
+
+    fun init(pContext: Context) {
+        this.mContext = pContext
+        // 获取系统默认的UncaughtException处理器
+        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
+        // 设置该CrashHandler为程序的默认处理器
+        Thread.setDefaultUncaughtExceptionHandler(this)
+    }
+
+    //当UncaughtException发生时会转入该函数来处理
+    override fun uncaughtException(t: Thread?, e: Throwable?) {
+        if (!handleException(e) && mDefaultHandler != null) {
+            //如果用户没有处理则让系统默认的异常处理器来处理
+            mDefaultHandler?.uncaughtException(t, e)
+        } else {
+            try {
+                //给Toast留出时间
+                Thread.sleep(2000)
+            } catch (e: InterruptedException) {
+                e.printStackTrace()
+            }
+            mContext?.let {
+                val intent = it.packageManager.getLaunchIntentForPackage(it.packageName);
+                intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                it.startActivity(intent)
+            }
+
+            //退出程序
+//            App.i.removeAllActivity()
+            android.os.Process.killProcess(android.os.Process.myPid())
+            System.exit(0)
+
+        }
+
+    }
+
+    fun handleException(ex: Throwable?): Boolean {
+        if (ex == null) {
+            return false
+        }
+        Thread {
+            Looper.prepare()
+            ToastUtils.tip("很抱歉,程序出现异常,即将退出")
+            Looper.loop()
+        }.start()
+        //收集设备参数信息
+        mContext?.let {
+            collectDeviceInfo(it)
+            //保存日志文件
+            saveCrashInfo2File(ex, it)
+        }
+        // 注:收集设备信息和保存日志文件的代码就没必要在这里贴出来了
+        //文中只是提供思路,并不一定必须收集信息和保存日志
+        //因为现在大部分的项目里都集成了第三方的崩溃分析日志,如`Bugly` 或 `啄木鸟等`
+        //如果自己定制化处理的话,反而比较麻烦和消耗精力,毕竟开发资源是有限的
+        return true
+    }
+
+    /**
+     * 收集设备参数信息
+     *
+     * @param ctx
+     */
+    private fun collectDeviceInfo(ctx: Context) {
+        try {
+            val pm = ctx.packageManager
+            val pi = pm.getPackageInfo(ctx.packageName, PackageManager.GET_ACTIVITIES)
+            pi?.let {
+                val versionName = if (it.versionName == null) "null" else it.versionName
+                val versionCode = it.versionCode.toString()
+                infos["versionName"] = versionName
+                infos["versionCode"] = versionCode
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            LogUtil.e(e)
+        }
+        val fields: Array<Field> = Build::class.java.declaredFields
+        fields?.forEach {
+            try {
+                it.isAccessible = true
+                infos[it.name] = it.get(null).toString()
+            } catch (e: Exception) {
+                LogUtil.e(e)
+            }
+        }
+    }
+
+    /**
+     * 保存错误信息到文件中
+     *
+     * @param ex
+     * @return 返回文件名称,便于将文件传送到服务器
+     */
+    private fun saveCrashInfo2File(ex: Throwable, ctx: Context): String? {
+        val sb = StringBuffer()
+        infos.forEach {
+            sb.append("${it.key}=${it.value}\n")
+        }
+        val writer: Writer = StringWriter()
+        val printWriter = PrintWriter(writer)
+        ex.printStackTrace(printWriter)
+        var cause = ex.cause
+        while (cause != null) {
+            cause.printStackTrace(printWriter)
+            cause = cause.cause
+        }
+        printWriter.close()
+        val result: String = writer.toString()
+        sb.append(result)
+        return null
+    }
+}

+ 16 - 0
app/src/main/java/com/grkj/iscs_mc/util/DownloadCallBack.kt

@@ -0,0 +1,16 @@
+package com.grkj.iscs_mc.util
+
+import cn.zhxu.okhttps.Process
+import java.io.File
+
+interface DownloadCallBack {
+
+    fun onProcess(process: Process)
+
+    fun onResult(
+        isSuccess: Boolean,
+        code: Int? = null,
+        file: File? = null,
+        errorMsg: String? = null
+    )
+}

+ 140 - 0
app/src/main/java/com/grkj/iscs_mc/util/Executor.kt

@@ -0,0 +1,140 @@
+package com.grkj.iscs_mc.util
+
+import android.os.Handler
+import android.os.Looper
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.CountDownLatch
+
+object Executor {
+
+    private var io: Handler? = null
+    private val main: Handler = Handler(Looper.getMainLooper())
+
+    private val latch = CountDownLatch(1)
+
+    init {
+        val thread = Thread {
+            Looper.prepare()
+            io = Handler()
+            latch.countDown()
+            Looper.loop()
+        }
+        thread.isDaemon = true
+        thread.start()
+    }
+
+    val mainHandler: Handler
+        get() = main
+
+    val ioHandler: Handler
+        get() {
+            if (io == null) {
+                latch.await()
+            }
+            return io!!
+        }
+
+    fun runOnMain(run: Runnable) {
+        main.post(run)
+    }
+
+    fun delayOnMain(delayMills: Long, run: Runnable) {
+        main.postDelayed(run, delayMills)
+    }
+
+    fun repeatOnMain(run: () -> Boolean, intervalMills: Long, immediately: Boolean = true) {
+        repeat(main, run, intervalMills, immediately)
+    }
+
+    fun runOnIO(run: Runnable) {
+        val traces = Thread.currentThread().stackTrace
+        ioHandler.post {
+            runWithTraces(traces, run)
+        }
+    }
+
+    fun runOnIO(run: Runnable, runnableTimeoutMillis: Long = 5000) {
+        val traces = Thread.currentThread().stackTrace
+        ioHandler.post {
+            runWithTraces(traces, run, runnableTimeoutMillis)
+        }
+    }
+
+    fun delayOnIO(run: Runnable, delayMills: Long, runnableTimeoutMillis: Long = 10_000) {
+        val traces = Thread.currentThread().stackTrace
+        ioHandler.postDelayed({
+            runWithTraces(traces, run, runnableTimeoutMillis)
+        }, delayMills)
+    }
+
+    fun delayOnIO(delayMills: Long, runnableTimeoutMillis: Long = 10_000, run: Runnable) {
+        val traces = Thread.currentThread().stackTrace
+        ioHandler.postDelayed({
+            runWithTraces(traces, run, runnableTimeoutMillis)
+        }, delayMills)
+    }
+
+    fun repeatOnIO(run: () -> Boolean, intervalMills: Long, immediately: Boolean = true, runnableTimeoutMillis: Long = 5_000) {
+        val traces = Thread.currentThread().stackTrace
+        repeat(ioHandler, {
+            var blocked = true
+            val thread = Thread.currentThread()
+            delayOnMain(runnableTimeoutMillis) {
+                // 如果线程被阻塞达到 1 秒,则打断唤醒
+                if (blocked) {
+                    printTimeoutTraces(traces)
+                    thread.interrupt()
+                }
+            }
+            val res = run()
+            blocked = false
+            return@repeat res
+        }, intervalMills, immediately)
+    }
+
+    private fun runWithTraces(traces: Array<StackTraceElement>, run: Runnable, runnableTimeoutMillis: Long = 10_000) {
+        var blocked = true
+        val thread = Thread.currentThread()
+        delayOnMain(runnableTimeoutMillis) {
+            // 如果线程被阻塞达到 1 秒,则打断唤醒
+            if (blocked) {
+                printTimeoutTraces(traces)
+                thread.interrupt()
+            }
+        }
+        run.run()
+        blocked = false
+    }
+
+    private fun printTimeoutTraces(traces: Array<StackTraceElement>) {
+        val writer = StringWriter()
+        val print = PrintWriter(writer)
+        for (traceElement in traces) {
+            print.println("\tat $traceElement")
+        }
+//        Logger.w("Executor", "执行器超时(10秒):\n${writer}")
+    }
+
+    private fun repeat(
+        handler: Handler,
+        run: () -> Boolean,
+        intervalMills: Long,
+        immediately: Boolean = true
+    ) {
+        if (immediately) {
+            if (run()) {
+                handler.postDelayed({
+                    repeat(handler, run, intervalMills, immediately)
+                }, intervalMills)
+            }
+        } else {
+            handler.postDelayed({
+                if (run()) {
+                    repeat(handler, run, intervalMills, immediately)
+                }
+            }, intervalMills)
+        }
+    }
+
+}

+ 141 - 0
app/src/main/java/com/grkj/iscs_mc/util/FileUtil.kt

@@ -0,0 +1,141 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.os.Environment
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.BufferedReader
+import java.io.File
+import java.io.File.separator
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.InputStreamReader
+
+object FileUtil {
+
+    private const val ROOT_APP = "iscs"
+    val DOWNLOAD_DIR = "${separator}download"
+    val LOG_DIR = "${separator}log"
+
+    /**
+     * @param permissionType:0:默认(根目录>私有可见文件缓存);1:私有可见缓存;2:私有可见文件;3:私有缓存;4:私有文件
+     */
+    fun getRootFolder(mContext: Context, permissionType: Int = 0): File? {
+        var rootFile: File? = null
+        when (permissionType) {
+            1 -> {
+                rootFile = File(mContext.externalCacheDir, "$separator$ROOT_APP")
+            }
+
+            2 -> {
+                rootFile = File(mContext.getExternalFilesDir(null), "$separator$ROOT_APP")
+            }
+
+            3 -> {
+                rootFile = File(mContext.cacheDir, "$separator$ROOT_APP")
+            }
+
+            4 -> {
+                rootFile = File(mContext.filesDir, "$separator$ROOT_APP")
+            }
+
+            else -> {
+                try {
+                    rootFile =
+                        File(Environment.getExternalStorageDirectory(), "$separator$ROOT_APP")
+                    if (rootFile.exists()) {
+                        return rootFile
+                    } else {
+                        val isSucc = rootFile.mkdirs()
+                        if (isSucc) return rootFile
+                    }
+                } catch (e: Exception) {
+//                        d("创建文件夹异常",e)
+                }
+                rootFile = File(mContext.getExternalFilesDir(null), "$separator$ROOT_APP")
+                if (rootFile.exists()) {
+                    return rootFile
+                } else {
+                    val isSucc = rootFile.mkdirs()
+                    if (isSucc) return rootFile
+                }
+//                    d("创建文件夹失败2:${rootFile?.absolutePath}")
+                return null
+            }
+        }
+        rootFile?.let {
+            if (it.exists()) {
+                return it
+            } else {
+                val isSucc = it.mkdirs()
+                if (isSucc) return it
+            }
+        }
+//            d("创建文件夹失败:${rootFile?.absolutePath}")
+        return null
+    }
+
+
+    /**
+     * 单个文件复制
+     */
+    fun copyfile(srcFile: File, destFile: File) {
+
+        var fis = FileInputStream(srcFile);
+        var fos = FileOutputStream(destFile)
+
+        var bis = BufferedInputStream(fis)
+        var bos = BufferedOutputStream(fos)
+
+
+        var buf = ByteArray(1024)
+
+        var len = 0;
+        while (true) {
+            len = bis.read(buf)
+            if (len == -1) break;
+            bos.write(buf, 0, len)
+        }
+        fis.close()
+        fos.close()
+
+    }
+
+    /**
+     * 带文件夹复制
+     */
+    fun copyDirToDir(srcFile: File, destFile: File) {
+        for (f in srcFile.listFiles()) {
+            //是文件就拷贝
+            var newfile = File(destFile.absolutePath, f.name)
+            if (f.isFile) {
+                println("${f.absolutePath}-->${newfile.absolutePath}")
+                copyfile(f, newfile)
+            } else { //如果是目录就递归复制
+                //如果目标文件不存在,则创建
+                if (!newfile.exists()) {
+                    if (!newfile.mkdir()) return
+                }
+                copyDirToDir(f, newfile)
+            }
+        }
+        return
+    }
+
+    fun readTxt(path: String?): String {
+        var str = ""
+        try {
+            val urlFile = File(path)
+            val isr = InputStreamReader(FileInputStream(urlFile), "UTF-8")
+            val br = BufferedReader(isr)
+
+            var mimeTypeLine: String? = null
+            while ((br.readLine().also { mimeTypeLine = it }) != null) {
+                str += mimeTypeLine
+            }
+        } catch (e: java.lang.Exception) {
+            e.printStackTrace()
+        }
+        return str
+    }
+}

+ 93 - 0
app/src/main/java/com/grkj/iscs_mc/util/Funcs.kt

@@ -0,0 +1,93 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.os.Environment
+import android.text.method.HideReturnsTransformationMethod
+import android.text.method.PasswordTransformationMethod
+import android.widget.EditText
+import android.widget.ImageView
+import cn.zhxu.data.TypeRef
+import cn.zhxu.okhttps.HttpResult
+import com.google.gson.Gson
+import com.grkj.iscs_mc.model.vo.BaseVO
+import java.io.Closeable
+import java.io.File
+
+
+val gson = Gson()
+
+/**
+ * 虚拟机相对时间(秒)
+ */
+fun jvmSeconds() : Long {
+    return System.nanoTime() / 1000_000_000
+}
+
+/**
+ * 当前时间(秒)
+ */
+fun nowSeconds() : Long {
+    return System.currentTimeMillis() / 1000
+}
+
+
+val EXT_ROOT_PATH : String = Environment.getExternalStorageDirectory().absolutePath
+
+fun extFile(path: String) : File {
+    return File(EXT_ROOT_PATH + File.separator + "refuse-class" + File.separator + path)
+}
+
+fun fileRootPath() : String {
+    return EXT_ROOT_PATH + File.separator + "refuse-class"
+}
+
+
+fun Closeable.closeQuietly() {
+    try {
+        close()
+    } catch (rethrown: RuntimeException) {
+        throw rethrown
+    } catch (_: Exception) {
+    }
+}
+
+fun dp2px(context: Context, dpValue: Float): Int {
+    val scale: Float = context.resources.displayMetrics.density
+    return (dpValue * scale + 0.5f).toInt()
+}
+
+fun px2dp(context: Context, pxValue: Float): Int {
+    val scale: Float = context.resources.displayMetrics.density
+    return (pxValue / scale + 0.5f).toInt()
+}
+
+inline fun <reified T> getRef(): TypeRef<BaseVO<T>> {
+    return object : TypeRef<BaseVO<T>>() {}
+}
+
+inline fun <reified T> getBaseVO(rst: HttpResult.Body): BaseVO<T>? {
+    return rst.toBean(getRef<T>())
+}
+
+inline fun <reified T> getRefBean(rst: HttpResult.Body): T? {
+    return rst.toBean(getRef<T>()).data
+}
+
+/**
+ * 密码显示效果切换
+ * @param view:输入控件
+ * @param imageView:明文和密文icon控件。selected属性
+ */
+fun passwordStyle(view: EditText?, imageView: ImageView) {
+    if (imageView.isSelected) {
+        view?.transformationMethod = PasswordTransformationMethod.getInstance()
+    } else {
+        view?.transformationMethod = HideReturnsTransformationMethod.getInstance()
+    }
+    view?.setSelection(view.text.toString().length)
+    imageView.isSelected = !imageView.isSelected
+}
+
+fun byteToHexString(byte: Byte): String {
+    return "0x%02X".format(byte)
+}

+ 689 - 0
app/src/main/java/com/grkj/iscs_mc/util/NetApi.kt

@@ -0,0 +1,689 @@
+package com.grkj.iscs_mc.util
+
+import com.grkj.iscs_mc.MyApplication
+import com.grkj.iscs_mc.model.Token
+import com.grkj.iscs_mc.model.UrlConsts
+import com.grkj.iscs_mc.model.vo.card.CardInfoRespVO
+import com.grkj.iscs_mc.model.vo.dept.DeptListRespVO
+import com.grkj.iscs_mc.model.vo.key.KeyInfoRespVO
+import com.grkj.iscs_mc.model.vo.lock.LockInfoRespVO
+import com.grkj.iscs_mc.model.vo.lock.LockTakeUpdateReqVO
+import com.grkj.iscs_mc.model.vo.machinery.MachineryDetailRespVO
+import com.grkj.iscs_mc.model.vo.machinery.MachineryPageRespVO
+import com.grkj.iscs_mc.model.vo.sop.SopInfoRespVO
+import com.grkj.iscs_mc.model.vo.sop.SopPageRespVO
+import com.grkj.iscs_mc.model.vo.ticket.LockPointUpdateReqVO
+import com.grkj.iscs_mc.model.vo.ticket.LotoMapRespVO
+import com.grkj.iscs_mc.model.vo.ticket.StepDetailRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketDetailMonitorRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketDetailRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketEquipDetailRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketPageRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketTypeRespVO
+import com.grkj.iscs_mc.model.vo.ticket.TicketUserReqVO
+import com.grkj.iscs_mc.model.vo.ticket.WorkstationTicketListRespVO
+import com.grkj.iscs_mc.model.vo.user.RoleListRespVO
+import com.grkj.iscs_mc.model.vo.user.UserInfoRespVO
+import com.grkj.iscs_mc.model.vo.user.UserListRespVO
+
+/**
+ * 网络请求
+ */
+object NetApi {
+    /**
+     * 登录
+     */
+    fun login(username: String, password: String, callBack: (Boolean?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.SIGN_IN,
+            false,
+            mapOf(
+                "username" to username,
+                "password" to password
+            ),
+            { res, _, _ ->
+                res?.let {
+                    val newToken = it.toBean(Token::class.java)
+                    newToken.saveToSp(MyApplication.instance!!.applicationContext)
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = false)
+    }
+
+    /**
+     * 退出登录
+     */
+    fun logout() {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOGOUT,
+            false,
+            mapOf<String, String>(),
+            { res, _, _ ->
+                SPUtils.clearLoginUser(MyApplication.instance!!.applicationContext)
+                Token.clear(MyApplication.instance!!.applicationContext)
+            }, isGet = true, isAuth = false
+        )
+    }
+
+    /**
+     * 刷卡登录
+     */
+    fun cardLogin(cardNfc: String, callBack: (Boolean?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOGIN_CARD,
+            false,
+            mapOf(
+                "cardNfc" to cardNfc
+            ),
+            { res, _, _ ->
+                res?.let {
+                    val token: String? = getRefBean(it)
+                    Token(token!!, 0).saveToSp(MyApplication.instance!!.applicationContext)
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = false)
+    }
+
+    /**
+     * 获取SOP分页
+     */
+    fun getSopPage(pages: Int, size: Int, machineryId: Long, sopType: Int, callBack: (SopPageRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.SOP_PAGE,
+            false,
+            mapOf(
+                "pages" to pages,
+                "size" to size,
+                "machineryId" to machineryId,
+                "sopType" to sopType
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true)
+    }
+
+    /**
+     * 获取工作票类型
+     */
+    @Deprecated("不使用")
+    fun getTicketType(callBack: (MutableList<TicketTypeRespVO>?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.TICKET_TYPE,
+            false,
+            mapOf<String,String>(),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true)
+    }
+
+    /**
+     * 获取一个自动编号
+     */
+    @Deprecated("不使用")
+    fun getAutoCode(type: String, callBack: (String?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.AUTO_CODE + "/" +type,
+            false,
+            mapOf<String, String>(),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(it.toString())
+                }
+            }, isGet = true, isAuth = true)
+    }
+
+    /**
+     * 获取SOP详情
+     */
+    @Deprecated("不使用")
+    fun getSopInfo(sopId: Long, callBack: (SopInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.SOP_INFO,
+            false,
+            mapOf(
+                "sopId" to sopId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true)
+    }
+
+    /**
+     * 获取用户列表
+     *
+     * @param unitId 9:玛氏内部
+     * @param roleId 3:上锁人  4:共锁人
+     */
+    fun getUserList(
+        pageNum: Int,
+        pageSize: Int,
+        workstationId: Long? = null,
+        roleId: Long? = null,
+        unitId: Long? = null,
+        callBack: (UserListRespVO?) -> Unit
+    ) {
+        val map: MutableMap<String, Any> = mutableMapOf("pageNum" to pageNum, "pageSize" to pageSize)
+        workstationId?.let {
+            map["workstationId"] = it
+        }
+        roleId?.let {
+            map["roleId"] = it
+        }
+        unitId?.let {
+            map["unitId"] = it
+        }
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.USER_LIST,
+            false,
+            map,
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(it.toBean(UserListRespVO::class.java))
+                }
+            }, isGet = true, isAuth = true)
+    }
+
+    /**
+     * 获取部门列表
+     */
+    @Deprecated("不使用")
+    fun getDeptList(pageNum: Int, pageSize: Int, callBack: (MutableList<DeptListRespVO>?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.DEPT_LIST,
+            false,
+            mapOf(
+                "pageNum" to pageNum,
+                "pageSize" to pageSize
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 创建工作票
+     *
+     * @return 工作票ID
+     */
+    fun createTicket(sopId: Long, callBack: (Long?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.CREATE_TICKET,
+            false,
+            mapOf(
+                "sopId" to sopId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 获取工作票分页
+     */
+    @Deprecated("不使用")
+    fun getTicketPage(pageNum: Int, pageSize: Int, userId: Long, ticketStatus: Int?, callBack: (TicketPageRespVO?) -> Unit) {
+        val map = mutableMapOf(
+            "pageNum" to pageNum,
+            "pageSize" to pageSize,
+            "userId" to userId
+        )
+        if (ticketStatus != null) {
+            map["ticketStatus"] = ticketStatus
+        }
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.PAGE_TICKET,
+            false,
+            map,
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取作业票详细信息
+     */
+    fun getTicketDetail(ticketId: Long, callBack: (TicketDetailRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.DETAIL_TICKET,
+            false,
+            mapOf("ticketId" to ticketId),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 根据登录用户获取刷卡信息
+     */
+    fun getCardInfoByLoginUser(callBack: (CardInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.CARD_INFO_BY_LOGIN_USER,
+            false,
+            mapOf<String, String>(),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 根据nfc编号获取刷卡信息
+     */
+    fun getCardInfoByNfc(cardNfc: String, callBack: (CardInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.CARD_INFO_BY_CARD_NFC,
+            false,
+            mapOf("cardNfc" to cardNfc),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                } ?: run {
+                    callBack.invoke(null)
+                }
+            }, isGet = true, isAuth = true
+        )
+   }
+
+    /**
+     * 通过nfc编号获取key信息
+     */
+    fun getKeyInfo(keyNfc: String, callBack: (KeyInfoRespVO?) -> Unit) {
+        val loginUser = SPUtils.getLoginUser(MyApplication.instance?.applicationContext!!)
+        NetHttpManager.getInstance().doRequestNet(
+            if (loginUser == null) {
+                UrlConsts.KEY_INFO_BY_NFC_WITHOUT_AUTH
+            } else {
+                UrlConsts.KEY_INFO_BY_NFC
+            },
+            false,
+            mapOf("nfc" to keyNfc),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = loginUser != null
+        )
+    }
+
+    /**
+     * 通过nfc编号获取lock信息,暂时用来判断是否是公司的锁
+     */
+    fun getLockInfo(lockNfc: String, callBack: (LockInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOCK_INFO_BY_NFC,
+            false,
+            mapOf("nfc" to lockNfc),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                } ?: let {
+                    callBack.invoke(null)
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 挂锁上锁时更新数据,更新挂锁和哪个隔离点进行了绑定
+     */
+    fun updateLockPoint(ticketId: Long, lockNfc: String, pointNfc: String, callBack: (Boolean?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOCK_POINT_UPDATE,
+            false,
+            mapOf(
+                "ticketId" to ticketId,
+                "lockNfc" to lockNfc,
+                "pointNfc" to pointNfc
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 获取作业票和关联数据
+     */
+    fun getTicketEquipDetail(ticketId: Long, callBack: (TicketEquipDetailRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.TICKET_EQUIP_DETAIL,
+            false,
+            mapOf("ticketId" to ticketId),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 取出钥匙
+     */
+    fun updateKeyTake(ticketId: Long, keyNfc: String, serialNumber: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.KEY_TAKE_UPDATE,
+            false,
+            mapOf(
+                "ticketId" to ticketId,
+                "keyNfc" to keyNfc,
+                "serialNumber" to serialNumber
+            ),
+            { res, _, _ ->
+                callBack.invoke(res != null)
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 更新作业票进度
+     */
+    fun updateTicketProgress(ticketId: Long, userId: Long, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.TICKET_UPDATE_PROGRESS,
+            false,
+            mapOf(
+                "ticketId" to ticketId,
+                "userId" to userId
+            ),
+            { res, _, _ ->
+                callBack.invoke(res != null)
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 批量更新作业票下隔离点的上锁解锁状况
+     */
+    fun updateLockPointBatch(list: MutableList<LockPointUpdateReqVO>, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOCK_POINT_UPDATE_BATCH,
+            false,
+            mapOf(
+                "list" to list
+            ),
+            { res, _, _ ->
+                callBack.invoke(res != null)
+            }, isGet = false, isAuth =true
+        )
+    }
+
+    /**
+     * 根据角色获取人员列表
+     */
+    @Deprecated("不使用")
+    fun getRoleList(pageNum: Int, pageSize: Int, roleKey: String, callBack: (RoleListRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.ROLE_LIST,
+            false,
+            mapOf(
+                "pageNum" to pageNum,
+                "pageSize" to pageSize,
+                "roleKey" to roleKey
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(it.toBean(RoleListRespVO::class.java))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 正在进行中的作业票列表
+     */
+    fun getWorkstationTicketList(pages: Int, size: Int, callBack: (MutableList<WorkstationTicketListRespVO>?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.WORKSTATION_TICKET_LIST,
+            false,
+            mapOf(
+                "pages" to pages,
+                "size" to size
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取工艺分页
+     */
+    fun getMachineryPage(pages: Int, size: Int, workstationId: Long, callBack: (MachineryPageRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.MACHINERY_PAGE,
+            false,
+            mapOf(
+                "pages" to pages,
+                "size" to size,
+                "workstationId" to workstationId,
+                "machineryType" to "工艺"
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取八大步骤详细
+     */
+    fun getStepDetail(ticketId: Long, callBack: (MutableList<StepDetailRespVO>?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.TICKET_STEP,
+            false,
+            mapOf(
+                "ticketId" to ticketId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取设备工艺详细信息
+     */
+    fun getMachineryDetail(machineryId: Long, callBack: (MachineryDetailRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.MACHINERY_DETAIL,
+            false,
+            mapOf(
+                "machineryId" to machineryId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取电柜map解析数据
+     */
+    fun getLotoMapData(lotoId: Long, callBack: (MutableList<LotoMapRespVO>?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.LOTO_MAP,
+            false,
+            mapOf(
+                "lotoId" to lotoId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+   }
+
+    /**
+     * 取消作业票
+     */
+    fun cancelTicket(ticketId: Long, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.CANCEL_TICKET,
+            false,
+            mapOf(
+                "ticketId" to ticketId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 结束作业票
+     */
+    fun finishTicket(ticketId: Long, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.FINISH_TICKET,
+            false,
+            mapOf(
+                "ticketId" to ticketId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 新增/更新作业票人员
+     */
+    fun updateTicketUser(ticketId: Long, userList: MutableList<TicketUserReqVO>, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.UPDATE_TICKET_USER,
+            false,
+            mapOf(
+                "ticketId" to ticketId,
+                "ticketUserDTOList" to userList
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 作业票详情监控
+     */
+    fun getTicketDetailMonitor(ticketId: Long, callBack: (TicketDetailMonitorRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.TICKET_DETAIL_MONITOR,
+            false,
+            mapOf(
+                "ticketId" to ticketId
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 八大步骤执行
+     */
+    fun updateStep(stepId: Long, stepStatus: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.UPDATE_STEP,
+            false,
+            mapOf(
+                "stepId" to stepId,
+                "stepStatus" to stepStatus
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 共锁人上锁/解锁
+     */
+    fun updateColockerStatus(ticketId: Long, cardNfc: String, jobStatus: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.UPDATE_COLOCKER_STATUS,
+            false,
+            mapOf(
+                "ticketId" to ticketId,
+                "cardNfc" to cardNfc,
+                "jobStatus" to jobStatus
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 获取用户信息
+     */
+    fun getUserInfo(callBack: (UserInfoRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.GET_USER_INFO,
+            false,
+            mapOf<String, String>(),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(it.toBean(UserInfoRespVO::class.java))
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+}

+ 294 - 0
app/src/main/java/com/grkj/iscs_mc/util/NetHttpManager.kt

@@ -0,0 +1,294 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import cn.zhxu.data.Mapper
+import cn.zhxu.okhttps.HTTP
+import cn.zhxu.okhttps.HttpResult
+import cn.zhxu.okhttps.HttpTask
+import cn.zhxu.okhttps.OkHttps
+import cn.zhxu.okhttps.gson.GsonMsgConvertor
+import cn.zhxu.okhttps.okhttp.OkHttpClientWrapper
+import com.grkj.iscs_mc.MyApplication
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.model.Constants.DEVICE_TYPE
+import com.grkj.iscs_mc.model.Constants.DEVICE_TYPE_HYBRID
+import com.grkj.iscs_mc.model.Constants.DEVICE_TYPE_MATERIAL
+import com.grkj.iscs_mc.model.Constants.DEVICE_TYPE_NORMAL
+import com.grkj.iscs_mc.model.Constants.DEVICE_TYPE_PORTABLE
+import com.grkj.iscs_mc.model.Token
+import com.grkj.iscs_mc.model.UrlConsts
+import com.grkj.iscs_mc.model.UrlConsts.LOGIN_CARD
+import com.grkj.iscs_mc.util.log.LogUtil
+import okhttp3.logging.HttpLoggingInterceptor
+import java.net.SocketTimeoutException
+import java.util.concurrent.TimeUnit
+
+class NetHttpManager {
+    lateinit var myHttp: HTTP
+    var context: Context? = null
+    var exceptionCount: Int = 0
+
+    companion object {
+        fun getInstance() = InstanceHelper.sSingle
+        val tagAuth = "Auth"
+    }
+
+    object InstanceHelper {
+        val sSingle = NetHttpManager()
+    }
+
+    fun initCtx(ctx: Context) {
+        context = ctx
+        myHttp = HTTP.builder()
+            .config {
+                it.connectTimeout(60, TimeUnit.SECONDS)
+                it.writeTimeout(20, TimeUnit.SECONDS)
+                it.readTimeout(20, TimeUnit.SECONDS)
+            }
+            .addMsgConvertor(GsonMsgConvertor())
+            .bodyType("application/json")
+            .baseUrl(UrlConsts.BASE_URL)
+            .responseListener { task: HttpTask<*>?, result: HttpResult? ->
+                if (result?.status != 200) {
+                    LogUtil.d(
+                        "Api fail : Url : ${task?.url}, " +
+                                "Status : ${result?.status}, " +
+                                "Params : ${task?.urlParas ?: task?.bodyParas}"
+                    )
+                }
+                true
+            }
+            .config {
+                it.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
+            }
+            .addSerialPreprocessor { itPreChain ->
+                itPreChain.task.addHeader("Module", when (DEVICE_TYPE) {
+                    2 -> DEVICE_TYPE_MATERIAL
+                    3 -> DEVICE_TYPE_PORTABLE
+                    4 -> DEVICE_TYPE_HYBRID
+                    else -> DEVICE_TYPE_NORMAL
+                })
+                if (!itPreChain.task.isTagged(tagAuth)) {
+                    itPreChain.proceed()
+                    return@addSerialPreprocessor
+                }
+                requestTokenAndRefreshIfExpired {
+                    itPreChain.task.addHeader("Authorization", it)
+                    itPreChain.proceed()
+                }
+            }
+            .build()
+    }
+
+    /**
+     * 获取TOKEN,若过期则刷新(代码中的字符串可以替换为常量)
+     */
+    fun requestTokenAndRefreshIfExpired(callback: (String?) -> Unit) {
+        val token = Token.fromSp(context!!)
+        if (token == null) {
+            doLogin(callback)
+            return
+        }
+        if (token.isValid()) {
+            callback(token.token)
+            return
+        }
+        if (context == null || SPUtils.getLoginUser(context!!) == null) {
+            ToastUtils.tip(context!!.resources.getString(R.string.please_login))
+            return
+        }
+        doLogin(callback)
+    }
+
+    private fun doLogin(callback: (String?) -> Unit) {
+        if (context == null) {
+            return
+        }
+        val loginUser = SPUtils.getLoginUser(context!!)
+        if (loginUser == null) {
+            ToastUtils.tip(context!!.resources.getString(R.string.please_login))
+            return
+        }
+        myHttp.async(LOGIN_CARD)
+            .skipPreproc()
+            .addBodyPara("cardNfc", loginUser.cardNfc)
+            .nextOnIO()
+            .setOnResponse {
+                exceptionCount = 0
+                if (!it.isSuccessful) {
+                    callback(null)
+                    return@setOnResponse
+                }
+                try {
+                    val newToken = it.body.toBean(Token::class.java)
+                    newToken.saveToSp(context!!)
+                    callback(newToken.token)
+                } catch (e: Exception) {
+                    callback(null)
+                    return@setOnResponse
+                }
+            }
+            .setOnException {
+                evictHttpConnectPool(it)
+                callback(null)
+            }
+            .post()
+    }
+
+    fun doRequestNet(
+        urlStr: String, isSkipPreproc: Boolean,
+        bodyParas: Map<String, *>,
+        callback: (HttpResult.Body?, String?, Int) -> Unit,
+        isGet: Boolean, isAuth: Boolean,
+        mapperCallBack: ((Mapper) -> Unit)? = null
+    ) {
+        var httpTask = myHttp.async(urlStr).setOnException {
+            evictHttpConnectPool(it)
+            callback(null, context?.getString(R.string.common_net_dis), 0)
+        }
+        if (isAuth) httpTask.tag(tagAuth)
+        if (isSkipPreproc) {
+            httpTask.skipPreproc()
+        }
+        httpTask.nextOnIO()
+            .setOnResponse {
+                exceptionCount = 0
+                if (it.isSuccessful) {
+                    MyApplication.instance?.applicationContext?.let { itCtx ->
+                        Token.refresh(itCtx)
+                    }
+                    // TODO 待观察
+                    try {
+                        it.body.cache()
+                        val baseVO = getBaseVO<Any>(it.body)
+                        if (baseVO?.code in 200 until 300) {
+                            callback(it.body, null, it.status)
+                        } else {
+                            baseVO?.msg?.let { itMsg ->
+                                ToastUtils.tip(itMsg)
+                            }
+                            val bobyStr = it.body.toString()
+                            callback(
+                                null, if (bobyStr.isEmpty()) {
+                                    it.toString()
+                                } else {
+                                    bobyStr
+                                }, it.status
+                            )
+                        }
+                    } catch (e: Exception) {
+                        println("非标准返回数据,特殊处理:${e.message}")
+                        callback(it.body, null, it.status)
+                    }
+                } else {
+                    var bobyStr = it.body.toString()
+                    callback(
+                        null, if (bobyStr.isNullOrEmpty()) {
+                            it.toString()
+                        } else {
+                            bobyStr
+                        }, it.status
+                    )
+                }
+            }
+            .setOnException {
+                evictHttpConnectPool(it)
+                callback(null, context?.getString(R.string.common_net_dis), 0)
+            }
+        mapperCallBack?.let {
+            httpTask.setOnResMapper {
+                mapperCallBack.invoke(it)
+            }
+        }
+//            .setOnResMapper {
+//                mapperCallBack?.invoke(it)
+//            }
+        if (isGet) {
+            httpTask.addUrlPara(bodyParas)
+            httpTask.get()
+        } else {
+            httpTask.addBodyPara(bodyParas)
+            httpTask.post()
+        }
+    }
+
+    fun downloadFileWithProcess(
+        url: String,
+        downloadCallBack: DownloadCallBack,
+        filePath: String? = null,
+        folderPath: String? = null
+    ) {
+        try {
+            if (filePath.isNullOrBlank() && folderPath.isNullOrBlank()) {
+                downloadCallBack.onResult(
+                    false,
+                    errorMsg = context?.getString(R.string.common_download_erro_notag)
+                )
+                return
+            }
+            OkHttps.async(url).setOnResponse {
+                exceptionCount = 0
+                if (it.isSuccessful) {
+                    if (filePath != null && filePath.isNotEmpty()) {
+                        it.body
+                            .stepRate(0.01)
+                            .setOnProcess {
+                                downloadCallBack.onProcess(it)
+                            }
+                            .toFile(filePath)
+                            .setOnSuccess {
+                                downloadCallBack.onResult(true, file = it)
+                            }
+                            .setOnFailure {
+                                downloadCallBack.onResult(false, errorMsg = it.exception.toString())
+                            }.start()
+                    } else if (folderPath != null && folderPath.isNotEmpty()) {
+                        it.body
+                            .stepRate(0.01)
+                            .setOnProcess {
+                                downloadCallBack.onProcess(it)
+                            }
+                            .toFolder(folderPath)
+                            .setOnSuccess {
+                                downloadCallBack.onResult(true, file = it)
+                            }
+                            .setOnFailure {
+                                downloadCallBack.onResult(false, errorMsg = it.exception.toString())
+                            }.start()
+                    }
+                } else {
+                    downloadCallBack.onResult(
+                        false,
+                        it.status,
+                        errorMsg = context?.getString(R.string.common_net_download)
+                    )
+                }
+            }.setOnException {
+                evictHttpConnectPool(it)
+                downloadCallBack.onResult(
+                    false,
+                    errorMsg = context?.getString(R.string.common_net_download)
+                )
+            }.get()
+        } catch (e: Exception) {
+            downloadCallBack.onResult(false, errorMsg = e.message)
+        }
+    }
+
+    private fun evictHttpConnectPool(e: Exception) {
+        try {
+            if (e is SocketTimeoutException) {
+                exceptionCount += 1
+                if (exceptionCount >= 5) {
+                    exceptionCount = 0
+                    (myHttp as OkHttpClientWrapper).okClient().connectionPool().evictAll()
+                }
+            } else {
+                exceptionCount = 0
+            }
+        } catch (e: Exception) {
+        }
+    }
+
+
+}

+ 101 - 0
app/src/main/java/com/grkj/iscs_mc/util/NetManager.kt

@@ -0,0 +1,101 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkRequest
+import android.os.Build
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+class NetManager private constructor(ctx: Context) {
+
+    @Volatile
+    private var available = false
+
+    val liveData = MutableLiveData<Boolean>()
+
+    private val latch = CountDownLatch(1)
+
+    private val callback = object : ConnectivityManager.NetworkCallback() {
+
+        override fun onAvailable(network: Network) {
+            liveData.postValue(true)
+            available = true
+            latch.countDown()
+        }
+
+        override fun onUnavailable() {
+            liveData.postValue(false)
+            available = false
+            latch.countDown()
+        }
+
+        override fun onLost(network: Network) {
+            liveData.postValue(false)
+            available = false
+            latch.countDown()
+        }
+
+    }
+
+    init {
+        (ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?)
+            ?.run {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    registerDefaultNetworkCallback(callback)
+                } else {
+                    registerNetworkCallback(NetworkRequest.Builder().build(), callback)
+                }
+            }
+    }
+
+    /**
+     * 当前是否有网络,不能在主线程使用
+     */
+    @WorkerThread
+    fun isAvailable() : Boolean {
+        liveData.value?.let {
+            return it
+        }
+        latch.await(100, TimeUnit.MILLISECONDS)
+        return available
+    }
+
+    companion object {
+
+        @Volatile
+        private var instance: NetManager? = null
+
+        fun getInstance(ctx: Context): NetManager {
+            return instance ?: synchronized(this) {
+                instance ?: NetManager(ctx)
+                    .also {
+                        instance = it
+                    }
+            }
+        }
+
+    }
+
+}
+
+abstract class OnceNetObserver : Observer<Boolean> {
+
+    private var observed = false
+
+    final override fun onChanged(available: Boolean) {
+        synchronized(this) {
+            if (available && !observed) {
+                onNetAvailable()
+                observed = true
+            }
+        }
+    }
+
+    abstract fun onNetAvailable()
+
+}

+ 105 - 0
app/src/main/java/com/grkj/iscs_mc/util/SPUtils.kt

@@ -0,0 +1,105 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import com.grkj.iscs_mc.model.vo.card.CardInfoRespVO
+
+object SPUtils {
+
+    private const val SP_NAME = "iscs"
+    private const val SP_CONFIG_NAME = "iscs_config"
+
+    private const val KEY_LOGIN_USER_CARD_ID = "card_id"
+    private const val KEY_LOGIN_USER_CARD_CODE = "card_code"
+    private const val KEY_LOGIN_USER_HARDWARE_ID = "hardware_id"
+    private const val KEY_LOGIN_USER_CARD_NFC = "card_nfc"
+    private const val KEY_LOGIN_USER_CARD_TYPE = "card_type"
+    private const val KEY_LOGIN_USER_USER_ID = "user_id"
+    private const val KEY_LOGIN_USER_USER_NAME = "user_name"
+    private const val KEY_LOGIN_USER_ROLE_KEY = "role_key"
+
+    private const val KEY_DOCK_CONFIG = "dock_config"
+    private const val KEY_PORT_CONFIG = "port_config"
+
+    fun getLoginUser(context: Context): CardInfoRespVO? {
+        val sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
+        if (sp.getString(KEY_LOGIN_USER_CARD_NFC, null) == null) {
+            return null
+        }
+        return CardInfoRespVO(
+            cardId = sp.getLong(KEY_LOGIN_USER_CARD_ID, 0),
+            cardCode = sp.getString(KEY_LOGIN_USER_CARD_CODE, null),
+            hardwareId = sp.getLong(KEY_LOGIN_USER_HARDWARE_ID, 0),
+            cardNfc = sp.getString(KEY_LOGIN_USER_CARD_NFC, null),
+            cardType = sp.getInt(KEY_LOGIN_USER_CARD_TYPE, 0),
+            userId = sp.getLong(KEY_LOGIN_USER_USER_ID, 0),
+            userName = sp.getString(KEY_LOGIN_USER_USER_NAME, null),
+            roleKeyList = sp.getString(KEY_LOGIN_USER_ROLE_KEY, null)?.split(",")?.toMutableList()
+        )
+    }
+
+    fun setLoginUser(context: Context, cardInfoRespVO: CardInfoRespVO) {
+        val sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
+        val edit = sp.edit()
+        cardInfoRespVO.cardId?.let {
+            edit.putLong(KEY_LOGIN_USER_CARD_ID, it)
+        }
+        cardInfoRespVO.cardCode?.let {
+            edit.putString(KEY_LOGIN_USER_CARD_CODE, it)
+        }
+        cardInfoRespVO.hardwareId?.let {
+            edit.putLong(KEY_LOGIN_USER_HARDWARE_ID, it)
+        }
+        cardInfoRespVO.cardNfc?.let {
+            edit.putString(KEY_LOGIN_USER_CARD_NFC, it)
+        }
+        cardInfoRespVO.cardType?.let {
+            edit.putInt(KEY_LOGIN_USER_CARD_TYPE, it)
+        }
+        cardInfoRespVO.userId?.let {
+            edit.putLong(KEY_LOGIN_USER_USER_ID, it)
+        }
+        cardInfoRespVO.userName?.let {
+            edit.putString(KEY_LOGIN_USER_USER_NAME, it)
+        }
+        cardInfoRespVO.roleKeyList?.let {
+            edit.putString(KEY_LOGIN_USER_ROLE_KEY, it.toString().replace("[", "").replace("]", ""))
+        }
+        edit.commit()
+    }
+
+    fun clearLoginUser(context: Context) : Boolean {
+        return try {
+            val sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
+            val edit = sp.edit()
+            edit.clear()
+            edit.apply()
+            true
+        } catch (e: Exception) {
+            false
+        }
+    }
+
+    fun saveDockConfig(context: Context, config: String) {
+        val sp = context.getSharedPreferences(SP_CONFIG_NAME, Context.MODE_PRIVATE)
+        val edit = sp.edit()
+        edit.putString(KEY_DOCK_CONFIG, config)
+        edit.commit()
+    }
+
+    fun getDockConfig(context: Context): String? {
+        val sp = context.getSharedPreferences(SP_CONFIG_NAME, Context.MODE_PRIVATE)
+        return sp.getString(KEY_DOCK_CONFIG, null)
+    }
+
+    fun savePortConfig(context: Context, config: String) {
+        val sp = context.getSharedPreferences(SP_CONFIG_NAME, Context.MODE_PRIVATE)
+        val edit = sp.edit()
+        edit.putString(KEY_PORT_CONFIG, config)
+        edit.commit()
+    }
+
+    fun getPortConfig(context: Context): String? {
+        val sp = context.getSharedPreferences(SP_CONFIG_NAME, Context.MODE_PRIVATE)
+        return sp.getString(KEY_PORT_CONFIG, null)
+    }
+}

+ 69 - 0
app/src/main/java/com/grkj/iscs_mc/util/StompApi.kt

@@ -0,0 +1,69 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import cn.zhxu.okhttps.OkHttps
+import cn.zhxu.stomp.Header
+import cn.zhxu.stomp.Stomp
+import com.grkj.iscs_mc.extentions.addNetObserver
+import com.grkj.iscs_mc.extentions.removeNetObserver
+import com.grkj.iscs_mc.model.Constants
+import com.grkj.iscs_mc.model.UrlConsts
+import com.grkj.iscs_mc.util.log.LogUtil
+
+object StompApi {
+
+    fun refuseStomp(ctx: Context, callback: (stomp: Stomp, baseDestination: String) -> Unit) {
+        stomp(ctx, "", callback)
+    }
+
+
+    private fun stomp(ctx: Context, destinationInfix: String, callback: (stomp: Stomp, baseDestination: String) -> Unit) {
+        Executor.runOnMain {
+            ctx.addNetObserver(object : OnceNetObserver() {
+                override fun onNetAvailable() {
+                    onNetAvailable(ctx, destinationInfix, callback)
+                    ctx.removeNetObserver(this)
+                }
+            })
+        }
+    }
+
+    private fun onNetAvailable(ctx: Context, destinationInfix: String, callback: (stomp: Stomp, baseDestination: String) -> Unit) {
+//        PlatformRepository(ctx).getPlatformId().observeForever { platformId ->
+//            if (platformId != null) {
+//                Authorization.requestToken(ctx) { token ->
+//                    if (token != null) {
+//                        connectStompServer(ctx, vhost, destinationInfix, platformId, token, callback)
+//                    }
+//                }
+//            } else {
+//                LogUtil.w("platformId = null")
+//            }
+//        }
+        connectStompServer(ctx, destinationInfix, callback)
+    }
+
+    private fun connectStompServer(ctx: Context, destinationInfix: String, callback: (stomp: Stomp, baseDestination: String) -> Unit) {
+        LogUtil.i("开始连接 STOMP 服务")
+//        Stomp.over(OkHttps.webSocket(Constants.WEB_SOCKET).heatbeat(20, 20))
+        Stomp.over(OkHttps.webSocket(UrlConsts.WEB_SOCKET))
+            .setOnConnected {
+                callback(it, "/p--a.${destinationInfix}")
+            }
+            .setOnDisconnected {
+                if (it.code != 1000) {
+                    LogUtil.i("Websockt 已断开:${it.code}: ${it.reason},10 秒后重连...")
+                    Executor.delayOnIO({
+                        stomp(ctx, destinationInfix, callback)
+                    }, 10000)
+                }
+            }
+            .connect()
+//            .connect(listOf(
+//                Header("login", token),
+//                Header("host", vhost)
+//            ))
+    }
+
+
+}

+ 116 - 0
app/src/main/java/com/grkj/iscs_mc/util/StompManager.kt

@@ -0,0 +1,116 @@
+package com.grkj.iscs_mc.util
+
+import androidx.lifecycle.MutableLiveData
+import cn.zhxu.okhttps.OkHttps
+import cn.zhxu.stomp.Header
+import cn.zhxu.stomp.Stomp
+import com.grkj.iscs_mc.model.Constants
+import com.grkj.iscs_mc.model.UrlConsts
+import com.grkj.iscs_mc.util.log.LogUtil
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+
+class StompManager {
+    var myStomp: Stomp?=null
+    var scheduledExecutorService:ScheduledExecutorService?=null
+    var cacheTime=0L
+    var cacheDisconnectTime=0L
+    var topicRefuseStr:String?=null
+    val serverData = MutableLiveData<String>()
+
+    companion object{
+        fun getInstance() = InstanceHelper.sSingle
+    }
+
+    object InstanceHelper{
+        val sSingle = StompManager()
+    }
+
+    fun startStomp(){
+        if (myStomp?.isConnected==true){return}
+        LogUtil.d("stomp连接开始!")
+        NetHttpManager.getInstance().requestTokenAndRefreshIfExpired {
+            if (!it.isNullOrBlank()){
+                myStomp=Stomp.over(OkHttps.webSocket(UrlConsts.WEB_SOCKET).heatbeat(10,10))
+                    .setOnConnected {
+                        scheduledExecutorService?.shutdownNow()
+                        scheduledExecutorService=null
+                        LogUtil.d("stomp连接完成!")
+                        cacheTime=System.currentTimeMillis()
+                        if (cacheDisconnectTime>20*1000){
+//                            NetStateUtil.instance.setNetState(0)
+                        }
+                        topicRefuse()
+                    }
+                    .setOnDisconnected {
+                        LogUtil.d("stomp连接断开:${it.reason}")
+                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
+//                        NetStateUtil.instance.setNetState(2)
+                        reConnectStomp()
+                    }
+                    .setOnException {
+                        LogUtil.d("stomp连接异常:${it.toString()}")
+                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
+//                        NetStateUtil.instance.setNetState(2)
+                        reConnectStomp()
+                    }
+                    .setOnError {
+                        LogUtil.d("stomp连接错误:${it.toString()}")
+                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
+//                        NetStateUtil.instance.setNetState(2)
+                        reConnectStomp()
+                    }
+                    .connect(listOf(Header("login",it),Header("host","/eiotyun-kitchen-refuse")))
+            }else{
+                reConnectStomp()
+            }
+        }
+    }
+
+    fun topicRefuse(){
+//        FenBiDeNetApi.getInstance().getPlatformInfo {
+//            it?.let {
+//                topicRefuseStr?.let {
+//                    myStomp?.untopic(it)
+//                }
+//                topicRefuseStr="/p-${it.platformId}-a.machine.${AppUtils.getSerialNo(FenBiDeApplication.instance)}"
+//                //创建设备
+//                myStomp?.topic(topicRefuseStr){
+//                    EventBus.getDefault().post(StompCreatEvent(it.payload=="1",it.payload))
+//                }
+//                //配置参数
+//                myStomp?.topic("${topicRefuseStr}.refresh"){
+//                    try {
+//                        EventBus.getDefault().post(Gson().fromJson(it.payload, StompRefreshEvent::class.java))
+//                    }catch (e:Exception){
+//                        ToastUtils.tip(FenBiDeApplication.instance.getString(R.string.str_stomp_exception,it.payload))
+//                        LogUtil.d("refresh:${FenBiDeApplication.instance.getString(R.string.str_stomp_exception,it.payload)}",e)
+//                    }
+//                }
+//            }?:let {
+//                try {
+//                    Thread.sleep(10000)
+//                }catch (e:Exception){}
+//                topicRefuse()
+//            }
+//        }
+    }
+
+    fun stopStomp(){
+        topicRefuseStr?.let {
+            myStomp?.untopic(it)
+        }
+        myStomp?.disconnect(true)
+    }
+
+    fun reConnectStomp(){
+        scheduledExecutorService?:let {
+            scheduledExecutorService= Executors.newScheduledThreadPool(1)
+            scheduledExecutorService?.scheduleWithFixedDelay(Runnable {
+                startStomp()
+            },0,30000, TimeUnit.MILLISECONDS)
+        }
+    }
+
+}

+ 26 - 0
app/src/main/java/com/grkj/iscs_mc/util/ToastUtils.kt

@@ -0,0 +1,26 @@
+package com.grkj.iscs_mc.util
+
+import android.content.Context
+import android.os.Looper
+import android.widget.Toast
+import com.grkj.iscs_mc.MyApplication
+import com.grkj.iscs_mc.R
+
+class ToastUtils private constructor(context: Context) : Toast(context) {
+
+    companion object {
+
+        fun tip(text: CharSequence?, duration: Int = LENGTH_SHORT) {
+            if (Looper.myLooper() != Looper.getMainLooper()) {
+                Executor.runOnMain {
+                    tip(text)
+                }
+                return
+            }
+            makeText(MyApplication.instance!!, text, duration).show()
+        }
+
+        fun tip(text: Int, duration: Int = LENGTH_SHORT) =
+            tip(MyApplication.instance?.applicationContext?.resources?.getString(text), duration)
+    }
+}

+ 41 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/ILog.kt

@@ -0,0 +1,41 @@
+package com.grkj.iscs_mc.util.log
+
+import android.content.Context
+
+interface ILog {
+    /**
+     * 日志工具初始化
+     * @param logPath 日志输出路径
+     */
+    fun init(mContext: Context, logPath: String)
+
+    /**
+     * debug日志
+     * @param msg
+     */
+    fun d(tag: String?, msg: String?)
+
+    /**
+     * info日志
+     * @param msg
+     */
+    fun i(tag: String?, msg: String?)
+
+    /**
+     * warn日志
+     * @param msg
+     */
+    fun w(tag: String?, msg: String?)
+
+    /**
+     * error日志
+     * @param msg
+     */
+    fun e(tag: String?, msg: String?)
+
+    /**
+     * exception日志
+     * @param throwable
+     */
+    fun e(tag: String?, throwable: Throwable)
+}

+ 16 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/LogDiskStrategy.kt

@@ -0,0 +1,16 @@
+package com.grkj.iscs_mc.util.log
+
+import android.os.Handler
+import com.orhanobut.logger.DiskLogStrategy
+
+/**
+ *
+ * 功能描述:日志打印策略
+ * 使用场景:需要打印日志并输出到本地文件夹中
+ */
+class LogDiskStrategy(private val handler: Handler) : DiskLogStrategy(handler) {
+    override fun log(level: Int, tag: String?, message: String) {
+        // do nothing on the calling thread, simply pass the tag/msg to the background thread
+        handler.sendMessage(handler.obtainMessage(level, message))
+    }
+}

+ 64 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/LogHandle.kt

@@ -0,0 +1,64 @@
+package com.grkj.iscs_mc.util.log
+
+import android.content.Context
+import android.os.Handler
+import android.os.HandlerThread
+import com.orhanobut.logger.*
+
+/**
+ * 功能描述:打印日志到本地
+ */
+class LogHandle : ILog {
+    override fun init(mContext: Context,logPath: String) {
+        val ht = HandlerThread("AndroidFileLogger.$logPath")
+        ht.start()
+        try {
+            //单个文件最大限制5M 超过则创建新文件记录日志
+            val maxFileSize = 5 * 1024 * 1024
+            //日志打印线程
+            val cxHandle: Handler = LogWriteHandler(ht.looper, logPath, maxFileSize)
+            //创建缓存策略
+            val diskLogStrategy: LogStrategy = LogDiskStrategy(cxHandle)
+            //构建格式策略
+//            val strategy: FormatStrategy = CsvFormatStrategy.newBuilder().logStrategy(diskLogStrategy).build()
+            val strategy: FormatStrategy =
+                MyCsvFormatStrategy.newBuilder().logStrategy(diskLogStrategy).build(mContext)
+
+//            val formatStrategy = PrettyFormatStrategy.newBuilder()
+//                    .showThreadInfo(true)     // (可选)是否显示线程信息。 默认值为true
+//                    .methodCount(5)            //(可选)要显示的方法行数。 默认2
+//                    .methodOffset(0)           //(可选)设置调用堆栈的函数偏移值,0的话则从打印该Log的函数开始输出堆栈信息,默认是0
+//                    .logStrategy(diskLogStrategy)  //(可选)更改要打印的日志策略。 默认LogCat
+//                    .tag("Logger") //(可选)TAG内容. 默认是 PRETTY_LOGGER
+//                    .build();
+
+            //创建适配器
+            val adapter = DiskLogAdapter(strategy)
+            //设置日志适配器
+            Logger.addLogAdapter(adapter)
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ht.quit() //退出
+        }
+    }
+
+    override fun d(tag: String?, log: String?) {
+        Logger.t(tag).d(log)
+    }
+
+    override fun i(tag: String?, log: String?) {
+        Logger.t(tag).d(log)
+    }
+
+    override fun w(tag: String?, log: String?) {
+        Logger.t(tag).d(log)
+    }
+
+    override fun e(tag: String?, log: String?) {
+        Logger.t(tag).e(log!!)
+    }
+
+    override fun e(tag: String?, throwable: Throwable) {
+        Logger.t(tag).e(throwable, "App crash")
+    }
+}

+ 178 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/LogUtil.kt

@@ -0,0 +1,178 @@
+package com.grkj.iscs_mc.util.log
+
+import android.content.Context
+import android.util.Log
+import java.io.File
+import java.text.DateFormat
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Collections
+
+/**
+ * Log工具类
+ */
+object LogUtil {
+    //使用接口 避免耦合 后续若要更换日志打印框架 直接new 其它实现类即可
+    private val log: ILog = LogHandle()
+
+    // 是否打印代码行数
+    private const val isPrintLine = false
+
+    /**
+     * 日志初始化
+     *
+     * @param path 日志文件路径
+     */
+    fun init(mContext: Context,path: String) {
+        log.init(mContext,path)
+        d("日志初始化成功!")
+        clearOldFolder(path)
+    }
+
+    /**
+     * debug日志
+     *
+     * @param str
+     */
+    fun d(str: String?) {
+        log.d(Thread.currentThread().id.toString() + "  " + generateTag(), str)
+        Log.d(generateTag(), str!!)
+    }
+
+    /**
+     * info日志
+     *
+     * @param str
+     */
+    fun i(str: String?) {
+        log.i(Thread.currentThread().id.toString() + "  " + generateTag(), str)
+        Log.i(generateTag(), str!!)
+    }
+
+    /**
+     * warn日志
+     *
+     * @param str
+     */
+    fun w(str: String?) {
+        log.w(Thread.currentThread().id.toString() + "  " + generateTag(), str)
+        Log.w(generateTag(), str!!)
+    }
+
+    /**
+     * error日志
+     *
+     * @param str
+     */
+    @JvmStatic
+    fun e(str: String?) {
+        log.e(Thread.currentThread().id.toString() + "  " + generateTag(), str)
+        Log.e(generateTag(), str!!)
+    }
+
+    /**
+     * exception日志
+     *
+     * @param throwable
+     */
+    fun e(throwable: Throwable) {
+        log.e(Thread.currentThread().id.toString() + "  " + generateTag(), throwable)
+        Log.e(generateTag(), throwable.message.toString())
+    }
+
+    fun generateTag(): String {
+        val caller = Thread.currentThread().stackTrace[4]
+        var callerClazzName = caller.className
+        callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1)
+        var tag = "%s.%s"
+        if (isPrintLine) {
+            tag += "(Line:%d)"
+            tag = String.format(tag, callerClazzName, caller.methodName, caller.lineNumber)
+        } else {
+            // 打印方法名
+            tag = String.format(tag, callerClazzName, caller.methodName)
+
+            // 不打印方法名
+            // tag = "%s";
+            // tag = String.format(tag, callerClazzName);
+        }
+        return tag
+    }
+
+    /**
+     * 清理超过15天的日志
+     *
+     * @param folderPath 文件夹路径
+     */
+    private fun clearOldFolder(folderPath: String) {
+        val list: List<String> = getAllFolderName(folderPath)
+        // 日志文件夹只保留15天的
+        if (list.size > 15) {
+            deleteFirstFolder(list, folderPath)
+        }
+    }
+
+    /**
+     * 获取所有文件夹名称
+     *
+     * @param folderPath 文件夹路径
+     * @return 文件夹名称集合
+     */
+    private fun getAllFolderName(folderPath: String): ArrayList<String> {
+        val fileList = ArrayList<String>()
+        val file = File(folderPath)
+        val tempList = file.listFiles()
+        tempList?.let {
+            for (i in it.indices) {
+                if (it[i].isDirectory) {
+                    d("文件夹:" + it[i].name)
+                    fileList.add(it[i].name)
+                }
+            }
+        }
+        return fileList
+    }
+
+    /**
+     * 按日期排序删除最早的一个文件夹
+     */
+    private fun deleteFirstFolder(list: List<String>?, path: String) {
+        if (list.isNullOrEmpty()) {
+            e("deleteFirstFolder fail")
+            return
+        }
+        // 先排序
+        Collections.sort(list) { o1: String, o2: String ->
+            // 按照时间进行降序排列
+            val timeFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd") //日期格式
+            try {
+                val date0 = timeFormat.parse(o1)
+                val date1 = timeFormat.parse(o2)
+                if (date0.time > date1.time) {
+                    return@sort 1
+                }
+                if (o1 === o2) {
+                    return@sort 0
+                }
+            } catch (e: ParseException) {
+                e("日期转换异常")
+                e.printStackTrace()
+            }
+            -1
+        }
+        val dirPath = path + File.separator + list[0]
+        e("dir path : $dirPath")
+        val file = File(dirPath)
+        if (file.exists() && file.isDirectory) {
+            val childFiles = file.listFiles()
+            if (childFiles == null || childFiles.size == 0) {
+                file.delete()
+            } else {
+                for (i in childFiles.indices) {
+                    childFiles[i].delete()
+                }
+                file.delete()
+            }
+        }
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/LogWriteHandler.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs_mc.util.log
+
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+
+import kotlin.Throws
+
+/**
+ * 功能描述:日志写入类
+ */
+class LogWriteHandler(
+    looper: Looper?, //日志存储路径
+    private val folder: String, //单个日志最大占用内存
+    private val maxFileSize: Int
+) : Handler(looper!!) {
+    private val sdfTime = SimpleDateFormat("yyyy-MM-dd")
+    override fun handleMessage(msg: Message) {
+        val content = msg.obj as String
+        var fileWriter: FileWriter? = null
+        val logFile = getLogFile(folder, "logs")
+        try {
+            fileWriter = FileWriter(logFile, true)
+            writeLog(fileWriter, content)
+            fileWriter.flush()
+            fileWriter.close()
+        } catch (e: IOException) {
+            handleException(fileWriter)
+        }
+    }
+
+    private fun handleException(fileWriter: FileWriter?) {
+        if (fileWriter == null) return
+        try {
+            fileWriter.flush()
+            fileWriter.close()
+        } catch (e1: IOException) {
+            e1.printStackTrace()
+        }
+    }
+
+    /**
+     * This is always called on a single background thread.
+     * Implementing classes must ONLY write to the fileWriter and nothing more.
+     * The abstract class takes care of everything else including close the stream and catching IOException
+     *
+     * @param fileWriter an instance of FileWriter already initialised to the correct file
+     */
+    @Throws(IOException::class)
+    private fun writeLog(fileWriter: FileWriter, content: String) {
+        fileWriter.append(content)
+    }
+
+    private fun getLogFile(folderName: String, fileName: String): File {
+        var folderName = folderName
+        var fileName = fileName
+        folderName = "$folderName/$time"
+        fileName = "$fileName-$time"
+        val folder = File(folderName)
+        if (!folder.exists()) folder.mkdirs()
+        var newFileCount = 0
+        var existingFile: File? = null
+        var newFile = File(folder, String.format("%s-%s.txt", fileName, newFileCount))
+        while (newFile.exists()) {
+            existingFile = newFile
+            newFileCount++
+            newFile = File(folder, String.format("%s-%s.txt", fileName, newFileCount))
+        }
+        if (existingFile == null) return newFile
+        return if (existingFile.length() >= maxFileSize) newFile else existingFile
+    }
+
+    private val time: String
+        private get() = sdfTime.format(Date())
+}

+ 147 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/MyCsvFormatStrategy.kt

@@ -0,0 +1,147 @@
+package com.grkj.iscs_mc.util.log
+
+import android.content.Context
+import android.os.Handler
+import android.os.HandlerThread
+import android.text.TextUtils
+import com.grkj.iscs_mc.util.FileUtil
+import com.orhanobut.logger.FormatStrategy
+import com.orhanobut.logger.LogStrategy
+import com.orhanobut.logger.Logger
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class MyCsvFormatStrategy private constructor(builder: Builder) : FormatStrategy {
+    private val date: Date
+    private val dateFormat: SimpleDateFormat
+    private val logStrategy: LogStrategy
+    private val tag: String?
+
+    init {
+        checkNotNull(builder)
+        date = builder.date!!
+        dateFormat = builder.dateFormat!!
+        logStrategy = builder.logStrategy!!
+        tag = builder.tag
+    }
+
+    override fun log(priority: Int, onceOnlyTag: String?, message: String) {
+        var message = message
+        checkNotNull(message)
+        val tag = formatTag(onceOnlyTag)
+        date.time = System.currentTimeMillis()
+        val builder = StringBuilder()
+
+        // machine-readable date/time
+//        builder.append(Long.toString(date.getTime()));
+
+        // human-readable date/time
+//        builder.append(SEPARATOR);
+        builder.append(dateFormat.format(date))
+
+        // level
+        builder.append(SEPARATOR + SEPARATOR)
+        builder.append(logLevel(priority))
+
+        // tag
+        builder.append(SEPARATOR + SEPARATOR)
+        //        builder.append(tag);
+        builder.append(onceOnlyTag)
+
+        // message
+        if (message.contains(NEW_LINE)) {
+            // a new line would break the CSV format, so we replace it here
+            message = message.replace(NEW_LINE.toRegex(), NEW_LINE_REPLACEMENT)
+        }
+        builder.append(" : ")
+        builder.append(message)
+
+        // new line
+        builder.append(NEW_LINE)
+        logStrategy.log(priority, tag, builder.toString())
+    }
+
+    private fun formatTag(tag: String?): String? {
+        return if (!TextUtils.isEmpty(tag) && !TextUtils.equals(this.tag, tag)) {
+            this.tag + "-" + tag
+        } else this.tag
+    }
+
+    class Builder {
+        var date: Date? = null
+        var dateFormat: SimpleDateFormat? = null
+        var logStrategy: LogStrategy? = null
+        var tag: String? = "PRETTY_LOGGER"
+        fun date(`val`: Date?): Builder {
+            date = `val`
+            return this
+        }
+
+        fun dateFormat(`val`: SimpleDateFormat?): Builder {
+            dateFormat = `val`
+            return this
+        }
+
+        fun logStrategy(`val`: LogStrategy?): Builder {
+            logStrategy = `val`
+            return this
+        }
+
+        fun tag(tag: String?): Builder {
+            this.tag = tag
+            return this
+        }
+
+        fun build(mContext: Context): MyCsvFormatStrategy {
+            if (date == null) {
+                date = Date()
+            }
+            if (dateFormat == null) {
+                dateFormat = SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS", Locale.UK)
+            }
+            if (logStrategy == null) {
+                val diskPath = FileUtil.getRootFolder(mContext)?.absolutePath
+                val folder = diskPath + File.separatorChar + "logger"
+                val ht = HandlerThread("AndroidFileLogger.$folder")
+                ht.start()
+                val handler: Handler = MyDiskLogStrategy.WriteHandler(ht.looper, folder, MAX_BYTES)
+                logStrategy = MyDiskLogStrategy(handler)
+            }
+            return MyCsvFormatStrategy(this)
+        }
+
+        companion object {
+            private const val MAX_BYTES = 500 * 1024 // 500K averages to a 4000 lines per file
+        }
+    }
+
+    companion object {
+        private val NEW_LINE = System.getProperty("line.separator")
+        private const val NEW_LINE_REPLACEMENT = " <br> "
+        private const val SEPARATOR = " "
+        fun newBuilder(): Builder {
+            return Builder()
+        }
+
+        fun logLevel(value: Int): String {
+            return when (value) {
+                Logger.VERBOSE -> "V"
+                Logger.DEBUG -> "D"
+                Logger.INFO -> "I"
+                Logger.WARN -> "W"
+                Logger.ERROR -> "E"
+                Logger.ASSERT -> "A"
+                else -> "UNKNOWN"
+            }
+        }
+
+        fun <T> checkNotNull(obj: T?): T {
+            if (obj == null) {
+                throw NullPointerException()
+            }
+            return obj
+        }
+    }
+}

+ 105 - 0
app/src/main/java/com/grkj/iscs_mc/util/log/MyDiskLogStrategy.kt

@@ -0,0 +1,105 @@
+package com.grkj.iscs_mc.util.log
+
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+
+import com.orhanobut.logger.LogStrategy
+
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+
+class MyDiskLogStrategy(handler: Handler) : LogStrategy {
+    private val handler: Handler
+
+    init {
+        this.handler = checkNotNull(handler)
+    }
+
+    override fun log(level: Int, tag: String?, message: String) {
+        checkNotNull(message)
+
+        // do nothing on the calling thread, simply pass the tag/msg to the background thread
+        handler.sendMessage(handler.obtainMessage(level, message))
+    }
+
+    internal class WriteHandler(looper: Looper, folder: String, maxFileSize: Int) : Handler(
+        checkNotNull(looper)
+    ) {
+        private val folder: String
+        private val maxFileSize: Int
+
+        init {
+            this.folder = checkNotNull(folder)
+            this.maxFileSize = maxFileSize
+        }
+
+        override fun handleMessage(msg: Message) {
+            val content = msg.obj as String
+            var fileWriter: FileWriter? = null
+            val logFile = getLogFile(folder, "logs")
+            try {
+                fileWriter = FileWriter(logFile, true)
+                writeLog(fileWriter, content)
+                fileWriter.flush()
+                fileWriter.close()
+            } catch (e: IOException) {
+                if (fileWriter != null) {
+                    try {
+                        fileWriter.flush()
+                        fileWriter.close()
+                    } catch (e1: IOException) { /* fail silently */
+                    }
+                }
+            }
+        }
+
+        /**
+         * This is always called on a single background thread.
+         * Implementing classes must ONLY write to the fileWriter and nothing more.
+         * The abstract class takes care of everything else including close the stream and catching IOException
+         *
+         * @param fileWriter an instance of FileWriter already initialised to the correct file
+         */
+        @Throws(IOException::class)
+        private fun writeLog(fileWriter: FileWriter, content: String) {
+            checkNotNull(fileWriter)
+            checkNotNull(content)
+            fileWriter.append(content)
+        }
+
+        private fun getLogFile(folderName: String, fileName: String): File {
+            checkNotNull(folderName)
+            checkNotNull(fileName)
+            val folder = File(folderName)
+            if (!folder.exists()) {
+                //TODO: What if folder is not created, what happens then?
+                folder.mkdirs()
+            }
+            var newFileCount = 0
+            var newFile: File
+            var existingFile: File? = null
+            newFile = File(folder, String.format("%s_%s.txt", fileName, newFileCount))
+            while (newFile.exists()) {
+                existingFile = newFile
+                newFileCount++
+                newFile = File(folder, String.format("%s_%s.txt", fileName, newFileCount))
+            }
+            return if (existingFile != null) {
+                if (existingFile.length() >= maxFileSize) {
+                    newFile
+                } else existingFile
+            } else newFile
+        }
+    }
+
+    companion object {
+        fun <T> checkNotNull(obj: T?): T {
+            if (obj == null) {
+                throw NullPointerException()
+            }
+            return obj
+        }
+    }
+}

+ 75 - 0
app/src/main/java/com/grkj/iscs_mc/view/activity/LoginActivity.kt

@@ -0,0 +1,75 @@
+package com.grkj.iscs_mc.view.activity
+
+import android.content.Intent
+import android.widget.ImageView
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.ActivityLoginBinding
+import com.grkj.iscs_mc.util.AppUtils
+import com.grkj.iscs_mc.view.base.BaseMvpActivity
+import com.grkj.iscs_mc.view.dialog.LoginDialog
+import com.grkj.iscs_mc.view.iview.ILoginView
+import com.grkj.iscs_mc.view.presenter.LoginPresenter
+import com.zhy.adapter.recyclerview.CommonAdapter
+import com.zhy.adapter.recyclerview.base.ViewHolder
+
+class LoginActivity : BaseMvpActivity<ILoginView, LoginPresenter, ActivityLoginBinding>() {
+
+    private var cardLoginDialog: LoginDialog? = null
+
+    override val viewBinding: ActivityLoginBinding
+        get() = ActivityLoginBinding.inflate(layoutInflater)
+
+    override fun initView() {
+        mBinding?.tvVersion?.text = "v${AppUtils.getPkgVerName(this)}"
+
+//        mBinding?.main?.setBackgroundResource(R.mipmap.login_bg)
+
+        val pairList = mutableListOf(
+            Pair(getString(R.string.login_face), R.mipmap.login_face),
+            Pair(getString(R.string.login_fingerprint), R.mipmap.login_fingerprint),
+            Pair(getString(R.string.login_card), R.mipmap.login_card),
+            Pair(getString(R.string.login_account), R.mipmap.login_account)
+        )
+
+        mBinding?.rvType?.adapter =
+            object : CommonAdapter<Pair<String, Int>>(this, R.layout.item_rv_login, pairList) {
+                override fun convert(holder: ViewHolder, pair: Pair<String, Int>, position: Int) {
+                    holder.setText(R.id.tv_name, pair.first)
+                    holder.getView<ImageView>(R.id.iv_icon).setImageResource(pair.second)
+                    holder.setOnClickListener(R.id.root) {
+                        showLoginDialog(position)
+                    }
+                }
+            }
+    }
+
+    /**
+     * @param loginType 0:人脸 1:工卡 2:账号 3:指纹
+     */
+    private fun showLoginDialog(loginType: Int) {
+        cardLoginDialog ?: run {
+            cardLoginDialog = LoginDialog(presenter, this) { isSuccess, cardInfoRespVO, userInfoRespVO ->
+                if (isSuccess) {
+                    val intent = Intent(this, HomeActivity::class.java)
+                    if (cardInfoRespVO != null) {
+                        intent.putExtra("cardInfo", cardInfoRespVO)
+                    }
+                    if (userInfoRespVO != null) {
+                        intent.putExtra("userInfo", userInfoRespVO)
+                    }
+                    startActivity(intent)
+                }
+            }
+        }
+        cardLoginDialog?.showByType(loginType)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        cardLoginDialog?.dismiss()
+    }
+
+    override fun initPresenter(): LoginPresenter {
+        return LoginPresenter()
+    }
+}

+ 102 - 0
app/src/main/java/com/grkj/iscs_mc/view/dialog/LoginDialog.kt

@@ -0,0 +1,102 @@
+package com.grkj.iscs_mc.view.dialog
+
+import android.content.Context
+import android.view.InputDevice
+import android.view.KeyEvent
+import android.view.View
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.databinding.DialogLoginBinding
+import com.grkj.iscs_mc.extentions.toByteArrays
+import com.grkj.iscs_mc.extentions.toHexStrings
+import com.grkj.iscs_mc.model.vo.card.CardInfoRespVO
+import com.grkj.iscs_mc.model.vo.user.UserInfoRespVO
+import com.grkj.iscs_mc.util.Executor
+import com.grkj.iscs_mc.view.base.BaseDialog
+import com.grkj.iscs_mc.view.presenter.LoginPresenter
+
+class LoginDialog(val presenter: LoginPresenter?, val ctx: Context, private var callBack: ((Boolean, CardInfoRespVO?, UserInfoRespVO?) -> Unit)? = null) :
+    BaseDialog<DialogLoginBinding>(ctx) {
+
+    private var cardNo = ""
+    private var mLoginType = 3 // 0:人脸 1:指纹 2:工卡 3:账号
+    private val mPairList = mutableListOf(
+        Pair(context.getString(R.string.please_scan_face), R.mipmap.login_face),
+        Pair(context.getString(R.string.please_scan_fingerprint), R.mipmap.login_fingerprint),
+        Pair(context.getString(R.string.please_swipe_card), R.mipmap.login_card)
+    )
+
+    override val viewBinding: DialogLoginBinding
+        get() = DialogLoginBinding.inflate(layoutInflater)
+
+    override fun initView() {
+        mBinding?.tvLogin?.setOnClickListener {
+            presenter?.login(ctx, mBinding?.etAccount?.text.toString(), mBinding?.etPassword?.text.toString()) { isSuccess, cardInfoRespVO, userInfoRespVO ->
+                Executor.runOnMain {
+                    if (isSuccess) {
+                        dismiss()
+                    }
+                    callBack?.invoke(isSuccess, cardInfoRespVO, userInfoRespVO)
+                }
+            }
+        }
+        mBinding?.tvCancel?.setOnClickListener { dismiss() }
+    }
+
+    /**
+     * 根据类型显示弹框
+     *
+     * @param loginType 0:人脸 1:指纹 2:工卡 3:账号
+     */
+    fun showByType(loginType: Int) {
+        mLoginType = loginType
+        if (loginType == 3) {
+            mBinding?.llAccountContainer?.visibility = View.VISIBLE
+            mBinding?.llEasyContainer?.visibility = View.GONE
+        } else {
+            mBinding?.llAccountContainer?.visibility = View.GONE
+            mBinding?.llEasyContainer?.visibility = View.VISIBLE
+            mBinding?.ivIcon?.setImageResource(mPairList[loginType].second)
+            mBinding?.tvTip?.text = mPairList[loginType].first
+        }
+        show()
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (mLoginType != 2) {
+            return super.dispatchKeyEvent(event)
+        }
+        if (event.action == KeyEvent.ACTION_UP && event.source == InputDevice.SOURCE_KEYBOARD) {
+//            val view = currentFocus
+//            if (view is EditText) {
+//                val text = view.text?.toString()
+//                if (text!!.isNotEmpty()) {
+//                    val rst = text.substring(0, text.length - 1)
+//                    view.setText(rst)
+//                }
+//            }
+            // 检测到回车开始处理
+            if (event.keyCode == 66) {
+                cardNo = cardNo.toLong().toByteArrays().toHexStrings(false)
+                presenter?.cardLogin(cardNo) { isSuccess, cardInfoRespVO, userInfoRespVO ->
+                    if (isSuccess) {
+                        dismiss()
+                    }
+                    callBack?.invoke(isSuccess, cardInfoRespVO, userInfoRespVO)
+                }
+                // 重置cardNo
+                cardNo = ""
+                return super.dispatchKeyEvent(event)
+            }
+            cardNo += event.keyCharacterMap.getDisplayLabel(event.keyCode)
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
+    override fun dismiss() {
+        super.dismiss()
+        if (mLoginType == 3) {
+            mBinding?.etAccount?.text?.clear()
+            mBinding?.etPassword?.text?.clear()
+        }
+    }
+}

+ 5 - 0
app/src/main/java/com/grkj/iscs_mc/view/iview/ILoginView.kt

@@ -0,0 +1,5 @@
+package com.grkj.iscs_mc.view.iview
+
+import com.grkj.iscs_mc.view.base.IView
+
+interface ILoginView : IView {}

+ 60 - 0
app/src/main/java/com/grkj/iscs_mc/view/presenter/LoginPresenter.kt

@@ -0,0 +1,60 @@
+package com.grkj.iscs_mc.view.presenter
+
+import android.content.Context
+import com.grkj.iscs_mc.R
+import com.grkj.iscs_mc.model.vo.card.CardInfoRespVO
+import com.grkj.iscs_mc.model.vo.user.UserInfoRespVO
+import com.grkj.iscs_mc.util.Executor
+import com.grkj.iscs_mc.util.NetApi
+import com.grkj.iscs_mc.util.SPUtils
+import com.grkj.iscs_mc.util.ToastUtils
+import com.grkj.iscs_mc.view.base.BasePresenter
+import com.grkj.iscs_mc.view.iview.ILoginView
+
+class LoginPresenter : BasePresenter<ILoginView>() {
+
+    fun login(context: Context, account: String, pwd: String, callBack: (Boolean, CardInfoRespVO?, UserInfoRespVO?) -> Unit) {
+        if (account.isEmpty()) {
+            ToastUtils.tip(context.getString(R.string.please_input_account))
+            return
+        } else if (account.isEmpty()) {
+            ToastUtils.tip(context.getString(R.string.please_input_password))
+            return
+        }
+        NetApi.login(account, pwd) {
+            if (it == true) {
+                NetApi.getUserInfo { userInfo ->
+                    NetApi.getCardInfoByLoginUser { itInfo ->
+                        itInfo?.let { info ->
+                            SPUtils.setLoginUser(mContext!!, info)
+                        }
+                        callBack.invoke(it, itInfo, userInfo)
+                    }
+                }
+            } else {
+                callBack.invoke(false, null, null)
+            }
+        }
+    }
+
+    fun cardLogin(card: String, callBack: (Boolean, CardInfoRespVO?, UserInfoRespVO?) -> Unit) {
+        NetApi.cardLogin(card) {
+            if (it == true) {
+                NetApi.getUserInfo { userInfo ->
+                    NetApi.getCardInfoByLoginUser { itInfo ->
+                        Executor.runOnMain {
+                            itInfo?.let { info ->
+                                SPUtils.setLoginUser(mContext!!, info)
+                            }
+                            callBack.invoke(it, itInfo, userInfo)
+                        }
+                    }
+                }
+            } else {
+                Executor.runOnMain {
+                    callBack.invoke(false, null, null)
+                }
+            }
+        }
+    }
+}

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

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

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

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

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

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

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

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

+ 8 - 0
app/src/main/res/drawable/white_stroke_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/common_radius_small" />
+    <stroke
+        android:width="@dimen/divider_line_width"
+        android:color="@color/white" />
+</shape>

+ 31 - 0
app/src/main/res/layout/activity_login.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="@dimen/page_padding"
+    tools:context=".view.activity.LoginActivity">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_type"
+        style="@style/CommonRecyclerView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:orientation="horizontal" />
+
+    <TextView
+        style="@style/CommonTextView"
+        android:layout_above="@id/rv_type"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="25dp"
+        android:text="@string/material_management_system"
+        android:textSize="30sp" />
+
+    <TextView
+        android:id="@+id/tv_version"
+        style="@style/CommonTextView"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true" />
+</RelativeLayout>

+ 70 - 0
app/src/main/res/layout/dialog_login.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    app:cardBackgroundColor="@color/dialog_card_login_bg"
+    app:strokeColor="@color/common_transparent">
+
+    <LinearLayout
+        android:id="@+id/ll_easy_container"
+        android:layout_width="165dp"
+        android:layout_height="146dp"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:visibility="gone">
+
+        <ImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:layout_marginBottom="5dp" />
+
+        <TextView
+            android:id="@+id/tv_tip"
+            style="@style/CommonTextView"
+            android:textSize="@dimen/common_text_size_big" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/ll_account_container"
+        android:layout_width="165dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:padding="20dp">
+
+        <EditText
+            android:id="@+id/et_account"
+            style="@style/CommonEdit"
+            android:layout_width="match_parent"
+            android:layout_marginBottom="10dp"
+            android:hint="@string/please_input_account" />
+
+        <EditText
+            android:id="@+id/et_password"
+            style="@style/CommonEdit"
+            android:layout_width="match_parent"
+            android:layout_marginBottom="10dp"
+            android:hint="@string/please_input_password" />
+
+        <TextView
+            android:id="@+id/tv_login"
+            style="@style/CommonBtn"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="10dp"
+            android:text="@string/login" />
+
+        <TextView
+            android:id="@+id/tv_cancel"
+            style="@style/CommonBtn"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@drawable/white_stroke_bg"
+            android:text="@string/cancel" />
+    </LinearLayout>
+</com.google.android.material.card.MaterialCardView>

+ 21 - 0
app/src/main/res/layout/item_rv_login.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:layout_width="@dimen/item_rv_login_width"
+    android:layout_height="@dimen/item_rv_login_height"
+    android:layout_margin="@dimen/item_rv_login_margin"
+    android:background="@drawable/item_rv_login_bg"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/iv_icon"
+        android:layout_width="@dimen/item_rv_login_icon_size"
+        android:layout_height="@dimen/item_rv_login_icon_size" />
+
+    <TextView
+        android:id="@+id/tv_name"
+        style="@style/CommonTextView"
+        android:layout_marginTop="8dp"
+        android:textSize="@dimen/common_text_size_big" />
+</LinearLayout>

BIN
app/src/main/res/mipmap/login_account.png


BIN
app/src/main/res/mipmap/login_bg.png


BIN
app/src/main/res/mipmap/login_card.png


BIN
app/src/main/res/mipmap/login_face.png


BIN
app/src/main/res/mipmap/login_fingerprint.png


+ 32 - 5
app/src/main/res/values/colors.xml

@@ -1,10 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <color name="purple_200">#FFBB86FC</color>
-    <color name="purple_500">#FF6200EE</color>
-    <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
-    <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
+    <color name="aquamarine">#7FFFD4</color>
+
+    <color name="main_color">#00AAFF</color>
+    <color name="main_color_dark">#CC055EB3</color>
+    <color name="common_transparent">#00000000</color>
+    <color name="common_bg_white_10">#1affffff</color>
+    <color name="common_bg_white_20">#33ffffff</color>
+    <color name="common_bg_white_30">#4DFFFFFF</color>
+    <color name="common_bg_white_40">#66FFFFFF</color>
+    <color name="common_bg_white_60">#99FFFFFF</color>
+    <color name="common_bg_white_70">#B3FFFFFF</color>
+    <color name="common_bg_white_80">#CCFFFFFF</color>
+    <color name="common_bg_white_90">#E6FFFFFF</color>
+
+    <color name="common_bg_black_30">#4D000000</color>
+
+    <color name="common_light_gray">#CCCCCC</color>
+    <color name="common_dlg_bg">#535B67</color>
+
+    <color name="selectable_input_prefix">#bd3124</color>
+    <color name="switch_track_on">#298EFF</color>
+    <color name="switch_track_off">#E9E9E9</color>
+    <color name="dialog_card_login_bg">#990E57EA</color>
+    <color name="home_menu_bg">#4D2B7AE9</color>
+    <color name="common_btn_red_bg">#80FF0000</color>
+    <color name="lock_status_unlocked">#99008000</color>
+    <color name="lock_status_locked">#CCFF0000</color>
+    <color name="point_text_bg">#70b26f</color>
+
+    <color name="item_rv_step_bg_done">#1d7153</color>
+    <color name="item_rv_step_bg_doing">#838a53</color>
+    <color name="item_rv_step_bg_ready">#B3FFFFFF</color>
 </resources>

+ 35 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="common_text_size">10dp</dimen>
+    <dimen name="common_text_size_big">14dp</dimen>
+    <dimen name="common_text_size_small">8dp</dimen>
+    <dimen name="common_text_padding">5dp</dimen>
+    <dimen name="common_font_txt_size_page_title">14dp</dimen>
+    <dimen name="common_radius">10dp</dimen>
+    <dimen name="common_radius_small">5dp</dimen>
+    <dimen name="common_spacing">10dp</dimen>
+    <dimen name="common_spacing_big">15dp</dimen>
+    <dimen name="common_spacing_small">5dp</dimen>
+    <dimen name="common_spacing_smallest">2dp</dimen>
+    <dimen name="common_icon_size">15dp</dimen>
+    <dimen name="common_icon_size_small">12dp</dimen>
+
+    <dimen name="common_btn_width">150dp</dimen>
+    <dimen name="common_btn_height">50dp</dimen>
+
+    <dimen name="switch_width">46dp</dimen>
+    <dimen name="switch_height">26dp</dimen>
+    <dimen name="switch_radius">13dp</dimen>
+    <dimen name="switch_thumb_stroke">2dp</dimen>
+    <dimen name="switch_thumb_size">22dp</dimen>
+
+    <dimen name="menu_padding">15dp</dimen>
+    <dimen name="divider_line_width">1dp</dimen>
+    <dimen name="divider_line_margin">5dp</dimen>
+    <dimen name="page_padding">20dp</dimen>
+
+    <dimen name="item_rv_login_width">100dp</dimen>
+    <dimen name="item_rv_login_height">130dp</dimen>
+    <dimen name="item_rv_login_margin">7dp</dimen>
+    <dimen name="item_rv_login_icon_size">40dp</dimen>
+</resources>

+ 19 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,22 @@
 <resources>
     <string name="app_name">ISCS_MC</string>
+
+    <string name="common_net_dis">请确认网络状态正常后重试!</string>
+    <string name="common_net_download">请确认网络状态正常且下载地址无误后重试!</string>
+    <string name="common_download_erro_notag">下载失败!请确认文件存储位置后重试</string>
+
+    <string name="material_management_system">物资管理系统</string>
+    <string name="material_management">物资管理</string>
+    <string name="please_input_account">请输入用户名</string>
+    <string name="please_input_password">请输入密码</string>
+    <string name="login_face">人脸登录</string>
+    <string name="login_fingerprint">指纹登录</string>
+    <string name="login_card">工卡登录</string>
+    <string name="login_account">用户名登录</string>
+    <string name="please_swipe_card">请刷卡</string>
+    <string name="please_scan_face">请刷脸</string>
+    <string name="please_scan_fingerprint">请刷指纹</string>
+    <string name="login">登录</string>
+    <string name="cancel">取消</string>
+    <string name="please_login">请登录</string>
 </resources>

+ 54 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="CommonTextView">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:textSize">@dimen/common_text_size</item>
+    </style>
+
+    <style name="CommonBtn">
+        <item name="android:layout_width">@dimen/common_btn_width</item>
+        <item name="android:layout_height">@dimen/common_btn_height</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:textSize">@dimen/common_text_size</item>
+        <item name="android:background">@drawable/common_btn_bg</item>
+        <item name="android:padding">@dimen/common_text_padding</item>
+    </style>
+
+    <style name="CommonBtnBlue" parent="CommonBtn">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:background">@drawable/common_btn_blue_bg</item>
+    </style>
+
+    <style name="CommonBtnRed" parent="CommonBtn">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:background">@drawable/common_btn_red_bg</item>
+    </style>
+
+    <style name="CommonRecyclerView">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:overScrollMode">never</item>
+        <item name="android:scrollbars">none</item>
+        <item name="layoutManager">LinearLayoutManager</item>
+    </style>
+
+    <style name="CommonEdit">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:textColorHint">@color/common_bg_white_20</item>
+        <item name="android:backgroundTint">@color/common_bg_white_40</item>
+        <item name="android:textSize">@dimen/common_text_size</item>
+        <item name="android:maxLines">1</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:padding">@dimen/common_text_padding</item>
+        <item name="android:background">@drawable/white_stroke_bg</item>
+    </style>
+</resources>

+ 2 - 1
gradle.properties

@@ -20,4 +20,5 @@ kotlin.code.style=official
 # Enables namespacing of each library's R class so that its R class includes only the
 # resources declared in the library itself and none from the library's dependencies,
 # thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
+android.nonTransitiveRClass=true
+android.enableJetifier=true