소스 검색

初版提交

zhouzhendong 4 년 전
커밋
e9ba044599
100개의 변경된 파일6776개의 추가작업 그리고 0개의 파일을 삭제
  1. 15 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 6 0
      .idea/compiler.xml
  4. 17 0
      .idea/deploymentTargetDropDown.xml
  5. 3 0
      .idea/dictionaries/oursdreams.xml
  6. 21 0
      .idea/gradle.xml
  7. 36 0
      .idea/inspectionProfiles/Project_Default.xml
  8. 41 0
      .idea/misc.xml
  9. 6 0
      .idea/vcs.xml
  10. 1 0
      app/.gitignore
  11. 73 0
      app/build.gradle
  12. 21 0
      app/proguard-rules.pro
  13. BIN
      app/release/SWMS.apk
  14. 20 0
      app/release/output-metadata.json
  15. 26 0
      app/src/androidTest/java/com/baoshi/swms/ExampleInstrumentedTest.java
  16. 55 0
      app/src/main/AndroidManifest.xml
  17. BIN
      app/src/main/ic_logo-playstore.png
  18. 64 0
      app/src/main/java/com/baoshi/swms/App.java
  19. 146 0
      app/src/main/java/com/baoshi/swms/MainActivity.java
  20. 37 0
      app/src/main/java/com/baoshi/swms/MyActivityManager.java
  21. 107 0
      app/src/main/java/com/baoshi/swms/activity/BatchRecoverActivity.java
  22. 523 0
      app/src/main/java/com/baoshi/swms/activity/CheckInventoryActivity.java
  23. 80 0
      app/src/main/java/com/baoshi/swms/activity/LoginActivity.java
  24. 232 0
      app/src/main/java/com/baoshi/swms/activity/MenuActivity.java
  25. 726 0
      app/src/main/java/com/baoshi/swms/activity/PickingActivity.java
  26. 114 0
      app/src/main/java/com/baoshi/swms/activity/PieceworkActivity.java
  27. 38 0
      app/src/main/java/com/baoshi/swms/activity/WaybillDispatchActivity.java
  28. 76 0
      app/src/main/java/com/baoshi/swms/activity/changeView/ListViewFooterChange.java
  29. 212 0
      app/src/main/java/com/baoshi/swms/activity/item/WaybillDispatchItemActivity.java
  30. 125 0
      app/src/main/java/com/baoshi/swms/activity/part/BaseListViewIntegration.java
  31. 55 0
      app/src/main/java/com/baoshi/swms/adapter/CheckInventoryItem.java
  32. 34 0
      app/src/main/java/com/baoshi/swms/adapter/WaybillDispatch.java
  33. 62 0
      app/src/main/java/com/baoshi/swms/adapter/WaybillDispatchList.java
  34. 83 0
      app/src/main/java/com/baoshi/swms/bean/Menu.java
  35. 60 0
      app/src/main/java/com/baoshi/swms/bean/Picking.java
  36. 95 0
      app/src/main/java/com/baoshi/swms/bean/PickingItem.java
  37. 44 0
      app/src/main/java/com/baoshi/swms/bean/PickingNumber.java
  38. 66 0
      app/src/main/java/com/baoshi/swms/bean/Stocktaking.java
  39. 91 0
      app/src/main/java/com/baoshi/swms/bean/StocktakingItem.java
  40. 42 0
      app/src/main/java/com/baoshi/swms/bean/User.java
  41. 174 0
      app/src/main/java/com/baoshi/swms/bean/Waybill.java
  42. 45 0
      app/src/main/java/com/baoshi/swms/bean/dto/Login.java
  43. 51 0
      app/src/main/java/com/baoshi/swms/bean/dto/PickingDto.java
  44. 38 0
      app/src/main/java/com/baoshi/swms/bean/dto/StockingData.java
  45. 53 0
      app/src/main/java/com/baoshi/swms/bean/dto/StocktakingData.java
  46. 30 0
      app/src/main/java/com/baoshi/swms/bean/dto/WaybillData.java
  47. 387 0
      app/src/main/java/com/baoshi/swms/fragment/WaybillDispatch.java
  48. 5 0
      app/src/main/java/com/baoshi/swms/inter/OnRefreshListener.java
  49. 29 0
      app/src/main/java/com/baoshi/swms/net/api/SwmsApi.java
  50. 54 0
      app/src/main/java/com/baoshi/swms/net/api/WasApi.java
  51. 98 0
      app/src/main/java/com/baoshi/swms/net/build/NetBuilder.java
  52. 39 0
      app/src/main/java/com/baoshi/swms/net/build/WebSocketClient.java
  53. 58 0
      app/src/main/java/com/baoshi/swms/net/callback/NetCallback.java
  54. 70 0
      app/src/main/java/com/baoshi/swms/net/callback/SwmsHandlerCallback.java
  55. 93 0
      app/src/main/java/com/baoshi/swms/net/callback/WasHandlerCallback.java
  56. 15 0
      app/src/main/java/com/baoshi/swms/net/constant/SwmsConstant.java
  57. 12 0
      app/src/main/java/com/baoshi/swms/net/constant/SwmsWebSocketConstant.java
  58. 16 0
      app/src/main/java/com/baoshi/swms/net/constant/WasConstant.java
  59. 17 0
      app/src/main/java/com/baoshi/swms/net/inter/APICallback.java
  60. 56 0
      app/src/main/java/com/baoshi/swms/net/interceptor/CacheInterceptor.java
  61. 66 0
      app/src/main/java/com/baoshi/swms/net/interceptor/HeaderInterceptor.java
  62. 131 0
      app/src/main/java/com/baoshi/swms/net/listener/SwmsWebSocketListener.java
  63. 73 0
      app/src/main/java/com/baoshi/swms/net/subscribe/BaseSubscribe.java
  64. 35 0
      app/src/main/java/com/baoshi/swms/net/subscribe/SwmsBaseSubscribe.java
  65. 52 0
      app/src/main/java/com/baoshi/swms/net/subscribe/SwmsSubscribe.java
  66. 46 0
      app/src/main/java/com/baoshi/swms/net/subscribe/WasBaseSubscribe.java
  67. 63 0
      app/src/main/java/com/baoshi/swms/net/subscribe/WasSubscribe.java
  68. 146 0
      app/src/main/java/com/baoshi/swms/part/InputEditDialog.java
  69. 150 0
      app/src/main/java/com/baoshi/swms/part/RefreshHeader.java
  70. 47 0
      app/src/main/java/com/baoshi/swms/part/ScrollListView.java
  71. 18 0
      app/src/main/java/com/baoshi/swms/permission/PermissionFail.java
  72. 18 0
      app/src/main/java/com/baoshi/swms/permission/PermissionSuccess.java
  73. 122 0
      app/src/main/java/com/baoshi/swms/permission/PermissionUtil.java
  74. 84 0
      app/src/main/java/com/baoshi/swms/sqlite/DBManager.java
  75. 29 0
      app/src/main/java/com/baoshi/swms/sqlite/ISQLiteOperate.java
  76. 198 0
      app/src/main/java/com/baoshi/swms/sqlite/SQLiteDataProxy.java
  77. 56 0
      app/src/main/java/com/baoshi/swms/sqlite/TaskTable.java
  78. 109 0
      app/src/main/java/com/baoshi/swms/util/Barcode.java
  79. 118 0
      app/src/main/java/com/baoshi/swms/util/DateUtil.java
  80. 50 0
      app/src/main/java/com/baoshi/swms/util/GlobalStorage.java
  81. 62 0
      app/src/main/java/com/baoshi/swms/util/Hint.java
  82. 33 0
      app/src/main/java/com/baoshi/swms/util/NetUtil.java
  83. 106 0
      app/src/main/java/com/baoshi/swms/util/QRCode.java
  84. 17 0
      app/src/main/java/com/baoshi/swms/util/StringUtil.java
  85. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  86. 6 0
      app/src/main/res/drawable/border.xml
  87. 5 0
      app/src/main/res/drawable/border_shape.xml
  88. 14 0
      app/src/main/res/drawable/bs_logo.xml
  89. 9 0
      app/src/main/res/drawable/btn_edit.xml
  90. 13 0
      app/src/main/res/drawable/edit_background.xml
  91. 4 0
      app/src/main/res/drawable/ic__4gf_lock.xml
  92. 4 0
      app/src/main/res/drawable/ic__4gf_unlock.xml
  93. 10 0
      app/src/main/res/drawable/ic_baseline_arrow_downward_24.xml
  94. 10 0
      app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml
  95. 5 0
      app/src/main/res/drawable/ic_baseline_bookmarks_24.xml
  96. 5 0
      app/src/main/res/drawable/ic_baseline_calendar_today_24.xml
  97. 10 0
      app/src/main/res/drawable/ic_baseline_camera_24.xml
  98. 10 0
      app/src/main/res/drawable/ic_baseline_chevron_left_24.xml
  99. 10 0
      app/src/main/res/drawable/ic_baseline_edit_24.xml
  100. 6 0
      app/src/main/res/drawable/ic_baseline_home_work_24.xml

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 17 - 0
.idea/deploymentTargetDropDown.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="deploymentTargetDropDown">
+    <targetSelectedWithDropDown>
+      <Target>
+        <type value="QUICK_BOOT_TARGET" />
+        <deviceKey>
+          <Key>
+            <type value="VIRTUAL_DEVICE_PATH" />
+            <value value="C:\Users\oursdreams\.android\avd\Nexus_S_API_29.avd" />
+          </Key>
+        </deviceKey>
+      </Target>
+    </targetSelectedWithDropDown>
+    <timeTargetWasSelectedWithDropDown value="2021-12-22T02:41:44.052050900Z" />
+  </component>
+</project>

+ 3 - 0
.idea/dictionaries/oursdreams.xml

@@ -0,0 +1,3 @@
+<component name="ProjectDictionaryState">
+  <dictionary name="oursdreams" />
+</component>

+ 21 - 0
.idea/gradle.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleJvm" value="11" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 36 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,36 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="TOP_LEVEL_CLASS_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="INNER_CLASS_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="METHOD_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+          <option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
+        </value>
+      </option>
+      <option name="FIELD_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="IGNORE_DEPRECATED" value="false" />
+      <option name="IGNORE_JAVADOC_PERIOD" value="true" />
+      <option name="IGNORE_DUPLICATED_THROWS" value="false" />
+      <option name="IGNORE_POINT_TO_ITSELF" value="false" />
+      <option name="myAdditionalJavadocTags" value="date" />
+    </inspection_tool>
+  </profile>
+</component>

+ 41 - 0
.idea/misc.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DesignSurface">
+    <option name="filePathToZoomLevelMap">
+      <map>
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2526041666666667" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable/border.xml" value="0.28055555555555556" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable/border_shape.xml" value="0.38229166666666664" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable/bs_logo.xml" value="0.2526041666666667" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable/ic_baseline_home_work_24.xml" value="0.38229166666666664" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/drawable/tlr_border.xml" value="0.17222222222222222" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_batch_recover.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_check_inventory.xml" value="0.1" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_login.xml" value="0.1" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_menu.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_picking.xml" value="0.27898550724637683" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_piecework.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/activity_waybill_dispatch.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/dialog_picking_selected.xml" value="0.2798913043478261" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/dispatch_item_waybill_show.xml" value="0.2694746376811594" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/dispatch_waybill_fragment.xml" value="0.2694746376811594" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/item_check_inventory.xml" value="0.20833333333333334" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/item_waybill_item.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/part_edit_keydown.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/part_refresh_header.xml" value="0.27355072463768115" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/layout/show_item_check_inventory.xml" value="0.27309782608695654" />
+        <entry key="..\:/Users/oursdreams/AndroidStudioProjects/swms/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.38229166666666664" />
+      </map>
+    </option>
+  </component>
+  <component name="ProjectResources">
+    <resource url="http://schemas.android.com/apk/res/android" location="$PROJECT_DIR$" />
+    <resource url="http://schemas.android.com/tools" location="$PROJECT_DIR$" />
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 73 - 0
app/build.gradle

@@ -0,0 +1,73 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdk 31
+
+    defaultConfig {
+        applicationId "com.baoshi.swms"
+        minSdk 28
+        targetSdk 31
+        versionCode 1
+        versionName "1.0.4"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            //是否开启zip优化
+            zipAlignEnabled true
+            //是否开启混淆
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
+
+            android.applicationVariants.all { variant ->
+                variant.outputs.all {
+                    outputFileName = "SWMS.apk"
+                }
+            }
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    buildFeatures {
+        viewBinding true
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.3.0'
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+
+    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
+
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'androidx.navigation:navigation-fragment:2.3.5'
+    implementation 'androidx.navigation:navigation-ui:2.3.5'
+
+    implementation 'com.google.code.gson:gson:2.8.6'  //JSON解析
+    implementation 'com.contrarywind:Android-PickerView:4.1.9' //picker视图
+    implementation 'com.jakewharton:butterknife:10.2.0' //view注入
+    implementation 'com.google.zxing:core:3.3.0' //二维码
+    implementation 'com.squareup.okhttp3:okhttp:4.9.1' //http请求
+    implementation "io.reactivex.rxjava2:rxjava:2.2.0" //订阅发布
+    implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
+    implementation 'com.squareup.retrofit2:retrofit:2.6.2'//对ok3的封装
+    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.2'//适配器
+    implementation 'com.squareup.retrofit2:converter-gson:2.6.2'//转换器
+    implementation 'com.teprinciple:updateapputils:2.3.0'//自动更新
+
+    def dialogx_version = "0.0.43.beta13"
+    implementation "com.github.kongzue.DialogX:DialogX:${dialogx_version}"//消息框组件
+    implementation 'com.github.kongzue.DialogXSample:DatePicker:0.0.1.alpha4'//日期选择框扩展包依赖上述
+    implementation "com.github.kongzue.DialogX:DialogXMIUIStyle:${dialogx_version}"//主题
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

BIN
app/release/SWMS.apk


+ 20 - 0
app/release/output-metadata.json

@@ -0,0 +1,20 @@
+{
+  "version": 3,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "com.baoshi.swms",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "attributes": [],
+      "versionCode": 1,
+      "versionName": "1.0.4",
+      "outputFile": "SWMS.apk"
+    }
+  ],
+  "elementType": "File"
+}

+ 26 - 0
app/src/androidTest/java/com/baoshi/swms/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.baoshi.swms;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.baoshi.swms", appContext.getPackageName());
+    }
+}

+ 55 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.baoshi.swms">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+    <application
+        android:name=".App"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_logo"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_logo"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Swms.NoActionBar">
+        <activity
+            android:name=".activity.PickingActivity"
+            android:exported="true" />
+        <activity
+            android:name=".activity.CheckInventoryActivity"
+            android:exported="true" />
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:theme="@style/Theme.Swms.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".activity.LoginActivity" />
+        <activity android:name=".activity.MenuActivity" />
+        <activity android:name=".activity.WaybillDispatchActivity" />
+        <activity android:name=".activity.PieceworkActivity" />
+        <activity android:name=".activity.BatchRecoverActivity" />
+        <activity android:name=".activity.item.WaybillDispatchItemActivity" />
+
+        <provider
+            android:name="update.UpdateFileProvider"
+            android:authorities="${applicationId}.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/update_file_paths" />
+        </provider>
+    </application>
+
+</manifest>

BIN
app/src/main/ic_logo-playstore.png


+ 64 - 0
app/src/main/java/com/baoshi/swms/App.java

@@ -0,0 +1,64 @@
+package com.baoshi.swms;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+
+
+/**
+ * 全局上下文获取器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:53
+ */
+public class App extends Application {
+    private static App mContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mContext = this;
+
+        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+
+            }
+
+            @Override
+            public void onActivityStarted(Activity activity) {
+
+            }
+
+            @Override
+            public void onActivityResumed(Activity activity) {
+                MyActivityManager.getInstance().setCurrentActivity(activity);
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+
+            }
+        });
+    }
+
+    public static Context getAppContext() {
+        return mContext;
+    }
+}

+ 146 - 0
app/src/main/java/com/baoshi/swms/MainActivity.java

@@ -0,0 +1,146 @@
+package com.baoshi.swms;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.StrictMode;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.activity.LoginActivity;
+import com.baoshi.swms.activity.MenuActivity;
+import com.baoshi.swms.permission.PermissionUtil;
+import com.baoshi.swms.util.GlobalStorage;
+import com.baoshi.swms.util.Hint;
+import com.baoshi.swms.util.NetUtil;
+import com.google.gson.Gson;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.style.MIUIStyle;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+
+import constant.UiType;
+import model.UiConfig;
+import model.UpdateConfig;
+import update.UpdateAppUtils;
+
+/**
+ * 程序启动入口
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:52
+ */
+public class MainActivity extends AppCompatActivity {
+
+    private final String APK_URL = "https://was.baoshi56.com/app/apk.cnf";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        DialogX.globalStyle = new MIUIStyle();
+        //权限获申请
+        permissionApply();
+
+        //检查更新
+        if (!checkUpdate()){
+            //启动并检查登录状态
+            startAndCheckLogin();
+        }
+    }
+
+    /**
+     * 发生错误或者服务器问题都跳过此次更新
+     * */
+    private boolean checkUpdate() {
+        if (!NetUtil.isNetworkConnected(this)){
+            Hint.toastShort(this,"您当前无网络!",Hint.COMMON);
+            return false;
+        }
+
+        String conf = readCnf();
+        if (conf==null){
+            return false;
+        }
+        try {
+            PackageInfo packInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), 0);
+            Gson gson = new Gson();
+            Map<String,String> map = gson.fromJson(conf, Map.class);
+            if (packInfo.versionName.equals(map.get("version"))){
+                return false;
+            }
+            executeUpdate(map.get("url"),map.get("title"),map.get("content"));
+            return true;
+        }catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private void executeUpdate(String url, String title, String content) {
+        UpdateAppUtils.init(this);
+
+        UpdateConfig updateConfig = new UpdateConfig();
+        updateConfig.setNeedCheckMd5(false); //目前过不了MD5签名校验,所以先FALSE
+        updateConfig.setForce(true);
+        updateConfig.setCheckWifi(true);
+        updateConfig.setNotifyImgRes(R.drawable.ic_update_logo);
+
+        UiConfig uiConfig = new UiConfig();
+        uiConfig.setUiType(UiType.PLENTIFUL);
+        UpdateAppUtils
+                .getInstance()
+                .apkUrl(url)
+                .updateTitle(title)
+                .updateContent(content)
+                .uiConfig(uiConfig)
+                .updateConfig(updateConfig)
+                .update();
+    }
+
+    private void startAndCheckLogin() {
+        String token = GlobalStorage.getGlobalStorage().get("token");
+        //判断是否已登录
+        Intent intent = new Intent(this, token==null ?
+                LoginActivity.class : MenuActivity.class);
+        //去往登录页时不进入返回栈
+        if (token==null)intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);//登录界面不进入返回栈
+        startActivity(intent);
+    }
+
+    private void permissionApply(){
+        //获取存储
+        if (!PermissionUtil.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
+            PermissionUtil.needPermission(this, 89, Manifest.permission.READ_EXTERNAL_STORAGE);
+        }
+    }
+
+    //MAIN无内容,离开即从栈内销毁,无需返回此处
+    @Override
+    protected void onPause() {
+        super.onPause();
+        finish();
+    }
+
+    public String readCnf() {
+        try {
+            StrictMode.ThreadPolicy policy=new StrictMode.ThreadPolicy.Builder().permitAll().build();
+            StrictMode.setThreadPolicy(policy);
+            URL url = new URL(APK_URL);
+            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
+            StringBuilder str = new StringBuilder();
+            String s;
+            while ((s = in.readLine()) != null) {
+                str.append(s);
+            }
+            in.close();
+            return str.toString();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 37 - 0
app/src/main/java/com/baoshi/swms/MyActivityManager.java

@@ -0,0 +1,37 @@
+package com.baoshi.swms;
+
+import android.app.Activity;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * 获取当前Activity 这将在子进程调用UI时使用
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/3 16:45
+ */
+public class MyActivityManager {
+    private static final MyActivityManager sInstance = new MyActivityManager();
+    private WeakReference<Activity> sCurrentActivityWeakRef;
+
+
+    private MyActivityManager() {
+
+    }
+
+    public static MyActivityManager getInstance() {
+        return sInstance;
+    }
+
+    public Activity getCurrentActivity() {
+        Activity currentActivity = null;
+        if (sCurrentActivityWeakRef != null) {
+            currentActivity = sCurrentActivityWeakRef.get();
+        }
+        return currentActivity;
+    }
+
+    public void setCurrentActivity(Activity activity) {
+        sCurrentActivityWeakRef = new WeakReference<Activity>(activity);
+    }
+}

+ 107 - 0
app/src/main/java/com/baoshi/swms/activity/BatchRecoverActivity.java

@@ -0,0 +1,107 @@
+package com.baoshi.swms.activity;
+
+import android.app.ProgressDialog;
+import android.os.Bundle;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.net.callback.WasHandlerCallback;
+import com.baoshi.swms.net.subscribe.WasSubscribe;
+import com.baoshi.swms.util.Hint;
+
+/**
+ * 波次恢复 ACTIVITY
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:04
+ */
+public class BatchRecoverActivity extends AppCompatActivity implements View.OnClickListener{
+
+    private EditText editText;
+    private String code;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_batch_recover);
+
+        loadPartParam();
+        registerEvent();
+    }
+
+    /**
+     * 加载局部变量
+     */
+    private void loadPartParam(){
+        editText = findViewById(R.id.batch_code);
+    }
+
+    /**
+     * 注册事件
+     */
+    private void registerEvent() {
+        ImageView back = findViewById(R.id.back);
+        Button btn = findViewById(R.id.btn);
+
+        editText.setOnEditorActionListener((v, actionId, event) -> {
+            if (actionId==EditorInfo.IME_ACTION_DONE){
+                batchRecover();
+            }
+            return true;
+        });
+
+        back.setOnClickListener(this);
+        btn.setOnClickListener(this);
+    }
+    private boolean check(){
+        code = editText.getText().toString();
+        if (code.length()!=13){
+            Hint.toastShort(this, "错误的波次号", Hint.COMMON);
+            return false;
+        }
+        return true;
+    }
+    public void batchRecover(){
+        if (!check()){
+            return;
+        }
+        ProgressDialog dialog = Hint.showProgressDialog("修复中", this);
+        WasSubscribe.recover(code, new WasHandlerCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean result) {
+                if (result){
+                    Hint.toastShort(BatchRecoverActivity.this, "修复成功", Hint.COMMON);
+                }else{
+                    Hint.toastShort(BatchRecoverActivity.this, "修复失败", Hint.ERROR);
+                }
+                super.onSuccess(result);
+            }
+            @Override
+            public void onFinally() {
+                dialog.dismiss();
+                editText.setText("");
+                editText.requestFocus();
+            }
+        });
+    }
+
+
+    /**
+     * 点击事件
+     * @param v
+     */
+    @Override
+    public void onClick(View v) {
+        if (v.getId()==R.id.back){
+            finish();
+        }else{
+            batchRecover();
+        }
+    }
+}

+ 523 - 0
app/src/main/java/com/baoshi/swms/activity/CheckInventoryActivity.java

@@ -0,0 +1,523 @@
+package com.baoshi.swms.activity;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.text.Html;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.adapter.CheckInventoryItem;
+import com.baoshi.swms.bean.StocktakingItem;
+import com.baoshi.swms.bean.User;
+import com.baoshi.swms.bean.dto.StockingData;
+import com.baoshi.swms.bean.dto.StocktakingData;
+import com.baoshi.swms.net.callback.WasHandlerCallback;
+import com.baoshi.swms.net.subscribe.WasSubscribe;
+import com.baoshi.swms.util.DateUtil;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.datepicker.DatePickerDialog;
+import com.kongzue.dialogx.datepicker.interfaces.OnDateSelected;
+import com.kongzue.dialogx.dialogs.InputDialog;
+import com.kongzue.dialogx.dialogs.MessageDialog;
+import com.kongzue.dialogx.dialogs.TipDialog;
+import com.kongzue.dialogx.dialogs.WaitDialog;
+import com.kongzue.dialogx.interfaces.DialogLifecycleCallback;
+import com.kongzue.dialogx.interfaces.OnDialogButtonClickListener;
+import com.kongzue.dialogx.interfaces.OnInputDialogButtonClickListener;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CheckInventoryActivity extends AppCompatActivity implements View.OnClickListener, View.OnFocusChangeListener, View.OnKeyListener {
+    private final String prefix = "stocktaking";
+    private final String DATE_DEFAULT = "点此选择日期";
+    private StocktakingData stocktakingData;
+    private StocktakingItem stocktakingItem;
+    private BaseAdapter adapter;
+    private String oldLoc;
+    private String oldBarcode;
+
+    private TextView userName;
+    private TextView taskCount;
+    private TextView offStockCount;
+    private TextView taskId;
+    private TextView startDate;
+    private EditText currentLoc;
+    private EditText barcode;
+    private EditText number;
+    private TextView quality;
+    private TextView prodDate;
+    private TextView attrWarehouse;
+    private TextView expiryDate;
+    private EditText lot;
+    private ListView list;
+    private TextView stockRatio;
+    private Button btn;
+    private TextView type;
+    private MediaPlayer mediaPlayer;
+    private MediaPlayer mediaPlayerErr;
+    private Vibrator vibrator;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_check_inventory);
+
+        registerLocalVar();
+
+        DialogX.init(this);
+        inquiryBill();
+        registerEvent();
+    }
+
+    private void registerEvent() {
+        prodDate.setOnClickListener(this);
+        findViewById(R.id.back).setOnClickListener(this);
+        expiryDate.setOnClickListener(this);
+        currentLoc.setOnFocusChangeListener(this);
+        barcode.setOnFocusChangeListener(this);
+        btn.setOnClickListener(this);
+        btn.setOnKeyListener(this);
+        currentLoc.setOnKeyListener(this);
+        barcode.setOnKeyListener(this);
+        number.setOnKeyListener(this);
+    }
+
+    /**
+     * 问询单据
+     * */
+    private void inquiryBill() {
+        new InputDialog("盘点单据号",null,"开始盘点","返回")
+                .setAutoShowInputKeyboard(true)
+                .setCancelButton(new OnInputDialogButtonClickListener<InputDialog>() {
+                    @Override
+                    public boolean onClick(InputDialog baseDialog, View v, String inputStr) {
+                        finish();
+                        return false;
+                    }
+                })
+                .setCancelable(false).setOkButton(new OnInputDialogButtonClickListener<InputDialog>() {
+            @Override
+            public boolean onClick(InputDialog baseDialog, View v, String inputStr) {
+                if(inputStr.isEmpty()){
+                    TipDialog.show("请输入单据号!",WaitDialog.TYPE.WARNING);
+                    return true;
+                }
+                loadingBillInfo(inputStr);
+                return false;
+            }
+        }).setMaskColor(getResources().getColor(R.color.dark_gray,null)).show();
+    }
+    /**
+     * 加载单据信息
+     * */
+    private void loadingBillInfo(String stocktakingId){
+        WaitDialog.show("获取单据中~");
+        WasSubscribe.getStocktaking(stocktakingId, new WasHandlerCallback<StocktakingData>() {
+            @Override
+            public void onSuccess(StocktakingData result) {
+                stocktakingData = result;
+                initData();
+                super.onSuccess(result);
+            }
+            @Override
+            public void onFault(int code, String errorMsg) {
+                super.onFault(code, errorMsg);
+                inquiryBill();
+            }
+            @Override
+            public void onError(Throwable e) {
+                super.onError(e);
+                inquiryBill();
+            }
+            @Override
+            public void onFinally() {
+                WaitDialog.dismiss();
+            }
+        });
+    }
+
+    private void registerLocalVar() {
+        userName = findViewById(R.id.user_name);
+        taskCount = findViewById(R.id.task_count);
+        offStockCount = findViewById(R.id.off_stock_count);
+        taskId = findViewById(R.id.task_id);
+        startDate = findViewById(R.id.start_date);
+        currentLoc = findViewById(R.id.current_loc);
+        stockRatio = findViewById(R.id.stock_ratio);
+        barcode = findViewById(R.id.barcode);
+        number = findViewById(R.id.number);
+        quality = findViewById(R.id.quality);
+        prodDate = findViewById(R.id.prod_date);
+        attrWarehouse = findViewById(R.id.attr_warehouse);
+        expiryDate = findViewById(R.id.expiry_date);
+        lot = findViewById(R.id.lot);
+        list = findViewById(R.id.list);
+        btn = findViewById(R.id.check_btn);
+        type = findViewById(R.id.task_type);
+
+        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(currentLoc.getWindowToken(),0);
+        imm.hideSoftInputFromWindow(barcode.getWindowToken(),0);
+        imm.hideSoftInputFromWindow(number.getWindowToken(),0);
+
+        prodDate.setText(DATE_DEFAULT);
+        expiryDate.setText(DATE_DEFAULT);
+
+        if (mediaPlayer==null){
+            mediaPlayer = MediaPlayer.create(this,R.raw.tip);
+        }
+        if (mediaPlayerErr==null){
+            mediaPlayerErr = MediaPlayer.create(this,R.raw.error);
+        }
+        if (vibrator==null){
+            vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+        }
+    }
+    private void setVibrator(){
+        mediaPlayerErr.start();
+        vibrator.vibrate(VibrationEffect.createOneShot(1000, -1));
+    }
+
+    private void initData() {
+        //设置一些label的颜色标签
+        String[] labels = new String[]{
+                "current_loc","barcode","number","quality","prod_date",
+                "attr_warehouse","expiry_date","lot"
+        };
+        //通过反射设置label资源,默认规则:资源加stocking前缀  id加label后缀
+        for (String val: labels) {
+            ((TextView) findViewById(getResources().getIdentifier(val+"_label","id",this.getPackageName()))).setText(
+                    Html.fromHtml(getString(getResources().getIdentifier(prefix+"_"+val,"string",this.getPackageName())),Html.FROM_HTML_MODE_COMPACT)
+            );
+        }
+        //一些需要组合赋值的显示数据
+        userName.setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_user_name, User.getLocalUser().getName()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        startDate.setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_start_date, DateUtil.getCurrentFormat(DateUtil.DATE_DATE_TIME_CN)),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+
+        taskCount.setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_task_count,stocktakingData.getStocktaking().getTotal()-stocktakingData.getStocktaking().getProcessed()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        offStockCount.setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_off_stock_count,stocktakingData.getTodayWorkNumber()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        taskId.setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_task_id,"PD"+stocktakingData.getStocktaking().getId()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        type.setText(stocktakingData.getStocktaking().getType());
+        adapter = new CheckInventoryItem(stocktakingData.getNotStocktakingList(),CheckInventoryActivity.this);
+        list.setAdapter(adapter);
+        currentLoc.requestFocus();
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId()==R.id.prod_date || v.getId()==R.id.expiry_date){
+            datePicker(v.getId());
+        }
+        if (v.getId()==R.id.check_btn){
+            stocktaking();
+        }
+        if (v.getId()==R.id.back){
+            finish();
+        }
+    }
+
+    private void stocktaking() {
+        if (stocktakingItem==null || stocktakingItem.getId()==null || number.getText().toString().isEmpty()){
+            TipDialog.show("单据获取失败或数量未填写!",WaitDialog.TYPE.WARNING);
+            return;
+        }
+        WaitDialog.show("提交盘点信息中...");
+        WasSubscribe.stocktaking(stocktakingParamFormat(), new WasHandlerCallback<StockingData>() {
+            @Override
+            public void onSuccess(StockingData data) {
+                super.onSuccess(data);
+                if (data.isDiff()){
+                    AtomicBoolean cancelSign = new AtomicBoolean(false);
+                    MessageDialog.show("盘点信息", "存在差异,是否重盘?", "重盘","取消")
+                            .setOkButtonClickListener(new OnDialogButtonClickListener<MessageDialog>() {
+                                @Override
+                                public boolean onClick(MessageDialog baseDialog, View v) {
+                                    cancelSign.set(true);
+                                    return false;
+                                }
+                            });
+                    if (cancelSign.get()){
+                        number.setText("");
+                        number.requestFocus();
+                        return;
+                    }
+                }
+                ArrayList<HashMap<String, String>> result = data.getList();
+                mediaPlayer.start();
+                //没有待盘项结束该盘单
+                if (result.size()==0){
+                    TipDialog.show("盘点完毕!",WaitDialog.TYPE.SUCCESS).setDialogLifecycleCallback(new DialogLifecycleCallback<WaitDialog>() {
+                        @Override
+                        public void onDismiss(WaitDialog dialog) {
+                            clearInfo();
+                            currentLoc.setText("");
+                            stockRatio.setText("已/总");
+                            inquiryBill();//唤起询单
+                        }
+                    });
+                    return;
+                }
+                //还有待盘 重设待盘列表
+                stocktakingData.getNotStocktakingList().clear();
+                stocktakingData.getNotStocktakingList().addAll(result);
+                adapter.notifyDataSetChanged();
+
+                //当前库位未盘完
+                AtomicBoolean breakSign = new AtomicBoolean(false);
+                AtomicInteger count = new AtomicInteger(0);
+                result.forEach(item->{
+                    if (item.get("location").equals(currentLoc.getText().toString())){
+                        breakSign.set(true);
+                        String[] all=stockRatio.getText().toString().split("/");
+                        String ratio = String.valueOf(((int)Integer.parseInt(all[1]))-((int)Integer.parseInt(item.get("count"))))+"/"+all[1];
+                        stockRatio.setText(ratio);
+                    }
+                    count.set(count.get()+Integer.parseInt(item.get("count")));
+                });
+                //重置未盘数
+                taskCount.setText(
+                        Html.fromHtml(
+                                getString(R.string.stocktaking_task_count,count.get()),
+                                Html.FROM_HTML_MODE_COMPACT));
+                clearInfo();
+                //改变已盘数
+                stocktakingData.setTodayWorkNumber(stocktakingData.getTodayWorkNumber()+1);
+                offStockCount.setText(
+                        Html.fromHtml(
+                                getString(R.string.stocktaking_off_stock_count,stocktakingData.getTodayWorkNumber()),
+                                Html.FROM_HTML_MODE_COMPACT));
+                if(breakSign.get()){
+                    barcode.requestFocus();
+                    return;
+                }
+                //当前库位已盘完
+                currentLoc.setText("");
+                stockRatio.setText("已/总");
+            }
+            @Override
+            public void onFinally() {
+                WaitDialog.dismiss();
+            }
+        });
+    }
+
+    private void clearInfo(){
+        number.setText("");
+        barcode.setText("");
+        quality.setText("");
+        attrWarehouse.setText("");
+        prodDate.setText(DATE_DEFAULT);
+        expiryDate.setText(DATE_DEFAULT);
+        lot.setText("");
+    }
+    private HashMap<String,Object> stocktakingParamFormat(){
+        return new HashMap<String,Object>(){{
+            put("task_item_id",stocktakingItem.getId());
+            put("verified_amount",number.getText().toString());
+            if (!(stocktakingItem.getProdAt()==null && prodDate.getText().toString().isEmpty()) &&
+                    !prodDate.getText().toString().equals(DATE_DEFAULT) &&
+                    !DateUtil.getTime(stocktakingItem.getProdAt()).contentEquals(prodDate.getText())){
+                put("produced_at",prodDate.getText().toString());
+            }
+            if (!(stocktakingItem.getExpiryDate()==null && expiryDate.getText().toString().isEmpty()) &&
+                    !expiryDate.getText().toString().equals(DATE_DEFAULT) &&
+                    !DateUtil.getTime(stocktakingItem.getExpiryDate()).contentEquals(expiryDate.getText())){
+                put("valid_at",expiryDate.getText().toString());
+            }
+            if (!(stocktakingItem.getLotNum()==null && lot.getText().toString().isEmpty()) &&
+                    !stocktakingItem.getLotNum().equals(lot.getText().toString())){
+                put("batch_number",lot.getText().toString());
+            }
+        }};
+    }
+    private void datePicker(int id){
+        Calendar cal = Calendar.getInstance();
+        int day = cal.get(Calendar.DATE);
+        int month = cal.get(Calendar.MONTH) + 1;
+        int year = cal.get(Calendar.YEAR);
+        DatePickerDialog picker = DatePickerDialog.build().setDefaultSelect(year,month,day);
+        if (id==R.id.expiry_date){
+            picker.setTitle("失效日期").setMinTime(year,month,day);
+        }else{
+            picker.setTitle("生产日期").setMaxTime(year,month,day);
+        }
+        picker.show(new OnDateSelected() {
+                    @Override
+                    public void onSelect(String text, int year, int month, int day) {
+                        if(id==R.id.expiry_date){
+                            expiryDate.setText(text);
+                        }else{
+                            prodDate.setText(text);
+                        }
+                    }
+                });
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v.getId()==R.id.current_loc){
+            getLocRatio();
+        }
+        if (v.getId()==R.id.barcode){
+            getLocGoodsInfo();
+        }
+    }
+
+    private void getLocGoodsInfo() {
+        String locStr = currentLoc.getText().toString();
+        String barcodeStr = barcode.getText().toString();
+        if (locStr.isEmpty() || barcodeStr.isEmpty()){
+            return;
+        }
+        if (oldLoc!=null && oldBarcode!=null && oldLoc.equals(locStr) && oldBarcode.equals(barcodeStr)){
+            return;
+        }
+        oldLoc = locStr;
+        oldBarcode = barcodeStr;
+        WasSubscribe.getLocGoodsInfo(stocktakingData.getStocktaking().getId(), locStr, barcodeStr, new WasHandlerCallback<StocktakingItem>() {
+            @Override
+            public void onSuccess(StocktakingItem result) {
+                stocktakingItem = result;
+                if (result.getVerifiedAmount()!=null){
+                    number.setText(String.valueOf(result.getVerifiedAmount()));
+                }
+                if (result.getQuality()!=null){
+                    quality.setText(result.getQuality());
+                }
+                if (result.getProdAt()!=null){
+                    prodDate.setText(DateUtil.getTime(result.getProdAt()));
+                }
+                if (result.getExpiryDate()!=null){
+                    expiryDate.setText(DateUtil.getTime(result.getExpiryDate()));
+                }
+                lot.setText(result.getLotNum());
+                attrWarehouse.setText(result.getAttrWarehouse());
+                number.requestFocus();
+            }
+
+            @Override
+            public void onFault(int code, String errorMsg) {
+                super.onFault(code, errorMsg);
+                barcode.requestFocus();
+                mediaPlayerErr.start();
+                setVibrator();
+            }
+
+            @Override
+            public void onError(Throwable e) {
+                super.onError(e);
+                barcode.requestFocus();
+                mediaPlayerErr.start();
+                setVibrator();
+            }
+
+            @Override
+            public void onFinally() {
+                oldLoc = null;
+                oldBarcode = null;
+                barcode.setText("");
+            }
+        });
+    }
+
+    private void getLocRatio() {
+        String loc = currentLoc.getText().toString();
+        if (loc.isEmpty()){
+            return;
+        }
+        if (oldLoc!=null && oldLoc.equals(loc)){
+            return;
+        }
+        oldLoc = loc;
+        WasSubscribe.getLocStocktakingRatio(stocktakingData.getStocktaking().getId(), loc, new WasHandlerCallback<HashMap<String,String>>(){
+            @Override
+            public void onSuccess(HashMap<String, String> result) {
+                stockRatio.setText(result.get("invCount")+"/"+result.get("total"));
+                barcode.requestFocus();
+            }
+            @Override
+            public void onFault(int code, String errorMsg) {
+                super.onFault(code, errorMsg);
+                currentLoc.requestFocus();
+                mediaPlayerErr.start();
+                setVibrator();
+            }
+            @Override
+            public void onError(Throwable e) {
+                super.onError(e);
+                currentLoc.requestFocus();
+                mediaPlayerErr.start();
+                setVibrator();
+            }
+            @Override
+            public void onFinally() {
+                oldLoc = null;
+                currentLoc.setText("");
+            }
+        });
+    }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ENTER){
+            if (event.getAction()==KeyEvent.ACTION_UP){
+                switch (v.getId()){
+                    case R.id.current_loc:
+                        getLocRatio();
+                        break;
+                    case R.id.barcode:
+                        getLocGoodsInfo();
+                        break;
+                }
+            }
+            if (event.getAction()==KeyEvent.ACTION_DOWN){
+                if (v.getId()==R.id.number){
+                    btn.requestFocus();
+                    btn.requestFocusFromTouch();
+                }
+                if (v.getId()==R.id.check_btn){
+                    stocktaking();
+                }
+            }
+        }
+        return false;
+    }
+}

+ 80 - 0
app/src/main/java/com/baoshi/swms/activity/LoginActivity.java

@@ -0,0 +1,80 @@
+package com.baoshi.swms.activity;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.User;
+import com.baoshi.swms.bean.dto.Login;
+import com.baoshi.swms.net.callback.WasHandlerCallback;
+import com.baoshi.swms.net.subscribe.WasBaseSubscribe;
+import com.baoshi.swms.net.subscribe.WasSubscribe;
+import com.baoshi.swms.util.GlobalStorage;
+import com.baoshi.swms.util.Hint;
+import com.baoshi.swms.util.StringUtil;
+import com.google.gson.Gson;
+
+/**
+ * 登录Activity
+ */
+public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
+
+    private String username;
+    private String password;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_login);
+        registerEvent();
+    }
+
+    private void registerEvent() {
+        Button submit = findViewById(R.id.login_submit);
+        submit.setOnClickListener(this);
+    }
+
+
+    private boolean check() {
+        EditText usernameView = findViewById(R.id.login_user_name);
+        EditText passwordView = findViewById(R.id.login_password);
+        username = StringUtil.trimLeft(usernameView.getText().toString());
+        password = StringUtil.trimLeft(passwordView.getText().toString());
+        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)){
+            Hint.toastShort(LoginActivity.this, "账号名或密码为空", Hint.COMMON);
+            return false;
+        }
+        return true;
+    }
+    @Override
+    public void onClick(View v) {
+        if (!check()){
+            return;
+        };
+        ProgressDialog dialog = Hint.showProgressDialog("登录中", LoginActivity.this);
+        WasSubscribe.login(username, password, new WasHandlerCallback<Login>() {
+            @Override
+            public void onSuccess(Login result) {
+                Gson gson = new Gson();
+                GlobalStorage storage = GlobalStorage.getGlobalStorage();
+                storage.put("token", result.getToken());
+                storage.put("menu", result.getMenu());
+                storage.put("user", gson.toJson(result.getUser(),User.class));
+                WasBaseSubscribe.refreshSingleton();//因为此处为登录请求为了防止请求头的设定,需要在成功后清除当前 NETWORK单例对象
+                startActivity(new Intent(LoginActivity.this, MenuActivity.class));
+                super.onSuccess(result);
+            }
+            @Override
+            public void onFinally(){
+                dialog.dismiss();
+            }
+        });
+    }
+}

+ 232 - 0
app/src/main/java/com/baoshi/swms/activity/MenuActivity.java

@@ -0,0 +1,232 @@
+package com.baoshi.swms.activity;
+
+import android.content.Intent;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.util.ArrayMap;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.Menu;
+import com.baoshi.swms.util.GlobalStorage;
+import com.baoshi.swms.util.Hint;
+import com.google.gson.Gson;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 菜单Activity
+ */
+public class MenuActivity extends AppCompatActivity implements View.OnClickListener {
+
+    private ArrayMap<String, HashMap<String, Menu>> menuMap = new ArrayMap<>();
+    private final int columnCount = 4; //每列多少元素节点
+    private int back = 0; //回退次数标记
+
+    /**
+     * 获取全部的菜单映射信息
+     */
+    public MenuActivity(){
+        this.menuMap = Menu.getMenuNeedVerifyList();
+    }
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_menu);
+        initData();
+        registerEvent(); //注册事件
+    }
+
+    //初始化数据
+    private void initData() {
+        formatData(GlobalStorage.getGlobalStorage().get("menu"));
+        if (this.menuMap.size()==0){
+            emptyView();
+        }else{
+            rendingView();
+        }
+    }
+
+    //格式化信息
+    private void formatData(String json) {
+        Gson gson = new Gson();
+        Map<String, ArrayList<String>> menus = gson.fromJson(json, Map.class);
+        ArrayMap<String, HashMap<String, Menu>> menuMap = new ArrayMap<>();
+        for (Map.Entry<String, ArrayList<String>> entry : menus.entrySet()) {
+            String key = entry.getKey();
+            ArrayList<String> values = entry.getValue();
+            HashMap<String, Menu> item = new HashMap<>();
+            item.put("icon",Objects.requireNonNull(this.menuMap.get(key)).get("icon"));
+            for (String itemKey : values){
+                if (Objects.requireNonNull(this.menuMap.get(key)).containsKey(itemKey))
+                    item.put(itemKey, Objects.requireNonNull(this.menuMap.get(key)).get(itemKey));
+            }
+            menuMap.put(key,item);
+        }
+        menuMap.putAll(Menu.getMenuNotNeedVerifyList());
+        this.menuMap = menuMap;
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (back==0){
+            back++;
+            Hint.toastShort(this, "再按一下退出应用", Hint.COMMON);
+        }else{
+            back = 0;
+            finish();
+        }
+    }
+
+    private void registerEvent() {
+        Button logout = findViewById(R.id.logout);
+        logout.setOnClickListener(this);
+    }
+    @Override
+    public void onClick(View v) {
+        //退出登录时清除当前登录信息
+        GlobalStorage storage = GlobalStorage.getGlobalStorage();
+        storage.remove("token");
+        storage.remove("menu");
+        Intent intent = new Intent(MenuActivity.this,LoginActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);//登录界面不进入返回栈
+        startActivity(intent);
+    }
+
+    //空数据渲染视图
+    private void emptyView(){
+        GridLayout grid = findViewById(R.id.menu);
+        TextView textView = new TextView(this);
+        textView.setGravity(Gravity.CENTER);
+        textView.setText("您暂未开通任何权限~");
+        textView.setTextColor(getResources().getColor(R.color.dark_gray));
+        GridLayout.LayoutParams params = new GridLayout.LayoutParams();
+        params.width =0;
+        params.height =100;
+        params.rowSpec = GridLayout.spec(0);
+        params.columnSpec = GridLayout.spec(0,columnCount,1f);
+        grid.addView(textView,params);
+    }
+    //渲染视图菜单列表
+    private void rendingView() {
+        GridLayout grid = findViewById(R.id.menu);
+        grid.setColumnCount(columnCount);
+        int index = 0;
+        for (Map.Entry<String, HashMap<String, Menu>> entry : this.menuMap.entrySet()) {
+            rendingChild(index,grid,entry.getKey(),entry.getValue().get("icon"));
+            int childIndex = 0;
+            for (String key : entry.getValue().keySet()) {
+                Menu value = entry.getValue().get(key);
+                if(key.equals("icon") || value == null)continue;
+                if (childIndex%columnCount==0){
+                    index++;childIndex = 0;
+                }
+                rendingChild(index,childIndex,grid,key,value);
+                childIndex++;
+            }
+            if (childIndex>0 && childIndex!=columnCount){
+                for (int i=0;i<columnCount-childIndex;i++){
+                    rendingChild(index,childIndex+i,grid);
+                }
+            }
+            index++;
+        }
+    }
+    //一级子节点
+    private void rendingChild(int index, GridLayout grid, String key, Menu menu){
+        LinearLayout container = new LinearLayout(this);
+        container.setBackgroundColor(getResources().getColor(R.color.secondary));
+        GridLayout.LayoutParams params = new GridLayout.LayoutParams();
+        params.setMargins(0,10,0,0);
+        params.width =0;
+        params.height =100;
+        params.rowSpec = GridLayout.spec(index);
+        params.columnSpec = GridLayout.spec(0,columnCount,1f);
+
+        //ICON渲染
+        ImageView icon = new ImageView(container.getContext());
+        LinearLayout.LayoutParams lParam = new LinearLayout.LayoutParams(65, 65);
+        lParam.setMarginEnd(7);
+        lParam.gravity = Gravity.CENTER;
+        lParam.setMarginStart(8);
+        icon.setLayoutParams(lParam);
+        icon.setBackground(getDrawable(menu!=null ? menu.icon : R.drawable.icon_default));
+        container.addView(icon);
+
+        //文本渲染
+        TextView textView = new TextView(container.getContext());
+        LinearLayout.LayoutParams tParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        tParam.gravity = Gravity.CENTER;
+        textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+        textView.setTextSize(20);
+        textView.setLayoutParams(tParam);
+        textView.setText(key);
+        textView.setTextColor(getResources().getColor(R.color.black));
+        container.addView(textView);
+
+        grid.addView(container,params);
+    }
+
+    //二级子节点
+    private void rendingChild(int index,int childIndex,GridLayout grid, String key, Menu menu){
+        if (menu==null)return;
+        LinearLayout container = new LinearLayout(this);
+        container.setPadding(25,25,25,25);
+        container.setOrientation(LinearLayout.VERTICAL);
+        GridLayout.LayoutParams params = new GridLayout.LayoutParams();
+        params.width =0;
+        params.rowSpec = GridLayout.spec(index);
+        params.columnSpec = GridLayout.spec(childIndex,1,1f);
+
+        //ICON渲染
+        ImageView icon = new ImageView(container.getContext());
+        LinearLayout.LayoutParams lParam = new LinearLayout.LayoutParams(55, 45);
+        lParam.gravity = Gravity.CENTER;
+        icon.setLayoutParams(lParam);
+        icon.setBackground(getDrawable(menu!=null ? menu.icon : R.drawable.icon_default));
+        container.addView(icon);
+
+        //文本渲染
+        TextView textView = new TextView(container.getContext());
+        LinearLayout.LayoutParams tParam = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        tParam.gravity = Gravity.CENTER;
+        textView.setTextSize(10);
+        textView.setTextColor(getResources().getColor(R.color.info));
+        textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+        textView.setLayoutParams(tParam);
+        textView.setText(key);
+        container.addView(textView);
+
+        grid.addView(container,params);
+        container.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent();
+                intent.setClass(MenuActivity.this, menu.reference);
+                startActivity(intent);
+            }
+        });
+    }
+    //空节点填充
+    private void rendingChild(int index,int childIndex,GridLayout grid){
+        TextView textView = new TextView(this);
+        GridLayout.LayoutParams params = new GridLayout.LayoutParams();
+        params.width =0;
+        params.rowSpec = GridLayout.spec(index);
+        params.columnSpec = GridLayout.spec(childIndex,1,1f);
+        grid.addView(textView,params);
+    }
+}

+ 726 - 0
app/src/main/java/com/baoshi/swms/activity/PickingActivity.java

@@ -0,0 +1,726 @@
+package com.baoshi.swms.activity;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.text.Html;
+import android.text.InputType;
+import android.util.DisplayMetrics;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.Picking;
+import com.baoshi.swms.bean.PickingItem;
+import com.baoshi.swms.bean.PickingNumber;
+import com.baoshi.swms.bean.User;
+import com.baoshi.swms.bean.dto.PickingDto;
+import com.baoshi.swms.net.callback.SwmsHandlerCallback;
+import com.baoshi.swms.net.subscribe.SwmsSubscribe;
+import com.baoshi.swms.sqlite.DBManager;
+import com.baoshi.swms.sqlite.SQLiteDataProxy;
+import com.baoshi.swms.sqlite.TaskTable;
+import com.baoshi.swms.util.Barcode;
+import com.baoshi.swms.util.DateUtil;
+import com.baoshi.swms.util.GlobalStorage;
+import com.google.gson.Gson;
+import com.kongzue.dialogx.DialogX;
+import com.kongzue.dialogx.dialogs.MessageDialog;
+import com.kongzue.dialogx.dialogs.TipDialog;
+import com.kongzue.dialogx.dialogs.WaitDialog;
+import com.kongzue.dialogx.interfaces.OnBindView;
+import com.kongzue.dialogx.interfaces.OnDialogButtonClickListener;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.reactivex.Observable;
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+public class PickingActivity extends AppCompatActivity implements View.OnKeyListener, View.OnClickListener {
+    private Picking picking;
+    private PickingItem pickingItem;
+    private PickingNumber pickingNumber;
+    private final String prefix = "picking";
+    private final String BTN_TEXT_BEFORE = "提前完成拣货";
+    private final String BTN_TEXT = "完成当前拣货";
+    private TextView taskCount;
+    private TextView offStockCount;
+    private TextView group;
+    private TextView gather;
+    private TextView taskId;
+    private TextView taskRatio;
+    private TextView startTime;
+    private TextView whenTime;
+    private TextView way;
+    private TextView currentLoc;
+    private TextView number;
+    private EditText barcode;
+    private EditText verifyNumber;
+    private long currentMillers;
+    private Button btn;
+    private RadioGroup ratio;
+    private TaskTable taskDb;
+    private MediaPlayer mediaPlayer;
+    private MediaPlayer mediaPlayerErr;
+    private Vibrator vibrator;
+    private boolean lockStatus;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_picking);
+
+        //初始化DialogX组件
+        DialogX.init(this);
+
+        //注册一些局部变量与赋值
+        registerLocalVar();
+        //注册事件
+        registerEvent();
+        //判断是否有本地任务未结束
+        if (!checkLocalTask()){
+            inquiryTaskStart();//询问是否获取新任务
+        }else{
+            loadLocalTask();//加载本地任务
+        }
+    }
+    @Override
+    public void onDestroy(){
+        super.onDestroy();
+        DBManager.close(this);
+    }
+
+    /**
+     * 注册事件
+     * */
+    private void registerEvent() {
+        barcode.setOnKeyListener(this);
+        verifyNumber.setOnKeyListener(this);
+        btn.setOnKeyListener(this);
+        btn.setOnClickListener(this);
+        findViewById(R.id.back).setOnClickListener(this);
+        findViewById(R.id.lock).setOnClickListener(this);
+        findViewById(R.id.give_up).setOnClickListener(this);
+    }
+
+    /**
+     * 注册局部变量
+     * */
+    private void registerLocalVar() {
+        taskCount = findViewById(R.id.task_count);
+        offStockCount = findViewById(R.id.off_stock_count);
+        group = findViewById(R.id.group);
+        gather = findViewById(R.id.gather);
+        taskId = findViewById(R.id.task_id);
+        taskRatio = findViewById(R.id.task_ratio);
+        startTime = findViewById(R.id.start_time);
+        whenTime = findViewById(R.id.when_time);
+        way = findViewById(R.id.way);
+        currentLoc = findViewById(R.id.current_loc);
+        number = findViewById(R.id.number);
+        barcode = findViewById(R.id.barcode);
+        verifyNumber = findViewById(R.id.verify_number);
+        btn = findViewById(R.id.btn);
+
+        lockStatus = true;
+        barcode.setInputType(InputType.TYPE_NULL);
+
+        if (mediaPlayer==null){
+            mediaPlayer = MediaPlayer.create(this,R.raw.tip);
+        }
+        if (mediaPlayerErr==null){
+            mediaPlayerErr = MediaPlayer.create(this,R.raw.error);
+        }
+        if (vibrator==null){
+            vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+        }
+        pickingNumber = new PickingNumber();
+    }
+    /**
+     * 用作错误时的 语音与震动
+     * */
+    private void setVibrator(){
+        mediaPlayerErr.start();
+        vibrator.vibrate(VibrationEffect.createOneShot(1000, -1));
+    }
+
+    /**
+     * 检查是否有本地任务
+     * */
+    private boolean checkLocalTask() {
+        taskDb = new TaskTable(this);
+        Cursor cursor = DBManager.query(this, "select 1 from task LIMIT 2");
+        int count = cursor.getCount();
+        cursor.close();
+        return count>0;
+    }
+    /**
+     * 询问是否获取新任务
+     * */
+    private void inquiryTaskStart() {
+        MessageDialog.show("宝时好拣单", "是否获取单据任务", "获取","返回").setOkButton(new OnDialogButtonClickListener<MessageDialog>() {
+            @Override
+            public boolean onClick(MessageDialog baseDialog, View v) {
+                getTask();
+                return false;
+            }
+        }).setCancelButton(new OnDialogButtonClickListener<MessageDialog>() {
+            @Override
+            public boolean onClick(MessageDialog baseDialog, View v) {
+                finish();
+                return false;
+            }
+        }).setCustomView(new OnBindView<MessageDialog>(R.layout.dialog_picking_selected) {
+            @Override
+            public void onBind(MessageDialog dialog, View v) {
+                ratio = v.findViewById(R.id.ratio);
+            }
+        }).setCancelable(false);
+    }
+
+    /**
+     * 加载本地任务 设置pickingNumber的一些数值 用以initData
+     * */
+    private void loadLocalTask(){
+        WaitDialog.show("加载任务信息中~");
+        Cursor cursor = DBManager.query(this,"SELECT * FROM "+TaskTable.TABLE_NAME);
+        if (cursor!=null){
+            pickingNumber.setUnfinishedTaskCount(0);
+            pickingNumber.setTotal(0);
+            pickingNumber.setBeenQty(0);
+            while(cursor.moveToNext()){
+                int aqIndex = cursor.getColumnIndex("anticipatedQuantity");
+                int rqIndex = cursor.getColumnIndex("realQuantity");
+                int piIndex = cursor.getColumnIndex("pickingTime");
+
+                if (cursor.getString(piIndex)==null){
+                    pickingNumber.setUnfinishedTaskCount(pickingNumber.getUnfinishedTaskCount()+1);
+                }
+                pickingNumber.setTotal(pickingNumber.getTotal()+cursor.getInt(aqIndex));
+                pickingNumber.setBeenQty(pickingNumber.getBeenQty()+cursor.getInt(rqIndex));
+            }
+            cursor.close();
+        }
+        Gson gson = new Gson();
+        GlobalStorage storage = GlobalStorage.getGlobalStorage();
+        picking = gson.fromJson(storage.get("PICKING_TASK"),Picking.class);
+        SwmsSubscribe.getUserTodayWorkNum(new SwmsHandlerCallback<Integer>() {
+            @Override
+            public void onSuccess(Integer result) {
+                pickingNumber.setUserBeenWorkCount(result);
+                super.onSuccess(result);
+            }
+            @Override
+            public void onFinally() {
+                initData();
+                WaitDialog.dismiss();
+            }
+        });
+    }
+
+    /**
+     * 发起请求,获取任务信息
+     * */
+    private void getTask() {
+        WaitDialog.show("任务获取中~");
+        String type = ((RadioButton)findViewById(ratio.getCheckedRadioButtonId())).getText().toString();
+        SwmsSubscribe.getPickingTask(type, new SwmsHandlerCallback<PickingDto>() {
+            @Override
+            public void onSuccess(PickingDto result) {
+                super.onSuccess(result);
+                handlerTask(result);
+            }
+            @Override
+            public void onFinally() {
+                WaitDialog.dismiss();
+            }
+        });
+    }
+
+    /**
+     * 清除当前本地任务
+     * */
+    private void clearTask(){
+        GlobalStorage storage = GlobalStorage.getGlobalStorage();
+        storage.remove("PICKING_TASK");
+        DBManager.execSQL(this,"DELETE FROM "+TaskTable.TABLE_NAME);
+        inquiryTaskStart();
+    }
+    /**
+     * 放弃任务
+     * */
+    private void giveUpTask(){
+        WaitDialog.show("任务取消中...");
+        SwmsSubscribe.giveUpTask(picking.getCode(), new SwmsHandlerCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean result) {
+                super.onSuccess(result);
+                clearTask();
+            }
+            @Override
+            public void onFinally() {
+                WaitDialog.dismiss();
+            }
+        });
+    }
+
+    /**
+     * 处理获取任务
+     * */
+    private void handlerTask(PickingDto pickingDto){
+        picking = pickingDto.getTask();
+        if (picking==null){
+            TipDialog.show("暂无拣货任务,请稍后尝试!", WaitDialog.TYPE.WARNING);
+            return;
+        }
+        pickingNumber.setUserBeenWorkCount(pickingDto.getTodayWorkNumber());
+        if (pickingDto.getUnfinished()){ //存在未完成任务
+            handlerUnfinished(pickingDto.getItems());//处理未完任务
+        }else{
+            loadTask(pickingDto.getItems());//处理新任务
+        }
+    }
+
+    /**
+     * 加载任务信息
+     * */
+    private void loadTask(ArrayList<PickingItem> items){
+        //将当前picking序列化存储
+        Gson gson = new Gson();
+        GlobalStorage storage = GlobalStorage.getGlobalStorage();
+        storage.put("PICKING_TASK",gson.toJson(picking));
+        SQLiteDatabase db = SQLiteDataProxy.getSQLiteProxy(this).getSQLiteDataBase();
+        db.beginTransaction();
+        try{
+            DBManager.execSQL(this,"DELETE FROM "+TaskTable.TABLE_NAME);
+            AtomicInteger index = new AtomicInteger(1);
+            pickingItem = null;
+            AtomicInteger atTotal = new AtomicInteger(0);
+            AtomicInteger atBeenQty = new AtomicInteger(0);
+            AtomicInteger unfinishedTaskCount = new AtomicInteger(0);
+            items.forEach(item->{
+                atTotal.set(atTotal.get()+item.getAnticipatedQuantity());
+                if (item.getRealQuantity()!=null){
+                    atBeenQty.set(atBeenQty.get()+item.getRealQuantity());
+                }
+                ContentValues values = new ContentValues();
+                values.put("id",index.get());
+                values.put("detailId",item.getDetailId());
+                values.put("location",item.getLocation());
+                values.put("way",item.getWay());
+                values.put("barcode",item.getBarcode());
+                values.put("barcodeAs",item.getBarcodeAs());
+                values.put("anticipatedQuantity",item.getAnticipatedQuantity());
+                values.put("realQuantity",item.getRealQuantity());
+                values.put("pickingTime",item.getPickingTime());
+                db.insert(TaskTable.TABLE_NAME,null,values);
+                index.getAndIncrement();
+                if (item.getPickingTime()==null){
+                    unfinishedTaskCount.getAndIncrement();
+                }
+            });
+            pickingNumber.setUnfinishedTaskCount(unfinishedTaskCount.get());
+            pickingNumber.setTotal(atTotal.get());
+            pickingNumber.setBeenQty(atBeenQty.get());
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        initData();
+    }
+
+    /**
+     * 询问是否放弃
+     * */
+    private void handlerUnfinished(ArrayList<PickingItem> items){
+        MessageDialog.show("存在未完任务", "是否要放弃历史未完任务", "加载任务","放弃任务").setOkButton(new OnDialogButtonClickListener<MessageDialog>() {
+            @Override
+            public boolean onClick(MessageDialog baseDialog, View v) {
+                loadTask(items);
+                return false;
+            }
+        }).setCancelButton(new OnDialogButtonClickListener<MessageDialog>() {
+            @Override
+            public boolean onClick(MessageDialog baseDialog, View v) {
+                giveUpTask();
+                return false;
+            }
+        }).setCancelable(false);
+    }
+
+
+    /**
+     * 视图数据的初始化渲染,需要等一切数据加载完毕后才可调用
+     * */
+    private void initData(){
+        //设置一些label的颜色标签
+        String[] labels = new String[]{
+                "way","current_loc","number","barcode"
+        };
+        //通过反射设置label资源,默认规则:资源加stocking前缀  id加label后缀
+        for (String val: labels) {
+            ((TextView) findViewById(getResources().getIdentifier(val+"_label","id",this.getPackageName()))).setText(
+                    Html.fromHtml(getString(getResources().getIdentifier(prefix+"_"+val,"string",this.getPackageName())),Html.FROM_HTML_MODE_COMPACT)
+            );
+        }
+
+        //一些需要组合赋值的显示数据
+        ((TextView)findViewById(R.id.user_name)).setText(
+                Html.fromHtml(
+                        getString(R.string.stocktaking_user_name, User.getLocalUser().getName()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        group.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_group, picking.getGroup()==null ? "" : picking.getGroup()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        gather.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_gather, picking.getVenue()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        taskId.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_task_id, picking.getCode()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        startTime.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_start_time, picking.getOperationTime()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        ((TextView)findViewById(R.id.owner_code)).setText(picking.getOwnerCode());
+        //渲染条码
+        WindowManager manager = this.getWindowManager();
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        manager.getDefaultDisplay().getMetrics(outMetrics);
+        ((ImageView)findViewById(R.id.barcode_img)).setImageBitmap(Barcode.createBarcode(picking.getWaveCode(),outMetrics.widthPixels,60,false));
+        //渲染计时器
+        workTimer();
+        //聚焦
+        barcode.requestFocus();
+        //渲染局部视图
+        changeNumberUi();
+        //渲染初始按钮与初始库位
+        btn.setText(BTN_TEXT);
+        pickingItem = new PickingItem();
+        pickingItem.setId(0);
+        completeLoad();
+    }
+
+    /**
+     * 更新数值部分UI
+     * */
+    void changeNumberUi(){
+        taskCount.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_task_count, pickingNumber.getUnfinishedTaskCount()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        offStockCount.setText(
+                Html.fromHtml(
+                        getString(R.string.picking_off_stock_count, pickingNumber.getUserBeenWorkCount()),
+                        Html.FROM_HTML_MODE_COMPACT)
+        );
+        taskRatio.setText(String.valueOf(pickingNumber.getBeenQty())+"/"+String.valueOf(pickingNumber.getTotal()));
+    }
+
+    /**
+     * 计时器开始
+     * */
+    private void workTimer() {
+        currentMillers = (new Date().getTime() - DateUtil.strToDate(picking.getOperationTime()).getTime())/1000;
+        Observable.interval(0,1, TimeUnit.SECONDS)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(new Observer<Long>() {
+                               @Override
+                               public void onSubscribe(@NonNull Disposable disposable) {
+                               }
+                               @Override
+                               public void onNext(@NonNull Long number) {
+                                   whenTime.setText(
+                                           Html.fromHtml(
+                                                   getString(R.string.picking_when_time, (int)(Math.floor((double)currentMillers/60.0)),(int)((double)currentMillers%60.0)),
+                                                   Html.FROM_HTML_MODE_COMPACT)
+                                   );
+                                   currentMillers += 1;
+                               }
+                               @Override
+                               public void onError(@NonNull Throwable e) {
+                               }
+                               @Override
+                               public void onComplete() {
+                               }
+                           });
+    }
+
+    /**
+     * 回车时的事件
+     * */
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction()==KeyEvent.ACTION_DOWN){
+            if (v.getId()==R.id.barcode || v.getId()==R.id.verify_number){
+                if (number.getText().toString().isEmpty()){//无拣货数 拿当前条码去加载当条信息
+                    loadCurrentBarcode();
+                }else{
+                    updateCurrent();
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 点击时的事件
+     * */
+    @Override
+    public void onClick(View v) {
+        if (v.getId()==R.id.btn){
+            picking();
+        }
+        if (v.getId()==R.id.back){
+            finish();
+        }
+        if (v.getId()==R.id.lock){
+            ImageView lock = (ImageView)findViewById(R.id.lock);
+            if (lockStatus){
+                lock.setImageResource(R.drawable.ic__4gf_unlock);
+                barcode.setInputType(InputType.TYPE_CLASS_TEXT);
+            }else{
+                lock.setImageResource(R.drawable.ic__4gf_lock);
+                barcode.setInputType(InputType.TYPE_NULL);
+            }
+        }
+        if (v.getId()==R.id.give_up){
+            MessageDialog.show("任务取消", "确定要放弃该任务吗?", "确定").setOkButtonClickListener(new OnDialogButtonClickListener<MessageDialog>() {
+                @Override
+                public boolean onClick(MessageDialog baseDialog, View v) {
+                    giveUpTask();
+                    return false;
+                }
+            }).setCancelable(true);
+        }
+    }
+    private void loadCurrentBarcode(){
+        String barcodeStr = barcode.getText().toString();
+        SQLiteDatabase db = taskDb.getReadableDatabase();
+        Cursor cursor=db.rawQuery("SELECT * FROM "+TaskTable.TABLE_NAME+" WHERE `location` = ? AND (barcode = ? OR barcodeAs = ?) AND pickingTime IS NULL LIMIT 1",
+                new String[]{pickingItem.getLocation(),barcodeStr,barcodeStr});
+        if (!cursor.moveToFirst()){
+            TipDialog.show("无此条码信息",WaitDialog.TYPE.ERROR);
+            setVibrator();//报警
+        }else{
+            int numIndex = cursor.getColumnIndex("anticipatedQuantity");
+            int idIndex = cursor.getColumnIndex("id");
+            pickingItem.setAnticipatedQuantity(cursor.getInt(numIndex));
+            pickingItem.setId(cursor.getInt(idIndex));
+            number.setText(String.valueOf(pickingItem.getAnticipatedQuantity()));
+            verifyNumber.setText(String.valueOf(pickingItem.getAnticipatedQuantity()));
+            mediaPlayer.start();//成功
+        }
+        cursor.close();
+    }
+    /**
+     * 点击完成当前库位拣货时 进行的加载事件
+     * */
+    private void completeLoad(){
+        Cursor cursor = null;
+        try {
+            cursor=DBManager.query(this,"SELECT * FROM "+TaskTable.TABLE_NAME+" WHERE `id` > ? AND pickingTime IS NULL LIMIT 2",
+                    new String[]{String.valueOf(pickingItem.getId())});
+            if (cursor.getCount()==0){
+                cursor.close();
+                TipDialog.show("任务拣货完毕!",WaitDialog.TYPE.SUCCESS);
+                inquiryTaskStart();
+                return;
+            }
+            //按钮仍为 完成当前拣货 加载当前第一条数据
+            cursor.moveToFirst();
+            int locationIndex = cursor.getColumnIndex("location");
+            int wayIndex = cursor.getColumnIndex("way");
+            PickingItem item = new PickingItem();
+            item.setLocation(cursor.getString(locationIndex));
+            item.setWay(cursor.getString(wayIndex));
+            pickingItem = item;
+            if (cursor.getCount()==2){
+                //如果有两条结果 对比两条库位是否一致 一致则修改按钮为 BTN_TEXT_BEFORE
+                cursor.moveToNext();
+                locationIndex = cursor.getColumnIndex("location");
+                if (pickingItem.getLocation().equals(cursor.getString(locationIndex))){
+                    btn.setText(BTN_TEXT_BEFORE);
+                }else{
+                    btn.setText(BTN_TEXT);
+                }
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }finally {
+            if (cursor!=null){
+                cursor.close();
+            }
+        }
+        //渲染子视图
+        way.setText(pickingItem.getWay());
+        currentLoc.setText(pickingItem.getLocation());
+        number.setText("");
+        barcode.setText("");
+        verifyNumber.setText("");
+
+        barcode.requestFocus();
+    }
+    /**
+     * 渲染局部视图
+     * */
+    private void renderingPartView(Integer realQty){
+        pickingNumber.setUnfinishedTaskCount(pickingNumber.getUnfinishedTaskCount()-1);
+        pickingNumber.setUserBeenWorkCount(pickingNumber.getUserBeenWorkCount()+realQty);
+        pickingNumber.setBeenQty(pickingNumber.getBeenQty()+realQty);
+        changeNumberUi();
+        //成功 声音播报
+        mediaPlayer.start();
+        number.setText("");
+        verifyNumber.setText("");
+        barcode.setText("");
+
+        if (btn.getText().toString().equals(BTN_TEXT_BEFORE)){
+            //btn 为 提前完成拣货时
+            Cursor cursor = null;
+            try {
+                cursor = DBManager.query(this,"SELECT 1 FROM "+TaskTable.TABLE_NAME+" WHERE `location` = ? AND pickingTime IS NULL LIMIT 2",
+                        new String[]{String.valueOf(pickingItem.getLocation())});
+                if (cursor.getCount()==0){
+                    TipDialog.show("指针错误,联系主管关闭当前任务重开",WaitDialog.TYPE.ERROR);
+                    setVibrator();
+                    return;
+                }
+                if (cursor.getCount()==1){
+                    btn.setText(BTN_TEXT);//库位待拣任务小于两条 变更按钮
+                }
+            }catch (Exception e){
+                e.printStackTrace();
+            }finally {
+                if (cursor!=null){
+                    cursor.close();
+                }
+            }
+            barcode.requestFocus();
+        }else{
+            btn.requestFocus();
+            btn.requestFocusFromTouch();
+        }
+    }
+    /**
+     * 更新当前ITEM信息
+     * */
+    private void updateCurrent(){
+        if (pickingItem.getPickingTime()!=null){
+            return;
+        }
+        if (verifyNumber.getText().toString().isEmpty()){
+            TipDialog.show("真实拣货数量未填写!",WaitDialog.TYPE.ERROR);
+            return;
+        }
+        //更新当前数据
+        int realQty = Integer.parseInt(verifyNumber.getText().toString());
+        pickingItem.setPickingTime(DateUtil.getCurrentFormat(DateUtil.DATE_DATE_TIME));
+        pickingItem.setRealQuantity(realQty);
+        ContentValues values = new ContentValues();
+        values.put("realQuantity", pickingItem.getRealQuantity());
+        values.put("pickingTime",pickingItem.getPickingTime());
+
+        SQLiteDatabase db = SQLiteDataProxy.getSQLiteProxy(this).getSQLiteDataBase();
+        try {
+            db.beginTransaction();
+            db.update(TaskTable.TABLE_NAME,values,"id = ?",new String[]{String.valueOf(pickingItem.getId())});
+            db.setTransactionSuccessful();
+        }catch (Exception e){
+            e.printStackTrace();
+        }finally {
+            db.endTransaction();
+        }
+        renderingPartView(realQty);
+    }
+    /**
+     * 按库位批量提交
+     * */
+    private void picking(){
+        if (pickingItem.getPickingTime()==null){
+            updateCurrent();
+        }
+        ArrayList<HashMap<String,Object>> list = new ArrayList<>();
+        Cursor cursor = null;
+        try {
+            cursor = DBManager.query(this,"SELECT * FROM "+TaskTable.TABLE_NAME+" WHERE `location` = ? ",
+                    new String[]{String.valueOf(pickingItem.getLocation())});
+            if (cursor!=null){
+                while (cursor.moveToNext()){
+                    int idIndex = cursor.getColumnIndex("detailId");
+                    int rqIndex = cursor.getColumnIndex("realQuantity");
+                    int ptIndex = cursor.getColumnIndex("pickingTime");
+                    String pickingTime = cursor.getString(ptIndex);
+                    if (pickingTime == null || pickingTime.isEmpty()){
+                        int barcodeIndex = cursor.getColumnIndex("barcode");
+                        TipDialog.show(cursor.getString(barcodeIndex)+"未拣完",WaitDialog.TYPE.WARNING);
+                        setVibrator();
+                        cursor.close();
+                        return;
+                    }
+                    HashMap<String, Object> item = new HashMap<>();
+                    item.put("id", cursor.getInt(idIndex));
+                    item.put("qty", cursor.getInt(rqIndex));
+                    item.put("pickingTime",pickingTime);
+                    list.add(item);
+                }
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }finally {
+            if (cursor!=null){
+                cursor.close();
+            }
+        }
+        WaitDialog.show("提交拣货记录中,请稍等...");
+        SwmsSubscribe.picking(list, picking.getCode(), btn.getText().toString().equals(BTN_TEXT_BEFORE), new SwmsHandlerCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean result) {
+                super.onSuccess(result);
+                if (btn.getText().toString().equals(BTN_TEXT)){
+                    //向下加载库位信息
+                    completeLoad();
+                }else{
+                    //后续
+                    TipDialog.show("任务拣货完毕!",WaitDialog.TYPE.SUCCESS);
+                    clearTask();
+                }
+            }
+            @Override
+            public void onFinally() {
+                WaitDialog.dismiss();
+            }
+        });
+    }
+}

+ 114 - 0
app/src/main/java/com/baoshi/swms/activity/PieceworkActivity.java

@@ -0,0 +1,114 @@
+package com.baoshi.swms.activity;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.User;
+import com.baoshi.swms.net.build.WebSocketClient;
+import com.baoshi.swms.net.constant.SwmsWebSocketConstant;
+import com.baoshi.swms.net.listener.SwmsWebSocketListener;
+import com.baoshi.swms.util.QRCode;
+
+/**
+ * 计件宝 ACTIVITY
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:04
+ */
+public class PieceworkActivity extends AppCompatActivity implements View.OnClickListener {
+
+    private static final String BASE_URL = "https://baoshi56.com/employee_qr/qr_code=";
+    private WebSocketClient webSocketClient;
+    private Handler handler;
+    private TextView num;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_piecework);
+        init();
+
+        num = findViewById(R.id.num);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        //启动socket刷新数量
+        startWebSocket();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        closeSocket();
+    }
+
+    @SuppressLint("SetTextI18n")
+    private void init() {
+        User user = User.getLocalUser();
+        ImageView imageView = findViewById(R.id.QRCode);
+        Bitmap bitmap = QRCode.createQRCodeBitmap(BASE_URL+user.getId().toString(),200,200);
+        imageView.setImageBitmap(bitmap);
+        ImageView back = findViewById(R.id.back);
+        TextView name = findViewById(R.id.user_name);
+        name.setText(name.getText()+User.getLocalUser().getName());
+
+
+        back.setOnClickListener(this);
+    }
+
+    @SuppressLint({"NonConstantResourceId", "SetTextI18n"})
+    @Override
+    public void onClick(View v) {
+        finish();
+    }
+
+    public void startWebSocket() {
+        if (webSocketClient == null) {
+            handler();
+            webSocketClient = new WebSocketClient();
+        }
+        webSocketClient.start(new SwmsWebSocketListener(handler));
+    }
+    public void closeSocket() {
+        webSocketClient.close();
+        webSocketClient = null;
+    }
+
+    private void handler(){
+        handler = new Handler(Looper.getMainLooper()){
+            @Override
+            public void handleMessage(Message msg) {
+                if (webSocketClient==null){
+                    return;
+                }
+                super.handleMessage(msg);
+                String status = (String) msg.obj;
+                //如果SOCKET监听因为一些原因被关闭,通过消息获取此通知,尝试重启
+                if (status.equals(SwmsWebSocketConstant.CLOSE_SIGN)){
+                    Toast.makeText(PieceworkActivity.this,"服务器连接失败",Toast.LENGTH_SHORT).show();
+                    //socketclient重新设置监听
+                    startWebSocket();
+                }
+                if (status.equals(SwmsWebSocketConstant.PULL_SIGN)){
+                    int amount = msg.getData().getInt("number");
+                    if (num.getText().toString().isEmpty() || Integer.parseInt((String) num.getText()) != amount){
+                        num.setText(String.valueOf(amount));
+                    }
+                }
+            }
+        };
+    }
+}

+ 38 - 0
app/src/main/java/com/baoshi/swms/activity/WaybillDispatchActivity.java

@@ -0,0 +1,38 @@
+package com.baoshi.swms.activity;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.adapter.WaybillDispatch;
+
+import java.util.ArrayList;
+
+/**
+ * 运单调配 ACTIVITY
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:05
+ */
+public class WaybillDispatchActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_waybill_dispatch);
+        initPager();
+    }
+
+    private void initPager() {
+        ViewPager2 viewPager = findViewById(R.id.dispatch_viewpager);
+        ArrayList<Fragment> fragments = new ArrayList<>();
+        //这里采用viewpager + fragment的方式是为了下方留有扩展,例如:统计信息等
+        fragments.add(com.baoshi.swms.fragment.WaybillDispatch.newInstance(R.layout.dispatch_waybill_fragment));
+        //与传统viewpager设置方式一样 区别是此处的适配器切换为fragment适配器
+        WaybillDispatch fa = new WaybillDispatch(getSupportFragmentManager(), getLifecycle(), fragments);
+        viewPager.setAdapter(fa);
+    }
+}

+ 76 - 0
app/src/main/java/com/baoshi/swms/activity/changeView/ListViewFooterChange.java

@@ -0,0 +1,76 @@
+package com.baoshi.swms.activity.changeView;
+
+import android.app.Activity;
+import android.view.Gravity;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.baoshi.swms.R;
+
+/**
+ * 调度时的视图变更
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/6 10:14
+ */
+public class ListViewFooterChange {
+    public LinearLayout mLoadLayout;
+    private final Activity activity;
+    private final ListView listView;
+    private final LinearLayout.LayoutParams mProgressBarLayoutParams = new LinearLayout.LayoutParams(
+            LinearLayout.LayoutParams.WRAP_CONTENT,
+            LinearLayout.LayoutParams.WRAP_CONTENT);
+    private final LinearLayout.LayoutParams mTipContentLayoutParams = new LinearLayout.LayoutParams(
+            LinearLayout.LayoutParams.WRAP_CONTENT,
+            LinearLayout.LayoutParams.WRAP_CONTENT);
+
+    public ListViewFooterChange(Activity activity, ListView listView){
+        this.activity = activity;
+        this.listView = listView;
+    }
+    //设置加载footer
+    public void addLoadFooter()
+    {
+        //"加载项"布局,此布局被添加到ListView的Footer中。
+        mLoadLayout = new LinearLayout(activity);
+        mLoadLayout.setMinimumHeight(60);
+        mLoadLayout.setGravity(Gravity.CENTER);
+        mLoadLayout.setOrientation(LinearLayout.HORIZONTAL);
+        //向"加载项"布局中添加一个圆型进度条。
+        ProgressBar mProgressBar = new ProgressBar(activity);
+        mProgressBar.setPadding(0, 0, 15, 0);
+        mLoadLayout.addView(mProgressBar, mProgressBarLayoutParams);
+        //向"加载项"布局中添加提示信息。
+        TextView mTipContent = new TextView(activity);
+        mTipContent.setText("加载中...");
+        mLoadLayout.addView(mTipContent, mTipContentLayoutParams);
+        //获取ListView组件,并将"加载项"布局添加到ListView组件的Footer中。
+        listView.addFooterView(mLoadLayout);
+    }
+    public void addLoadStopFooter()
+    {
+        mLoadLayout = new LinearLayout(activity);
+        mLoadLayout.setMinimumHeight(60);
+        mLoadLayout.setGravity(Gravity.CENTER);
+        mLoadLayout.setOrientation(LinearLayout.HORIZONTAL);
+        TextView mTipContent = new TextView(activity);
+        mTipContent.setText("已经到底了~");
+        mTipContent.setTextColor(activity.getResources().getColor(R.color.dark_gray));
+        mLoadLayout.addView(mTipContent, mTipContentLayoutParams);
+        listView.addFooterView(mLoadLayout);
+    }
+    public void addLoadNoneFooter()
+    {
+        mLoadLayout = new LinearLayout(activity);
+        mLoadLayout.setGravity(Gravity.CENTER);
+        mLoadLayout.setMinimumHeight(500);
+        mLoadLayout.setOrientation(LinearLayout.HORIZONTAL);
+        TextView mTipContent = new TextView(activity);
+        mTipContent.setText("暂无数据......");
+        mTipContent.setTextColor(activity.getResources().getColor(R.color.dark_gray));
+        mLoadLayout.addView(mTipContent, mTipContentLayoutParams);
+        listView.addFooterView(mLoadLayout);
+    }
+}

+ 212 - 0
app/src/main/java/com/baoshi/swms/activity/item/WaybillDispatchItemActivity.java

@@ -0,0 +1,212 @@
+package com.baoshi.swms.activity.item;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.App;
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.Waybill;
+import com.baoshi.swms.net.callback.WasHandlerCallback;
+import com.baoshi.swms.net.subscribe.WasSubscribe;
+import com.baoshi.swms.util.Hint;
+import com.google.gson.Gson;
+import com.google.gson.internal.LinkedTreeMap;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 调配详情操作 ACTIVITY
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/6 11:48
+ */
+public class WaybillDispatchItemActivity extends AppCompatActivity {
+    private Waybill waybill;
+    private EditText carrierBill;
+    private EditText inquireTel;
+    private EditText weight;
+    private EditText volume;
+    private EditText amount;
+    private EditText subjoinFee;
+    private RadioGroup amountUnitName;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.dispatch_item_waybill_show);
+
+        registerLocalVar();
+        initData();
+        registerEvent();
+    }
+
+    /**
+     * 注册局部变量
+     * */
+    private void registerLocalVar() {
+        Intent intent=getIntent();
+        Bundle bundle=intent.getExtras();
+        waybill= (Waybill)bundle.getSerializable("waybill");
+        carrierBill = findViewById(R.id.carrier_bill);
+        inquireTel = findViewById(R.id.inquire_tel);
+        weight = findViewById(R.id.weight);
+        volume = findViewById(R.id.volume);
+        amount = findViewById(R.id.amount);
+        subjoinFee = findViewById(R.id.subjoin_fee);
+        amountUnitName = findViewById(R.id.amount_unit_name);
+    }
+
+    /**
+     * 注册事件
+     * */
+    private void registerEvent() {
+        Button btn = findViewById(R.id.submit);
+        btn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (checkData())submitUpdate();
+            }
+        });
+        ImageView img = findViewById(R.id.waybill_show_back);
+        img.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onBackPressed();
+            }
+        });
+    }
+
+    /**
+     * 格式化参数
+     * */
+    private Map<String,String> formatParam(){
+        RadioButton rb = findViewById(amountUnitName.getCheckedRadioButtonId());
+        waybill.setCarrierBill(carrierBill.getText().toString());
+        waybill.setInquireTel(inquireTel.getText().toString());
+        waybill.setAmount(amount.getText().toString().equals("") ? null : Integer.parseInt(amount.getText().toString()));
+        waybill.setAmountUnitName(rb.getText().toString());
+        waybill.setWeight(weight.getText().toString().equals("") ? null : Double.parseDouble(weight.getText().toString()));
+        waybill.setVolume(volume.getText().toString().equals("") ? null : Double.parseDouble(volume.getText().toString()));
+        waybill.setSubjoinFee(subjoinFee.getText().toString());
+        return new HashMap<String,String>() {{
+            put("id",String.valueOf(waybill.getId()));
+            put("carrier_bill",waybill.getCarrierBill());
+            put("inquire_tel",waybill.getInquireTel());
+            put("amount",String.valueOf(waybill.getAmount()));
+            put("amount_unit_name",waybill.getAmountUnitName());
+            put("carrier_weight_other",waybill.getWeight()==null ? "" : String.valueOf(waybill.getWeight()));
+            put("carrier_weight",waybill.getVolume()==null ? "" : String.valueOf(waybill.getVolume()));
+            put("subjoin_fee",waybill.getSubjoinFee());
+        }};
+    }
+    private void submitUpdate() {
+        WasSubscribe.dispatch(formatParam(), new WasHandlerCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean result) {
+                if (result) {
+                    back();
+                }else{
+                    Hint.toastShort(WaybillDispatchItemActivity.this, "服务器拒绝,修改失败", Hint.ERROR);
+                }
+            }
+            @Override
+            public void onFault(int code, String errorMsg){
+                if (code==0){
+                    Hint.toastShort(App.getAppContext(), errorMsg, Hint.COMMON);
+                }else {
+                    Gson gson = new Gson();
+                    LinkedTreeMap<String, ArrayList<String>> err = gson.fromJson(errorMsg, LinkedTreeMap.class);
+                    if (err.get("carrier_bill")!=null)carrierBill.setError(err.get("carrier_bill").get(0));
+                    if (err.get("inquire_tel")!=null)inquireTel.setError(err.get("inquire_tel").get(0));
+                    if (err.get("amount")!=null)amount.setError(err.get("amount").get(0));
+                    if (err.get("amount_unit_name")!=null) Hint.toastShort(WaybillDispatchItemActivity.this,"必须选择数量单位",Hint.COMMON);
+                    if (err.get("carrier_weight_other")!=null)weight.setError(err.get("carrier_weight_other").get(0));
+                    if (err.get("carrier_weight")!=null)volume.setError(err.get("carrier_weight").get(0));
+                    if (err.get("subjoin_fee")!=null)subjoinFee.setError(err.get("subjoin_fee").get(0));
+                }
+            }
+        });
+    }
+
+    /**
+     * 回到主列表
+     * */
+    private void back()
+    {
+        Intent intent = new Intent();
+        intent.putExtra("waybill", waybill);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    /**
+     * 参数检查
+     * */
+    private boolean checkData() {
+        boolean sign = true;
+        if (carrierBill.getText()==null || carrierBill.getText().toString().equals("")){
+            carrierBill.setError("必填");
+            sign = false;
+        }
+        if (inquireTel.getText()==null || inquireTel.getText().toString().equals("")){
+            inquireTel.setError("必填");
+            sign = false;
+        }
+        if ((weight.getText()==null || weight.getText().toString().equals("")) &&
+                (volume.getText()==null || volume.getText().toString().equals(""))){
+            weight.setError("重量与体积至少填写一项");
+            sign = false;
+        }
+        if ((amount.getText()==null || amount.getText().toString().equals(""))){
+            amount.setError("必填");
+            sign = false;
+        }
+        if (amountUnitName.getCheckedRadioButtonId()==0){
+            Hint.toastShort(this,"必须选择数量单位",Hint.COMMON);
+            sign = false;
+        }
+        return sign;
+    }
+
+    /**
+     * 初始化参数
+     * */
+    private void initData() {
+        TextView waybillNumber = findViewById(R.id.waybill_number);
+        TextView deliverAt = findViewById(R.id.deliver_at);
+        TextView carrierName = findViewById(R.id.carrier_name);
+        TextView destination = findViewById(R.id.destination);
+        TextView recipient = findViewById(R.id.recipient);
+        TextView origination = findViewById(R.id.origination);
+        TextView warehouseWeightOther = findViewById(R.id.warehouse_weight_other);
+        TextView warehouseWeight = findViewById(R.id.warehouse_weight);
+
+        waybillNumber.setText(waybill.getWaybillNumber());
+        deliverAt.setText(waybill.getDeliverAt());
+        carrierName.setText(waybill.getCarrierName());
+        destination.setText(waybill.getDestination());
+        recipient.setText(waybill.getRecipient());
+        origination.setText(waybill.getOrigination());
+        warehouseWeightOther.setText(waybill.getWarehouseWeightOther()==null ? "" : String.valueOf(waybill.getWarehouseWeightOther()));
+        warehouseWeight.setText(waybill.getWarehouseWeight()==null ? "" : String.valueOf(waybill.getWarehouseWeight()));
+        carrierBill.setText(waybill.getCarrierBill());
+        inquireTel.setText(waybill.getInquireTel());
+        weight.setText(waybill.getWeight()==null ? "" : String.valueOf(waybill.getWeight()));
+        volume.setText(waybill.getVolume()==null ? "" : String.valueOf(waybill.getVolume()));
+        amount.setText(waybill.getAmount()==null ? "" : String.valueOf(waybill.getAmount()));
+        subjoinFee.setText(waybill.getSubjoinFee());
+        amountUnitName.check(waybill.getAmountUnitName().equals("托") ? R.id.radio_2 : R.id.radio_1);
+    }
+}

+ 125 - 0
app/src/main/java/com/baoshi/swms/activity/part/BaseListViewIntegration.java

@@ -0,0 +1,125 @@
+package com.baoshi.swms.activity.part;
+
+import android.app.Activity;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.baoshi.swms.activity.changeView.ListViewFooterChange;
+import com.baoshi.swms.inter.OnRefreshListener;
+import com.baoshi.swms.part.RefreshHeader;
+
+/**
+ * listview在activity中的简单集成
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/14 17:49
+ */
+public class BaseListViewIntegration extends AppCompatActivity implements
+        AbsListView.OnScrollListener, OnRefreshListener {
+    protected RefreshHeader listView;
+    protected int scrollState;
+    protected int topIndex;
+    protected ListViewFooterChange viewChange; //变更视图对象
+    protected boolean isNoneLoad = false;//防重载标记
+    protected int paginate = 20; //每次加载大小
+    protected int page = 1; //当前页数
+    protected int currentLoadSize; //当前加载数据大小
+    protected BaseAdapter adapter;//适配器
+
+    protected void registerListViewEvent(Activity activity) {
+        viewChange = new ListViewFooterChange(activity, listView);
+
+        listView.setOnScrollListener(this);//滚动
+        listView.setOnRefreshListener(this);//下拉
+    }
+
+    //初始化listview
+    protected void loadListViewItem() {
+        if (isNoneLoad)return; //正确做法应该卸载监听器 暂时不知 所以用标记来跳过加载
+        viewChange.addLoadFooter();
+        isNoneLoad = true;
+        requestData(false);
+    }
+
+    //数据加载
+    protected void requestData(boolean after) {
+    }
+
+    protected void afterOperation(boolean after){
+        listView.removeFooterView(viewChange.mLoadLayout);
+        if (after){//如果是追加数据 重载适配器
+            listView.setAdapter(adapter);
+            listView.onRefreshComplete();
+        }else{//如果是刷新数据 通知容器视图变更
+            adapter.notifyDataSetChanged();
+        }
+
+        if (currentLoadSize<paginate){
+            viewChange.addLoadStopFooter();
+            isNoneLoad = true; //如果本次加载数据不足N条 则标记加载完毕
+        }else if (currentLoadSize==0){ //如果加载完毕了 数据总量仍为0 那么属于没有数据 显示空数据视图
+            viewChange.addLoadNoneFooter();
+            isNoneLoad = true;
+        }else {
+            isNoneLoad = false;
+        }
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        this.scrollState = scrollState;
+    }
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+        topIndex = firstVisibleItem;
+        switch (this.scrollState){
+            case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
+                if (topIndex+visibleItemCount==totalItemCount && !isNoneLoad){
+                    page++;
+                    loadListViewItem();
+                }
+                break;
+            case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
+                break;
+            case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
+        }
+    }
+    @Override
+    public void refreshData() {
+        if (topIndex==0){
+            page = 1;
+            requestData(true);
+        } else listView.onRefreshComplete();
+    }
+
+    /*
+    * 使用方式
+    1.渲染视图后调用渲染适配器
+    private void registerEvent() {
+        //视图渲染后必须先设定一次适配器
+        adapter = new CheckInventoryItem(list,CheckInventoryActivity.this);
+        listView.setAdapter(adapter);
+        listView.setOnItemClickListener(this);//listview item click
+    }
+    2. 调用registerListViewEvent
+    3. 覆写请求方法
+    @Override
+    protected void requestData(boolean after) {
+        List<Stocktaking> stocktakingList = new ArrayList<>();
+        Stocktaking stock = new Stocktaking();
+        stock.setId(1L);
+        stocktakingList.add(stock);
+
+        //重载覆盖,续载追加
+        if (after){
+            list = stocktakingList;
+            adapter = new CheckInventoryItem(list,CheckInventoryActivity.this);
+        }else{
+            list.addAll(stocktakingList);
+        }
+        currentLoadSize = list.size();
+        afterOperation(after);
+     }
+    */
+}

+ 55 - 0
app/src/main/java/com/baoshi/swms/adapter/CheckInventoryItem.java

@@ -0,0 +1,55 @@
+package com.baoshi.swms.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.baoshi.swms.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 盘点子项
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/17 13:48
+ */
+public class CheckInventoryItem extends BaseAdapter {
+    private final ArrayList<HashMap<String,String>> list;
+    private final Context context;
+
+    public CheckInventoryItem(ArrayList<HashMap<String,String>> list, Context context){
+        this.list =list;
+        this.context =context;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return list.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null){
+            convertView = LayoutInflater.from(context).inflate(R.layout.item_check_inventory, parent, false);
+        }
+        HashMap<String,String> stock = list.get(position);
+        ((TextView)convertView.findViewById(R.id.loc)).setText(stock.get("location"));
+        ((TextView)convertView.findViewById(R.id.count)).setText(stock.get("count"));
+        return convertView;
+    }
+}

+ 34 - 0
app/src/main/java/com/baoshi/swms/adapter/WaybillDispatch.java

@@ -0,0 +1,34 @@
+package com.baoshi.swms.adapter;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.List;
+
+/**
+ * 运单调配适配器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:11
+ */
+public class WaybillDispatch extends FragmentStateAdapter {
+    List<Fragment> fragmentList;
+
+    public WaybillDispatch(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle,
+                           List<Fragment> fragments) {
+        super(fragmentManager, lifecycle);
+        fragmentList = fragments;
+    }
+    @NonNull
+    @Override
+    public Fragment createFragment(int position) {
+        return fragmentList.get(position);
+    }
+    @Override
+    public int getItemCount() {
+        return fragmentList.size();
+    }
+}

+ 62 - 0
app/src/main/java/com/baoshi/swms/adapter/WaybillDispatchList.java

@@ -0,0 +1,62 @@
+package com.baoshi.swms.adapter;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.baoshi.swms.R;
+import com.baoshi.swms.bean.Waybill;
+
+import java.util.List;
+
+/**
+ * 运单列表适配器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:11
+ */
+public class WaybillDispatchList extends BaseAdapter {
+
+    private final List<Waybill> list;
+    private final Context context;
+
+    public WaybillDispatchList(List<Waybill> list, Context context) {
+        this.list = list;
+        this.context = context;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @Override
+    public Object getItem(int i) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int i) {
+        return i;
+    }
+
+    @SuppressLint("ResourceAsColor")
+    @Override
+    public View getView(int i, View view, ViewGroup viewGroup) {
+        if (view == null)view = LayoutInflater.from(context).inflate(R.layout.item_waybill_item, viewGroup, false);
+        Waybill waybill = list.get(i);
+        TextView waybillNumber =  view.findViewById(R.id.waybill_number);
+        waybillNumber.setText(waybill.getWaybillNumber());
+        TextView deliverAt =  view.findViewById(R.id.deliver_at);
+        deliverAt.setText(waybill.getDeliverAt());
+        TextView number =  view.findViewById(R.id.number);
+        number.setText((waybill.getWeight()!=null && waybill.getWeight()!=0.0) ? waybill.getWeight() +" KG" :
+                ((waybill.getVolume()!=null && waybill.getVolume()!=0.0) ? waybill.getVolume()+" M³" : "暂无"));
+        if (number.getText().equals("暂无"))number.setTextColor(R.color.dark_gray);
+        return view;
+    }
+}

+ 83 - 0
app/src/main/java/com/baoshi/swms/bean/Menu.java

@@ -0,0 +1,83 @@
+package com.baoshi.swms.bean;
+
+import android.util.ArrayMap;
+
+import com.baoshi.swms.MainActivity;
+import com.baoshi.swms.R;
+import com.baoshi.swms.activity.BatchRecoverActivity;
+import com.baoshi.swms.activity.CheckInventoryActivity;
+import com.baoshi.swms.activity.PickingActivity;
+import com.baoshi.swms.activity.PieceworkActivity;
+import com.baoshi.swms.activity.WaybillDispatchActivity;
+
+import java.util.HashMap;
+
+/**
+ * 菜单实体
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/3 17:21
+ */
+public class Menu {
+    public Class<?> reference;
+    public int icon;
+
+    public Menu(Class<?> reference,int icon){
+        this.reference = reference;
+        this.icon = icon;
+    }
+    public Menu(int icon){
+        this.icon = icon;
+    }
+
+    public static ArrayMap<String, HashMap<String, Menu>> getMenuNeedVerifyList(){
+        ArrayMap<String, HashMap<String, Menu>> menuMap = new ArrayMap<>();
+        menuMap.put("收货", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.ic_baseline_login_24));
+
+            put("盲收", new Menu(MainActivity.class,R.drawable.truck));
+            put("盘收一体", new Menu(MainActivity.class,R.drawable.truck));
+            put("快速入库", new Menu(MainActivity.class,R.drawable.truck));
+            put("收货", new Menu(MainActivity.class,R.drawable.truck));
+        }});
+        menuMap.put("订单管理", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.ic_baseline_calendar_today_24));
+
+            put("波次恢复", new Menu(BatchRecoverActivity.class,R.drawable.ic_baseline_camera_24));
+        }});
+        menuMap.put("运输管理", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.truck));
+
+            put("调度", new Menu(WaybillDispatchActivity.class,R.drawable.truck));
+        }});
+        menuMap.put("包裹管理", new HashMap<String, Menu>() {{
+            put("手动录入", new Menu(MainActivity.class,R.drawable.truck));
+        }});
+        menuMap.put("库存管理", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.ic_inventory));
+
+            put("盘点", new Menu(CheckInventoryActivity.class,R.drawable.ic_check_inventory));
+        }});
+        menuMap.put("缓存架", new HashMap<String, Menu>() {{
+            put("入库", new Menu(MainActivity.class,R.drawable.truck));
+            put("容器", new Menu(MainActivity.class,R.drawable.truck));
+        }});
+        return menuMap;
+    }
+
+    public static ArrayMap<String, HashMap<String, Menu>> getMenuNotNeedVerifyList(){
+        ArrayMap<String, HashMap<String, Menu>> menuMap = new ArrayMap<>();
+        menuMap.put("仓库人工", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.ic_baseline_home_work_24));
+
+            put("计件宝", new Menu(PieceworkActivity.class,R.drawable.ic_baseline_bookmarks_24));
+            put("拣货", new Menu(PickingActivity.class,R.drawable.ic_picking));
+        }});
+        menuMap.put("库存管理", new HashMap<String, Menu>() {{
+            put("icon", new Menu(R.drawable.ic_inventory));
+
+            put("盘点", new Menu(CheckInventoryActivity.class,R.drawable.ic_check_inventory));
+        }});
+        return menuMap;
+    }
+}

+ 60 - 0
app/src/main/java/com/baoshi/swms/bean/Picking.java

@@ -0,0 +1,60 @@
+package com.baoshi.swms.bean;
+
+import java.io.Serializable;
+
+/**
+ * 拣货单
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/20 14:51
+ */
+public class Picking implements Serializable {
+    private static final long serialVersionUID = 1314L;
+
+    private String code;//任务号
+    private String ownerCode;//任务号
+    private String waveCode;//波次号
+    private String venue;//集货站
+    private String operationTime;//开始时间
+
+    private String group;//员工所属组
+
+    public String getOperationTime() {
+        return operationTime;
+    }
+    public void setOperationTime(String operationTime) {
+        this.operationTime = operationTime;
+    }
+    public String getVenue() {
+        return venue;
+    }
+    public void setVenue(String venue) {
+        this.venue = venue;
+    }
+    public String getGroup() {
+        return group;
+    }
+    public void setGroup(String group) {
+        this.group = group;
+    }
+    public String getCode() {
+        return code;
+    }
+    public void setCode(String code) {
+        this.code = code;
+    }
+    public String getWaveCode() {
+        return waveCode;
+    }
+    public void setWaveCode(String waveCode) {
+        this.waveCode = waveCode;
+    }
+
+    public String getOwnerCode() {
+        return ownerCode;
+    }
+
+    public void setOwnerCode(String ownerCode) {
+        this.ownerCode = ownerCode;
+    }
+}

+ 95 - 0
app/src/main/java/com/baoshi/swms/bean/PickingItem.java

@@ -0,0 +1,95 @@
+package com.baoshi.swms.bean;
+
+import java.io.Serializable;
+
+/**
+ * 拣货单子任务
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/20 15:00
+ */
+public class PickingItem implements Serializable {
+    private static final long serialVersionUID = 1315L;
+
+    private Integer id;
+    private Long detailId;
+    private String location;
+    private String way;
+    private String barcode;
+    private String barcodeAs;
+    private Integer anticipatedQuantity;
+    private Integer realQuantity;
+    private String pickingTime;
+
+    public Integer getRealQuantity() {
+        return realQuantity;
+    }
+
+    public void setRealQuantity(Integer realQuantity) {
+        this.realQuantity = realQuantity;
+    }
+
+    public Integer getAnticipatedQuantity() {
+        return anticipatedQuantity;
+    }
+
+    public void setAnticipatedQuantity(Integer anticipatedQuantity) {
+        this.anticipatedQuantity = anticipatedQuantity;
+    }
+
+    public String getBarcodeAs() {
+        return barcodeAs;
+    }
+
+    public void setBarcodeAs(String barcodeAs) {
+        this.barcodeAs = barcodeAs;
+    }
+
+    public String getBarcode() {
+        return barcode;
+    }
+
+    public void setBarcode(String barcode) {
+        this.barcode = barcode;
+    }
+
+    public String getWay() {
+        return way;
+    }
+
+    public void setWay(String way) {
+        this.way = way;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    public Long getDetailId() {
+        return detailId;
+    }
+
+    public void setDetailId(Long detailId) {
+        this.detailId = detailId;
+    }
+
+    public String getPickingTime() {
+        return pickingTime;
+    }
+
+    public void setPickingTime(String pickingTime) {
+        this.pickingTime = pickingTime;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

+ 44 - 0
app/src/main/java/com/baoshi/swms/bean/PickingNumber.java

@@ -0,0 +1,44 @@
+package com.baoshi.swms.bean;
+
+/**
+ * 拣货数值
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/25 14:39
+ */
+public class PickingNumber {
+
+    private Integer unfinishedTaskCount;//未完成的任务数
+    private Integer userBeenWorkCount;//员工今天拣货数
+    private Integer total;
+    private Integer beenQty;
+
+    public Integer getUserBeenWorkCount() {
+        return userBeenWorkCount;
+    }
+    public void setUserBeenWorkCount(Integer userBeenWorkCount) {
+        this.userBeenWorkCount = userBeenWorkCount;
+    }
+    public Integer getUnfinishedTaskCount() {
+        return unfinishedTaskCount;
+    }
+    public void setUnfinishedTaskCount(Integer unfinishedTaskCount) {
+        this.unfinishedTaskCount = unfinishedTaskCount;
+    }
+
+    public Integer getBeenQty() {
+        return beenQty;
+    }
+
+    public void setBeenQty(Integer beenQty) {
+        this.beenQty = beenQty;
+    }
+
+    public Integer getTotal() {
+        return total;
+    }
+
+    public void setTotal(Integer total) {
+        this.total = total;
+    }
+}

+ 66 - 0
app/src/main/java/com/baoshi/swms/bean/Stocktaking.java

@@ -0,0 +1,66 @@
+package com.baoshi.swms.bean;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 盘点单
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/15 9:46
+ */
+public class Stocktaking implements Serializable {
+    private static final long serialVersionUID = 111230L;
+
+    private Long id;
+
+    @SerializedName("end_at")
+    private String endAt;//最后操作时间
+
+    private String type;//任务类型
+
+    private Integer total;//任务数
+
+    private Integer processed;//已盘数
+
+    public Integer getProcessed() {
+        return processed;
+    }
+
+    public void setProcessed(Integer processed) {
+        this.processed = processed;
+    }
+
+    public Integer getTotal() {
+        return total;
+    }
+
+    public void setTotal(Integer total) {
+        this.total = total;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String  getEndAt() {
+        return endAt;
+    }
+
+    public void setEndAt(String endAt) {
+        this.endAt = endAt;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+}

+ 91 - 0
app/src/main/java/com/baoshi/swms/bean/StocktakingItem.java

@@ -0,0 +1,91 @@
+package com.baoshi.swms.bean;
+
+import com.baoshi.swms.util.DateUtil;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 盘点单子项
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/17 15:46
+ */
+public class StocktakingItem implements Serializable {
+    private static final long serialVersionUID = 111231L;
+
+    private Long id;
+
+    @SerializedName("produced_at")
+    private String prodAt;
+
+    @SerializedName("verified_amount")
+    private Integer verifiedAmount;
+
+    @SerializedName("valid_at")
+    private String expiryDate;
+
+    @SerializedName("batch_number")
+    private String lotNum;
+
+    @SerializedName("erp_type_position")
+    private String attrWarehouse;
+    private String quality;
+
+    public String getQuality() {
+        return quality;
+    }
+
+    public void setQuality(String quality) {
+        this.quality = quality;
+    }
+
+    public String getAttrWarehouse() {
+        return attrWarehouse;
+    }
+
+    public void setAttrWarehouse(String attrWarehouse) {
+        this.attrWarehouse = attrWarehouse;
+    }
+
+    public String getLotNum() {
+        return lotNum;
+    }
+
+    public void setLotNum(String lotNum) {
+        this.lotNum = lotNum;
+    }
+
+    public Date getExpiryDate() {
+        return DateUtil.strToDate(expiryDate);
+    }
+
+    public void setExpiryDate(String expiryDate) {
+        this.expiryDate = expiryDate;
+    }
+
+    public Date getProdAt() {
+        return DateUtil.strToDate(prodAt);
+    }
+
+    public void setProdAt(String prodAt) {
+        this.prodAt = prodAt;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Integer getVerifiedAmount() {
+        return verifiedAmount;
+    }
+
+    public void setVerifiedAmount(Integer verifiedAmount) {
+        this.verifiedAmount = verifiedAmount;
+    }
+}

+ 42 - 0
app/src/main/java/com/baoshi/swms/bean/User.java

@@ -0,0 +1,42 @@
+package com.baoshi.swms.bean;
+
+import com.baoshi.swms.util.GlobalStorage;
+import com.google.gson.Gson;
+
+import java.io.Serializable;
+
+/**
+ * 用户信息
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/6 13:12
+ */
+public class User implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Integer id;
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+
+    public static User getLocalUser(){
+        Gson gson = new Gson();
+        String user = GlobalStorage.getGlobalStorage().get("user");
+        if (user==null)return null;
+        return gson.fromJson(user,User.class);
+    }
+}

+ 174 - 0
app/src/main/java/com/baoshi/swms/bean/Waybill.java

@@ -0,0 +1,174 @@
+package com.baoshi.swms.bean;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 运单列表
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/6 11:36
+ */
+public class Waybill implements Serializable {
+    int id;
+    @SerializedName("waybill_number")
+    String waybillNumber;
+    String destination;
+    String recipient;
+    @SerializedName("recipient_mobile")
+    String recipientMobile;
+    @SerializedName("carrier_bill")
+    String carrierBill;
+    @SerializedName("warehouse_weight")
+    Double warehouseWeight;
+    @SerializedName("carrier_weight")
+    Double volume;
+    @SerializedName("inquire_tel")
+    String inquireTel;
+    @SerializedName("warehouse_weight_other")
+    Double warehouseWeightOther;
+    @SerializedName("carrier_weight_other")
+    Double weight;
+    Integer amount;
+    String origination;
+    @SerializedName("carrier_name")
+    String carrierName;
+    @SerializedName("amount_unit_name")
+    String amountUnitName;
+    @SerializedName("deliver_at")
+    String deliverAt;
+    @SerializedName("subjoin_fee")
+    String subjoinFee;
+
+    public void setSubjoinFee(String subjoinFee) {
+        this.subjoinFee = subjoinFee;
+    }
+
+    public String getSubjoinFee() {
+        return subjoinFee;
+    }
+
+    public void setDestination(String destination) {
+        this.destination = destination;
+    }
+
+    public void setRecipient(String recipient) {
+        this.recipient = recipient;
+    }
+
+    public void setRecipientMobile(String recipientMobile) {
+        this.recipientMobile = recipientMobile;
+    }
+
+    public void setCarrierBill(String carrierBill) {
+        this.carrierBill = carrierBill;
+    }
+
+    public void setWarehouseWeight(Double warehouseWeight) {
+        this.warehouseWeight = warehouseWeight;
+    }
+
+    public void setInquireTel(String inquireTel) {
+        this.inquireTel = inquireTel;
+    }
+
+    public void setWarehouseWeightOther(Double warehouseWeightOther) {
+        this.warehouseWeightOther = warehouseWeightOther;
+    }
+
+    public void setAmount(Integer amount) {
+        this.amount = amount;
+    }
+
+    public void setOrigination(String origination) {
+        this.origination = origination;
+    }
+
+    public void setCarrierName(String carrierName) {
+        this.carrierName = carrierName;
+    }
+
+    public void setAmountUnitName(String amountUnitName) {
+        this.amountUnitName = amountUnitName;
+    }
+
+    public String getDestination() {
+        return destination;
+    }
+
+    public String getRecipient() {
+        return recipient;
+    }
+
+    public String getRecipientMobile() {
+        return recipientMobile;
+    }
+
+    public String getCarrierBill() {
+        return carrierBill;
+    }
+
+    public Double getWarehouseWeight() {
+        return warehouseWeight;
+    }
+
+    public String getInquireTel() {
+        return inquireTel;
+    }
+
+    public Double getWarehouseWeightOther() {
+        return warehouseWeightOther;
+    }
+
+    public Integer getAmount() {
+        return amount;
+    }
+
+    public String getOrigination() {
+        return origination;
+    }
+
+    public String getCarrierName() {
+        return carrierName;
+    }
+
+    public String getAmountUnitName() {
+        return amountUnitName;
+    }
+
+    public void setWeight(Double weight) {
+        this.weight = weight;
+    }
+    public Double getWeight() {
+        return weight;
+    }
+
+    public void setVolume(Double volume) {
+        this.volume = volume;
+    }
+    public Double getVolume() {
+        return volume;
+    }
+
+    public void setWaybillNumber(String waybillNumber) {
+        this.waybillNumber = waybillNumber;
+    }
+    public String getWaybillNumber() {
+        return waybillNumber;
+    }
+
+    public void setDeliverAt(String deliverAt) {
+        this.deliverAt = deliverAt;
+    }
+    public String getDeliverAt() {
+        return deliverAt;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+    public int getId() {
+        return id;
+    }
+}

+ 45 - 0
app/src/main/java/com/baoshi/swms/bean/dto/Login.java

@@ -0,0 +1,45 @@
+package com.baoshi.swms.bean.dto;
+
+import com.baoshi.swms.bean.User;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+/**
+ * 登录信息
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/6 11:36
+ */
+public class Login implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private String token;
+    private String menu;
+
+    @SerializedName("info")
+    private User user;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getMenu() {
+        return menu;
+    }
+
+    public void setMenu(String menu) {
+        this.menu = menu;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+}

+ 51 - 0
app/src/main/java/com/baoshi/swms/bean/dto/PickingDto.java

@@ -0,0 +1,51 @@
+package com.baoshi.swms.bean.dto;
+
+import com.baoshi.swms.bean.Picking;
+import com.baoshi.swms.bean.PickingItem;
+
+import java.util.ArrayList;
+
+/**
+ * 拣货单DTO
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/24 17:44
+ */
+public class PickingDto {
+   private Boolean unfinished;
+   private Picking task;
+   private ArrayList<PickingItem> items;
+   private Integer todayWorkNumber;
+
+   public Integer getTodayWorkNumber() {
+      return todayWorkNumber;
+   }
+
+   public void setTodayWorkNumber(Integer todayWorkNumber) {
+      this.todayWorkNumber = todayWorkNumber;
+   }
+
+   public ArrayList<PickingItem> getItems() {
+      return items;
+   }
+
+   public void setItems(ArrayList<PickingItem> items) {
+      this.items = items;
+   }
+
+   public Picking getTask() {
+      return task;
+   }
+
+   public void setTask(Picking task) {
+      this.task = task;
+   }
+
+   public Boolean getUnfinished() {
+      return unfinished;
+   }
+
+   public void setUnfinished(Boolean unfinished) {
+      this.unfinished = unfinished;
+   }
+}

+ 38 - 0
app/src/main/java/com/baoshi/swms/bean/dto/StockingData.java

@@ -0,0 +1,38 @@
+package com.baoshi.swms.bean.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 提交盘点信息的DTO
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/22 11:42
+ */
+public class StockingData implements Serializable {
+    private static final long serialVersionUID = 2L;
+
+    private boolean isDiff;
+
+    @SerializedName("notStocktakingList")
+    private ArrayList<HashMap<String,String>> list;
+
+    public boolean isDiff() {
+        return isDiff;
+    }
+
+    public void setDiff(boolean diff) {
+        isDiff = diff;
+    }
+
+    public ArrayList<HashMap<String, String>> getList() {
+        return list;
+    }
+
+    public void setList(ArrayList<HashMap<String, String>> list) {
+        this.list = list;
+    }
+}

+ 53 - 0
app/src/main/java/com/baoshi/swms/bean/dto/StocktakingData.java

@@ -0,0 +1,53 @@
+package com.baoshi.swms.bean.dto;
+
+import com.baoshi.swms.bean.Stocktaking;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 获取盘点信息
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/17 13:23
+ */
+public class StocktakingData implements Serializable {
+    private static final long serialVersionUID = 1001L;
+
+    @SerializedName("sameDayInventoryCount")
+    private Integer todayWorkNumber;
+
+    @SerializedName("stocktakingTask")
+    private Stocktaking stocktaking;
+
+    @SerializedName("notStocktakingList")
+    private ArrayList<HashMap<String,String>> notStocktakingList;
+
+    public ArrayList<HashMap<String,String>> getNotStocktakingList() {
+        return notStocktakingList;
+    }
+
+    public void setNotStocktakingList(ArrayList<HashMap<String,String>> notStocktakingList) {
+        this.notStocktakingList = notStocktakingList;
+    }
+
+    public Stocktaking getStocktaking() {
+        return stocktaking;
+    }
+
+    public void setStocktaking(Stocktaking stocktaking) {
+        this.stocktaking = stocktaking;
+    }
+
+    public Integer getTodayWorkNumber() {
+        return todayWorkNumber;
+    }
+
+    public void setTodayWorkNumber(Integer todayWorkNumber) {
+        this.todayWorkNumber = todayWorkNumber;
+    }
+}

+ 30 - 0
app/src/main/java/com/baoshi/swms/bean/dto/WaybillData.java

@@ -0,0 +1,30 @@
+package com.baoshi.swms.bean.dto;
+
+import com.baoshi.swms.bean.Waybill;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 调度回馈信息
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/6 10:08
+ */
+public class WaybillData {
+    private ArrayList<Waybill> waybills;
+    private HashMap<String, Double> mapping;
+
+    public void setWaybills(ArrayList<Waybill> waybills) {
+        this.waybills = waybills;
+    }
+    public void setMapping(HashMap<String, Double> mapping) {
+        this.mapping = mapping;
+    }
+    public HashMap<String, Double> getMapping() {
+        return mapping;
+    }
+    public ArrayList<Waybill> getWaybills() {
+        return waybills;
+    }
+}

+ 387 - 0
app/src/main/java/com/baoshi/swms/fragment/WaybillDispatch.java

@@ -0,0 +1,387 @@
+package com.baoshi.swms.fragment;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.baoshi.swms.activity.changeView.ListViewFooterChange;
+import com.baoshi.swms.activity.item.WaybillDispatchItemActivity;
+import com.baoshi.swms.adapter.WaybillDispatchList;
+import com.baoshi.swms.bean.Waybill;
+import com.baoshi.swms.bean.dto.WaybillData;
+import com.baoshi.swms.inter.OnRefreshListener;
+import com.baoshi.swms.net.callback.WasHandlerCallback;
+import com.baoshi.swms.net.subscribe.WasSubscribe;
+import com.baoshi.swms.part.InputEditDialog;
+import com.baoshi.swms.part.RefreshHeader;
+import com.baoshi.swms.R;
+import com.baoshi.swms.util.DateUtil;
+import com.baoshi.swms.util.Hint;
+import com.bigkoo.pickerview.builder.TimePickerBuilder;
+import com.bigkoo.pickerview.listener.OnTimeSelectListener;
+import com.bigkoo.pickerview.view.TimePickerView;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 运单调配Fragment
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/6 11:37
+ */
+public class WaybillDispatch extends Fragment implements View.OnClickListener, TextView.OnEditorActionListener ,
+        InputEditDialog.OnTextSendListener, AbsListView.OnScrollListener, OnRefreshListener ,
+        AdapterView.OnItemClickListener, OnTimeSelectListener {
+    private static final String ARG_KEY = "VIEW_ID";
+
+    private int id;                                         //当前碎片视图ID
+    private final List<Waybill> waybills = new ArrayList<>();     //listview数据池
+    private final Map<String,Double> mapping = new HashMap<>();     //专线费映射
+    private View rootView;                                  //当前视图
+    //局部控件对象
+    private TextView view;
+    private TextView fee;
+    //滚动元素节点
+    private int topIndex;
+    private int status;
+    //软键盘文本框对象
+    private InputEditDialog mInputEditDialog;
+
+    private RefreshHeader listView;                              //ListView对象
+    private WaybillDispatchList adapter;
+    private ListViewFooterChange viewChange; //变更视图对象
+    private boolean isNoneLoad = false;
+    private final HashMap<String,String> params = new HashMap<String,String>(){{
+        put("search","");
+        put("deliver_at","");
+        put("page","1");
+        put("paginate","20");
+    }};
+    private int currentIndex;
+
+    //构造方法传递目标fragment碎片视图
+    public static WaybillDispatch newInstance(int param) {
+        WaybillDispatch fragment = new WaybillDispatch();
+        Bundle args = new Bundle();
+        args.putInt(ARG_KEY, param);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    /**
+     * dispatch生命周期
+     * */
+    //初始化
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (getArguments() != null){
+            id = getArguments().getInt(ARG_KEY);
+        }
+    }
+    //加载视图
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        if (rootView==null)rootView = inflater.inflate(id, container, false);
+        return rootView;
+    }
+    //初始化activity 到此视图方才加载完毕
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        //渲染视图与加载局部变量
+        rendingView();
+        //视图渲染完毕才能加载视图更新对象
+        viewChange = new ListViewFooterChange(getActivity(), listView);
+        //加载数据
+        loadListViewItem();
+        //注册事件
+        registerEvent();
+    }
+    //子ITEM返回时的携参
+    @Override
+    public void onActivityResult(int requestCode, int resultCode,@Nullable Intent data) {
+        if (data==null || requestCode!=1)return;
+        waybills.set(currentIndex-1,(Waybill) data.getSerializableExtra("waybill"));
+        adapter.notifyDataSetChanged();
+        Hint.toastShort(getContext(),"修改成功",Hint.COMMON);
+    }
+
+    /**
+     * 初始化与全局行为
+     * */
+    //注册事件
+    private void registerEvent() {
+        ImageView imgView = getActivity().findViewById(R.id.date_picker_2);
+        ImageView edit = getActivity().findViewById(R.id.edit_fee);
+        ImageView back = getActivity().findViewById(R.id.waybill_back);
+        EditText searchView = getActivity().findViewById(R.id.search_waybill);
+
+        //回退
+        back.setOnClickListener(this);
+        //搜索
+        searchView.setOnEditorActionListener(this);
+        //时间控件监听
+        view.setOnClickListener(this);
+        imgView.setOnClickListener(this);
+        //文本框控件监听
+        edit.setOnClickListener(this);
+        //滚动条监听
+        listView.setOnScrollListener(this);
+        //下拉刷新
+        listView.setOnRefreshListener(this);
+        //listview item点击
+        listView.setOnItemClickListener(this);
+    }
+    //渲染视图
+    private void rendingView() {
+        mInputEditDialog = new InputEditDialog(getActivity(), R.style.InputDialog, "请输入按日专线费", InputType.TYPE_NUMBER_FLAG_SIGNED);
+        listView = getActivity().findViewById(R.id.bill_list);
+        adapter = new WaybillDispatchList(waybills,getActivity());
+        view = getActivity().findViewById(R.id.date_picker_1);
+        fee = getActivity().findViewById(R.id.fee);
+        listView.setAdapter(adapter);
+    }
+    //初始化listview
+    private void loadListViewItem() {
+        if (isNoneLoad)return; //正确做法应该卸载监听器 暂时不知 所以用标记来跳过加载
+        viewChange.addLoadFooter();
+        isNoneLoad = true;
+        requestData(false);
+    }
+    //加载数据
+    private void requestData(boolean after)
+    {
+        String search = params.get("search");
+        String deliverAt = params.get("deliver_at");
+        int page = Integer.parseInt(params.get("page"));
+        int paginate = Integer.parseInt(params.get("paginate"));
+        WasSubscribe.getDispatchList(search, deliverAt, page, paginate, new WasHandlerCallback<WaybillData>() {
+            @Override
+            public void onSuccess(WaybillData result) {
+                ArrayList<Waybill> currentData = result.getWaybills();
+                if (currentData.size()>0)mapping.putAll(result.getMapping());//合并映射
+                if (after)waybills.clear();
+                waybills.addAll(currentData);
+                if (waybills.size()==0){
+                    view.setText("暂无");
+                    fee.setText("");
+                }else{
+                    String date = currentData.get(0).getDeliverAt().substring(0,10);
+                    view.setText(date);
+                    fee.setText(mapping.get(date)==null ? "" : String.valueOf(mapping.get(date)));
+                }
+                super.onSuccess(result); //这里觉得何时执行onFinally
+                if (currentData.size()<Integer.parseInt(params.get("paginate"))){
+                    viewChange.addLoadStopFooter();
+                    isNoneLoad = true; //如果本次加载数据不足N条 则标记加载完毕
+                }else if (waybills.size()==0){ //如果加载完毕了 数据总量仍为0 那么属于没有数据 显示空数据视图
+                    viewChange.addLoadNoneFooter();
+                    isNoneLoad = true;
+                }else isNoneLoad = false;
+                if (after){
+                    listView.setAdapter(adapter);
+                    listView.onRefreshComplete();
+                }else adapter.notifyDataSetChanged();
+            }
+            @Override
+            public void onFinally() {
+                listView.removeFooterView(viewChange.mLoadLayout);
+                isNoneLoad = false;
+            }
+        });
+    }
+
+    /**
+     * 控件获取与变更
+     * */
+    //专线费文本框控件
+    private void showInputMsgDialog() {
+        WindowManager windowManager = getActivity().getWindowManager();
+        Display display = windowManager.getDefaultDisplay();
+        WindowManager.LayoutParams lp = mInputEditDialog.getWindow().getAttributes();
+        lp.width = (int) (display.getWidth()); //设置宽度
+        mInputEditDialog.getWindow().setAttributes(lp);
+        mInputEditDialog.setCancelable(true);
+        mInputEditDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        mInputEditDialog.show();
+    }
+    //初始化calender 时间选择器控件
+    private void initCalendar() {
+        String[] dateTxt = view.getText().toString().split("-");
+        if (dateTxt.length!=3){
+            Toast.makeText(getActivity(), "数据异常,无法处理", Toast.LENGTH_SHORT).show();
+            return;
+        }
+        TimePickerView pvTime = new TimePickerBuilder(getActivity(), this).build();
+        Calendar startDate = Calendar.getInstance();
+        startDate.set(Integer.parseInt(dateTxt[0]), Integer.parseInt(dateTxt[1])-1,
+                Integer.parseInt(dateTxt[2]));
+        pvTime.setDate(startDate);
+        pvTime.setTitleText("选择发货时间");
+        pvTime.show();
+    }
+
+    /**
+     * 事件监听实例
+     * */
+    @Override
+    public void onClick(View v) {
+        if (v.getId()==R.id.waybill_back){
+            getActivity().finish();
+        }else if (v.getId()==R.id.date_picker_1 || v.getId()==R.id.date_picker_2){
+            initCalendar();
+        }else if (v.getId()==R.id.edit_fee){
+            showInputMsgDialog();
+            mInputEditDialog.setmOnTextSendListener(this);
+        }
+    }
+    @Override
+    public boolean onEditorAction(TextView v, int i, KeyEvent keyEvent) {
+        if (i == EditorInfo.IME_ACTION_SEARCH){
+            InputMethodManager manager = ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE));
+            if (manager != null)
+                manager.hideSoftInputFromWindow(view.getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
+            String txt = v.getText().toString();
+            if (!txt.equals(params.get("search"))){
+                isNoneLoad = false;
+                params.put("search", txt);
+                params.put("deliver_at", "");
+                params.put("page", "1");
+                waybills.clear();
+                adapter = new WaybillDispatchList(waybills,getActivity());
+                listView.setAdapter(adapter);
+                loadListViewItem();
+            }
+        }
+        return false;
+    }
+    @Override
+    public void onTextSend(String msg) {
+        Double feeInfo = checkDailyBillingFee(msg);
+        if (feeInfo==null){
+            return;
+        }
+        String date = checkDailyBillingDate();
+        if (date==null){
+            return;
+        }
+
+        WasSubscribe.dailyBilling(date, feeInfo, new WasHandlerCallback<Boolean>() {
+            @Override
+            public void onSuccess(Boolean result) {
+                if (result){
+                    fee.setText(msg);
+                    mapping.put(date,feeInfo);
+                    Hint.toastShort(getContext(), "修改成功", Hint.COMMON);
+                }else{
+                    Hint.toastShort(getContext(), "专线费修改异常", Hint.ERROR);
+                }
+            }
+        });
+    }
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        status = scrollState;
+    }
+    @Override
+    public void onScroll(AbsListView v, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+        topIndex = firstVisibleItem;
+        switch (this.status){
+            case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
+                if (topIndex+visibleItemCount==totalItemCount){
+                    if (!isNoneLoad)params.put("page",String.valueOf(Integer.parseInt(params.get("page"))+1));
+                    loadListViewItem();
+                }
+                String date = waybills.get(topIndex).getDeliverAt().substring(0,10);
+                if (!date.equals(view.getText())){
+                    view.setText(date);
+                    fee.setText(mapping.get(date)==null ? "" : String.valueOf(mapping.get(date)));
+                }
+                break;
+            case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
+                break;
+            case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
+        }
+    }
+    @Override
+    public void refreshData() {
+        if (topIndex==0){
+            params.put("search","");
+            params.put("deliver_at","");
+            params.put("page","1");
+            requestData(true);
+        } else listView.onRefreshComplete();
+    }
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        currentIndex = position;
+
+        Intent intent = new Intent(getActivity(), WaybillDispatchItemActivity.class);
+        Bundle bundle = new Bundle();
+        bundle.putSerializable("waybill", waybills.get(position-1));
+        intent.putExtras(bundle);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        startActivityForResult(intent,1);
+    }
+    @Override
+    public void onTimeSelect(Date date, View v) {
+        String deliverDate = DateUtil.getTime(date);
+        if (!deliverDate.equals("") && !deliverDate.equals(params.get("deliver_at"))){
+            view.setText(deliverDate);
+            isNoneLoad = false;
+            params.put("search", "");
+            params.put("deliver_at", deliverDate);
+            params.put("page", "1");
+            waybills.clear();
+            adapter = new WaybillDispatchList(waybills,getActivity());
+            listView.setAdapter(adapter);
+            loadListViewItem();
+        }
+    }
+
+    /**
+     * 参数校验与格式化
+     * */
+    private Double checkDailyBillingFee(String msg){
+        double feeInfo;
+        try {
+            feeInfo = Double.parseDouble(msg);
+        }catch (NumberFormatException e){
+            Hint.toastShort(getContext(), "输入非法,非数字与科学计数法", Hint.COMMON);
+            return null;
+        }
+        return feeInfo;
+    }
+    private String checkDailyBillingDate(){
+        String date = view.getText().toString();
+        if (date.split("-").length!=3){
+            Toast.makeText(getActivity(), "请选择正确日期", Toast.LENGTH_SHORT).show();
+            return null;
+        }
+        return date;
+    }
+}

+ 5 - 0
app/src/main/java/com/baoshi/swms/inter/OnRefreshListener.java

@@ -0,0 +1,5 @@
+package com.baoshi.swms.inter;
+
+public interface OnRefreshListener {
+    void refreshData();
+}

+ 29 - 0
app/src/main/java/com/baoshi/swms/net/api/SwmsApi.java

@@ -0,0 +1,29 @@
+package com.baoshi.swms.net.api;
+
+import io.reactivex.Observable;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import retrofit2.http.Body;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+
+/**
+ * SWAS API ROUTER(JAVA)
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/21 15:55
+ */
+public interface SwmsApi {
+    @GET("api/picking/getTask")
+    Observable<ResponseBody> getPickingTask(@Query(value = "userId") Long userId, @Query(value = "type") String pickingName);
+
+    @GET("api/picking/getUserTodayWorkNum")
+    Observable<ResponseBody> getUserTodayWorkNum(@Query("userId") Long userId);
+
+    @GET("api/picking/giveUpTask")
+    Observable<ResponseBody> giveUpTask(@Query("userId") Long userId, @Query("code") String code);
+
+    @POST("api/picking")
+    Observable<ResponseBody> picking(@Body RequestBody body);
+}

+ 54 - 0
app/src/main/java/com/baoshi/swms/net/api/WasApi.java

@@ -0,0 +1,54 @@
+package com.baoshi.swms.net.api;
+
+import androidx.annotation.Nullable;
+
+import java.util.Map;
+
+import io.reactivex.Observable;
+import okhttp3.ResponseBody;
+import retrofit2.http.Field;
+import retrofit2.http.FieldMap;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+
+/**
+ * WAS API ROUTER(PHP)
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:55
+ */
+public interface WasApi{
+    @POST("login")
+    @FormUrlEncoded
+    Observable<ResponseBody> login(@Field("username") String username, @Field("password") String password);
+
+    @GET("order/batchRecover")
+    Observable<ResponseBody> recover(@Query("code") String batch);
+
+    @POST("waybill/dispatch/dailyBilling")
+    @FormUrlEncoded
+    Observable<ResponseBody> dailyBilling(@Field("deliver_at") String deliverAt, @Field("fee") double fee);
+
+    @POST("waybill/dispatch")
+    @FormUrlEncoded
+    Observable<ResponseBody> dispatch(@FieldMap Map<String,String> map);
+
+    @GET("waybill/dispatch")
+    Observable<ResponseBody> getDispatchList(@Query("search") String searchTxt, @Nullable @Query("deliver_at") String deliverAt, @Query("page") Integer page, @Query("paginate") Integer paginate);
+
+    @GET("inventory/inventoryTask")
+    Observable<ResponseBody> getStocktaking(@Query("taskId") String stocktakingId);
+
+    @POST("inventory/locationInvPro")
+    @FormUrlEncoded
+    Observable<ResponseBody> getLocStocktakingRatio(@Field("taskId") Long stocktakingId, @Field("location") String location);
+
+    @POST("inventory/getInventoryDetail")
+    @FormUrlEncoded
+    Observable<ResponseBody> getLocGoodsInfo(@Field("taskId") Long stocktakingId, @Field("location") String location, @Field("barcode") String barcode);
+
+    @POST("inventory/stockInventory")
+    Observable<ResponseBody> stocktaking(@FieldMap Map<String,Object> map);
+}

+ 98 - 0
app/src/main/java/com/baoshi/swms/net/build/NetBuilder.java

@@ -0,0 +1,98 @@
+package com.baoshi.swms.net.build;
+
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Cache;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * 网络请求构建器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:54
+ */
+public class NetBuilder {
+
+    private final OkHttpClient.Builder okHttpBuilder;
+    public NetBuilder() {
+        //手动创建一个OkHttpClient并设置超时时间
+        okHttpBuilder = new OkHttpClient.Builder();
+    }
+     /**
+     * 连接超时
+     * @param outTime
+     * @return
+     */
+    public NetBuilder setConnectTimeout(int outTime) {
+        okHttpBuilder.connectTimeout(outTime, TimeUnit.SECONDS);
+        return this;
+    }
+    /**
+     * 读超时
+     * @param outTime
+     * @return
+     */
+    public NetBuilder setReadTimeout(int outTime) {
+        okHttpBuilder.readTimeout(outTime, TimeUnit.SECONDS);
+        return this;
+    }
+    /**
+     * 写超时
+     * @param outTime
+     * @return
+     */
+    public NetBuilder setWriteTimeout(int outTime) {
+        okHttpBuilder.writeTimeout(outTime, TimeUnit.SECONDS);
+        return this;
+    }
+    /**
+     * 错误重连
+     * @param retry
+     * @return
+     */
+    public NetBuilder setRetryOnConnectionFailure(boolean retry) {
+        okHttpBuilder.retryOnConnectionFailure(retry);
+        return this;
+    }
+    /**
+     * 添加拦截器
+     * @param interceptor
+     * @return
+     */
+    public NetBuilder addInterceptor(Interceptor interceptor) {
+        okHttpBuilder.addInterceptor(interceptor);
+        return this;
+    }
+    /**
+     * 添加倒数第二层拦截器
+     * @param interceptor
+     * @return
+     */
+    public NetBuilder addNetInterceptor(Interceptor interceptor) {
+        okHttpBuilder.addNetworkInterceptor(interceptor);
+        return this;
+    }
+    /**
+     * 添加缓存文件
+     * @param cache
+     * @return
+     */
+    public NetBuilder addCache(Cache cache) {
+        okHttpBuilder.cache(cache);
+        return this;
+    }
+
+    public Retrofit build(String url) {
+       //构建Retrofit实例
+        return new Retrofit.Builder()
+                .client(okHttpBuilder.build())
+                .addConverterFactory(GsonConverterFactory.create())//json转换成JavaBean
+                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+                .baseUrl(url)
+                .build();
+    }
+}

+ 39 - 0
app/src/main/java/com/baoshi/swms/net/build/WebSocketClient.java

@@ -0,0 +1,39 @@
+package com.baoshi.swms.net.build;
+
+import com.baoshi.swms.bean.User;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+
+/**
+ * websocket客户端
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/9 15:24
+ */
+public class WebSocketClient {
+    private final Request request;
+    private final OkHttpClient client;
+    private WebSocket webSocket;
+
+    public WebSocketClient() {
+        client = new OkHttpClient();
+        request = new Request.Builder()
+                .url("wss://device.baoshi56.com/ws/android/"+ User.getLocalUser().getId())
+                .build();
+    }
+
+    public void start(WebSocketListener listener) {
+        client.dispatcher().cancelAll();
+        webSocket = client.newWebSocket(request, listener);
+    }
+
+    public void close() {
+        if (webSocket != null) {
+            webSocket.close(1000, null);
+        }
+        client.dispatcher().executorService().shutdown();
+    }
+}

+ 58 - 0
app/src/main/java/com/baoshi/swms/net/callback/NetCallback.java

@@ -0,0 +1,58 @@
+package com.baoshi.swms.net.callback;
+
+import androidx.annotation.NonNull;
+
+import com.baoshi.swms.net.inter.APICallback;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import io.reactivex.observers.DisposableObserver;
+import okhttp3.ResponseBody;
+
+/**
+ * 处理各种业务逻辑及错误
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:49
+ */
+public abstract class NetCallback<T> extends DisposableObserver<ResponseBody> implements APICallback<T> {
+    @Override
+    public void onNext(@NonNull ResponseBody body) {
+    }
+
+    @Override
+    public void onComplete() {//发射成功
+    }
+    /**
+     * 请求出错,包含网络错误
+     * @param e
+     */
+    @Override
+    public void onError(Throwable e)
+    {
+        onFinally();
+    }
+    /**
+     * 自定义的一些处理方法
+     * */
+    @Override
+    public void onFault(int code, String errorMsg){
+        onFinally();
+    }
+    @Override
+    public void onSuccess(T result){
+        onFinally();
+    }
+    @Override
+    public void onFinally() {
+        //无论何种结果都执行此方法 确保重写上述方法(onComplete,onError,onFault,onSuccess)时执行super
+    }
+
+    protected Type getType() {
+        //获取第一个泛型类型
+        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
+        assert parameterizedType != null;
+        return parameterizedType.getActualTypeArguments()[0];
+    }
+}

+ 70 - 0
app/src/main/java/com/baoshi/swms/net/callback/SwmsHandlerCallback.java

@@ -0,0 +1,70 @@
+package com.baoshi.swms.net.callback;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.baoshi.swms.App;
+import com.baoshi.swms.util.Hint;
+import com.baoshi.swms.util.NetUtil;
+import com.google.gson.Gson;
+
+import org.json.JSONObject;
+
+import okhttp3.ResponseBody;
+
+/**
+ * WAS
+ * 统一处理的逻辑,都在类似这样的方法中处理。暴露在每个最外层的callback只处理指定场景下的业务逻辑。
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:48
+ */
+public abstract class SwmsHandlerCallback<T> extends NetCallback<T> {
+    @Override
+    public void onNext(@NonNull ResponseBody body) {
+        try {
+            String result = body.string();
+            //Log.e("e","响应数据为:"+result);
+            JSONObject jsonObject = new JSONObject(result);
+            int resultCode = jsonObject.getInt("code");
+            if (resultCode == 200) {
+                Gson gson = new Gson();
+                String data = jsonObject.getString("data");
+                if (!TextUtils.isEmpty(data)) {
+                    T t = gson.fromJson(data, getType());
+                    onSuccess(t);
+                }
+            } else {
+                String errorMsg = jsonObject.getString("message");
+                onFault(0, errorMsg);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            onError(e);
+        }
+    }
+
+    @Override
+    public void logout(){
+    }
+
+    @Override
+    public void onFault(int code, String errorMsg) {
+        super.onFault(code,errorMsg);
+        if (code==0){
+            Hint.toastShort(App.getAppContext(), errorMsg, Hint.COMMON);
+        }
+    }
+
+    @Override
+    public void onError(Throwable e) {
+        super.onError(e);
+        e.printStackTrace();
+        if (NetUtil.isNetworkConnected(App.getAppContext())){
+            Hint.toastShort(App.getAppContext(), "内部服务器错误", Hint.ERROR);
+        }else{
+            Hint.toastShort(App.getAppContext(),"网络连接失败",Hint.COMMON);
+        }
+    }
+}

+ 93 - 0
app/src/main/java/com/baoshi/swms/net/callback/WasHandlerCallback.java

@@ -0,0 +1,93 @@
+package com.baoshi.swms.net.callback;
+
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import com.baoshi.swms.App;
+import com.baoshi.swms.MyActivityManager;
+import com.baoshi.swms.activity.LoginActivity;
+import com.baoshi.swms.util.GlobalStorage;
+import com.baoshi.swms.util.Hint;
+import com.baoshi.swms.util.NetUtil;
+import com.google.gson.Gson;
+
+import org.json.JSONObject;
+
+import okhttp3.ResponseBody;
+
+/**
+ * WAS
+ * 统一处理的逻辑,都在类似这样的方法中处理。暴露在每个最外层的callback只处理指定场景下的业务逻辑。
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:48
+ */
+public abstract class WasHandlerCallback<T> extends NetCallback<T> {
+    @Override
+    public void onNext(@NonNull ResponseBody body) {
+        try {
+            String result = body.string();
+            //Log.e("e","响应数据为:"+result);
+            JSONObject jsonObject = new JSONObject(result);
+            double resultCode = jsonObject.getDouble("status_code");
+            if (resultCode == 200.0) {
+                Gson gson = new Gson();
+                String data = jsonObject.getString("data");
+                if (!TextUtils.isEmpty(data)) {
+                    T t = gson.fromJson(data, getType());
+                    onSuccess(t);
+                }
+            } else if (resultCode == 401.0) {
+                logout();
+            } else if (resultCode == 400.0){
+                onFault(1, jsonObject.getString("data"));
+            } else {
+                String errorMsg = jsonObject.getString("message");
+                onFault(0, errorMsg);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            onError(e);
+        }
+    }
+
+    @Override
+    public void logout() {
+        Context context = MyActivityManager.getInstance().getCurrentActivity();
+        GlobalStorage storage = GlobalStorage.getGlobalStorage();
+        androidx.appcompat.app.AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setMessage("登录已过期,请重新登录")
+                .setPositiveButton("确定", (dialogInterface, i) -> {
+                    storage.remove("token");
+                    storage.remove("user");
+                    storage.remove("menu");
+                    Intent intent = new Intent(context, LoginActivity.class);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+                    context.startActivity(intent);
+                }).create().show();
+    }
+
+    @Override
+    public void onFault(int code, String errorMsg) {
+        super.onFault(code,errorMsg);
+        if (code==0){
+            Hint.toastShort(App.getAppContext(), errorMsg, Hint.COMMON);
+        }
+    }
+
+    @Override
+    public void onError(Throwable e) {
+        super.onError(e);
+        e.printStackTrace();
+        if (NetUtil.isNetworkConnected(App.getAppContext())){
+            Hint.toastShort(App.getAppContext(), "内部服务器错误", Hint.ERROR);
+        }else{
+            Hint.toastShort(App.getAppContext(),"网络连接失败",Hint.COMMON);
+        }
+    }
+}

+ 15 - 0
app/src/main/java/com/baoshi/swms/net/constant/SwmsConstant.java

@@ -0,0 +1,15 @@
+package com.baoshi.swms.net.constant;
+
+/**
+ * Swms网络请求全局变量
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/21 15:56
+ */
+public class SwmsConstant {
+    public static final String BASE_URL = "http://192.168.7.12:8116/";
+    public static final String CACHE_NAME = "swms_cache_channel";
+    public static final int DEFAULT_CONNECT_TIMEOUT = 3;
+    public static final int DEFAULT_WRITE_TIMEOUT = 3;
+    public static final int DEFAULT_READ_TIMEOUT = 5;
+}

+ 12 - 0
app/src/main/java/com/baoshi/swms/net/constant/SwmsWebSocketConstant.java

@@ -0,0 +1,12 @@
+package com.baoshi.swms.net.constant;
+
+/**
+ * WEBSOCKET的一些标识
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/10 14:17
+ */
+public class SwmsWebSocketConstant {
+    public static final String CLOSE_SIGN = "CLOSE";
+    public static final String PULL_SIGN = "DATA";
+}

+ 16 - 0
app/src/main/java/com/baoshi/swms/net/constant/WasConstant.java

@@ -0,0 +1,16 @@
+package com.baoshi.swms.net.constant;
+
+/**
+ * Was网络请求全局变量
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/3 14:12
+ */
+public class WasConstant {
+    public static final String VERSION_CODE = "v1";
+    public static final String BASE_URL = "https://was.baoshi56.com/api/"+VERSION_CODE+"/";
+    public static final String CACHE_NAME = "was_cache_channel";
+    public static final int DEFAULT_CONNECT_TIMEOUT = 3;
+    public static final int DEFAULT_WRITE_TIMEOUT = 3;
+    public static final int DEFAULT_READ_TIMEOUT = 5;
+}

+ 17 - 0
app/src/main/java/com/baoshi/swms/net/inter/APICallback.java

@@ -0,0 +1,17 @@
+package com.baoshi.swms.net.inter;
+
+/**
+ * API响应回调接口
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:47
+ */
+public interface APICallback<T> {
+    void logout();
+
+    void onSuccess(T result);
+
+    void onFault(int code, String errorMsg);
+
+    void onFinally();
+}

+ 56 - 0
app/src/main/java/com/baoshi/swms/net/interceptor/CacheInterceptor.java

@@ -0,0 +1,56 @@
+package com.baoshi.swms.net.interceptor;
+
+import android.content.Context;
+
+import com.baoshi.swms.net.constant.WasConstant;
+import com.baoshi.swms.util.NetUtil;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+import okhttp3.CacheControl;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * 请求Cache拦截器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:54
+ */
+public class CacheInterceptor implements Interceptor {
+    private final Context mContext;
+    public CacheInterceptor(Context context) {
+        mContext = context;
+    }
+
+    @NotNull
+    @Override
+    public Response intercept(@NotNull Chain chain) throws IOException {
+        Request request = chain.request();
+        if (!NetUtil.isNetworkConnected(mContext)) {
+            request = request.newBuilder()
+                    .cacheControl(CacheControl.FORCE_CACHE)
+                    .build();
+        }
+        Response response = chain.proceed(request);
+        if (NetUtil.isNetworkConnected(mContext)) {
+            int maxAge = 0;
+            // 有网络时 设置缓存超时时间0个小时
+            response.newBuilder()
+                    .header("Cache-Control", "public, max-age=" + maxAge)
+                    .removeHeader(WasConstant.CACHE_NAME)// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
+                    .build();
+        }  else {
+            // 无网络时,设置超时为4周
+            int maxStale = 60 * 60 * 24 * 28;
+            response.newBuilder()
+                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
+                    .removeHeader(WasConstant.CACHE_NAME)
+                    .build();
+        }
+        return response;
+    }
+}

+ 66 - 0
app/src/main/java/com/baoshi/swms/net/interceptor/HeaderInterceptor.java

@@ -0,0 +1,66 @@
+package com.baoshi.swms.net.interceptor;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okio.Buffer;
+
+/**
+ * 请求Headers拦截器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:54
+ */
+public class HeaderInterceptor implements Interceptor {
+    private final Map<String, String> params;
+
+    public HeaderInterceptor(Map<String, String> p) {
+        this.params = p;
+    }
+
+    @NotNull
+    @Override
+    public Response intercept(@NotNull Chain chain) throws IOException {
+        Request originalRequest = chain.request();
+        //设置头部信息
+        Request.Builder requestBuilder = originalRequest.newBuilder();
+        if (params != null && params.size() > 0) {
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                String value = entry.getValue();
+                if (TextUtils.isEmpty(value) || "null".equals(value)) {
+                    value = "";
+                }
+                requestBuilder.addHeader(entry.getKey(), value);
+            }
+        }
+        requestBuilder.addHeader("Connection","close");
+        requestBuilder.method(originalRequest.method(), originalRequest.body());
+        Request request = requestBuilder.build();
+
+        //TODO 打印请求信息,正式项目中可以注释掉
+        /*RequestBody requestBody = request.body();
+        String body = null;
+        if (requestBody != null) {
+            Buffer buffer = new Buffer();
+            requestBody.writeTo(buffer);
+
+            body = buffer.readString(StandardCharsets.UTF_8);
+        }
+        Log.e("METHOD",request.method());
+        Log.e("URL", String.valueOf(request.url()));
+        Log.e("HEADERS", String.valueOf(request.headers()));
+        Log.e("BODY","body:"+body);*/
+
+        return chain.proceed(request);
+    }
+}

+ 131 - 0
app/src/main/java/com/baoshi/swms/net/listener/SwmsWebSocketListener.java

@@ -0,0 +1,131 @@
+package com.baoshi.swms.net.listener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import com.baoshi.swms.net.constant.SwmsWebSocketConstant;
+import com.google.gson.Gson;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Date;
+
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+
+/**
+ * websocket监听程序
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/9 16:55
+ */
+public class SwmsWebSocketListener extends WebSocketListener {
+
+    private final int HEARTBEAT_HZ = 10; //S
+    private final String SUBSCRIBE = "{\"dataType\":\"subscribe\",\"subscribeList\":[{\"subType\":\"pieceSize\"}]}";
+    public boolean START_STATUS = false;
+    public WebSocket webSocket;
+    public Handler handler;
+    private final Long timestamp;
+
+    //构造方法获取主线程的消息处理器
+    public SwmsWebSocketListener(Handler handler){
+        this.timestamp = new Date().getTime(); //先记录下尝试启动时的时间戳
+        this.handler = handler;
+    }
+    @Override
+    public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
+        this.webSocket = webSocket;
+        openTimeTask();
+    }
+
+    @Override
+    public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
+        Gson gson = new Gson();
+        Struct struct = gson.fromJson(text, Struct.class);
+        if (isPushMsg(struct)){
+            //执行UI更新
+            Message message = handler.obtainMessage();
+            message.obj = SwmsWebSocketConstant.PULL_SIGN;
+            Bundle arg = new Bundle();
+            arg.putInt("number",((Double) struct.getData()).intValue());
+            message.setData(arg);
+            handler.sendMessage(message);
+        }
+    }
+
+    @Override
+    public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
+        closeTimeTask();
+    }
+
+    @Override
+    public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
+        closeTimeTask();
+    }
+
+    @Override
+    public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
+        closeTimeTask();
+    }
+
+    private void openTimeTask(){
+        START_STATUS = true;
+        new Thread(()->{
+            try {
+                while (START_STATUS){
+                    webSocket.send(SUBSCRIBE);
+                    Thread.sleep(HEARTBEAT_HZ * 1000);
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }).start();
+    }
+
+    private void closeTimeTask(){
+        START_STATUS = false;
+
+        //如果启动时间至关闭 不足十秒让他sleep 防止频繁的消息推送
+        long diff = new Date().getTime()-this.timestamp;
+        long touch = (long) (HEARTBEAT_HZ * 1000);
+        if (diff < touch){
+            try{
+                Thread.sleep(touch-diff);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        //通知主线程 websocket监听被关闭,让它重启
+        Message message = handler.obtainMessage();
+        message.obj = SwmsWebSocketConstant.CLOSE_SIGN;
+        handler.sendMessage(message);
+    }
+
+    private boolean isPushMsg(Struct struct){
+        return "subscribe.pieceSize".equals(struct.getDataType());
+    }
+
+    static class Struct{
+        private Object data;
+        private String dataType;
+
+        public String getDataType() {
+            return dataType;
+        }
+
+        public void setDataType(String dataType) {
+            this.dataType = dataType;
+        }
+
+        public Object getData() {
+            return data;
+        }
+
+        public void setData(Object data) {
+            this.data = data;
+        }
+    }
+}

+ 73 - 0
app/src/main/java/com/baoshi/swms/net/subscribe/BaseSubscribe.java

@@ -0,0 +1,73 @@
+package com.baoshi.swms.net.subscribe;
+
+import com.baoshi.swms.App;
+import com.baoshi.swms.net.build.NetBuilder;
+import com.baoshi.swms.net.constant.WasConstant;
+import com.baoshi.swms.net.interceptor.CacheInterceptor;
+import com.baoshi.swms.net.interceptor.HeaderInterceptor;
+
+import java.io.File;
+import java.util.Map;
+
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.observers.DisposableObserver;
+import io.reactivex.schedulers.Schedulers;
+import okhttp3.Cache;
+import okhttp3.ResponseBody;
+
+/**
+ * 基础全局网络订阅者
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/21 15:43
+ */
+public class BaseSubscribe {
+    /**
+     * 关键方法,可以根据需求产出不同的api
+     * @param baseUrl
+     * @param params
+     * @param apiClass
+     * @param <T>
+     * @return
+     */
+    public static <T>T newApi(String baseUrl, Map<String, String> params, Class<T> apiClass) {
+        NetBuilder builder = new NetBuilder()
+                .setConnectTimeout(WasConstant.DEFAULT_CONNECT_TIMEOUT)
+                .setReadTimeout(WasConstant.DEFAULT_READ_TIMEOUT)
+                .setWriteTimeout(WasConstant.DEFAULT_WRITE_TIMEOUT)
+                .setRetryOnConnectionFailure(true);
+        //添加header
+        if (params != null && params.size()>0) {
+            HeaderInterceptor headerInterceptor = new HeaderInterceptor(params);
+            builder.addInterceptor(headerInterceptor);
+        }
+        //添加缓存
+        File cacheFile = new File(App.getAppContext().getExternalCacheDir(), WasConstant.CACHE_NAME);
+        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
+        builder.addCache(cache);
+
+        //添加缓存拦截器
+        CacheInterceptor cacheInterceptor = new CacheInterceptor(App.getAppContext());
+        builder.addInterceptor(cacheInterceptor);
+
+        return builder.build(baseUrl).create(apiClass);
+    }
+
+    /**
+     * 设置订阅 和 所在的线程环境
+     * @param o
+     * @param s
+     */
+    public static void toSubscribe(Observable<ResponseBody> o, DisposableObserver<ResponseBody> s) {
+        o.subscribeOn(Schedulers.io())
+                .unsubscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .retry(1)//请求失败重连次数
+                .subscribe(s);
+    }
+
+    protected static Map<String, String> getHeaderParams() {
+        return null;
+    }
+}

+ 35 - 0
app/src/main/java/com/baoshi/swms/net/subscribe/SwmsBaseSubscribe.java

@@ -0,0 +1,35 @@
+package com.baoshi.swms.net.subscribe;
+
+import com.baoshi.swms.net.api.SwmsApi;
+import com.baoshi.swms.net.constant.SwmsConstant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * SWMS基础发布者
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/21 15:59
+ */
+public class SwmsBaseSubscribe extends BaseSubscribe{
+    private static SwmsApi okHttpApi;
+
+    public static SwmsApi getOkHttpApi() {
+        if (okHttpApi==null) {
+            okHttpApi = newApi(SwmsConstant.BASE_URL, getHeaderParams(), SwmsApi.class);
+        }
+        return okHttpApi;
+    }
+
+    public static void refreshSingleton(){
+        okHttpApi=null;
+    }
+
+    //设置表头
+    protected static Map<String, String> getHeaderParams() {
+        Map<String, String> params = new HashMap<>();
+        params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
+        return params;
+    }
+}

+ 52 - 0
app/src/main/java/com/baoshi/swms/net/subscribe/SwmsSubscribe.java

@@ -0,0 +1,52 @@
+package com.baoshi.swms.net.subscribe;
+
+import com.baoshi.swms.bean.User;
+
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import io.reactivex.Observable;
+import io.reactivex.observers.DisposableObserver;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+
+/**
+ * SWMS API请求订阅器
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/25 14:52
+ */
+public class SwmsSubscribe {
+    private static final Long userId = Long.valueOf(User.getLocalUser().getId());
+
+    public static void getPickingTask(String waveType, DisposableObserver<ResponseBody> subscriber) {
+        Observable<ResponseBody> observable = SwmsBaseSubscribe.getOkHttpApi().getPickingTask(userId,waveType);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void getUserTodayWorkNum(DisposableObserver<ResponseBody> subscriber) {
+        Observable<ResponseBody> observable = SwmsBaseSubscribe.getOkHttpApi().getUserTodayWorkNum(userId);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void giveUpTask(String code, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = SwmsBaseSubscribe.getOkHttpApi().giveUpTask(userId,code);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void picking(ArrayList<HashMap<String,Object>> items, String code, Boolean completeSign, DisposableObserver<ResponseBody> subscriber){
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("userId", userId);
+        map.put("items", items);
+        map.put("code", code);
+        map.put("completeSign", completeSign);
+
+        MediaType mediaType=MediaType.Companion.parse("application/json;charset=utf-8");
+        RequestBody body=RequestBody.Companion.create(new JSONObject(map).toString(),mediaType);
+        Observable<ResponseBody> observable = SwmsBaseSubscribe.getOkHttpApi().picking(body);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+}

+ 46 - 0
app/src/main/java/com/baoshi/swms/net/subscribe/WasBaseSubscribe.java

@@ -0,0 +1,46 @@
+package com.baoshi.swms.net.subscribe;
+
+import android.text.TextUtils;
+
+import com.baoshi.swms.net.api.WasApi;
+import com.baoshi.swms.net.constant.WasConstant;
+import com.baoshi.swms.util.GlobalStorage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Was基础发布者
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/3 14:42
+ */
+public class WasBaseSubscribe extends BaseSubscribe{
+    private static WasApi okHttpApi;
+
+    public static WasApi getOkHttpApi() {
+        if (okHttpApi==null) {
+            okHttpApi = newApi(WasConstant.BASE_URL, getHeaderParams(), WasApi.class);
+        }
+        return okHttpApi;
+    }
+
+    public static void refreshSingleton(){
+        okHttpApi=null;
+    }
+
+    //设置表头
+    protected static Map<String, String> getHeaderParams() {
+        String token = GlobalStorage.getGlobalStorage().get("token");
+
+        String timestamp;
+        timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+        Map<String, String> params = new HashMap<>();
+        if (!TextUtils.isEmpty(token)) {
+            params.put("token", token);
+        }
+        params.put("timestamp", timestamp);
+
+        return params;
+    }
+}

+ 63 - 0
app/src/main/java/com/baoshi/swms/net/subscribe/WasSubscribe.java

@@ -0,0 +1,63 @@
+package com.baoshi.swms.net.subscribe;
+
+import android.util.Base64;
+
+import java.util.Map;
+
+import io.reactivex.Observable;
+import io.reactivex.observers.DisposableObserver;
+import okhttp3.ResponseBody;
+
+/**
+ * WAS API请求订阅器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:53
+ */
+public class WasSubscribe {
+    public static void login(String username, String pwd, DisposableObserver<ResponseBody> subscriber) {
+        pwd = Base64.encodeToString(pwd.getBytes(),Base64.DEFAULT);//base64加密
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().login(username,pwd);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void recover(String batch, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().recover(batch);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void dailyBilling(String deliverAt, double fee, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().dailyBilling(deliverAt, fee);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void dispatch(Map<String,String> map, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().dispatch(map);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void getDispatchList(String searchTxt, String deliverAt, Integer page, Integer paginate, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().getDispatchList(searchTxt,deliverAt,page,paginate);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void getStocktaking(String stocktakingId, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().getStocktaking(stocktakingId);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void getLocStocktakingRatio(Long stocktakingId, String location, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().getLocStocktakingRatio(stocktakingId,location);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void getLocGoodsInfo(Long stocktakingId, String location, String barcode, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().getLocGoodsInfo(stocktakingId,location, barcode);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+
+    public static void stocktaking(Map<String,Object> map, DisposableObserver<ResponseBody> subscriber){
+        Observable<ResponseBody> observable = WasBaseSubscribe.getOkHttpApi().stocktaking(map);
+        WasBaseSubscribe.toSubscribe(observable, subscriber);
+    }
+}

+ 146 - 0
app/src/main/java/com/baoshi/swms/part/InputEditDialog.java

@@ -0,0 +1,146 @@
+package com.baoshi.swms.part;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.baoshi.swms.R;
+
+/**
+ * 定位在软键盘上方的仿VX输入框
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:06
+ */
+public class InputEditDialog extends Dialog {
+    public interface OnTextSendListener {
+        void onTextSend(String msg);
+    }
+    private final EditText messageTextView; //文本框
+    private final Context mContext; //上下文
+    private final InputMethodManager imm;//键盘
+    private int mLastDiff = 0;//最后的标记
+    private OnTextSendListener mOnTextSendListener;//文本发送事件监听对象
+    protected InputMethodManager inputManager;//输入框类型
+
+    /**
+     *
+     * @param context 上下文
+     * @param theme   theme的样式ID
+     * @param hint    输入框提示文本
+     */
+    public InputEditDialog(Context context, int theme, String hint, int InputType) {
+        super(context, theme); //调用父类构造
+        mContext = context;    //上下文传递给全局
+        setContentView(R.layout.part_edit_keydown);//设置上下文的视图
+        inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);//输入框服务
+        messageTextView = (EditText) findViewById(R.id.et_input_message);//文本框对象
+        messageTextView.setBackgroundColor(getContext().getResources().getColor(R.color.black));
+        messageTextView.setInputType(InputType);//文本类型
+        messageTextView.setHint(hint);//提示
+        messageTextView.getBackground().setColorFilter(context.getResources().getColor(R.color.info), PorterDuff.Mode.CLEAR);//下划线颜色
+        ImageView confirmBtn = (ImageView) findViewById(R.id.confrim_btn); //发送按钮对象
+        imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); //键盘服务
+        confirmBtn.setOnClickListener(new View.OnClickListener() { //监听点击发送事件
+            @Override
+            public void onClick(View view) {
+                String msg = messageTextView.getText().toString().trim(); //获取输入框文本
+                if (!TextUtils.isEmpty(msg)) {
+                    mOnTextSendListener.onTextSend(msg);
+                    imm.showSoftInput(messageTextView, InputMethodManager.SHOW_FORCED);
+                    imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
+                    messageTextView.setText("");
+                    dismiss();
+                }
+                messageTextView.setText(null);
+            }
+        });
+        messageTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() {//键盘输入事件监听
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                switch (actionId) {
+                    case KeyEvent.KEYCODE_ENDCALL:
+                    case KeyEvent.KEYCODE_ENTER:
+                        if (messageTextView.getText().length() > 0) {
+                            imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
+                            dismiss();
+                        } else Toast.makeText(mContext, "input can not be empty!", Toast.LENGTH_LONG).show();
+                        return true;
+                    case KeyEvent.KEYCODE_BACK:
+                        dismiss();
+                        return false;
+                    default:
+                        return false;
+                }
+            }
+        });
+        messageTextView.setOnKeyListener(new View.OnKeyListener() {//按键监听
+            @Override
+            public boolean onKey(View view, int i, KeyEvent keyEvent) {
+                return false;
+            }
+        });
+        RelativeLayout rlDlg = (RelativeLayout) findViewById(R.id.rl_outside_view);//视图容器布局对象
+        rlDlg.setOnClickListener(new View.OnClickListener() {//点击容器关闭输入框与键盘
+            @Override
+            public void onClick(View v) {
+                if (v.getId() != R.id.rl_inputdlg_view) dismiss();
+            }
+        });
+        final LinearLayout rldlgview = (LinearLayout) findViewById(R.id.rl_inputdlg_view);//输入框容器对象
+        rldlgview.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {//监听容器变更事件
+            @Override
+            public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
+                Rect r = new Rect();
+                //获取当前界面可视部分
+                getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
+                //获取屏幕的高度
+                int screenHeight = getWindow().getDecorView().getRootView().getHeight();
+                //获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数
+                int heightDifference = screenHeight - r.bottom;
+                if (heightDifference <= 0 && mLastDiff > 0)dismiss();
+                mLastDiff = heightDifference;
+            }
+        });
+        rldlgview.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                imm.hideSoftInputFromWindow(messageTextView.getWindowToken(), 0);
+                dismiss();
+            }
+        });
+    }
+
+    public void setmOnTextSendListener(OnTextSendListener onTextSendListener) {//文本发送对象监听
+        this.mOnTextSendListener = onTextSendListener;
+    }
+
+    @Override
+    public void dismiss() {
+        super.dismiss();
+        //dismiss之前重置mLastDiff值避免下次无法打开
+        mLastDiff = 0;
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        showSoftKeyboard(messageTextView);
+    }
+    private void showSoftKeyboard(EditText et) {
+        if (et == null)return;
+        et.requestFocus();
+        inputManager.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT);
+    }
+}

+ 150 - 0
app/src/main/java/com/baoshi/swms/part/RefreshHeader.java

@@ -0,0 +1,150 @@
+package com.baoshi.swms.part;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import androidx.annotation.RequiresApi;
+import com.baoshi.swms.R;
+import com.baoshi.swms.inter.OnRefreshListener;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class RefreshHeader extends ListView {
+    private static final int STATE_PULL_REFRESH = 0;      //下拉刷新状态
+    private static final int STATE_RELEASE_REFRESH = 1;       //松开刷新的状态
+    private static final int STATE_REFRESHING_REFRESH = 2;        //正在刷新
+    @BindView(R.id.pull_pre_img)
+    ImageView pullPreImg;
+    @BindView(R.id.pull_tv)
+    TextView pullTv;
+    @BindView(R.id.pull_next_img)
+    ProgressBar pullNextImg;
+    private OnRefreshListener onRefreshListener;
+
+    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
+        this.onRefreshListener = onRefreshListener;
+    }
+
+    private int CurrentStateCode = STATE_PULL_REFRESH;
+    private View headView;
+    private int measuredHeight, startY;
+    private RotateAnimation imageAnimaRotate, imageAnimaReset;
+
+    public RefreshHeader(Context context) {
+        super(context);
+        initHead();
+    }
+
+    public RefreshHeader(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initHead();
+    }
+
+    public RefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initHead();
+    }
+
+
+    private void initHead() {
+        headView = View.inflate(getContext(), R.layout.part_refresh_header, null);
+        ButterKnife.bind(this, headView);
+        this.addHeaderView(headView);
+        headView.measure(0, 0); //测量头部布局
+        measuredHeight = headView.getMeasuredHeight();
+        headView.setPadding(0, -measuredHeight, 0, 0);
+        initAnimation();
+    }
+
+    public void initAnimation() {
+        imageAnimaRotate = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+        imageAnimaRotate.setFillAfter(true);
+        imageAnimaRotate.setDuration(200);
+
+        imageAnimaReset = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+        imageAnimaReset.setFillAfter(true);
+        imageAnimaReset.setDuration(200);
+
+    }
+
+
+    @RequiresApi(api = Build.VERSION_CODES.S)
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                startY = (int) ev.getRawY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (startY == -1) {
+                    startY = (int) ev.getY();
+                }
+                if (CurrentStateCode == STATE_REFRESHING_REFRESH) {
+                    break;
+                }
+                int endY = (int) ev.getRawY();
+                int des = endY - startY;
+                if (des > 0 && CurrentStateCode != STATE_RELEASE_REFRESH) {
+                    CurrentStateCode = STATE_RELEASE_REFRESH;
+                    headView.setPadding(0, des, 0, 0);
+                    refresh();
+                } else if (des < 0 && CurrentStateCode != STATE_PULL_REFRESH) {
+                    CurrentStateCode = STATE_PULL_REFRESH;
+                    refresh();
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                startY = -1;
+                if (CurrentStateCode == STATE_RELEASE_REFRESH) {
+                    CurrentStateCode = STATE_REFRESHING_REFRESH;
+                    refresh();
+                } else if (CurrentStateCode == STATE_PULL_REFRESH) {
+                    headView.setPadding(0, -measuredHeight, 0, 0);
+                    refresh();
+                }
+                break;
+        }
+        return super.onTouchEvent(ev);
+    }
+
+    public void refresh() {
+        switch (CurrentStateCode) {
+            case STATE_PULL_REFRESH:
+                pullPreImg.setVisibility(View.VISIBLE);
+                pullNextImg.setVisibility(View.GONE);
+                pullTv.setText("下拉刷新");
+                pullPreImg.startAnimation(imageAnimaReset);
+                break;
+            case STATE_RELEASE_REFRESH:
+                pullPreImg.setVisibility(View.VISIBLE);
+                pullNextImg.setVisibility(View.GONE);
+                pullTv.setText("释放刷新");
+                pullPreImg.startAnimation(imageAnimaRotate);
+                break;
+            case STATE_REFRESHING_REFRESH:
+                pullPreImg.setVisibility(View.GONE);
+                pullNextImg.setVisibility(View.VISIBLE);
+                pullTv.setText("正在刷新");
+                pullPreImg.clearAnimation();
+                if(onRefreshListener != null){
+                    onRefreshListener.refreshData(); //回调
+                }
+                break;
+        }
+    }
+
+    //初始化
+    public void onRefreshComplete(){
+        CurrentStateCode = STATE_PULL_REFRESH;
+        headView.setPadding(0,-measuredHeight,0,0);
+    }
+
+}

+ 47 - 0
app/src/main/java/com/baoshi/swms/part/ScrollListView.java

@@ -0,0 +1,47 @@
+package com.baoshi.swms.part;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.ListView;
+
+/**
+ * 滚动listview配合scrollview使用
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/21 13:35
+ */
+public class ScrollListView extends ListView {
+    public ScrollListView(Context context) {
+        super(context);
+    }
+
+    public ScrollListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ScrollListView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public ScrollListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                getParent().requestDisallowInterceptTouchEvent(true);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                getParent().requestDisallowInterceptTouchEvent(false);
+                break;
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+}

+ 18 - 0
app/src/main/java/com/baoshi/swms/permission/PermissionFail.java

@@ -0,0 +1,18 @@
+package com.baoshi.swms.permission;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限失败回调
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 13:41
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PermissionFail {
+    int requestCode();
+}

+ 18 - 0
app/src/main/java/com/baoshi/swms/permission/PermissionSuccess.java

@@ -0,0 +1,18 @@
+package com.baoshi.swms.permission;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限成功回调
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 13:39
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PermissionSuccess {
+    int requestCode();
+}

+ 122 - 0
app/src/main/java/com/baoshi/swms/permission/PermissionUtil.java

@@ -0,0 +1,122 @@
+package com.baoshi.swms.permission;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import java.lang.reflect.Method;
+
+/**
+ * 权限申请
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 13:41
+ */
+public class PermissionUtil {
+    //获取权限
+    public static void needPermission(Fragment context, int reqCode, String... permissions) {
+        needPermission(context.getActivity(), reqCode, permissions);
+    }
+
+    //获取权限
+    public static void needPermission(Activity context, int reqCode, String... permissions) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            //6.0以下版本不需要代码申请权限
+            return;
+        }
+        //检查权限
+        boolean granted = hasPermission(context, permissions);
+        if (granted) {
+            //已获得权限
+            executeSuccessResult(context, reqCode);
+        } else {
+            //申请权限
+            ActivityCompat.requestPermissions(context, permissions, reqCode);
+        }
+    }
+
+    private static void executeSuccessResult(Object context, int reqCode) {
+        Method successMethod = getTargetMethod(context, reqCode, PermissionSuccess.class);
+        try {
+            successMethod.invoke(context);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void executeFailResult(Object context, int reqCode) {
+        Method successMethod = getTargetMethod(context, reqCode, PermissionFail.class);
+        try {
+            successMethod.invoke(context);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static Method getTargetMethod(Object context, int reqCode, Class annotation) {
+        Method[] declaredMethods = context.getClass().getDeclaredMethods();
+        for (Method method : declaredMethods) {
+            if (!method.isAccessible()) {
+                //私有的方法必须强制
+                method.setAccessible(true);
+            }
+            //判断方法上是否使用了目标注解
+            boolean annotationPresent = method.isAnnotationPresent(annotation);
+            if (annotationPresent) {
+                //比较requestCode是否相等
+                if (isTargetMethod(method, reqCode, annotation)) {
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static boolean isTargetMethod(Method method, int reqCode, Class cls) {
+        if (cls.equals(PermissionSuccess.class)) {
+            return reqCode == method.getAnnotation(PermissionSuccess.class).requestCode();
+        } else if (cls.equals(PermissionFail.class)) {
+            return reqCode == method.getAnnotation(PermissionFail.class).requestCode();
+        }
+        return false;
+    }
+
+
+    public static boolean hasPermission(Context context, String... permissions) {
+        for (String permission : permissions) {
+            int granted = ContextCompat.checkSelfPermission(context, permission);
+            if (granted != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public static void onRequestPermissionsResult(android.app.Fragment context, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        onRequestPermissionsResult(context, requestCode, permissions, grantResults);
+    }
+
+    public static void onRequestPermissionsResult(Activity context, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        boolean permissionGranted = true;
+        for (int grant : grantResults) {
+            if (grant == PackageManager.PERMISSION_DENIED) {
+                permissionGranted = false;
+                break;
+            }
+        }
+        if (permissionGranted) {
+            //获得权限
+            executeSuccessResult(context, requestCode);
+        } else {
+            //权限被用户拒绝
+            executeFailResult(context, requestCode);
+        }
+    }
+}

+ 84 - 0
app/src/main/java/com/baoshi/swms/sqlite/DBManager.java

@@ -0,0 +1,84 @@
+package com.baoshi.swms.sqlite;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import java.util.List;
+
+/**
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/27 13:46
+ */
+public class DBManager {
+    public static void asyncExecSQL(final Context context, final String sql){
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                SQLiteDataProxy.getSQLiteProxy(context).execSQL(sql);
+            }
+        }).start();
+    }
+
+    public static void asyncExecSQLList(final Context context,final List<String> sqlList){
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                SQLiteDataProxy.getSQLiteProxy(context).execSQLList(sqlList);
+            }
+        }).start();
+    }
+
+    public static void asyncExecSQLs(final Context context,final List<String[]> sqlList){
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                SQLiteDataProxy.getSQLiteProxy(context).execSQLs(sqlList);
+            }
+        }).start();
+    }
+
+    public static void asyncExecSQLIgnoreError(final Context context,final List<String> sqlList){
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                SQLiteDataProxy.getSQLiteProxy(context).execSQLIgnoreError(sqlList);
+            }
+        }).start();
+    }
+
+    public static boolean execSQL( Context context, String sql){
+        return SQLiteDataProxy.getSQLiteProxy(context).execSQL(sql);
+    }
+
+    public static boolean execSQLList( Context context, List<String> sqlList){
+        return SQLiteDataProxy.getSQLiteProxy(context).execSQLList(sqlList);
+    }
+
+    public static boolean execSQLs( Context context, List<String[]> sqlList){
+        return SQLiteDataProxy.getSQLiteProxy(context).execSQLs(sqlList);
+    }
+
+    public static boolean execSQL( Context context, List<String> sqlList){
+        return SQLiteDataProxy.getSQLiteProxy(context).execSQLIgnoreError(sqlList);
+    }
+
+    public static Cursor query(Context context, String sql){
+        return SQLiteDataProxy.getSQLiteProxy(context).query(sql);
+    }
+
+    public static Cursor query(Context context, String sql, String[] params){
+        return SQLiteDataProxy.getSQLiteProxy(context).query(sql, params);
+    }
+
+    public static void close(Context context){
+        SQLiteDataProxy.getSQLiteProxy(context).close();
+    }
+
+    public static void close(Context context, Cursor cursor){
+        if (cursor!=null){
+            cursor.close();
+        }
+        SQLiteDataProxy.getSQLiteProxy(context).close();
+    }
+}

+ 29 - 0
app/src/main/java/com/baoshi/swms/sqlite/ISQLiteOperate.java

@@ -0,0 +1,29 @@
+package com.baoshi.swms.sqlite;
+
+import android.database.Cursor;
+
+import java.util.List;
+
+/**
+ * 数据库接口
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/27 13:36
+ */
+public interface ISQLiteOperate {
+    boolean execSQL(String sql);
+
+    boolean execSQLList(List<String> sqlList);
+
+    boolean execSQLs(List<String[]> sqlList);
+
+    boolean execSQLIgnoreError(List<String> sqlList);
+
+    Cursor query(String sql);
+
+    Cursor query(String sql, String[] params);
+
+    void close();
+
+    void closeWhileError();
+}

+ 198 - 0
app/src/main/java/com/baoshi/swms/sqlite/SQLiteDataProxy.java

@@ -0,0 +1,198 @@
+package com.baoshi.swms.sqlite;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * SQL proxy
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/27 13:40
+ */
+public class SQLiteDataProxy implements ISQLiteOperate{
+    private java.util.concurrent.Semaphore semaphoreTransaction = new java.util.concurrent.Semaphore(1);
+    private ThreadLocal<Boolean> isQuery = new ThreadLocal<>();
+    private AtomicInteger mOpenCounter = new AtomicInteger();
+    private SQLiteDatabase db;
+    private Cursor cursor;
+
+    private SQLiteDataProxy() {
+    }
+
+    private static SQLiteDataProxy proxy;
+    private static TaskTable helper;
+
+    public static SQLiteDataProxy getSQLiteProxy(Context context) {
+        helper = TaskTable.getInstance(context);
+        if (proxy == null) {
+            synchronized (SQLiteDataProxy.class) {
+                if (proxy == null) {
+                    proxy = new SQLiteDataProxy();
+                }
+            }
+        }
+        return proxy;
+    }
+
+    public SQLiteDatabase getSQLiteDataBase() {
+        if (mOpenCounter.incrementAndGet() == 1 || db == null) {
+            db = helper.getWritableDatabase();
+        }
+        return db;
+    }
+
+    private void closeSQLiteDatabase(){
+        if(mOpenCounter.decrementAndGet() == 0){
+            db.close();
+        }
+    }
+
+    @Override
+    public boolean execSQL(String sql) {
+        boolean result = true;
+        db = getSQLiteDataBase();
+        try {
+            db.execSQL(sql);
+        } catch (Exception e) {
+            Log.e("SQLERROR", "In SQLDA:" + e.getMessage() + sql);
+            result = false;
+        } finally {
+            closeSQLiteDatabase();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean execSQLList(List<String> sqlList) {
+        boolean result = true;
+        db = getSQLiteDataBase();
+        String currentSqlString = "";
+        try {
+            semaphoreTransaction.acquire();
+            db.beginTransaction();
+            for (String sql : sqlList) {
+                currentSqlString = sql;
+                db.execSQL(sql);
+            }
+            db.setTransactionSuccessful();
+            result = true;
+        } catch (Exception e) {
+            result = false;
+            Log.e("SQLERROR", "IN SQLDA: " + e.getMessage() + currentSqlString);
+        } finally {
+            db.endTransaction();
+            semaphoreTransaction.release();
+            closeSQLiteDatabase();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean execSQLs(List<String[]> sqlList) {
+        boolean result = true;
+        db = getSQLiteDataBase();
+        String currentSql = "";
+        try {
+            semaphoreTransaction.acquire();
+            db.beginTransaction();
+            for (String[] arr : sqlList) {
+                currentSql = arr[0];
+                Cursor curCount = db.rawQuery(arr[0], null);
+                curCount.moveToFirst();
+                int count = curCount.getInt(0);
+                curCount.close();
+                if (count == 0) {
+                    if (arr[1] != null && arr[1].length() > 0) {
+                        currentSql = arr[1];
+                        db.execSQL(arr[1]);
+                    }
+                } else {
+                    if (arr.length > 2 && arr[2] != null && arr[2].length() > 0) {
+                        currentSql = arr[2];
+                        db.execSQL(arr[2]);
+                    }
+                }
+            }
+            db.setTransactionSuccessful();
+            result = true;
+        } catch (Exception e) {
+            Log.e("SQLERROR", "IN SQLDA: " + currentSql + e.getMessage());
+            result = false;
+        } finally {
+            db.endTransaction();
+            semaphoreTransaction.release();
+            closeSQLiteDatabase();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean execSQLIgnoreError(List<String> sqlList) {
+        db = getSQLiteDataBase();
+        try {
+            semaphoreTransaction.acquire();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        db.beginTransaction();
+        for (String sql : sqlList) {
+            try {
+                db.execSQL(sql);
+            } catch (Exception e) {
+                Log.e("SQLERROR", "IN SQLDA: " + sql + e.getMessage());
+            }
+        }
+        db.setTransactionSuccessful();
+        db.endTransaction();
+        semaphoreTransaction.release();
+        closeSQLiteDatabase();
+        return true;
+    }
+
+    @Override
+    public Cursor query(String sql) {
+        return query(sql, null);
+    }
+
+    @Override
+    public Cursor query(String sql, String[] params) {
+        isQuery.set(true);//设置为true,表示正在查询</span>
+        db = getSQLiteDataBase();
+        cursor = db.rawQuery(sql, params);
+        return cursor;
+    }
+
+    /*如果调用query方法,抛异常时要调用此方法</span>
+     */
+    @Override
+    public void closeWhileError(){
+        if (cursor != null) {
+            cursor.close();
+        }
+        if(isQuery.get()){//没有执行完毕,异常后需要去关闭数据库
+            closeSQLiteDataBase();
+        }
+    }
+
+    @Override
+    public void close() {
+        if (cursor != null) {
+            cursor.close();
+        }
+        closeSQLiteDatabase();
+    }
+
+
+    private void closeSQLiteDataBase(){
+        if(mOpenCounter.decrementAndGet() == 0){
+            db.close();
+            isQuery.set(false);//设置为false,表示执行完毕</span>
+            Log.i("DataBaseState","DB------Closed");
+        }
+    }
+}

+ 56 - 0
app/src/main/java/com/baoshi/swms/sqlite/TaskTable.java

@@ -0,0 +1,56 @@
+package com.baoshi.swms.sqlite;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import androidx.annotation.Nullable;
+
+/**
+ * 任务表
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/24 16:21
+ */
+public class TaskTable extends SQLiteOpenHelper{
+    public static final String TABLE_NAME = "task";
+    private final String SQL;
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(SQL);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
+    }
+    private volatile static TaskTable uniqueInstance;
+    public static TaskTable getInstance(Context context) {
+        if (uniqueInstance == null) {
+            synchronized (TaskTable.class) {
+                if (uniqueInstance == null) {
+                    uniqueInstance = new TaskTable(context);
+                }
+            }
+        }
+        return uniqueInstance;
+    }
+    //重写SQLiteOpenHelper的有参构造方法
+    public TaskTable(@Nullable Context context) {
+        //第一个参数为Context对象,第二个参数为数据库名称,第三个设置为null,第四个为sql版本号
+        super(context, "swms.db", null, 1);
+        SQL="create table "+
+                TABLE_NAME+"("+
+                "id INTEGER PRIMARY KEY,"+
+                "detailId integer,"+
+                "location varchar(50),"+
+                "way varchar(50),"+
+                "barcode varchar(50),"+
+                "barcodeAs varchar(50),"+
+                "anticipatedQuantity int,"+
+                "realQuantity int null,"+
+                //这里日期用text格式存储
+                "pickingTime text null"
+                +")";
+    }
+}

+ 109 - 0
app/src/main/java/com/baoshi/swms/util/Barcode.java

@@ -0,0 +1,109 @@
+package com.baoshi.swms.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.text.TextUtils;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 一维码生成器
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/20 11:50
+ */
+public class Barcode {
+    /**
+     * 绘制条形码
+     * @param content 要生成条形码包含的内容
+     * @param widthPix 条形码的宽度
+     * @param heightPix 条形码的高度
+     * @param isShowContent  否则显示条形码包含的内容
+     * @return 返回生成条形的位图
+     */
+    public static Bitmap createBarcode(String content, int widthPix, int heightPix, boolean isShowContent) {
+        if (TextUtils.isEmpty(content)){
+            return null;
+        }
+        //配置参数
+        Map<EncodeHintType,Object> hints = new HashMap<>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        // 容错级别 这里选择最高H级别
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+        MultiFormatWriter writer = new MultiFormatWriter();
+
+        try {
+            // 图像数据转换,使用了矩阵转换 参数顺序分别为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
+            BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.CODE_128, widthPix, heightPix, hints);
+            int[] pixels = new int[widthPix * heightPix];
+//             下面这里按照二维码的算法,逐个生成二维码的图片,
+            // 两个for循环是图片横列扫描的结果
+            for (int y = 0; y < heightPix; y++) {
+                for (int x = 0; x < widthPix; x++) {
+                    if (bitMatrix.get(x, y)) {
+                        pixels[y * widthPix + x] = 0xff000000; // 黑色
+                    } else {
+                        pixels[y * widthPix + x] = 0xffffffff;// 白色
+                    }
+                }
+            }
+            Bitmap bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888);
+            bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix);
+            if (isShowContent){
+                bitmap = showContent(bitmap,content);
+            }
+            return bitmap;
+        } catch (WriterException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    /**
+     * 显示条形的内容
+     * @param bCBitmap 已生成的条形码的位图
+     * @param content  条形码包含的内容
+     * @return 返回生成的新位图,它是 方法{@link #createBarcode(String, int, int, boolean)} (String, int, int, Bitmap)}返回的位图与新绘制文本content的组合
+     */
+    private static Bitmap showContent(Bitmap bCBitmap , String content){
+        if (TextUtils.isEmpty(content) || null == bCBitmap){
+            return null;
+        }
+        Paint paint = new Paint();
+        paint.setColor(Color.BLACK);
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.FILL);//设置填充样式
+        paint.setTextSize(20);
+        //测量字符串的宽度
+        int textWidth = (int) paint.measureText(content);
+        Paint.FontMetrics fm = paint.getFontMetrics();
+        //绘制字符串矩形区域的高度
+        int textHeight = (int) (fm.bottom - fm.top);
+        // x 轴的缩放比率
+        float scaleRateX = bCBitmap.getWidth() / textWidth;
+        paint.setTextScaleX(scaleRateX);
+        //绘制文本的基线
+        int baseLine = bCBitmap.getHeight() + textHeight;
+        //创建一个图层,然后在这个图层上绘制bCBitmap、content
+        Bitmap  bitmap = Bitmap.createBitmap(bCBitmap.getWidth(),bCBitmap.getHeight() + 2 * textHeight,Bitmap.Config.ARGB_4444);
+        Canvas canvas = new Canvas();
+        canvas.drawColor(Color.WHITE);
+        canvas.setBitmap(bitmap);
+        canvas.drawBitmap(bCBitmap, 0, 0, null);
+        canvas.drawText(content,bCBitmap.getWidth() / 10,baseLine,paint);
+        canvas.save();
+        canvas.restore();
+        return bitmap;
+    }
+}

+ 118 - 0
app/src/main/java/com/baoshi/swms/util/DateUtil.java

@@ -0,0 +1,118 @@
+package com.baoshi.swms.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * 时间处理工具类
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/6 10:12
+ */
+public class DateUtil {
+    public static final String DATE_DATE_TIME = "yyyy-MM-dd HH:mm:ss";
+    public static final String DATE_TIME = "HH:mm:ss";
+    public static final String DATE_DATE_TIME_CN = "yyyy年MM月dd日 HH时mm分ss秒";
+
+    //字符串转日期
+    public static Date strToDate(String date){
+        if (date.isEmpty()){
+            return null;
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        try {
+            return sdf.parse(date);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+    //日期转字符
+    public static String getTime(Date date) {//可根据需要自行截取数据显示
+        if (date==null){
+            return "";
+        }
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+        return format.format(date);
+    }
+    public static String getFormat(Date date, String type){
+        if (date==null){
+            return "";
+        }
+        SimpleDateFormat format = new SimpleDateFormat(type);
+        return format.format(date);
+    }
+
+    public static String getCurrentFormat(String type){
+        Date date = new Date();
+        SimpleDateFormat format = new SimpleDateFormat(type);
+        return format.format(date);
+    }
+
+    public static String formatReadable(Date date) {
+        Calendar calendar = Calendar.getInstance();
+        //当前年
+        int currYear = calendar.get(Calendar.YEAR);
+        //当前日
+        int currDay = calendar.get(Calendar.DAY_OF_YEAR);
+        //当前时
+        int currHour = calendar.get(Calendar.HOUR_OF_DAY);
+        //当前分
+        int currMinute = calendar.get(Calendar.MINUTE);
+        //当前秒
+        int currSecond = calendar.get(Calendar.SECOND);
+
+        calendar.setTime(date);
+        int msgYear = calendar.get(Calendar.YEAR);
+        //说明不是同一年
+        if (currYear != msgYear) {
+            return new SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault()).format(date);
+        }
+        int msgDay = calendar.get(Calendar.DAY_OF_YEAR);
+        //超过7天,直接显示xx月xx日
+        if (currDay - msgDay > 7) {
+            return new SimpleDateFormat("MM月dd日", Locale.getDefault()).format(date);
+        }
+        //不是当天
+        if (currDay - msgDay > 0) {
+            if (currDay - msgDay == 1) {
+                return "昨天";
+            } else {
+                return currDay - msgDay + "天前";
+            }
+        }
+        int msgHour = calendar.get(Calendar.HOUR_OF_DAY);
+        int msgMinute = calendar.get(Calendar.MINUTE);
+        //不是当前小时内
+        if (currHour - msgHour > 0) {
+            //如果当前分钟小,说明最后一个不满一小时
+            if (currMinute < msgMinute) {
+                if (currHour - msgHour == 1) {//当前只大一个小时值,说明不够一小时
+                    return 60 - msgMinute + currMinute + "分钟前";
+                } else {
+                    return currHour - msgHour - 1 + "小时前";
+                }
+            }
+            //如果当前分钟数大,够了一个周期
+            return currHour - msgHour + "小时前";
+        }
+        int msgSecond = calendar.get(Calendar.SECOND);
+        //不是当前分钟内
+        if (currMinute - msgMinute > 0) {
+            //如果当前秒数小,说明最后一个不满一分钟
+            if (currSecond < msgSecond) {
+                if (currMinute - msgMinute == 1) {//当前只大一个分钟值,说明不够一分钟
+                    return "刚刚";
+                } else {
+                    return currMinute - msgMinute - 1 + "分钟前";
+                }
+            }
+            //如果当前秒数大,够了一个周期
+            return currMinute - msgMinute + "分钟前";
+        }
+        //x秒前
+        return "刚刚";
+    }
+}

+ 50 - 0
app/src/main/java/com/baoshi/swms/util/GlobalStorage.java

@@ -0,0 +1,50 @@
+package com.baoshi.swms.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.baoshi.swms.App;
+
+/**
+ * 全局存储器
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:42
+ */
+public class GlobalStorage {
+    public static String MODEL = "Global";
+    private static SharedPreferences share;
+    private static GlobalStorage storage;
+
+    public static GlobalStorage getGlobalStorage() {
+        if (storage==null) {
+            share = App.getAppContext().getSharedPreferences(MODEL,Context.MODE_PRIVATE);
+            storage = new GlobalStorage();
+        }
+        return storage;
+    }
+    public GlobalStorage(){
+    }
+    public GlobalStorage(String key){
+        MODEL = key;
+        share = App.getAppContext().getSharedPreferences(MODEL,Context.MODE_PRIVATE);
+    }
+
+    //存数据
+    public void put(String key, String val) {
+        SharedPreferences.Editor editor= share.edit();
+        editor.putString(key,val);
+        editor.commit();
+    }
+    //读数据
+    public String get(String key) {
+        return share.getString(key, null);
+    }
+
+    public void remove(String key)
+    {
+        SharedPreferences.Editor editor= share.edit();
+        editor.remove(key);
+        editor.commit();
+    }
+}

+ 62 - 0
app/src/main/java/com/baoshi/swms/util/Hint.java

@@ -0,0 +1,62 @@
+package com.baoshi.swms.util;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.view.Gravity;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.baoshi.swms.R;
+
+/**
+ * 提示相关工具类
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:06
+ */
+public class Hint {
+    public static final int COMMON    = 0;
+    public static final int ERROR     = 1;
+
+    public static void toastShort(Context context, CharSequence text, int mode)
+    {
+        int background;
+        int textColor = Color.WHITE;
+        switch (mode){
+            case COMMON:
+                background = R.drawable.shape_corner;
+                break;
+            default:
+                background = R.drawable.shape_corner_error;
+        }
+        Toast toast = new Toast(context);
+        toast.setDuration(Toast.LENGTH_SHORT);
+        LinearLayout layout = new LinearLayout(context);
+        layout.setBackgroundResource(background);
+        layout.setPadding(30,30,30,30);
+        TextView tv = new TextView(context);
+        tv.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));
+        tv.setText(text);
+        tv.setTextSize(15);
+        tv.setTextColor(textColor);
+        layout.addView(tv);
+        toast.setView(layout);
+        toast.setGravity(Gravity.CENTER, 0, 0);
+        toast.show();
+    }
+
+    public static ProgressDialog showProgressDialog(String message, Activity mActivity) {
+        ProgressDialog mProgressDialog;
+        mProgressDialog = new ProgressDialog(mActivity);
+        mProgressDialog.setCanceledOnTouchOutside(false);
+        mProgressDialog.setCancelable(false);
+        mProgressDialog.setMessage(message);
+        if (!mProgressDialog.isShowing()) {
+            mProgressDialog.show();
+        }
+        return mProgressDialog;
+    }
+}

+ 33 - 0
app/src/main/java/com/baoshi/swms/util/NetUtil.java

@@ -0,0 +1,33 @@
+package com.baoshi.swms.util;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Build;
+
+/**
+ * 网络相关工具
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 15:53
+ */
+public class NetUtil {
+    public static boolean isNetworkConnected(Context context) {
+        ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (manager != null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(manager.getActiveNetwork());
+                if (networkCapabilities != null) {
+                    return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+                            || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                            || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
+                }
+            } else {
+                NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+                return networkInfo != null && networkInfo.isConnected();
+            }
+        }
+        return false;
+    }
+}

+ 106 - 0
app/src/main/java/com/baoshi/swms/util/QRCode.java

@@ -0,0 +1,106 @@
+package com.baoshi.swms.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.text.TextUtils;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.CharacterSetECI;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.util.Hashtable;
+
+/**
+ * 二维码生成工具类
+ *
+ * @author Zhendong Zhou
+ * @date   2021/12/3 18:06
+ */
+public class QRCode {
+    /**
+     * 创建二维码位图
+     *
+     * @param content 字符串内容(支持中文)
+     * @param width 位图宽度(单位:px)
+     * @param height 位图高度(单位:px)
+     * @return
+     */
+    @Nullable
+    public static Bitmap createQRCodeBitmap(String content, int width, int height){
+        return createQRCodeBitmap(content, width, height, "utf-8", "H", "2", Color.BLACK, Color.WHITE);
+    }
+
+    /**
+     * 创建二维码位图 (支持自定义配置和自定义样式)
+     *
+     * @param content 字符串内容
+     * @param width 位图宽度,要求>=0(单位:px)
+     * @param height 位图高度,要求>=0(单位:px)
+     * @param character_set 字符集/字符转码格式 (支持格式:{@link CharacterSetECI })。传null时,zxing源码默认使用 "ISO-8859-1"
+     * @param error_correction 容错级别 (支持级别:{@link ErrorCorrectionLevel })。传null时,zxing源码默认使用 "L"
+     * @param margin 空白边距 (可修改,要求:整型且>=0), 传null时,zxing源码默认使用"4"。
+     * @param color_black 黑色色块的自定义颜色值
+     * @param color_white 白色色块的自定义颜色值
+     * @return Bitmap
+     */
+    @Nullable
+    public static Bitmap createQRCodeBitmap(String content, int width, int height,
+                                            @Nullable String character_set, @Nullable String error_correction, @Nullable String margin,
+                                            @ColorInt int color_black, @ColorInt int color_white){
+
+        /** 1.参数合法性判断 */
+        if(TextUtils.isEmpty(content)){ // 字符串内容判空
+            return null;
+        }
+
+        if(width < 0 || height < 0){ // 宽和高都需要>=0
+            return null;
+        }
+
+        try {
+            /** 2.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */
+            Hashtable<EncodeHintType, String> hints = new Hashtable<>();
+
+            if(!TextUtils.isEmpty(character_set)) {
+                hints.put(EncodeHintType.CHARACTER_SET, character_set); // 字符转码格式设置
+            }
+
+            if(!TextUtils.isEmpty(error_correction)){
+                hints.put(EncodeHintType.ERROR_CORRECTION, error_correction); // 容错级别设置
+            }
+
+            if(!TextUtils.isEmpty(margin)){
+                hints.put(EncodeHintType.MARGIN, margin); // 空白边距设置
+            }
+            BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
+
+            /** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */
+            int[] pixels = new int[width * height];
+            for(int y = 0; y < height; y++){
+                for(int x = 0; x < width; x++){
+                    if(bitMatrix.get(x, y)){
+                        pixels[y * width + x] = color_black; // 黑色色块像素设置
+                    } else {
+                        pixels[y * width + x] = color_white; // 白色色块像素设置
+                    }
+                }
+            }
+
+            /** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+            return bitmap;
+        } catch (WriterException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 17 - 0
app/src/main/java/com/baoshi/swms/util/StringUtil.java

@@ -0,0 +1,17 @@
+package com.baoshi.swms.util;
+
+/**
+ * 字符串处理类
+ *
+ * @author Zhendong Zhou
+ * @date 2021/12/3 17:06
+ */
+public class StringUtil {
+    public static String trimLeft(java.lang.String str) {
+        if (str == null || str.equals("")) {
+            return str;
+        } else {
+            return str.replaceAll("^[  ]+", "");
+        }
+    }
+}

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

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

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#00000000"/>
+    <stroke android:width="1dp" android:color="#C0C0C0" />
+    <!--<corners android:radius="2dp"/>-->
+</shape>

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

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

+ 14 - 0
app/src/main/res/drawable/bs_logo.xml

@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <group android:scaleX="0.061171874"
+      android:scaleY="0.061171874"
+      android:translateX="22.68"
+      android:translateY="22.68">
+    <path
+        android:pathData="M995.05,342.48l-153.34,-265.65a25.6,25.6 0,0 0,-22.17 -12.8l-0.46,0.05 -0.26,-0.03L205.34,64.05a25.55,25.55 0,0 0,-22.17 12.8c-0.33,0.59 -0.46,1.23 -0.74,1.84L29.11,344.24a25.68,25.68 0,0 0,4.35 31.13l458.62,574.98a25.7,25.7 0,0 0,40.04 0L992.56,372.99a25.22,25.22 0,0 0,4.71 -10.32,15.41 15.41,0 0,0 0.72,-3.51 25.47,25.47 0,0 0,-2.94 -16.69zM774.48,115.25l-109.06,188.85 -109.03,-188.85h218.09zM621.11,331.08h-218.06l109.03,-188.85 109.03,188.85zM467.74,115.25l-109.03,188.85 -109.03,-188.85h218.06zM204.65,142.59l109.03,188.85L95.62,331.44L204.65,142.59zM104.76,382.64h234.37l116.94,440.45L104.76,382.64zM512.08,834.64l-120.01,-451.99h239.97l-119.96,451.99zM568.09,823.07l116.92,-440.45h234.34L568.09,823.07zM710.48,329.68l109.06,-188.85 109.03,188.85h-218.09z"
+        android:fillColor="#1296db"/>
+  </group>
+</vector>

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

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <!-- 圆角的半径 -->
+    <corners android:radius="15dp"/>
+    <!-- 填充颜色 -->
+    <solid android:color="#3a8fea"/>
+</shape>

+ 13 - 0
app/src/main/res/drawable/edit_background.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:shape="rectangle">
+            <solid android:color="#efefef"/>
+            <corners android:radius="5dp"/>
+            <stroke
+                android:width="1dp"
+                android:color="#0066FF"/>
+        </shape>
+    </item>
+</layer-list>

+ 4 - 0
app/src/main/res/drawable/ic__4gf_lock.xml

@@ -0,0 +1,4 @@
+<vector android:height="24dp" android:viewportHeight="1024"
+    android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#8a8a8a" android:pathData="M842.67,384h-74.67V277.33a234.67,234.67 0,1 0,-469.33 0v106.67H224a53.39,53.39 0,0 0,-53.33 53.33v490.67a53.39,53.39 0,0 0,53.33 53.33h618.67a53.39,53.39 0,0 0,53.33 -53.33V437.33a53.39,53.39 0,0 0,-53.33 -53.33zM341.33,277.33c0,-105.87 86.13,-192 192,-192s192,86.13 192,192v106.67H341.33z"/>
+</vector>

+ 4 - 0
app/src/main/res/drawable/ic__4gf_unlock.xml

@@ -0,0 +1,4 @@
+<vector android:height="24dp" android:viewportHeight="1024"
+    android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#1afa29" android:pathData="M1005.55,186A234.67,234.67 0,0 0,554.67 277.33v106.67H53.33a53.39,53.39 0,0 0,-53.33 53.33v490.67a53.39,53.39 0,0 0,53.33 53.33h618.67a53.39,53.39 0,0 0,53.33 -53.33V437.33a53.39,53.39 0,0 0,-53.33 -53.33H597.33V277.33c0,-105.87 86.13,-192 192,-192s192,86.13 192,192v128a21.33,21.33 0,0 0,42.67 0V277.33a233.22,233.22 0,0 0,-18.45 -91.33zM384,679.01V789.33a21.33,21.33 0,0 1,-42.67 0v-110.33a64,64 0,1 1,42.67 0z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_baseline_arrow_downward_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,10l5,5 5,-5z"/>
+</vector>

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

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#0C0B0B"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M19,18l2,1V3c0,-1.1 -0.9,-2 -2,-2H8.99C7.89,1 7,1.9 7,3h10c1.1,0 2,0.9 2,2v13zM15,5H5c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3V7c0,-1.1 -0.9,-2 -2,-2z"/>
+</vector>

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

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#1B4B72"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_baseline_camera_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_baseline_chevron_left_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
+</vector>

+ 10 - 0
app/src/main/res/drawable/ic_baseline_edit_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>

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

@@ -0,0 +1,6 @@
+<vector android:height="24dp" android:tint="#403F3F"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M8.17,5.7L1,10.48V21h5v-8h4v8h5V10.25z"/>
+    <path android:fillColor="@android:color/white" android:pathData="M10,3v1.51l2,1.33L13.73,7L15,7v0.85l2,1.34L17,11h2v2h-2v2h2v2h-2v4h6L23,3L10,3zM19,9h-2L17,7h2v2z"/>
+</vector>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.