Ver Fonte

app创建代码仓库

Zimo há 1 mês atrás
commit
af883c6544
100 ficheiros alterados com 12865 adições e 0 exclusões
  1. 51 0
      .gitignore
  2. 185 0
      App.vue
  3. 29 0
      android/build.gradle
  4. 20 0
      android/gradle.properties
  5. BIN
      android/gradle/wrapper/gradle-wrapper.jar
  6. 6 0
      android/gradle/wrapper/gradle-wrapper.properties
  7. 172 0
      android/gradlew
  8. 84 0
      android/gradlew.bat
  9. 1 0
      android/settings.gradle
  10. 81 0
      android/simpleDemo/build.gradle
  11. BIN
      android/simpleDemo/depol.keystore
  12. BIN
      android/simpleDemo/libs/android-gif-drawable-1.2.28.aar
  13. BIN
      android/simpleDemo/libs/breakpad-build-release.aar
  14. BIN
      android/simpleDemo/libs/install-apk-release.aar
  15. BIN
      android/simpleDemo/libs/lib.5plus.base-release.aar
  16. BIN
      android/simpleDemo/libs/media-release.aar
  17. BIN
      android/simpleDemo/libs/oaid_sdk_1.0.25.aar
  18. BIN
      android/simpleDemo/libs/sqlite-release.aar
  19. BIN
      android/simpleDemo/libs/uniapp-v8-release.aar
  20. BIN
      android/simpleDemo/libs/utsplugin-release.aar
  21. BIN
      android/simpleDemo/libs/weex_videoplayer-release.aar
  22. 9 0
      android/simpleDemo/local.properties
  23. 22 0
      android/simpleDemo/proguard-rules.pro
  24. BIN
      android/simpleDemo/src/dev/res/drawable/icon.png
  25. 112 0
      android/simpleDemo/src/main/AndroidManifest.xml
  26. 6 0
      android/simpleDemo/src/main/assets/data/dcloud_control.xml
  27. 92 0
      android/simpleDemo/src/main/assets/data/dcloud_error.html
  28. 47 0
      android/simpleDemo/src/main/assets/data/dcloud_properties.xml
  29. 23 0
      android/simpleDemo/src/main/assets/dcloud_uniplugins.json
  30. 55 0
      android/simpleDemo/src/main/java/uni/deepoil/com/DingTalkModule.java
  31. 78 0
      android/simpleDemo/src/main/java/uni/deepoil/com/LocationModule.java
  32. 88 0
      android/simpleDemo/src/main/java/uni/deepoil/com/PermissionModule.java
  33. 44 0
      android/simpleDemo/src/main/java/uni/deepoil/com/ddauth/DDAuthActivity.java
  34. 75 0
      android/simpleDemo/src/main/java/uni/deepoil/com/ddshare/DDShareActivity.java
  35. BIN
      android/simpleDemo/src/main/res/drawable/icon.png
  36. BIN
      android/simpleDemo/src/main/res/drawable/push.png
  37. BIN
      android/simpleDemo/src/main/res/drawable/splash.png
  38. 3 0
      android/simpleDemo/src/main/res/values/strings.xml
  39. 9 0
      android/simpleDemo/src/main/res/values/styles.xml
  40. BIN
      android/simpleDemo/test.jks
  41. 10 0
      api/app.js
  42. 29 0
      api/device.js
  43. 34 0
      api/deviceUser.js
  44. 73 0
      api/fault.js
  45. 66 0
      api/file.js
  46. 127 0
      api/index.js
  47. 65 0
      api/inspection.js
  48. 9 0
      api/inventory.js
  49. 34 0
      api/ledger.js
  50. 129 0
      api/login.js
  51. 71 0
      api/maintenance.js
  52. 35 0
      api/material.js
  53. 32 0
      api/message.js
  54. 36 0
      api/realTimeData.js
  55. 83 0
      api/recordFilling.js
  56. 70 0
      api/repair.js
  57. 72 0
      api/ruiDu.js
  58. 275 0
      api/statistic.js
  59. 22 0
      api/statusChange.js
  60. 132 0
      api/task.js
  61. 20 0
      api/warn.js
  62. 405 0
      components/device-transfer/index.vue
  63. 223 0
      components/device/brandChoose.vue
  64. 221 0
      components/device/modelChoose.vue
  65. 223 0
      components/device/multiple.vue
  66. 201 0
      components/device/single.vue
  67. 47 0
      components/global/message-popup.vue
  68. 115 0
      components/ignore/reason.vue
  69. 224 0
      components/language-popup.vue
  70. 208 0
      components/local-search.vue
  71. 495 0
      components/maintenance/delay.vue
  72. 219 0
      components/materials/add.vue
  73. 301 0
      components/materials/choose.vue
  74. 167 0
      components/materials/count.vue
  75. 259 0
      components/materials/master-data.vue
  76. 91 0
      components/materials/view.vue
  77. 213 0
      components/repair/add.vue
  78. 179 0
      components/repair/multiple.vue
  79. 530 0
      components/statistic/front.vue
  80. 259 0
      components/statistic/inspection.vue
  81. 292 0
      components/statistic/maintenance.vue
  82. 259 0
      components/statistic/rapair.vue
  83. 235 0
      components/supplier/choose.vue
  84. 322 0
      components/tpf-time-range/tpf-time-range.vue
  85. 336 0
      components/upgrade.vue
  86. 102 0
      composables/useDingTalkLogin.js
  87. 14 0
      config/env.dev.js
  88. 14 0
      config/env.prod.js
  89. 20 0
      index.html
  90. 485 0
      locale/en.json
  91. 12 0
      locale/index.js
  92. 485 0
      locale/ja.json
  93. 485 0
      locale/ru.json
  94. 36 0
      locale/uni-app.ja.json
  95. 694 0
      locale/zh-Hans.json
  96. 491 0
      locale/zh-Hant.json
  97. 31 0
      main.js
  98. 127 0
      manifest.json
  99. 1116 0
      package-lock.json
  100. 13 0
      package.json

+ 51 - 0
.gitignore

@@ -0,0 +1,51 @@
+# Mac
+.DS_Store
+**/.DS_Store
+
+# vim/vi
+*.swp
+
+# JavaScript
+node_modules/
+.node_modules/
+.eslintcache
+unpackage/dist/
+unpackage/dist/build/
+unpackage/dist/dev/
+/unpackage/resources/
+/unpackage/resources/*
+unpackage/*
+
+# python
+*.pyc
+
+#uniapp
+#uni_modules/
+
+#.hbuilderx
+.hbuilderx/
+
+#vscode
+.vscode/
+
+
+#git
+.git/
+
+pages/lanhu_*/
+static/static/
+.idea
+pnpm-lock.yaml
+
+**/.idea
+android/simpleDemo/src/main/assets/apps/*
+/android/simpleDemo/release/
+/android/simpleDemo/prod/
+/android/simpleDemo/dev/
+**/*.iml
+android/.gradle/
+android/simpleDemo/.gradle/
+**/build/
+/app.db
+
+android/local.properties

+ 185 - 0
App.vue

@@ -0,0 +1,185 @@
+<script>
+import { initAppDatabase } from "@/utils/appDb";
+
+export default {
+  onLaunch: (options) => {
+    // #ifdef APP
+    initAppDatabase();
+    // #endif
+
+    console.log("App Launch");
+    console.log(options);
+    // uni.onNetworkStatusChange((event) => {
+    //   console.log(event)
+    // })
+
+    // #ifdef H5
+    // 保存钉钉消息传递的参数,参数可能存在path或query中
+    if (options.query.type) {
+      uni.setStorageSync("dingTalkJson", JSON.stringify(options.query));
+    } else if (options.path && options.path.includes("type")) {
+      const path = options.path;
+      const args = path.split("&");
+      const params = {};
+
+      args.forEach((arg) => {
+        const [key, value] = arg.split("=");
+        if (key && value) {
+          params[key] = value;
+        }
+        console.log(params);
+      });
+      uni.setStorageSync("dingTalkJson", JSON.stringify(params));
+    }
+    // #endif
+
+    // #ifdef APP
+    plus.globalEvent.addEventListener("newintent", () => {
+      const args = plus.runtime.arguments;
+      const parts = args.match(/^deepoil:\/\/([^/]+)\/([^/]+)$/);
+      if (parts) {
+        const type = parts[1];
+        const id = parts[2];
+
+        uni.setStorageSync("dingTalkJson", JSON.stringify({ type, id }));
+        console.log(
+          "App: dingTalkJson -> " + uni.getStorageSync("dingTalkJson")
+        );
+      }
+    });
+    // #endif
+  },
+  onExit: () => {
+    // #ifdef APP
+    // sqlite.closeDB('app')
+    // #endif
+  },
+  onShow: function () {
+    // console.log('App Show')
+  },
+  onHide: function () {
+    // console.log('App Hide')
+  },
+};
+</script>
+
+<style lang="scss">
+/*每个页面公共css */
+@import "./style/common.scss";
+@import "./style/fonts.scss";
+
+/* uniapp组件 样式覆盖  */
+uni-button[type="primary"] {
+  background: #004098 !important;
+}
+
+uni-button[disabled][type="primary"] {
+  background: rgba(0, 64, 152, 0.6) !important;
+}
+uni-button[type="primary"][plain] {
+  color: #004098 !important;
+  border: 1px solid #004098 !important;
+  background-color: transparent !important;
+}
+
+uni-page-body,
+body {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  position: relative;
+}
+
+:deep(.uni-tabbar-bottom .uni-tabbar) {
+  box-shadow: 0px -2px 10px 0px rgba(0, 0, 0, 0.1);
+  // padding-top: 9px !important;
+  // padding-bottom: 10px !important;
+  box-sizing: border-box;
+
+  .uni-tabbar__icon {
+    margin-top: 4px;
+  }
+}
+
+.page {
+  position: relative;
+  padding: 20rpx;
+  padding-top: 0;
+  box-sizing: border-box;
+  background: #f3f5f9;
+  width: 100%;
+  height: 100%;
+  font-family: PingFangSC, PingFang SC;
+  overflow: hidden;
+}
+
+.page-nopadding {
+  position: relative;
+  box-sizing: border-box;
+  background: #f3f5f9;
+  width: 100%;
+  height: 100%;
+  font-family: PingFangSC, PingFang SC;
+  overflow: hidden;
+}
+
+.page-back {
+  background-image: url("/static/common/1.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 350px;
+  z-index: 0;
+}
+
+.navgator {
+  width: 100%;
+  height: $header-height;
+  line-height: 1;
+  position: fixed;
+  top: $header-top-height;
+  left: 0;
+  background-color: transparent !important;
+  padding-top: calc(7px + env(safe-area-inset-top));
+  box-sizing: border-box;
+  z-index: 22;
+
+  .nav-title {
+    font-family: PingFang-SC, PingFang-SC;
+    font-weight: bold;
+    font-size: 16px;
+    color: #ffffff;
+    line-height: 22px;
+    text-align: right;
+    font-style: normal;
+  }
+
+  .nav-back {
+    width: 40rpx;
+    line-height: 1;
+    position: absolute;
+    left: 20rpx;
+
+    .uni-icons {
+      color: #fff !important;
+    }
+  }
+}
+
+.page-content {
+  position: relative;
+  box-sizing: border-box;
+  width: 100%;
+  height: calc(100% - $header-height - $header-top-height);
+  margin-top: calc($header-height + $header-top-height);
+  overflow: hidden;
+  overflow-y: auto;
+}
+
+.item {
+  box-sizing: border-box;
+}
+</style>

+ 29 - 0
android/build.gradle

@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        maven {url 'https://maven.aliyun.com/repository/google'}
+        maven {url 'https://maven.aliyun.com/repository/gradle-plugin'}
+        maven {url 'https://maven.aliyun.com/repository/public'}
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:8.7.3'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        maven {url 'https://maven.aliyun.com/repository/google'}
+        maven {url 'https://maven.aliyun.com/repository/gradle-plugin'}
+        maven {url 'https://maven.aliyun.com/repository/public'}
+        maven { url 'https://jitpack.io' }
+        google()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 20 - 0
android/gradle.properties

@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+android.useAndroidX=true
+android.enableJetifier=true

BIN
android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Mon Dec 28 18:07:31 CST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip

+ 172 - 0
android/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
android/gradlew.bat

@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
android/settings.gradle

@@ -0,0 +1 @@
+include ':simpleDemo'

+ 81 - 0
android/simpleDemo/build.gradle

@@ -0,0 +1,81 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdk 35
+    // compileSdkVersion 35
+    // buildToolsVersion '35.0.0'
+    namespace 'uni.deepoil.com'
+    defaultConfig {
+        applicationId "uni.deepoil.com"
+        minSdkVersion 21
+        // targetSdkVersion 33
+        targetSdk 33
+        versionCode 10204
+        versionName "1.2.4"
+        multiDexEnabled true
+        compileOptions {
+            sourceCompatibility JavaVersion.VERSION_1_8
+            targetCompatibility JavaVersion.VERSION_1_8
+        }
+    }
+    signingConfigs {
+        config {
+            keyAlias '__uni__6e4bc49'
+            keyPassword '5yOMqFfh'
+            storeFile file('depol.keystore')
+            storePassword '5yOMqFfh'
+            v1SigningEnabled true
+            v2SigningEnabled true
+        }
+    }
+
+    buildTypes {
+        debug {
+            signingConfig signingConfigs.config
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            signingConfig signingConfigs.config
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            ndk {
+                abiFilters 'armeabi-v7a', 'arm64-v8a'
+            }
+        }
+    }
+    aaptOptions {
+        additionalParameters '--auto-add-overlay'
+        ignoreAssetsPattern "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~"
+    }
+    buildFeatures {
+        buildConfig true
+    }
+    productFlavors {
+        flavorDimensions 'env'
+        dev {
+            dimension 'env'
+        }
+        prod {
+            dimension 'env'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
+    implementation 'androidx.core:core:1.1.0'
+    implementation "androidx.fragment:fragment:1.1.0"
+    implementation 'androidx.recyclerview:recyclerview:1.1.0'
+    implementation 'com.facebook.fresco:fresco:2.5.0'
+    implementation "com.facebook.fresco:animated-gif:2.5.0"
+    implementation 'com.github.bumptech.glide:glide:4.9.0'
+    implementation 'com.alibaba:fastjson:1.2.83'
+    implementation 'androidx.webkit:webkit:1.5.0'
+    implementation 'com.alibaba.android:ddopenauth:1.5.0.10'
+    implementation 'com.github.getActivity:XXPermissions:21.3'
+    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.21'
+}
+

BIN
android/simpleDemo/depol.keystore


BIN
android/simpleDemo/libs/android-gif-drawable-1.2.28.aar


BIN
android/simpleDemo/libs/breakpad-build-release.aar


BIN
android/simpleDemo/libs/install-apk-release.aar


BIN
android/simpleDemo/libs/lib.5plus.base-release.aar


BIN
android/simpleDemo/libs/media-release.aar


BIN
android/simpleDemo/libs/oaid_sdk_1.0.25.aar


BIN
android/simpleDemo/libs/sqlite-release.aar


BIN
android/simpleDemo/libs/uniapp-v8-release.aar


BIN
android/simpleDemo/libs/utsplugin-release.aar


BIN
android/simpleDemo/libs/weex_videoplayer-release.aar


+ 9 - 0
android/simpleDemo/local.properties

@@ -0,0 +1,9 @@
+## This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Mon May 26 17:22:55 CST 2025
+sdk.dir=C\:\\Users\\elaine\\AppData\\Local\\Android\\Sdk
+

+ 22 - 0
android/simpleDemo/proguard-rules.pro

@@ -0,0 +1,22 @@
+# 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
+-keep class com.android.dingtalk.openauth.**{*;}

BIN
android/simpleDemo/src/dev/res/drawable/icon.png


+ 112 - 0
android/simpleDemo/src/main/AndroidManifest.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="uni.deepoil.com">
+
+  <uses-feature
+    android:name="android.hardware.camera"
+    android:required="false"/>
+  <uses-permission android:name="android.permission.INTERNET"/>
+  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
+  <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
+  <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
+  <uses-permission android:name="android.permission.CAMERA"/>
+  <uses-permission
+    android:name="android.permission.READ_EXTERNAL_STORAGE"
+    android:maxSdkVersion="32"/>
+  <uses-permission
+    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+    android:maxSdkVersion="32"/>
+  <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+
+  <queries>
+    <package android:name="com.alibaba.android.rimet"/>
+    <intent>
+      <action android:name="android.media.action.IMAGE_CAPTURE"/>
+    </intent>
+    <intent>
+      <action android:name="android.media.action.ACTION_VIDEO_CAPTURE"/>
+    </intent>
+    <intent>
+      <action android:name="android.intent.action.GET_CONTENT"/>
+    </intent>
+    <intent>
+      <action android:name="android.intent.action.PICK"/>
+    </intent>
+  </queries>
+
+  <application
+    android:allowBackup="true"
+    android:allowClearUserData="true"
+    android:extractNativeLibs="true"
+    android:icon="@drawable/icon"
+    android:label="@string/app_name"
+    android:largeHeap="true"
+    android:requestLegacyExternalStorage="true"
+    android:supportsRtl="true">
+    <activity
+      android:name="io.dcloud.PandoraEntry"
+      android:configChanges="orientation|keyboardHidden|keyboard|navigation"
+      android:exported="true"
+      android:hardwareAccelerated="true"
+      android:label="@string/app_name"
+      android:launchMode="singleTask"
+      android:screenOrientation="user"
+      android:theme="@style/TranslucentTheme"
+      android:windowSoftInputMode="adjustResize">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+
+      <intent-filter>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <action android:name="android.intent.action.VIEW"/>
+        <data android:scheme="deepoil"/>
+      </intent-filter>
+
+      <intent-filter android:autoVerify="true">
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <action android:name="android.intent.action.VIEW"/>
+        <data
+          android:host="app.iot.deepoil.cc"
+          android:scheme="https"/>
+      </intent-filter>
+    </activity>
+    <activity
+      android:name="io.dcloud.PandoraEntryActivity"
+      android:configChanges="orientation|keyboardHidden|screenSize|mcc|mnc|fontScale|keyboard|smallestScreenSize|screenLayout|screenSize|uiMode"
+      android:exported="true"
+      android:hardwareAccelerated="true"
+      android:launchMode="singleTask"
+      android:permission="com.miui.securitycenter.permission.AppPermissionsEditor"
+      android:screenOrientation="user"
+      android:theme="@style/DCloudTheme"
+      android:windowSoftInputMode="adjustResize">
+      <intent-filter>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <action android:name="android.intent.action.VIEW"/>
+        <data android:scheme=" "/>
+      </intent-filter>
+    </activity>
+
+    <activity
+      android:name=".ddauth.DDAuthActivity"
+      android:exported="true"/>
+
+    <activity
+      android:name=".ddshare.DDShareActivity"
+      android:exported="true"
+      android:launchMode="singleInstance"
+      android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
+
+    <meta-data
+      android:name="dcloud_appkey"
+      android:value="279875c70b71ed146f6d9ab123e49ba0"/>
+  </application>
+</manifest>

+ 6 - 0
android/simpleDemo/src/main/assets/data/dcloud_control.xml

@@ -0,0 +1,6 @@
+<hbuilder>
+    <!--    debug="true" syncDebug="true" -->
+  <apps>
+    <app appid="__UNI__6E4BC49" appver=""/>
+  </apps>
+</hbuilder>

+ 92 - 0
android/simpleDemo/src/main/assets/data/dcloud_error.html

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
+	<meta name="HandheldFriendly" content="true"/>
+	<meta name="MobileOptimized" content="320"/>
+	<title>Error</title>
+	<script type="text/javascript">
+// H5 plus事件处理
+var ws=null;
+function plusReady(){
+	// Android处理返回键
+	plus.key.addEventListener('backbutton',function(){
+		(history.length==1)&&ws.close();
+		var c=setTimeout(function(){
+			ws.close();
+		},1000);
+		window.onbeforeunload=function(){
+			clearTimeout(c);
+		}
+		history.go(-2);
+	},false);
+	ws=plus.webview.currentWebview();
+}
+if(window.plus){
+	plusReady();
+}else{
+	document.addEventListener('plusready',plusReady,false);
+}
+document.addEventListener('touchstart',function(){
+    return false;
+},true);
+// 禁止选择
+document.oncontextmenu=function(){
+	return false;
+};
+// 获取错误信息
+document.addEventListener("error",function(e){
+	info.innerText="请求的页面("+e.url+")无法打开";
+	console.log("请求的页面无法打开:"+e.href);
+},false);
+	</script>
+	<style>
+*{
+	-webkit-user-select: none;
+}
+html,body{
+	margin: 0px;
+	padding: 0px;
+	width: 100%;
+	height: 100%;
+	text-align: center;
+	word-break: break-all;
+	-webkit-touch-callout:none;
+	-webkit-tap-highlight-color:rgba(0,0,0,0);
+}
+.button{
+	width: 50%;
+	font-size: 18px;
+	font-weight: normal;
+	text-decoration: none;
+	text-align: center;
+	padding: .5em 0em;
+	margin: .5em auto;
+	color: #333333;
+	background-color: #EEEEEE;
+	border: 1px solid #CCCCCC;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+}
+.button:active{
+	background-color: #CCCCCC;
+}
+	</style>
+</head>
+<body>
+	<div style="width:100%;height:20%;"></div>
+	<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512" style="height:20%;width:30%"> 
+	<g id="icomoon-ignore">
+		<line stroke-width="1" x1="" y1="" x2="" y2="" stroke="#449FDB" opacity=""></line>
+	</g>
+	<path d="M256 0c-141.385 0-256 114.615-256 256s114.615 256 256 256 256-114.615 256-256-114.615-256-256-256zM352 128c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32zM160 128c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32zM352.049 390.37c-19.587-32.574-55.272-54.37-96.049-54.37s-76.462 21.796-96.049 54.37l-41.164-24.698c27.98-46.535 78.958-77.672 137.213-77.672s109.232 31.137 137.213 77.672l-41.164 24.698z" fill="#666666"></path>
+    </svg>
+	<p style="font-size:18px;font-weight:bolder;">We're sorry ...</p>
+	<p id="info" style="font-size:12px;"></p>
+	<!--<div class="button" onclick="history.back()">Retry</div>-->
+	<div class="button" onclick="if(history.length == 1){ws.close();}else{ws.back();ws.back();}">Back</div>
+	<div class="button" onclick="ws.close()">Close</div>
+	<div class="button" onclick="plus.runtime.restart()">Restart</div>
+</body>
+</html>

+ 47 - 0
android/simpleDemo/src/main/assets/data/dcloud_properties.xml

@@ -0,0 +1,47 @@
+<properties>
+	<features>
+		<feature name="Sqlite" value="io.dcloud.feature.sqlite.DataBaseFeature" />
+		<feature name="Barcode" value="io.dcloud.feature.barcode2.BarcodeFeatureImpl"/>
+		<feature name="Maps" value="io.dcloud.js.map.amap.JsMapPluginImpl"/>
+        <!--<feature name="Maps" value="io.dcloud.js.map.JsMapPluginImpl"/>-->
+		<feature name="Contacts" value="io.dcloud.feature.contacts.ContactsFeatureImpl"/>
+		<feature name="Messaging" value="io.dcloud.adapter.messaging.MessagingPluginImpl"/>
+		<feature name="Camera" value="io.dcloud.js.camera.CameraFeatureImpl"/>
+		<feature name="Console" value="io.dcloud.feature.pdr.LoggerFeatureImpl"/>
+		<feature name="Device" value="io.dcloud.feature.device.DeviceFeatureImpl"/>
+		<feature name="File" value="io.dcloud.js.file.FileFeatureImpl"/>
+		<feature name="Proximity" value="io.dcloud.feature.sensor.ProximityFeatureImpl"/>
+		<feature name="Storage" value="io.dcloud.feature.pdr.NStorageFeatureImpl"/>
+		<feature name="Cache" value="io.dcloud.feature.pdr.CoreCacheFeatureImpl"/>
+		<feature name="Invocation" value="io.dcloud.invocation.Invocation"/>
+		<feature name="Navigator" value="io.dcloud.feature.ui.navigator.NavigatorUIFeatureImpl"/>
+		<feature name="NativeUI" value="io.dcloud.feature.ui.nativeui.NativeUIFeatureImpl"/>
+		<feature name="UI" value="io.dcloud.feature.ui.UIFeatureImpl">
+			<module name="Navigator" value="io.dcloud.feature.ui.NavView"/>
+		</feature>
+		<feature name="Gallery" value="io.dcloud.js.gallery.GalleryFeatureImpl"/>
+		<feature name="Downloader" value="io.dcloud.net.DownloaderFeatureImpl"/>
+		<feature name="Uploader" value="io.dcloud.net.UploadFeature"/>
+		<feature name="Zip" value="io.dcloud.feature.pdr.ZipFeature"/>
+		<feature name="Audio" value="io.dcloud.feature.audio.AudioFeatureImpl"/>
+		<feature name="Runtime" value="io.dcloud.feature.pdr.RuntimeFeatureImpl"/>
+        <feature name="VideoPlayer" value="io.dcloud.media.MediaFeatureImpl"/>
+        <feature name="LivePusher" value="io.dcloud.media.live.LiveMediaFeatureImpl"/>
+		<feature name="XMLHttpRequest" value="io.dcloud.net.XMLHttpRequestFeature"/>
+		<feature name="Statistic" value="io.dcloud.feature.statistics.StatisticsFeatureImpl"/>
+		<feature name="Accelerometer" value="io.dcloud.feature.sensor.AccelerometerFeatureImpl"/>
+		<feature name="Orientation" value="io.dcloud.feature.sensor.OrientationFeatureImpl"/>
+		<feature name="NativeObj" value="io.dcloud.feature.nativeObj.FeatureImpl"/>		
+		<feature name="Geolocation" value="io.dcloud.js.geolocation.GeolocationFeatureImpl"/>
+		<feature name="Stream" value="io.dcloud.appstream.js.StreamAppFeatureImpl"/>
+        <feature name="plugintest" value="com.example.H5PlusPlugin.PGPlugintest"/>
+
+	</features>
+
+	<services>
+		<service name="push" value="io.dcloud.feature.aps.APSFeatureImpl"/>
+		<service name="Statistic" value="io.dcloud.feature.statistics.StatisticsBootImpl"/>
+		<service name="Downloader" value="io.dcloud.net.DownloaderBootImpl"/>
+		<!--<service name="Maps" value="io.dcloud.js.map.MapInitImpl"/>-->
+	</services>
+</properties>

+ 23 - 0
android/simpleDemo/src/main/assets/dcloud_uniplugins.json

@@ -0,0 +1,23 @@
+{
+  "nativePlugins": [
+    {
+      "plugins": [
+        {
+          "type": "module",
+          "name": "DingTalk",
+          "class": "uni.deepoil.com.DingTalkModule"
+        },
+        {
+          "type": "module",
+          "name": "Location",
+          "class": "uni.deepoil.com.LocationModule"
+        },
+        {
+          "type": "module",
+          "name": "Permission",
+          "class": "uni.deepoil.com.PermissionModule"
+        }
+      ]
+    }
+  ]
+}

+ 55 - 0
android/simpleDemo/src/main/java/uni/deepoil/com/DingTalkModule.java

@@ -0,0 +1,55 @@
+package uni.deepoil.com;
+
+import android.os.Handler;
+import android.util.Log;
+
+import com.alibaba.fastjson.JSONObject;
+import com.android.dingtalk.openauth.AuthLoginParam;
+import com.android.dingtalk.openauth.DDAuthApiFactory;
+import com.android.dingtalk.openauth.IDDAuthApi;
+
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+/**
+ * 钉钉登录插件
+ *
+ * @author Berial
+ * @since 2025-06-09
+ */
+public class DingTalkModule extends UniModule {
+
+    private static String state;
+    public static UniJSCallback sCallback;
+    public static Handler handler = new Handler(msg -> {
+        Log.d("DingTalkModule", "callback: " + sCallback);
+        if (sCallback != null && (msg.what == 1 || msg.what == 2)) {
+            Log.d("DingTalkModule", "what: " + msg.what + ", obj: " + msg.obj);
+            JSONObject data = new JSONObject();
+            data.put("success", msg.what);
+            data.put("code", msg.obj);
+            data.put("state", state);
+            sCallback.invoke(data);
+        }
+        return false;
+    });
+
+    @UniJSMethod
+    public void login(UniJSCallback callback) {
+         sCallback = callback;
+
+        state = String.valueOf(System.currentTimeMillis());
+        AuthLoginParam params = AuthLoginParam.AuthLoginParamBuilder.newBuilder()
+                .appId("dingmr9ez0ecgbmscfeb")
+                .redirectUri("https://iot.deepoil.cc/")
+                .responseType("code")
+                .scope("openid%20corpid")
+                .prompt("consent")
+                .nonce("nonce")
+                .state(state)
+                .build();
+        IDDAuthApi api = DDAuthApiFactory.createDDAuthApi(mUniSDKInstance.getContext(), params);
+        api.authLogin();
+    }
+}

+ 78 - 0
android/simpleDemo/src/main/java/uni/deepoil/com/LocationModule.java

@@ -0,0 +1,78 @@
+package uni.deepoil.com;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.Looper;
+
+import com.alibaba.fastjson.JSONObject;
+import com.hjq.permissions.XXPermissions;
+
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+/**
+ * 定位插件
+ * @author Berial
+ * @since 2025-06-11
+ */
+public class LocationModule extends UniModule {
+
+    @UniJSMethod(uiThread = true)
+    public void getLocationInfo(UniJSCallback callback) {
+        final Context context = mUniSDKInstance.getContext();
+        String[] permissions = {
+                Manifest.permission.ACCESS_COARSE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION
+        };
+        if (XXPermissions.isGranted(context, permissions)) {
+            requestLocation(context, callback);
+        } else {
+            XXPermissions.with(context)
+                    .permission(permissions)
+                    .request((perms, allGranted) -> {
+                        System.out.println(perms);
+                        System.out.println("allGranted: " + allGranted);
+                        if (allGranted) {
+                            requestLocation(context, callback);
+                        } else {
+                            JSONObject json = new JSONObject();
+                            json.put("success", false);
+                            json.put("msg", "location permission is denied");
+                            callback.invoke(json);
+                        }
+                    });
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private void requestLocation(Context context, UniJSCallback callback) {
+        final LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+        String provider;
+        if (lm.isProviderEnabled(LocationManager.FUSED_PROVIDER)) {
+            provider = "fused";
+        } else {
+            provider = LocationManager.GPS_PROVIDER;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            lm.getCurrentLocation(provider, null, context.getMainExecutor(), location -> {
+                JSONObject json = new JSONObject();
+                json.put("lat", location.getLatitude());
+                json.put("lon", location.getLongitude());
+                json.put("success", true);
+                callback.invoke(json);
+            });
+        } else {
+            lm.requestSingleUpdate(provider, location -> {
+                JSONObject json = new JSONObject();
+                json.put("lat", location.getLatitude());
+                json.put("lon", location.getLongitude());
+                json.put("success", true);
+                callback.invoke(json);
+            }, Looper.getMainLooper());
+        }
+    }
+}

+ 88 - 0
android/simpleDemo/src/main/java/uni/deepoil/com/PermissionModule.java

@@ -0,0 +1,88 @@
+package uni.deepoil.com;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.hjq.permissions.Permission;
+import com.hjq.permissions.XXPermissions;
+
+import java.util.ArrayList;
+
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+/**
+ * 权限插件
+ *
+ * @author Berial
+ * @since 2025-06-12
+ */
+public class PermissionModule extends UniModule {
+
+    @UniJSMethod
+    public void requestImagePermissions(UniJSCallback callback) {
+        ArrayList<String> list = new ArrayList<>();
+        list.add(Permission.CAMERA);
+        list.add(Permission.READ_MEDIA_VISUAL_USER_SELECTED);
+//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            list.add(Permission.READ_MEDIA_IMAGES);
+            list.add(Permission.READ_MEDIA_VIDEO);
+            list.add(Permission.READ_MEDIA_AUDIO);
+//        } else {
+//            list.add(Permission.WRITE_EXTERNAL_STORAGE);
+//            list.add(Permission.READ_EXTERNAL_STORAGE);
+//        }
+
+        try {
+            Context context = mUniSDKInstance.getContext();
+            if (XXPermissions.isGranted(context, list)) {
+                callback.invoke(generateResult(true));
+            } else {
+                XXPermissions.with(context)
+                        .permission(list)
+                        .request((perms, allGranted) -> {
+                            callback.invoke(generateResult(allGranted));
+                        });
+            }
+        } catch (Exception e) {
+            Log.e("PermissionModule", e.toString());
+        }
+    }
+
+    @UniJSMethod
+    public void requestPermissions(JSONObject options, UniJSCallback callback) {
+        JSONArray array = options.getJSONArray("permissions");
+        if (array != null) {
+            ArrayList<String> list = new ArrayList<>();
+            for (int i = 0; i < array.size(); i++) {
+                String permission = array.getString(i);
+                if (!TextUtils.isEmpty(permission)) {
+                    list.add(permission);
+                }
+            }
+
+            Context context = mUniSDKInstance.getContext();
+            if (XXPermissions.isGranted(context, list)) {
+                callback.invoke(generateResult(true));
+            } else {
+                XXPermissions.with(context)
+                        .permission(list)
+                        .request((perms, allGranted) -> {
+                            callback.invoke(generateResult(allGranted));
+                        });
+            }
+        } else {
+            callback.invoke(generateResult(false));
+        }
+    }
+
+    private JSONObject generateResult(boolean success) {
+        JSONObject obj = new JSONObject();
+        obj.put("success", success);
+        return obj;
+    }
+}

+ 44 - 0
android/simpleDemo/src/main/java/uni/deepoil/com/ddauth/DDAuthActivity.java

@@ -0,0 +1,44 @@
+package uni.deepoil.com.ddauth;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.dingtalk.openauth.utils.DDAuthConstant;
+
+import uni.deepoil.com.DingTalkModule;
+
+/**
+ * @author Berial
+ * @since 2025-06-09
+ */
+public class DDAuthActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        String authCode = intent.getStringExtra(DDAuthConstant.CALLBACK_EXTRA_AUTH_CODE);
+        String state = intent.getStringExtra(DDAuthConstant.CALLBACK_EXTRA_STATE);
+        String error = intent.getStringExtra(DDAuthConstant.CALLBACK_EXTRA_ERROR);
+
+        Log.d("DingTalk", "authCode: " + authCode + " state: " + state + " error: " + error);
+
+        if (!TextUtils.isEmpty(authCode)) {
+            // 授权成功
+            Message message = DingTalkModule.handler.obtainMessage(1, authCode);
+            message.sendToTarget();
+        } else {
+            // 授权失败
+            Message message = DingTalkModule.handler.obtainMessage(2, error);
+            message.sendToTarget();
+        }
+        finish();
+    }
+}

+ 75 - 0
android/simpleDemo/src/main/java/uni/deepoil/com/ddshare/DDShareActivity.java

@@ -0,0 +1,75 @@
+package uni.deepoil.com.ddshare;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * @author Berial
+ * @since 2025-06-16
+ */
+public class DDShareActivity extends AppCompatActivity { // implements IDDAPIEventHandler {
+
+    // private IDDShareApi mIDDShareApi;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.d("lzc" ,"onCreate==========>");
+        try {
+            // activity的export为true,try起来,防止第三方拒绝服务攻击。APP_ID指通过步骤一获取的应用的AppKey
+            // mIDDShareApi = DDShareApiFactory.createDDShareApi(this, "dingcrhejkptu0mcsw3r", true);
+            // mIDDShareApi.handleIntent(getIntent(), this);
+            // DingTalkModule.shareApi.handleIntent(getIntent(), this);
+        } catch (Exception e) {
+            // e.printStackTrace();
+            Log.d("lzc" , "e===========>" + e);
+        }
+    }
+
+//    @Override
+//    public void onReq(BaseReq baseReq) {
+//        System.out.println(baseReq);
+//        Bundle bundle = new Bundle();
+//        baseReq.toBundle(bundle);
+//        System.out.println(bundle);
+//    }
+
+//    @Override
+//    public void onResp(BaseResp baseResp) {
+//        System.out.println(baseResp);
+//        int errCode = baseResp.mErrCode;
+//        Log.d("lzc", "errorCode==========>" + errCode);
+//        String errMsg = baseResp.mErrStr;
+//        Log.d("lzc", "errMsg==========>" + errMsg);
+//        if (baseResp.getType() == ShareConstant.COMMAND_SENDAUTH_V2 && (baseResp instanceof SendAuth.Resp)) {
+//            SendAuth.Resp authResp = (SendAuth.Resp) baseResp;
+//            switch (errCode) {
+//                case BaseResp.ErrCode.ERR_OK:
+//                    Toast.makeText(this, "授权成功,授权码为:" + authResp.code, Toast.LENGTH_SHORT).show();
+//                    break;
+//                case BaseResp.ErrCode.ERR_USER_CANCEL:
+//                    Toast.makeText(this, "授权取消", Toast.LENGTH_SHORT).show();
+//                    break;
+//                default:
+//                    Toast.makeText(this, "授权异常" + baseResp.mErrStr, Toast.LENGTH_SHORT).show();
+//                    break;
+//            }
+//        } else {
+//            switch (errCode) {
+//                case BaseResp.ErrCode.ERR_OK:
+//                    Toast.makeText(this, "分享成功", Toast.LENGTH_SHORT).show();
+//                    break;
+//                case BaseResp.ErrCode.ERR_USER_CANCEL:
+//                    Toast.makeText(this, "分享取消", Toast.LENGTH_SHORT).show();
+//                    break;
+//                default:
+//                    Toast.makeText(this, "分享失败" + baseResp.mErrStr, Toast.LENGTH_SHORT).show();
+//                    break;
+//            }
+//        }
+//        finish();
+//    }
+}

BIN
android/simpleDemo/src/main/res/drawable/icon.png


BIN
android/simpleDemo/src/main/res/drawable/push.png


BIN
android/simpleDemo/src/main/res/drawable/splash.png


+ 3 - 0
android/simpleDemo/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">DEEP OIL</string>
+</resources>

+ 9 - 0
android/simpleDemo/src/main/res/values/styles.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="DCloudActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:colorEdgeEffect">#333333</item>
+        <item name="android:windowBackground">@color/ime_background</item>
+        <item name="android:forceDarkAllowed">false</item>
+    </style>
+</resources>

BIN
android/simpleDemo/test.jks


+ 10 - 0
api/app.js

@@ -0,0 +1,10 @@
+import {
+	request
+} from '@/utils/request.js';
+
+export function getAppVersion() {
+	return request({
+		url: '/rq/iot-app/new',
+		method: 'GET',
+	});
+}

+ 29 - 0
api/device.js

@@ -0,0 +1,29 @@
+import { request } from "@/utils/request";
+// 获取可选择设备列表
+export function devicePage(params) {
+  return request({
+    url: "/rq/iot-device/page/app",
+    method: "get",
+    params,
+  });
+}
+
+/**
+ * 获取设备规格型号
+ * @param params
+ */
+export const getDeviceModelList = (params) =>
+  request({
+    url: "/rq/iot-model/page",
+    method: "GET",
+    params,
+  })
+
+/**
+ * 获取设备类别
+ */
+export const getAllDeviceTypeList = () =>
+  request({
+    url: "/rq/iot-product-classify/simple-list",
+    method: "GET",
+  })

+ 34 - 0
api/deviceUser.js

@@ -0,0 +1,34 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取设备责任人列表
+ * @param params
+ */
+export const getDeviceUserList = (params) =>
+  request({
+    url: '/rq/iot-device/responsiblePage',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 获取设备责任人调整记录
+ * @param params
+ */
+export const getDeviceUserHistoryList = (params) =>
+  request({
+    url: '/pms/iot-device-person-log/page',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 提交设备责任人
+ * @param data
+ */
+export const submitDeviceUser = (data) =>
+  request({
+    url: '/pms/iot-device-person/saveDevicePersons',
+    method: 'POST',
+    data,
+  })

+ 73 - 0
api/fault.js

@@ -0,0 +1,73 @@
+import {
+	request
+} from '@/utils/request'
+// 获取故障上报统计
+export function getFaultCount(params) {
+	return request({
+		url: '/rq/stat/main/total',
+		method: 'get',
+		params
+	})
+}
+// 故障上报列表
+export function getFaultList(params) {
+	return request({
+		url: '/rq/iot-failure-report/page/app',
+		method: 'get',
+		params
+	})
+}
+// 故障上报
+export function createFault(data) {
+	return request({
+		url: '/rq/iot-failure-report/create',
+		method: 'post',
+		data: data
+	})
+}
+// 故障上报详情
+export function getFaultDetail(params) {
+	return request({
+		url: '/rq/iot-failure-report/get',
+		method: 'get',
+		params
+	})
+}
+// 故障上报修改
+export function updateFault(data) {
+	return request({
+		url: '/rq/iot-failure-report/update',
+		method: 'put',
+		data: data
+	})
+}
+// 故障上报删除
+export function deleteFault(params) {
+	return request({
+		url: '/rq/iot-failure-report/delete',
+		method: 'delete',
+		params
+	})
+}
+
+/**
+ * 更新故障上报流程信息
+ * @param id 故障id
+ * @param type 维修类型
+ * @param assigneeUserId 负责人
+ */
+export const updateFaultProcess = (id, type, assigneeUserId) =>
+	request({
+		url: '/rq/iot-failure-report/process-info',
+		method: 'PUT',
+		params: { id, type, assigneeUserId }
+	})
+
+/**
+ * 故障上报审批人列表
+ */
+export const getFailureApprovalList = () =>
+	request({
+		url: '/rq/iot-failure-report/get/approval',
+		method: 'GET',
+	})

+ 66 - 0
api/file.js

@@ -0,0 +1,66 @@
+import config from '@/utils/config';
+import {
+	getAccessToken,
+} from "@/utils/auth.js"
+import request from '@/utils/request';
+
+// 上传文件
+export function uploadFile(file, directory) {
+	uni.showLoading({
+		title: '上传中',
+	});
+	return new Promise((resolve, reject) => {
+		uni.uploadFile({
+			url: config.default.apiUrl + config.default.apiUrlSuffix + '/infra/file/upload',
+			filePath: file,
+			name: 'file',
+			header: {
+				Accept: '*/*',
+				'tenant-id': tenantId,
+				Authorization: 'Bearer ' + getAccessToken(),
+			},
+			formData: {
+				directory,
+			},
+			success: (uploadFileRes) => {
+				let result = JSON.parse(uploadFileRes.data);
+				if (result.error === 1) {
+					uni.showToast({
+						icon: 'none',
+						title: result.msg,
+					});
+				} else {
+					return resolve(result);
+				}
+			},
+			fail: (error) => {
+				console.log('上传失败:', error);
+				return resolve(false);
+			},
+			complete: () => {
+				uni.hideLoading();
+			},
+		});
+	});
+}
+
+// 获取文件预签名地址
+export function getFilePresignedUrl(name, directory) {
+	return request({
+		url: '/infra/file/presigned-url',
+		method: 'GET',
+		params: {
+			name,
+			directory,
+		},
+	});
+}
+
+// 创建文件
+export function createFile(data) {
+	return request({
+		url: '/infra/file/create', // 请求的 URL
+		method: 'POST', // 请求方法
+		data: data, // 要发送的数据
+	});
+}

+ 127 - 0
api/index.js

@@ -0,0 +1,127 @@
+import { request, upload } from "@/utils/request.js";
+import { getDeptId } from "@/utils/auth";
+
+export const getAllDataDictList = () =>
+  request({
+    url: "/system/dict-data/simple-list",
+    method: "GET",
+  });
+
+export const getAllDeptList = (name) =>
+  request({
+    url: "/system/dept/simple-list",
+    method: "GET",
+    params: {
+      name,
+    },
+  });
+
+export const getAllDeviceList = (deptId) =>
+  request({
+    url: "/rq/iot-device/simple-list",
+    method: "GET",
+    params: {
+      deptId,
+    },
+  });
+
+/**
+ * 获取工厂、成本中心、库存地点列表
+ * @param type 1-工厂 2-成本中心 3-库存地点
+ */
+export const getAllCostCenterOrFactoryOrStorageLocationList = (type) =>
+  request({
+    url: "/system/sap-org/simple-list",
+    method: "GET",
+    params: {
+      type,
+    },
+  });
+/**
+ * 获取工厂、成本中心列表 - 新
+ * @param type 1-工厂 2-成本中心 
+ */
+export const filteredSimpleSapOrgList = (type) =>
+  request({
+    url: "/system/sap-org/filteredSimpleSapOrgList",
+    method: "GET",
+    params: {
+      type,
+      deptId: getDeptId(), // 获取当前部门ID
+    },
+  });
+
+/**
+ * 供应商分页数据
+ * @param params 筛选参数
+ */
+export const getSupplierList = (params) =>
+  request({
+    url: "/supplier/base/page",
+    method: "GET",
+    params,
+  });
+
+/**
+ * 获取字典分页数据
+ * @param params
+ */
+export const getDataDictList = (params) =>
+  request({
+    url: "/system/dict-data/page",
+    method: "GET",
+    params,
+  });
+
+/**
+ * 上传文件
+ * @param filePath
+ */
+export const uploadFile = (filePath) =>
+  upload("/infra/file/upload/path", {
+    // #ifdef MP-ALIPAY
+    fileType: "image/video/audio", // 仅支付宝小程序,且必填。
+    // #endif
+    filePath: filePath, // 要上传文件资源的路径。
+    name: "file", // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
+  });
+
+/**
+ * 获取用户列表
+ * @param userId
+ */
+export const getUserList = (userId) =>
+  request({
+    url: "/system/user/dept/users",
+    method: "GET",
+    params: {
+      userId,
+    },
+  });
+
+// 获取用户信息
+export const getUserInfo = (params) =>
+  request({
+    url: "/system/user/get",
+    method: "GET",
+    params,
+  });
+
+// 获取用户部门信息
+export const getUserDeptInfo = (params) =>
+  request({
+    url: "/system/dept/get",
+    method: "GET",
+    params,
+  });
+
+/**
+ * 获取部门下用户列表
+ * @param deptId
+ */
+export const getUserListByDeptId = (deptId) =>
+  request({
+    url: '/system/user/simpleUserList',
+    method: 'GET',
+    params: { deptId },
+  })

+ 65 - 0
api/inspection.js

@@ -0,0 +1,65 @@
+import {
+	request
+} from '@/utils/request'
+
+// 获取巡检数量统计
+export function getInspectCount(params) {
+	return request({
+		url: '/rq/stat/inspect/total',
+		method: 'get',
+		params
+	})
+}
+// 巡检工单列表
+export function getInspectOrderList(params) {
+	return request({
+		url: '/rq/iot-inspect-order/page/app',
+		method: 'get',
+		params
+	})
+}
+
+// 巡检工单列表 - 忽略
+export function inspectOrderIgnore(data) {
+	return request({
+		url: '/rq/iot-inspect-order/ignore',
+		method: 'post',
+		params: data
+	})
+}
+
+// 巡检工单详情 - 查看
+export function getInspectOrderGet(params) {
+	return request({
+		url: '/rq/iot-inspect-order/get',
+		method: 'get',
+		params
+	})
+}
+// 巡检工单详情 - 填写
+export function getInspectOrderGetDetails(params) {
+	return request({
+		url: '/rq/iot-inspect-order/get/details',
+		method: 'get',
+		params
+	})
+}
+// 巡检工单详情 - 提交
+export function inspectOrderWrite(id, data) {
+	return request({
+		url: '/rq/iot-inspect-order/write/' + id,
+		method: 'post',
+		data
+	})
+}
+
+/**
+ * 获取启用的巡检计划
+ * @param params
+ */
+export const getInspectPlanList = (params) =>
+	request({
+		url: '/rq/iot-inspect-plan/page',
+		method: 'GET',
+		params: { pageNo: 1, pageSize: 100, status: 0 },
+	})

+ 9 - 0
api/inventory.js

@@ -0,0 +1,9 @@
+import { request } from "@/utils/request";
+
+export const getInventoryList = (data) => {
+  return request({
+    url: '/pms/iot-lock-stock/page',
+    methods: 'GET',
+    data,
+  })
+}

+ 34 - 0
api/ledger.js

@@ -0,0 +1,34 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取设备台账列表
+ * @param params
+ */
+export const getDeviceLedgerList = (params) =>
+  request({
+    url: '/rq/iot-device/page/app',
+    method: 'GET',
+    params
+  })
+
+/**
+ * 新增台账
+ * @param data
+ */
+export const submitDeviceLedger = (data) =>
+  request({
+    url: '/rq/iot-device/create',
+    method: 'POST',
+    data
+  })
+
+/**
+ * 获取台账详情
+ * @param id
+ */
+export const getDeviceLedgerDetail = (id) =>
+  request({
+    url: '/rq/iot-device/get',
+    method: 'GET',
+    params: { id }
+  })

+ 129 - 0
api/login.js

@@ -0,0 +1,129 @@
+import {
+	request, upload
+} from '@/utils/request.js';
+import {
+	getRefreshToken
+} from '@/utils/auth';
+
+
+// 登录方法
+export function appLogin(data) {
+	return request({
+		url: '/system/auth/login',
+		headers: {
+			isToken: false
+		},
+		method: 'POST',
+		data
+	});
+}
+
+/**
+ * 钉钉授权登录
+ * @param data
+ */
+export function dingTalkLogin(data) {
+	return request({
+		url: '/system/auth/appSocialLogin',
+		headers: { isToken: false },
+		method: 'POST',
+		data
+	})
+}
+/**
+ * 钉钉授权登录 - H5
+ * @param data
+ */
+export function dingTalkLoginH5(data) {
+	return request({
+		url: '/system/auth/h5SocialLogin',
+		headers: { isToken: false },
+		method: 'POST',
+		data
+	})
+}
+
+
+
+/**
+ * 获取用户信息
+ */
+export const getLoginUserInfo = () =>
+	request({
+		url: '/system/user/profile/get',
+		method: 'GET',
+	})
+
+// 获取用户详细信息
+export function getInfo() {
+	return request({
+		url: '/system/auth/get-permission-info',
+		method: 'GET'
+	})
+}
+
+// 退出方法
+export function logout() {
+	return request({
+		url: '/system/auth/logout',
+		method: 'POST'
+	})
+}
+// 刷新访问令牌
+export function refreshToken() {
+	return request({
+		url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
+		method: 'POST',
+		custom: {
+			showLoading: false, // 不用加载中
+			showError: false, // 不展示错误提示
+		},
+	});
+}
+
+/**
+ * 修改用户信息
+ * @param avatar
+ * @param mobile
+ */
+export const updateUserInfo = (mobile) =>
+	request({
+		url: '/system/user/profile/update',
+		method: 'PUT',
+		data: { mobile }
+	})
+
+/**
+ * 修改头像
+ * @param avatar
+ */
+export const updateAvatar = (avatar) =>
+	upload('/system/user/profile/update-avatar', {
+		// #ifdef MP-ALIPAY
+		fileType: 'image/video/audio', // 仅支付宝小程序,且必填。
+		// #endif
+		filePath: avatar, // 要上传文件资源的路径。
+		name: 'avatarFile', // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
+	})
+
+/**
+ * 修改密码
+ * @param oldPwd
+ * @param newPwd
+ */
+export const changePassword = (oldPwd, newPwd) =>
+	request({
+		url: '/system/user/profile/update-password',
+		method: 'PUT',
+		data: { oldPassword: oldPwd, newPassword: newPwd },
+	})
+
+/**
+ * 通过userId获取token
+ * @param userId
+ */
+export const getTokenByUserId = (userId) =>
+	request({
+		url: '/system/auth/simple/login/' + userId,
+		method: 'POST'
+	})

+ 71 - 0
api/maintenance.js

@@ -0,0 +1,71 @@
+import {
+	request
+} from '@/utils/request'
+// 获取保养工单统计数量
+export function getMaintenanceCount(params) {
+  return request({
+    url: '/rq/stat/maintenance/total',
+    method: 'get',
+    params
+  })
+}
+
+// 获取保养工单列表
+export function getMaintenanceList(params) {
+  return request({
+    url: '/pms/iot-main-work-order/sortedMainWorkOrderPage',
+    // url: '/pms/iot-main-work-order/page',
+    method: 'get',
+    params
+  })
+}
+// 根据设备id获取设备保养项
+export function getDeviceAssociateBomList(params) {
+  return request({
+    url: '/rq/iot-device/deviceAssociateBomList',
+    method: 'get',
+    params
+  })
+}
+
+// 保存保养工单
+export function saveMaintenance(data) {
+  return request({
+    url: '/pms/iot-main-work-order/addWorkOrder',
+    method: 'put',
+    data
+  })
+}
+
+// 查询保养工单详情
+export function getMaintenanceDetail(params) {
+  return request({
+    url: '/pms/iot-main-work-order/get',
+    method: 'get',
+    params
+  })
+}
+ // 查询保养工单详情 - 保养工单明细BOM列表
+ export function getWorkOrderBOMs(params) {
+  return request({
+    url: '/pms/iot-main-work-order-bom/getWorkOrderBOMs',
+    method: 'get',
+    params
+  })
+}
+// 填报保养工单
+export function fillWorkOrder(data) {
+  return request({
+    url: '/pms/iot-main-work-order/fillWorkOrder',
+    method: 'put',
+    data
+  })
+}
+// 查询保养工单已经选择的所有物料
+export function getBomMaterialsByWorkOrderId(params) {
+  return request({
+    url: '/pms/iot-main-work-order-bom-material/list',
+    method: 'get',
+    params
+  })
+}

+ 35 - 0
api/material.js

@@ -0,0 +1,35 @@
+import { request } from "@/utils/request";
+
+// 获取物料列表
+export function getMaterialList(params) {
+  return request({
+    url: "/pms/iot-main-work-order-bom-material/workOrderMaterials",
+    method: "get",
+    params
+  });
+}
+// 新增物料 查询物料主数据
+
+export function getMaterialListData(params) {
+  return request({
+    url: "/rq/iot-material/page",
+    method: "get",
+    params
+  });
+}
+// 物料详情
+export function getMaterialDetail(params) {
+	return request({
+    url: "/pms/iot-main-work-order-bom-material/page",
+    method: "get",
+    params
+  });
+}
+// 物料详情 - 维修工单
+export function getRepairMaterialDetail(params) {
+	return request({
+    url: "/rq/iot-maintain-materials/page",
+    method: "get",
+    params
+  });
+}

+ 32 - 0
api/message.js

@@ -0,0 +1,32 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取未读消息数量
+ */
+export const getUnreadMessageCount = () =>
+  request({
+    url: '/system/notify-message/get-unread-count',
+    method: 'GET',
+  });
+
+/**
+ * 系统消息列表
+ * @param params
+ */
+export const getMessageList = (params) =>
+  request({
+    url: '/system/notify-message/my-page',
+    method: 'GET',
+    params
+  });
+
+/**
+ * 将消息标为已读
+ * @param id
+ */
+export const markReadMessage = (id) =>
+  request({
+    url: '/system/notify-message/update-read',
+    method: 'PUT',
+    params: { ids: id }
+  })

+ 36 - 0
api/realTimeData.js

@@ -0,0 +1,36 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取设备实时数据列表
+ * @param params
+ */
+export const getDeviceRealTimeDataList = (params) =>
+  request({
+    url: '/rq/iot-device/td/page/app',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 获取设备实时数据详情
+ * @param id
+ */
+export const getDeviceRealTimeDataDetail = (id) =>
+  request({
+    url: '/rq/iot-device/get/td',
+    method: 'GET',
+    params: { id },
+  })
+
+/**
+ * 获取设备实时数据图表数据
+ * @param deviceCode
+ * @param chartData
+ * @param params
+ */
+export const getDeviceRealTimeChartData = (deviceCode, chartData, params) =>
+  request({
+    url: `/rq/stat/td/chart/${deviceCode}/${chartData}`,
+    method: 'GET',
+    params,
+  })

+ 83 - 0
api/recordFilling.js

@@ -0,0 +1,83 @@
+import {
+	request
+} from '@/utils/request.js';
+
+export function getRecordFillingList(params) {
+	return request({
+		url: '/rq/iot-opeation-fill/page1',
+		method: 'GET',
+		params
+	});
+}
+export function getRecordFillingDetailGetPage(params) {
+	return request({
+		url: '/rq/iot-opeation-fill/page',
+		method: 'GET',
+		params
+	});
+}
+export function getRecordFillingDetailGetAttrs(params) {
+	return request({
+		url: '/rq/iot-opeation-fill/getAttrs',
+		method: 'GET',
+		params
+	});
+}
+// 运行记录填报
+export function recordFillingDetailInsertLog(data) {
+	return request({
+		url: '/rq/iot-opeation-fill/insertLog',
+		method: 'POST',
+		data
+	});
+}
+
+/**
+ * 运气记录详情
+ * @param id
+ */
+export const getRecordFillingDetail = (id) =>
+	request({
+		url: '/rq/iot-opeation-fill/get',
+		method: 'GET',
+		params: { id }
+	})
+
+/**
+ * 运行记录模板列表
+ */
+export const getRecordFillingTemplateList = () =>
+	request({
+		url: '/rq/iot-model-template/page',
+		method: 'GET',
+		params: { pageNo: 1, pageSize: 100, status: 0 },
+	})
+
+/**
+ * 获取设备模板属性列表
+ * @param deviceCategoryId
+ */
+export const getRecordFillingOptionList = (deviceCategoryId) =>
+	request({
+		url: 'rq/iot-model-template-attrs/page',
+		method: 'GET',
+		params: { pageNo: 1, pageSize: 100, deviceCategoryId },
+	})
+	
+	// 忽略运行工单
+export function recordFillingIgnore(data) {
+	return request({
+		url: '/rq/iot-opeation-fill/update1',
+		method: 'put',
+		data: data
+	})
+}
+
+// 运行记录填报后更新工单状态
+export function recordFillingUpOperationOrder(data) {
+	return request({
+		url: '/rq/iot-opeation-fill/upOperationOrder',
+		method: 'POST',
+		data: data
+	})
+}

+ 70 - 0
api/repair.js

@@ -0,0 +1,70 @@
+import {
+	request
+} from '@/utils/request'
+// 获取维修数量统计
+export function getRepairCount(params) {
+	return request({
+		url: '/rq/stat/main/status',
+		method: 'get',
+		params
+	})
+}
+// 维修工单列表
+export function getRepairList(params) {	
+	return request({
+		url: '/rq/iot-maintain/page/app',
+		method: 'get',
+		params
+	})
+}
+// 维修工单 - 获取选择维修项列表 
+export function deviceAssociateBomListPage(params) {
+	return request({
+		url: '/rq/iot-device/deviceAssociateBomListPage',
+		method: 'get',
+		data:params // 这里使用data是因为传输的数据中包含数组
+	})
+}
+// 创建维修工单
+export function createRepair(data) {
+	return request({
+		url: '/rq/iot-maintain/create',
+		method: 'post',
+		data
+	})
+}
+
+// 填报维修工单
+export function updateRepair(data) {
+	return request({
+		url: '/rq/iot-maintain/update',
+		method: 'put',
+		data
+	})
+}
+
+// 获取维修工单详情
+export function getRepairDetail(params) {
+	return request({
+		url: '/rq/iot-maintain/get',
+		method: 'get',
+		params
+	})
+}
+
+// 维修工单获取申请人列表
+export function getRepairApplicantList(params) {
+	return request({
+		url: `/rq/iot-maintain/maintain/applyusers`,
+		method: 'get',
+		params
+	})
+}
+// 维修工单获取项目经理列表
+export function getRepairProjectManagerList(params) {
+	return request({
+		url: `/rq/iot-maintain/maintain/project`,
+		method: 'get',
+		params
+	})
+}

+ 72 - 0
api/ruiDu.js

@@ -0,0 +1,72 @@
+import { request, upload } from "@/utils/request";
+import config from "@/utils/config";
+
+/**
+ * 获取瑞都日报列表
+ * @param params 查询参数
+ * @param pageNo 页码
+ * @param pageSize 每页数量
+ */
+export function getRuiDuReportPage(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/page",
+    method: "get",
+    params,
+  });
+}
+
+/**
+ * 获取瑞都日报详情 - 任务信息
+ * @param params 查询参数
+ * @param id 瑞都日报ID
+ */
+export function getRuiDuReportDetail(params) {
+  return request({
+    url: "/pms/iot-rd-daily-report/get",
+    method: "get",
+    params,
+  });
+}
+
+/**
+ * 瑞都日报填报
+ * 根据选择的‘施工工艺’选项,查询施工工艺对应的工作量属性字段
+ */
+export function getRuiDuReportAttrs(params) {
+  return request({
+    url: "/rq/iot-daily-report-attrs/dailyReportAttrs",
+    method: "get",
+    params,
+  });
+}
+
+/**
+ * 上传瑞都日报附件
+ * @param filePath
+ */
+export const uploadAttachmentsFile = (
+  filePath,
+  deviceId = undefined
+) =>
+  upload("/rq/file/upload", {
+    // #ifdef MP-ALIPAY
+    fileType: "image/video/audio", // 仅支付宝小程序,且必填。
+    // #endif
+    filePath: filePath, // 要上传文件资源的路径。
+    name: "files", // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
+    header: {
+      "device-id": deviceId,
+    } /* 会与全局header合并,如有同名属性,局部覆盖全局 */,
+  });
+
+/**
+ * 瑞都日报填报 - 更新
+ * @param data 提交参数
+ */
+export function updateRuiDuReport(data) {
+  return request({
+    url: "/pms/iot-rd-daily-report/update",
+    method: "put",
+    data,
+  });
+}

+ 275 - 0
api/statistic.js

@@ -0,0 +1,275 @@
+import { request } from "@/utils/request";
+import { getDeptId } from "@/utils/auth";
+// <editor-fold desc="首页统计">
+/**
+ * 获取设备数
+ */
+export const getDeviceCount = () =>
+  request({
+    url: '/rq/stat/home/device/count/undefined',
+    method: 'GET',
+  })
+  
+/**
+ * 获取维修工单数
+ */
+export const getMaintainCount = () =>
+  request({
+    url: '/rq/stat/home/maintain/count/undefined',
+    method: 'GET',
+  })
+
+/**
+ * 获取运行记录未/填报数
+ */
+export const getPendingCount = (params) =>
+  request({
+    url: '/rq/iot-opeation-fill/getCount',
+    method: 'GET',
+    params,
+
+  })
+/**
+ * 获取保养工单数
+ */
+export const getMaintenanceCount = () =>
+  request({
+    url: '/rq/stat/maintenance/status/undefined',
+    method: 'GET',
+  })
+/**
+ * 获取巡检工单数
+ */
+export const getInspectCount = (params) =>
+  request({
+    url: '/rq/stat/inspect/statuss/'+getDeptId(), // deptId
+    method: 'GET',
+    params,
+  })
+  /**
+ * 获取MTTR 平均解决时间
+ */
+export const getMTTR = () =>
+  request({
+    url: '/rq/stat/mttr',
+    method: 'GET',
+  })
+/**
+ * 获取库存预警物料数量
+ */
+export const getStockWarningCount = () =>
+  request({
+    url: '/rq/stat/home/safe',
+    method: 'GET',
+  })
+/**
+ * 获取设备状态统计
+ */
+export const getDeviceStatusStatistic = () =>
+  request({
+    url: '/rq/stat/home/device/status/undefined',
+    method: 'GET',
+  })
+
+
+/**
+ * 获取设备类别TOP5
+ */
+export const getDeviceTypeCount = () =>
+  request({
+    url: '/rq/stat/home/device/type/yf',
+    method: 'GET',
+  })
+  /**
+ * 获取近一周用户活跃度
+ */
+export const getWeeklyUserActivity = () =>
+  request({
+    url: '/rq/stat/home/dept',
+    method: 'GET',
+  })
+
+/**
+ * 获取工单数量统计
+ */
+export const getWorkOrderCount = () =>
+  request({
+    url: '/rq/stat/rh/order/'+getDeptId(), // deptId
+    method: 'GET',
+  })
+
+// </editor-fold>
+
+// <editor-folder desc="维修统计">
+
+/**
+ * 获取当天维修统计
+ */
+export const getTodayRepairStatistic = () =>
+  request({
+    url: '/rq/stat/main/day',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一周维修统计
+ */
+export const getWeeklyRepairStatistic = () =>
+  request({
+    url: '/rq/stat/main/week',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一月维修统计
+ */
+export const getMonthlyRepairStatistic = () =>
+  request({
+    url: '/rq/stat/main/month',
+    method: 'GET',
+  })
+
+/**
+ * 获取总维修统计
+ */
+export const getTotalRepairStatistic = () =>
+  request({
+    url: '/rq/stat/main/total',
+    method: 'GET',
+  })
+
+/**
+ * 获取维修数据统计
+ */
+export const getOtherRepairStatistic = () =>
+  request({
+    url: '/rq/stat/main/status',
+    method: 'GET',
+  })
+
+// </editor-folder>
+
+// <editor-folder desc="保养统计">
+
+/**
+ * 获取昨日保养统计
+ */
+export const getYesterdayMaintenanceStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/day',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一周保养统计
+ */
+export const getWeeklyMaintenanceStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/week',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一月保养统计
+ */
+export const getMonthlyMaintenanceStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/month',
+    method: 'GET',
+  })
+
+/**
+ * 获取总保养统计
+ */
+export const getTotalMaintenanceStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/total',
+    method: 'GET',
+  })
+
+/**
+ * 获取保养工单状态数据统计
+ */
+export const getMaintenanceStatusStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/status/undefined',
+    method: 'GET',
+  })
+
+/**
+ * 获取今日保养工单状态数据统计
+ */
+export const getMaintenanceTodayStatusStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/today/status',
+    method: 'GET',
+  })
+
+/**
+ * 获取保养工单类型数据统计
+ */
+export const getMaintenanceTypeStatistic = () =>
+  request({
+    url: '/rq/stat/maintenance/type',
+    method: 'GET',
+  })
+
+// </editor-folder>
+
+// <editor-folder desc="巡检统计">
+
+/**
+ * 获取昨日巡检统计
+ */
+export const getYesterdayInspectionStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/day',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一周巡检统计
+ */
+export const getWeeklyInspectionStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/week',
+    method: 'GET',
+  })
+
+/**
+ * 获取近一月巡检统计
+ */
+export const getMonthlyInspectionStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/month',
+    method: 'GET',
+  })
+
+/**
+ * 获取总巡检统计
+ */
+export const getTotalInspectionStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/total',
+    method: 'GET',
+  })
+
+/**
+ * 获取巡检工单状态数据统计
+ */
+export const getInspectionStatusStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/status',
+    method: 'GET',
+  })
+
+/**
+ * 获取今日巡检工单状态数据统计
+ */
+export const getInspectionTodayStatusStatistic = () =>
+  request({
+    url: '/rq/stat/inspect/today/status',
+    method: 'GET',
+  })
+
+// </editor-folder>

+ 22 - 0
api/statusChange.js

@@ -0,0 +1,22 @@
+import { request } from "@/utils/request";
+
+export const getDeviceStatusChangeList = (params) =>
+  request({
+    url: '/rq/iot-device/statusRelationDevices',
+    method: 'GET',
+    params,
+  })
+
+export const getDeviceStatusHistoryList = (params) =>
+  request({
+    url: '/pms/iot-device-status-log/page',
+    method: 'GET',
+    params,
+  })
+
+export const submitDeviceChangeRecord = (data) =>
+  request({
+    url: '/rq/iot-device/saveDeviceStatuses',
+    method: 'POST',
+    data,
+  })

+ 132 - 0
api/task.js

@@ -0,0 +1,132 @@
+import { request } from "@/utils/request";
+
+/**
+ * 获取待办列表
+ * @param params
+ */
+export const getTodoList = (params) =>
+  request({
+    url: '/bpm/task/todo-page',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 审批流程列表
+ * @param params
+ */
+export const getApprovalList = (params) =>
+  request({
+    url: '/bpm/process-instance/my-page',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 获取审批详情
+ * @param params
+ */
+export const getApprovalDetail = (params) =>
+  request({
+    url: '/bpm/process-instance/get-approval-detail',
+    method: 'GET',
+    params,
+  })
+
+/**
+ * 任务审批通过
+ * @param id 任务ID
+ * @param reason 理由
+ */
+export const approvalTask = (id, reason) =>
+  request({
+    url: '/bpm/task/approve',
+    method: 'PUT',
+    data: { id, reason, variables: {}, nextAssignees: {} },
+  })
+
+/**
+ * 拒绝任务
+ * @param id
+ * @param reason
+ */
+export const rejectTask = (id, reason) =>
+  request({
+    url: '/bpm/task/reject',
+    method: 'PUT',
+    data: { id, reason },
+  })
+
+/**
+ * 转派任务
+ * @param id
+ * @param assigneeUserId
+ * @param reason
+ */
+export const transferTask = (id, assigneeUserId, reason) =>
+  request({
+    url: '/bpm/task/transfer',
+    method: 'PUT',
+    data: { id, assigneeUserId, reason },
+  })
+
+/**
+ * 委派任务
+ * @param id
+ * @param delegateUserId
+ * @param reason
+ */
+export const delegateTask = (id, delegateUserId, reason) =>
+  request({
+    url: '/bpm/task/delegate',
+    method: 'PUT',
+    data: { id, delegateUserId, reason }
+  })
+
+/**
+ * 加签任务
+ * @param id
+ * @param userIds
+ * @param type before/after
+ * @param reason
+ */
+export const createSignTask = (id, userIds, type, reason) =>
+  request({
+    url: '/bpm/task/create-sign',
+    method: 'PUT',
+    data: { id, userIds, type, reason },
+  })
+
+/**
+ * 退回任务
+ * @param id
+ */
+export const returnTask = (id) =>
+  request({
+    url: '/bpm/task/list-by-return',
+    method: 'GET',
+    params: { id },
+  })
+
+/**
+ * 取消任务
+ * @param id
+ * @param reason
+ */
+export const cancelTask = (id, reason) =>
+  request({
+    url: '/bpm/process-instance/cancel-by-start-user',
+    method: 'DELETE',
+    data: { id, reason },
+  })
+
+/**
+ * 获取超时消息
+ * @param params
+ */
+export const getOvertimeTaskList = (params) =>
+  request({
+    url: '/rq/stat/notice',
+    method: 'GET',
+    params,
+  })

+ 20 - 0
api/warn.js

@@ -0,0 +1,20 @@
+import {
+	request
+} from '@/utils/request.js';
+
+// 获取报警列表
+export function getAlarmList(data) {
+	return request({
+		url: '/alarm/list',
+		method: 'GET',
+		data
+	});
+}
+// 报警处置
+export function handleAlarm(data) {
+	return request({
+		url: '/alarm/handle',
+		method: 'POST',
+		data
+	});
+}

+ 405 - 0
components/device-transfer/index.vue

@@ -0,0 +1,405 @@
+<template>
+  <uni-popup
+    ref="popupTransferRef"
+    background-color="#fff"
+    border-radius="10px 10px 0 0"
+    type="bottom"
+  >
+    <view class="popup-transfer-container">
+      <!-- 标题 -->
+      <uni-row class="popup-header align-center justify-center">
+        <uni-col :span="4" class="align-center justify-start">
+          <view class="confirm-btn" @click="handleConfirm">
+            {{ $t("operation.confirm") }}
+          </view>
+        </uni-col>
+        <uni-col :span="18" class="align-center justify-center">
+          {{ $t("operation.select") }}{{ $t("ruiDu.constructionEquipment") }}
+        </uni-col>
+        <uni-col :span="4" class="align-center justify-end">
+          <uni-icons
+            type="close"
+            color="#666"
+            size="24"
+            @click="close"
+          ></uni-icons>
+        </uni-col>
+      </uni-row>
+      <scroll-view scroll-y="true" class="popup-transfer-content">
+        <!-- 可选设备 -->
+        <uni-card
+          :spacing="'0'"
+          :margin="'10px'"
+          :is-shadow="false"
+          :title="$t('ruiDu.optionalEquipment')"
+          :extra="`${unSelectedChecked.length}/${localDataUncheckedOriginal.length}`"
+        >
+          <!-- 搜索框 -->
+          <uni-easyinput
+            v-model="searchValueUnchecked"
+            :placeholder="$t('operation.searchText')"
+          ></uni-easyinput>
+          <!-- 可选设备列表 -->
+          <scroll-view scroll-y="true" class="scroll-card">
+            <template v-if="localDataUnchecked.length > 0">
+              <uni-data-checkbox
+                mode="list"
+                :multiple="true"
+                :wrap="true"
+                v-model="unSelectedChecked"
+                :localdata="localDataUnchecked"
+              ></uni-data-checkbox>
+            </template>
+            <template v-else>
+              <view class="empty-state align-center justify-center">
+                <uni-icons type="none" size="48" color="#ccc"></uni-icons>
+                <text class="empty-text">{{ $t("common.noData") }}</text>
+              </view>
+            </template>
+          </scroll-view>
+        </uni-card>
+        <!-- 操作按钮 -->
+        <uni-row class="transfer-btn-row flex-row align-center justify-center">
+          <uni-col :span="8" class="align-center justify-center">
+            <button
+              class="mini-btn align-center justify-center"
+              size="mini"
+              type="primary"
+              :disabled="localDataUncheckedOriginal.length === 0"
+              @click="handleTransferToSelected(unSelectedChecked)"
+            >
+              <uni-icons type="down" color="#fff" size="20"></uni-icons>
+            </button>
+            <button
+              class="mini-btn align-center justify-center"
+              size="mini"
+              type="primary"
+              :disabled="localDataCheckedOriginal.length === 0"
+              @click="handleTransferToUnSelected(selectedChecked)"
+            >
+              <uni-icons type="up" color="#fff" size="20"></uni-icons>
+            </button>
+          </uni-col>
+        </uni-row>
+        <!-- 已选设备 -->
+        <uni-card
+          :spacing="'0'"
+          :margin="'10px'"
+          :is-shadow="false"
+          :title="$t('ruiDu.selectedEquipment')"
+          :extra="`${selectedChecked.length}/${localDataCheckedOriginal.length}`"
+        >
+          <!-- 搜索框 -->
+          <uni-easyinput
+            v-model="searchValueChecked"
+            :placeholder="$t('operation.searchText')"
+          ></uni-easyinput>
+          <!-- 已选设备列表 -->
+          <scroll-view scroll-y="true" class="scroll-card">
+            <template v-if="localDataChecked.length > 0">
+              <uni-data-checkbox
+                mode="list"
+                :multiple="true"
+                :wrap="true"
+                v-model="selectedChecked"
+                :localdata="localDataChecked"
+              ></uni-data-checkbox>
+            </template>
+            <template v-else>
+              <view class="empty-state align-center justify-center">
+                <uni-icons type="none" size="48" color="#ccc"></uni-icons>
+                <text class="empty-text">{{ $t("common.noData") }}</text>
+              </view>
+            </template>
+          </scroll-view>
+        </uni-card>
+      </scroll-view>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import {
+  ref,
+  reactive,
+  computed,
+  getCurrentInstance,
+  onMounted,
+  nextTick,
+  watch
+} from "vue";
+// -------------------------- 全局变量与国际化 -------------------------------
+const { appContext } = getCurrentInstance();
+const t = appContext.config.globalProperties.$t;
+
+// -------------------------- 父组件通信 -------------------------------
+const emit = defineEmits(["confirm"]);
+const props = defineProps({
+  allList: { type: Array, default: () => [] },
+  selected: { type: Array, default: () => [] },
+});
+
+// -------------------------- 组件内部变量 -------------------------------
+const popupTransferRef = ref(null);
+const selectedChecked = ref([]); // 已选列表的选中项(用于转移)
+const unSelectedChecked = ref([]); // 未选列表的选中项(用于转移)
+const searchValueChecked = ref(""); // 已选搜索框值
+const searchValueUnchecked = ref(""); // 未选搜索框值
+
+// 1. 原始数据(格式化后:过滤无效+去重)
+const localDataOriginal = ref([]);
+
+// 2. 已选/未选「原始列表」(未过滤,初始化时从原始数据拆分,后续转移时增删项)
+const localDataCheckedOriginal = ref([]); // 已选完整列表
+const localDataUncheckedOriginal = ref([]); // 未选完整列表
+
+// 3. 已选/未选「过滤后列表」(基于原始列表+搜索条件,转移时直接操作此列表)
+const localDataChecked = ref([]); // 已选搜索过滤后列表
+const localDataUnchecked = ref([]); // 未选搜索过滤后列表
+
+// -------------------------- 搜索过滤逻辑(基于原始列表,保留搜索状态) -------------------------------
+// 已选列表过滤:实时基于「已选原始列表」+ 搜索值
+const filterCheckedList = computed(() => {
+  const searchVal = searchValueChecked.value.trim().toLowerCase();
+  return localDataCheckedOriginal.value.filter((item) =>
+    item.text.toLowerCase().includes(searchVal)
+  );
+});
+
+// 未选列表过滤:实时基于「未选原始列表」+ 搜索值
+const filterUncheckedList = computed(() => {
+  const searchVal = searchValueUnchecked.value.trim().toLowerCase();
+  return localDataUncheckedOriginal.value.filter((item) =>
+    item.text.toLowerCase().includes(searchVal)
+  );
+});
+
+// 监听搜索值变化,更新过滤后列表(保留搜索状态)
+watch(
+  [searchValueChecked, searchValueUnchecked],
+  () => {
+    localDataChecked.value = [...filterCheckedList.value];
+    localDataUnchecked.value = [...filterUncheckedList.value];
+  },
+  { immediate: true }
+);
+
+// -------------------------- 核心优化:基于当前列表更新(而非重建) -------------------------------
+// 初始化:从原始数据拆分「已选/未选原始列表」(仅初始化时调用)
+const initCheckedUncheckedList = () => {
+  const { allList = [], selected = [] } = props;
+  const selectedSet = new Set(selected.map((val) => String(val)));
+
+  // 格式化原始数据(过滤无效+去重)
+  localDataOriginal.value = allList
+    .filter(
+      (item) => item && item.id != null && item.deviceCode && item.deviceName
+    )
+    .map((item) => ({
+      text: `${item.deviceCode} - ${item.deviceName}`,
+      value: item.id,
+    }))
+    .filter(
+      (item, idx, self) =>
+        self.findIndex((i) => String(i.value) === String(item.value)) === idx
+    );
+
+  // 初始化已选/未选原始列表
+  localDataCheckedOriginal.value = localDataOriginal.value.filter((item) =>
+    selectedSet.has(String(item.value))
+  );
+  localDataUncheckedOriginal.value = localDataOriginal.value.filter(
+    (item) => !selectedSet.has(String(item.value))
+  );
+
+  // 初始化过滤后列表(应用初始搜索值)
+  localDataChecked.value = [...filterCheckedList.value];
+  localDataUnchecked.value = [...filterUncheckedList.value];
+};
+
+// 转移:未选 → 已选(在当前列表基础上更新)
+const handleTransferToSelected = () => {
+  if (!unSelectedChecked.value.length) return;
+
+  // 1. 从「未选过滤列表」中找到被选中的项(要转移的项)
+  const transferItems = localDataUnchecked.value.filter((item) =>
+    unSelectedChecked.value.includes(item.value)
+  );
+  if (!transferItems.length) return;
+
+  // 2. 去重:避免已选列表中已有该项目
+  const uniqueTransferItems = transferItems.filter(
+    (item) =>
+      !localDataCheckedOriginal.value.some(
+        (i) => String(i.value) === String(item.value)
+      )
+  );
+
+  // 3. 更新「已选列表」:添加转移项(原始+过滤后)
+  localDataCheckedOriginal.value.push(...uniqueTransferItems);
+  // 过滤后列表直接添加(保留当前搜索状态)
+  localDataChecked.value.push(...uniqueTransferItems);
+
+  // 4. 更新「未选列表」:移除转移项(原始+过滤后)
+  // 原始列表:过滤掉转移项
+  localDataUncheckedOriginal.value = localDataUncheckedOriginal.value.filter(
+    (item) =>
+      !uniqueTransferItems.some((t) => String(t.value) === String(item.value))
+  );
+  // 过滤后列表:过滤掉转移项(保留搜索结果)
+  localDataUnchecked.value = localDataUnchecked.value.filter(
+    (item) =>
+      !uniqueTransferItems.some((t) => String(t.value) === String(item.value))
+  );
+
+  // 5. 清空未选列表的选中状态
+  unSelectedChecked.value = [];
+};
+
+// 转移:已选 → 未选(在当前列表基础上更新)
+const handleTransferToUnSelected = () => {
+  if (!selectedChecked.value.length) return;
+
+  // 1. 从「已选过滤列表」中找到被选中的项(要转移的项)
+  const transferItems = localDataChecked.value.filter((item) =>
+    selectedChecked.value.includes(item.value)
+  );
+  if (!transferItems.length) return;
+
+  // 2. 去重:避免未选列表中已有该项目
+  const uniqueTransferItems = transferItems.filter(
+    (item) =>
+      !localDataUncheckedOriginal.value.some(
+        (i) => String(i.value) === String(item.value)
+      )
+  );
+
+  // 3. 更新「未选列表」:添加转移项(原始+过滤后)
+  localDataUncheckedOriginal.value.push(...uniqueTransferItems);
+  // 过滤后列表直接添加(保留当前搜索状态)
+  localDataUnchecked.value.push(...uniqueTransferItems);
+
+  // 4. 更新「已选列表」:移除转移项(原始+过滤后)
+  // 原始列表:过滤掉转移项
+  localDataCheckedOriginal.value = localDataCheckedOriginal.value.filter(
+    (item) =>
+      !uniqueTransferItems.some((t) => String(t.value) === String(item.value))
+  );
+  // 过滤后列表:过滤掉转移项(保留搜索结果)
+  localDataChecked.value = localDataChecked.value.filter(
+    (item) =>
+      !uniqueTransferItems.some((t) => String(t.value) === String(item.value))
+  );
+
+  // 5. 清空已选列表的选中状态
+  selectedChecked.value = [];
+};
+
+// -------------------------- 其他方法 -------------------------------
+const open = () => {
+  initCheckedUncheckedList(); // 仅打开时初始化一次
+  popupTransferRef.value.open();
+};
+
+const close = () => {
+  clear();
+  popupTransferRef.value.close();
+};
+
+const clear = () => {
+  selectedChecked.value = [];
+  unSelectedChecked.value = [];
+  searchValueChecked.value = "";
+  searchValueUnchecked.value = "";
+  // 不清空已选/未选列表,仅清空选中和搜索
+};
+
+const handleConfirm = () => {
+  // 最终已选结果:取已选原始列表的value
+  const finalSelected = localDataCheckedOriginal.value.map(
+    (item) => item.value
+  );
+  emit("confirm", [...new Set(finalSelected)]);
+  close();
+};
+
+// -------------------------- 生命周期与监听 -------------------------------
+onMounted(() => {
+  console.log("device-transfer mounted", props);
+});
+
+// 监听父组件selected变化(仅初始化时用,后续转移不依赖)
+watch(
+  () => props.selected,
+  () => {
+    if (popupTransferRef.value?.isOpen) {
+      initCheckedUncheckedList();
+    }
+  },
+  { deep: true }
+);
+// --------------------------组件暴露的方法--------------------------------
+defineExpose({
+  open,
+  close,
+});
+
+</script>
+
+<style lang="scss" scoped>
+.popup-transfer-container {
+  height: calc(100vh - 160rpx);
+}
+.popup-header {
+  padding: 10px 10px 0 10px;
+  box-sizing: border-box;
+  height: 80rpx;
+}
+.confirm-btn {
+  font-weight: bold;
+  color: #004098;
+}
+.popup-transfer-content {
+  box-sizing: border-box;
+  height: calc(100% - 80rpx - 20rpx);
+}
+:deep(.uni-card) {
+  box-sizing: border-box;
+  padding: 0;
+  min-height: 570rpx;
+  height: calc(50% - 30rpx - 40rpx);
+}
+:deep(.uni-card__header) {
+  background: #f5f7fa;
+}
+:deep(.uni-card__content) {
+  box-sizing: border-box;
+  height: calc(100% - 160rpx);
+}
+
+/* 空状态样式 */
+.empty-state {
+  color: #999;
+  padding: 40rpx 0;
+}
+
+.empty-text {
+  margin-top: 16rpx;
+  font-size: 28rpx;
+}
+
+.scroll-card {
+  box-sizing: border-box;
+  height: 100%;
+}
+.transfer-btn-row {
+  height: 60rpx;
+}
+.mini-btn {
+  display: flex;
+  width: 100rpx;
+  height: 60rpx;
+  padding: 0;
+  line-height: 1.5;
+}
+</style>

+ 223 - 0
components/device/brandChoose.vue

@@ -0,0 +1,223 @@
+<template>
+	<uni-popup class="materials-popup" ref="materialChoosePopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="z-page-popup" ref="paging" :default-page-size="20" style="top: 200px;" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="6" class="head-cancel align-center justify-start" @click="oncancel">
+							{{$t('operation.cancel')}}
+						</uni-col>
+						<uni-col :span="12" class="head-title justify-center">
+							{{ $t('ledger.form.brand') }}
+						</uni-col>
+					</uni-row>
+					<uni-row class="search-row flex-row justify-between">
+						<uni-col :span="24">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('ledger.brand.labelHint')}`"
+								@confirm="searchList">
+							</uni-easyinput>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="5" class="table-header flex-row align-center justify-between">
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.brand.id') }}
+						</uni-col>
+						<uni-col :span="8" class="flex-row justify-center">
+							{{ $t('ledger.brand.label')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.brand.status')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.form.remark')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.supplier.time')}}
+						</uni-col>
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<uni-row
+          :gutter="5"
+          class="item-row align-center"
+					:class="{'choosed': selectedItemId === item.id}"
+          v-for="(item,index) in dataList"
+					:key="index"
+          @click="onChoose(item)"
+        >
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ item.id }}
+					</uni-col>
+					<uni-col :span="8" class="item-col flex-row justify-center">
+						{{ item.label }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ getStatusName(item.status) }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ item.remark }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ item.createTime ? formatDate(item.createTime) : '' }}
+					</uni-col>
+				</uni-row>
+			</view>
+		</z-paging>
+	</uni-popup>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		onMounted,
+	} from 'vue'
+	import { useI18n } from 'vue-i18n'
+  import { getDataDictList, getSupplierList } from "@/api";
+  import { useDataDictStore } from "@/store/modules/dataDict";
+	// 接收父组件传递的参数
+	const props = defineProps({
+    selectedItemId: {
+      type: Number,
+      default: 0
+    }
+	})
+
+  const getStatusName = (status) => {
+    if (status === 0) {
+      return t('ledger.brand.status0')
+    } else {
+      return t('ledger.brand.status1')
+    }
+  }
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+  const filterList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		getDataDictList({
+			pageNo,
+			pageSize,
+      label: searchValue.value,
+      dictType: 'pms_device_brand',
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			res.data.list.forEach(item => {
+				item.chooseKey = item.id
+			})
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+    paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+
+
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.chooseKey)
+	})
+	const onChoose = (item) => {
+		emit('confirm', item)
+    close()
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+
+	const { t } = useI18n({ useScope: 'global' })
+
+	onMounted (() => {})
+
+	const materialChoosePopRef = ref(null)
+
+	// 打开弹窗
+	const open = () => {
+		materialChoosePopRef.value.open()
+	}
+
+	// 关闭弹窗
+	const close = () => {
+		materialChoosePopRef.value.close()
+	}
+
+  const emit = defineEmits(['confirm'])
+	// 提供外部方法
+	const expose = { open }
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+
+	.z-page-popup {
+		padding: 20px 15px;
+		box-sizing: border-box;
+		background: #FFF;
+		border-radius: 10px 10px 0 0;
+	}
+
+	.page-top {
+		background: #fff;
+		padding: 0;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+
+	.table-header {
+		background: #FFF;
+		border-bottom: 1px solid #CACCCF;
+	}
+
+	.page-table {
+		padding: 0;
+	}
+
+	.item-col {
+		font-size: 11px;
+	}
+</style>

+ 221 - 0
components/device/modelChoose.vue

@@ -0,0 +1,221 @@
+<template>
+	<uni-popup class="materials-popup" ref="materialChoosePopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="z-page-popup" ref="paging" :default-page-size="20" style="top: 200px;" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="6" class="head-cancel align-center justify-start" @click="oncancel">
+							{{$t('operation.cancel')}}
+						</uni-col>
+						<uni-col :span="12" class="head-title justify-center">
+							{{ $t('ledger.form.model') }}
+						</uni-col>
+					</uni-row>
+					<uni-row class="search-row flex-row justify-between">
+						<uni-col :span="24">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('ledger.brand.labelHint')}`"
+								@confirm="searchList">
+							</uni-easyinput>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="5" class="table-header flex-row align-center justify-between">
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('ledger.model.name') }}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('ledger.model.standard')}}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('ledger.brand.status')}}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('ledger.supplier.time')}}
+						</uni-col>
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<uni-row
+          :gutter="5"
+          class="item-row align-center"
+					:class="{'choosed': selectedItemId === item.id}"
+          v-for="(item,index) in dataList"
+					:key="index"
+          @click="onChoose(item)"
+        >
+					<uni-col :span="6" class="item-col flex-row justify-center">
+						{{ item.name }}
+					</uni-col>
+					<uni-col :span="6" class="item-col flex-row justify-center">
+						{{ item.standard }}
+					</uni-col>
+					<uni-col :span="6" class="item-col flex-row justify-center">
+						{{ getStatusName(item.status) }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ item.createTime ? formatDate(item.createTime) : '' }}
+					</uni-col>
+				</uni-row>
+			</view>
+		</z-paging>
+	</uni-popup>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		onMounted,
+	} from 'vue'
+	import { useI18n } from 'vue-i18n'
+  import { getDataDictList, getSupplierList } from "@/api";
+  import { useDataDictStore } from "@/store/modules/dataDict";
+  import { getDeviceModelList } from "@/api/device";
+	// 接收父组件传递的参数
+	const props = defineProps({
+    selectedItemId: {
+      type: Number,
+      default: 0
+    },
+    brandId: {
+      type: Number,
+    }
+	})
+
+  const getStatusName = (status) => {
+    if (status === 0) {
+      return t('ledger.brand.status0')
+    } else {
+      return t('ledger.brand.status1')
+    }
+  }
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+  const filterList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+    getDeviceModelList({
+			pageNo,
+			pageSize,
+      brand: props.brandId,
+      name: searchValue.value,
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			res.data.list.forEach(item => {
+				item.chooseKey = item.id
+			})
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+    paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+
+
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.chooseKey)
+	})
+	const onChoose = (item) => {
+		emit('confirm', item)
+    close()
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+
+	const { t } = useI18n({ useScope: 'global' })
+
+	onMounted (() => {})
+
+	const materialChoosePopRef = ref(null)
+
+	// 打开弹窗
+	const open = () => {
+		materialChoosePopRef.value.open()
+	}
+
+	// 关闭弹窗
+	const close = () => {
+		materialChoosePopRef.value.close()
+	}
+
+  const emit = defineEmits(['confirm'])
+	// 提供外部方法
+	const expose = { open }
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+
+	.z-page-popup {
+		padding: 20px 15px;
+		box-sizing: border-box;
+		background: #FFF;
+		border-radius: 10px 10px 0 0;
+	}
+
+	.page-top {
+		background: #fff;
+		padding: 0;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+
+	.table-header {
+		background: #FFF;
+		border-bottom: 1px solid #CACCCF;
+	}
+
+	.page-table {
+		padding: 0;
+	}
+
+	.item-col {
+		font-size: 11px;
+	}
+</style>

+ 223 - 0
components/device/multiple.vue

@@ -0,0 +1,223 @@
+<template>
+  <uni-popup
+    class="device-popup"
+    ref="deviceChoosePopRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0"
+  >
+    <z-paging
+      class="page-nopadding z-page-popup"
+      ref="paging"
+      style="top: 100px"
+      :default-page-size="15"
+      v-model="dataList"
+      @query="queryList"
+    >
+      <!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+      <!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+      <template #top>
+        <view class="page-top">
+          <uni-row class="head-row">
+            <uni-col :span="22" class="head-title justify-center">
+              {{ $t("device.selectDevice") }}
+            </uni-col>
+            <uni-col
+              :span="2"
+              class="head-cancel align-center justify-end"
+              @click="oncancel"
+            >
+              <uni-icons type="closeempty" color="#333"></uni-icons>
+            </uni-col>
+          </uni-row>
+          <view
+            class="choice-row flex-row flex-wrap align-center justify-start"
+          >
+            <view
+              class="choice-item align-center justify-center"
+              v-for="item in chooseList"
+              @click="onChooseDel(item)"
+            >
+              {{ item.deviceName }}
+              <uni-icons
+                class="choice-close-icon"
+                type="closeempty"
+                color="#fff"
+              ></uni-icons>
+            </view>
+          </view>
+          <uni-row class="search-row flex-rowc justify-between">
+            <uni-col :span="18">
+              <uni-easyinput
+                v-model="searchValue"
+                :styles="inputStyles"
+                :placeholderStyle="placeholderStyle"
+                :placeholder="`${$t('operation.PleaseInput')}`"
+              >
+              </uni-easyinput>
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-end">
+              <button
+                class="mini-btn"
+                type="primary"
+                size="mini"
+                @click="searchList"
+              >
+                {{ $t("operation.search") }}
+              </button>
+            </uni-col>
+          </uni-row>
+          <uni-row
+            :gutter="2"
+            class="table-header flex-row align-center justify-between"
+          >
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.deviceName") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.assetCode") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.department") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("operation.createTime") }}
+            </uni-col>
+          </uni-row>
+        </view>
+      </template>
+      <view class="page-table">
+        <view class="table-content">
+          <uni-row
+            :gutter="2"
+            class="item-row align-center"
+            :class="{
+              choosed: chooseIds.includes(item.id),
+              'no-boms': item.hasSetMaintenanceBom === false,
+            }"
+            v-for="(item, index) in dataList"
+            :key="index"
+            @click="onChoose(item)"
+          >
+            <uni-col :span="6" class="item-col flex-row justify-start">
+              {{ item.deviceName }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-center">
+              {{ item.deviceCode }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-center">
+              {{ item.deptName }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-end">
+              {{ item.createTime ? formatDate(item.createTime) : "" }}
+            </uni-col>
+          </uni-row>
+        </view>
+      </view>
+      <template #bottom>
+        <button style="border-radius: 0" type="primary" @click="onSubmit">
+          {{ $t("operation.submit") }}
+        </button>
+      </template>
+    </z-paging>
+  </uni-popup>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import dayjs from "dayjs";
+import { devicePage } from "@/api/device";
+
+const searchValue = ref("");
+const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
+const inputStyles = reactive({
+  backgroundColor: "#FFFFFF",
+  color: "#797979",
+});
+const paging = ref(null);
+// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+const dataList = ref([]);
+
+// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+const queryList = (pageNo, pageSize) => {
+  // 此处请求仅为演示,请替换为自己项目中的请求
+  devicePage({
+    pageNo,
+    pageSize,
+    commonParam: searchValue.value,
+  })
+    .then((res) => {
+      // 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+      paging.value.complete(res.data.list);
+    })
+    .catch((res) => {
+      // 如果请求失败写paging.value.complete(false);
+      // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+      // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+      paging.value.complete(false);
+    });
+};
+const searchList = () => {
+  paging.value.reload();
+};
+const formatDate = (time) => {
+  return dayjs(time).format("YYYY-MM-DD");
+};
+// ----------------------------------------------
+const deviceChoosePopRef = ref(null);
+const open = () => {
+  deviceChoosePopRef.value.open();
+};
+const close = () => {
+  deviceChoosePopRef.value.close();
+};
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+const emit = defineEmits(["multiple-devide-submit"]);
+// ---------------------------------------------
+const chooseList = ref([]);
+const chooseIds = computed(() => {
+  // 返回chooseList的id组成新数组
+  return chooseList.value.map((c) => c.id);
+});
+const onChoose = (item) => {
+  // 判断当前点击的设备是否存在于chooseList中,
+  // 若存在,则从chooseList中删除,否则添加到chooseList中
+  // 若不存在则添加
+  const index = chooseList.value.findIndex((v) => v.id === item.id);
+  if (index > -1) {
+    // chooseList.value.splice(index, 1)
+  } else {
+    chooseList.value.push(item);
+  }
+};
+const onChooseDel = (item) => {
+  // 判断当前点击的设备是否存在于chooseList中,
+  // 若存在,则从chooseList中删除,否则添加到chooseList中
+  const index = chooseList.value.findIndex((v) => v.id === item.id);
+  if (index > -1) {
+    chooseList.value.splice(index, 1);
+  }
+};
+const oncancel = () => {
+  chooseList.value = [];
+  close();
+};
+const onSubmit = () => {
+  emit("multiple-devide-submit", chooseList.value);
+  chooseList.value = [];
+  close();
+  // uni.$emit('multiple-devide-submit', chooseList.value)
+  // uni.navigateBack()
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/choose-device.scss";
+.no-boms {
+  background-color: #f2f6fc;
+}
+</style>

+ 201 - 0
components/device/single.vue

@@ -0,0 +1,201 @@
+<template>
+  <uni-popup
+    class="device-popup"
+    ref="deviceChoosePopRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0"
+  >
+    <z-paging
+      class="page-nopadding z-page-popup"
+      ref="paging"
+      style="top: 150px"
+      :default-page-size="15"
+      v-model="dataList"
+      @query="queryList"
+    >
+      <!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+      <!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+      <template #top>
+        <view class="page-top">
+          <uni-row class="head-row">
+            <uni-col :span="22" class="head-title justify-center">
+              {{ $t("device.selectDevice") }}
+            </uni-col>
+            <uni-col
+              :span="2"
+              class="head-cancel align-center justify-end"
+              @click="oncancel"
+            >
+              <uni-icons type="closeempty" color="#333"></uni-icons>
+            </uni-col>
+          </uni-row>
+          <uni-row class="search-row flex-rowc justify-between">
+            <uni-col :span="18">
+              <uni-easyinput
+                v-model="searchValue"
+                :styles="inputStyles"
+                :placeholderStyle="placeholderStyle"
+                :placeholder="`${$t('operation.PleaseInput')}`"
+              >
+              </uni-easyinput>
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-end">
+              <button
+                class="mini-btn"
+                type="primary"
+                size="mini"
+                @click="searchList"
+              >
+                {{ $t("operation.search") }}
+              </button>
+            </uni-col>
+          </uni-row>
+          <uni-row
+            :gutter="2"
+            class="table-header flex-row align-center justify-between"
+          >
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.deviceName") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.assetCode") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("device.department") }}
+            </uni-col>
+            <uni-col :span="6" class="flex-row justify-center">
+              {{ $t("operation.createTime") }}
+            </uni-col>
+          </uni-row>
+        </view>
+      </template>
+      <view class="page-table">
+        <view class="table-content">
+          <uni-row
+            :gutter="2"
+            class="item-row align-center"
+            :class="{
+              choosed: chooseIds.includes(item.id),
+              'no-boms': item.hasSetMaintenanceBom === false,
+            }"
+            v-for="(item, index) in dataList"
+            :key="index"
+            @click="onChoose(item)"
+          >
+            <uni-col :span="6" class="item-col flex-row justify-start">
+              {{ item.deviceName }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-center">
+              {{ item.deviceCode }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-center">
+              {{ item.deptName }}
+            </uni-col>
+            <uni-col :span="6" class="item-col flex-row justify-end">
+              {{ item.createTime ? formatDate(item.createTime) : "" }}
+            </uni-col>
+          </uni-row>
+        </view>
+      </view>
+    </z-paging>
+  </uni-popup>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import dayjs from "dayjs";
+import { devicePage } from "@/api/device";
+
+const searchValue = ref("");
+const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
+const inputStyles = reactive({
+  backgroundColor: "#FFFFFF",
+  color: "#797979",
+});
+const paging = ref(null);
+// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+const dataList = ref([]);
+
+// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+const queryList = (pageNo, pageSize) => {
+  // 此处请求仅为演示,请替换为自己项目中的请求
+  devicePage({
+    pageNo,
+    pageSize,
+    commonParam: searchValue.value,
+  })
+    .then((res) => {
+      // 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+      paging.value.complete(res.data.list);
+    })
+    .catch((res) => {
+      // 如果请求失败写paging.value.complete(false);
+      // 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+      // 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+      paging.value.complete(false);
+    });
+};
+const searchList = () => {
+  paging.value.reload();
+};
+const formatDate = (time) => {
+  return dayjs(time).format("YYYY-MM-DD");
+};
+// ----------------------------------------------
+const deviceChoosePopRef = ref(null);
+const open = () => {
+  deviceChoosePopRef.value.open();
+};
+const close = () => {
+  deviceChoosePopRef.value.close();
+};
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+const emit = defineEmits(["devide-submit"]);
+// ---------------------------------------------
+const chooseList = ref([]);
+const chooseIds = computed(() => {
+  // 返回chooseList的id组成新数组
+  return chooseList.value.map((c) => c.id);
+});
+const onChoose = (item) => {
+  // 判断当前点击的设备是否存在于chooseList中,
+  // 若存在,则从chooseList中删除,否则添加到chooseList中
+  // 若不存在则添加
+  const index = chooseList.value.findIndex((v) => v.id === item.id);
+  if (index > -1) {
+    // chooseList.value.splice(index, 1)
+  } else {
+    chooseList.value = [item];
+    onSubmit();
+  }
+};
+const onChooseDel = (item) => {
+  // 判断当前点击的设备是否存在于chooseList中,
+  // 若存在,则从chooseList中删除,否则添加到chooseList中
+  const index = chooseList.value.findIndex((v) => v.id === item.id);
+  if (index > -1) {
+    chooseList.value.splice(index, 1);
+  }
+};
+const oncancel = () => {
+  chooseList.value = [];
+  close();
+};
+const onSubmit = () => {
+  emit("devide-submit", chooseList.value[0]);
+  console.log("devide-submit", chooseList.value[0]);
+  oncancel();
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/choose-device.scss";
+.no-boms {
+  background-color: #f2f6fc;
+}
+</style>

+ 47 - 0
components/global/message-popup.vue

@@ -0,0 +1,47 @@
+<template>
+  <view>
+
+    <!-- 提示信息弹窗 -->
+    <uni-popup ref="messageRef" type="message">
+      <uni-popup-message
+        :type="msgType"
+        :message="messageText"
+        :duration="duration"
+      ></uni-popup-message>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+
+
+const props = defineProps({
+  msgType: {
+    type: String,
+    default: "success", // success, error, warning, info
+  },
+  messageText: {
+    type: String,
+    default: "",
+  },
+  duration: {
+    type: Number,
+    default: 3000,
+  },
+});
+
+const messageRef = ref(null);
+
+const openMessage = () => {
+  messageRef.value.open();
+};
+
+defineExpose({
+  openMessage,
+});
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 115 - 0
components/ignore/reason.vue

@@ -0,0 +1,115 @@
+<template>
+  <!-- 忽略输入弹窗 -->
+  <uni-popup
+    class="ignore-unipopup"
+    ref="ignorePopupRef"
+    type="bottom"
+    :animation="false"
+    :mask-click="false"
+    borderRadius="10px 10px 10px 10px"
+  >
+    <view class="ignore-popup-box">
+      <view>
+        <uni-row class="flex-row align-center">
+          <uni-col class="ignore-popup-title" :span="22">
+            {{ $t("operation.ignoreReason") }}
+          </uni-col>
+          <uni-col :span="2">
+            <uni-icons
+              type="closeempty"
+              size="20"
+              color="#999"
+              @click="closeIgnorePopup"
+            ></uni-icons>
+          </uni-col>
+        </uni-row>
+      </view>
+      <view class="ignore-popup-content">
+        <uni-easyinput
+          type="textarea"
+          class="ignore-popup-textarea"
+          :placeholder="
+            $t('operation.PleaseFillIn') + $t('operation.ignoreReason')
+          "
+          v-model="ignoreReason"
+        ></uni-easyinput>
+      </view>
+      <view class="ignore-popup-btn">
+        <button type="primary" @click="confirmIgnore">
+          {{ $t("operation.submit") }}
+        </button>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import dayjs from "dayjs";
+import { computed, ref, reactive, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+
+const { t, locale } = useI18n({
+  useScope: "global",
+});
+
+// 接收父组件传递的参数
+const props = defineProps({});
+
+// 忽略弹窗
+const ignorePopupRef = ref(null);
+
+// 忽略理由
+const ignoreReason = ref("");
+const openIgnore = () => {
+  ignorePopupRef.value.open();
+};
+const closeIgnorePopup = () => {
+  ignorePopupRef.value.close();
+  ignoreReason.value = "";
+};
+// 忽略弹窗确认
+const confirmIgnore = (e) => {
+  console.log("🚀 ~ confirmIgnore ~ e:", e);
+  if (!ignoreReason.value) {
+    uni.showToast({
+      title: t("operation.PleaseFillIn") + t("operation.ignoreReason"),
+      icon: "none",
+    });
+    return;
+  }
+  emit("ignore-submit", ignoreReason.value);
+};
+
+onMounted(() => {});
+
+const emit = defineEmits(["ignore-submit"]);
+// 提供外部方法
+const expose = {
+  openIgnore,
+  close: closeIgnorePopup,
+};
+defineExpose(expose);
+</script>
+
+<style lang="scss" scoped>
+.btn-ignore {
+  margin-left: 10px;
+  background-color: #e6a23c !important;
+  border-color: #e6a23c !important;
+  color: #fff;
+}
+.ignore-popup-box {
+  padding: 20px;
+  background: #fff;
+  border-radius: 10px 10px 0 0;
+}
+.ignore-popup-title {
+  text-align: center;
+}
+.ignore-popup-close {
+  text-align: right;
+}
+.ignore-popup-content {
+  margin: 15px;
+}
+</style>

+ 224 - 0
components/language-popup.vue

@@ -0,0 +1,224 @@
+<template>
+  <uni-popup
+    class="language-popup"
+    ref="languageRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0"
+  >
+    <view class="popup-content">
+      <view class="popup-header">
+        <text style="width: 100%">{{ $t("index.languageChange") }}</text>
+        <text class="close-btn" @click="close">×</text>
+      </view>
+      <view class="language-options">
+        <view
+          v-for="(item, index) in locales"
+          :key="item.code"
+          class="language-option"
+          :class="{ selected: getDisplayLanguageCode === item.code }"
+          @click="handleLanguageSelection(item.code)"
+        >
+          {{ item.text }}
+        </view>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import { computed, ref, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+
+// 定义语言映射关系
+const LANGUAGE_MAPPING = {
+  'zh-Hans': 'zh-Hans',
+  'zh-CN': 'zh-Hans',
+  'en': 'en',
+  'ru': 'ru',
+  'auto': 'auto'
+}
+
+const emit = defineEmits(['close', 'language-change'])
+
+const { t, locale } = useI18n({
+  useScope: 'global'
+})
+
+// 语言列表
+const locales = [
+  { text: t('locale.auto'), code: 'auto' },
+  { text: t('locale.en'), code: 'en' },
+  { text: t('locale.zh-hans'), code: 'zh-Hans' },
+  { text: t('locale.ru'), code: 'ru' }
+]
+
+// 当前选择的语言(存储在本地的设置)
+const selectedLanguage = ref(uni.getStorageSync("language") || "auto");
+// 获取系统语言并映射到支持的语言
+const getSystemLanguage = () => {
+  try {
+    const systemLang = uni.getSystemInfoSync().language;
+    console.log("🚀 ~ getSystemLanguage ~ systemLang:", systemLang)
+    
+    // 检查是否在映射表中
+    if (LANGUAGE_MAPPING[systemLang]) {
+      return LANGUAGE_MAPPING[systemLang];
+    }
+    
+    // 检查语言前缀(例如 "en-US" -> "en")
+    const langPrefix = systemLang.split('-')[0];
+    if (SUPPORTED_LANGUAGES.includes(langPrefix)) {
+      return langPrefix;
+    }
+    
+    // 默认返回中文
+    return 'zh-Hans';
+  } catch (error) {
+    console.error('获取系统语言失败:', error);
+    return 'zh-Hans';
+  }
+}
+// 计算属性:获取实际使用的语言代码
+const activeLanguage = computed(() => {
+  if (selectedLanguage.value === "auto") {
+    return getSystemLanguage();
+  }
+  return LANGUAGE_MAPPING[selectedLanguage.value] || selectedLanguage.value;
+});
+
+// 计算属性:用于显示的语言代码(处理auto选项)
+const getDisplayLanguageCode = computed(() => {
+  return selectedLanguage.value === "auto" ? "auto" : activeLanguage.value;
+});
+
+const languageRef = ref(null);
+
+onMounted(() => {
+  console.log("language-popup mounted", activeLanguage.value);
+  console.log("language-popup mounted", uni.getSystemInfoSync().language);
+  // 初始化时设置语言
+  setAppLanguage(activeLanguage.value);
+});
+
+// 设置应用语言
+const setAppLanguage = (lang) => {
+  try {
+    locale.value = lang;
+    uni.setLocale(lang);
+    emit("language-change", lang);
+  } catch (error) {
+    console.error("Failed to set locale:", error);
+  }
+};
+
+// 打开弹窗
+const open = () => {
+  languageRef.value.open();
+};
+
+// 关闭弹窗
+const close = () => {
+  languageRef.value.close();
+};
+
+// 处理语言选择
+const handleLanguageSelection = (lang) => {
+  console.log("🚀 ~ handleLanguageSelection ~ lang:", lang)
+  console.log("🚀 ~ handleLanguageSelection ~ selectedLanguage.value:", selectedLanguage.value)
+  if (lang === selectedLanguage.value) {
+    console.log(`🚀 ~ handleLanguageSelection ~ uni.getStorageSync("language"):`, uni.getStorageSync("language"))
+    if (!uni.getStorageSync("language") ) {
+      uni.setStorageSync("language", lang);
+    }
+    close();
+    return;
+  }
+
+  uni.showModal({
+    content: t("index.language-change-confirm"),
+    cancelText: t("operation.cancel"),
+    confirmText: t("operation.confirm"),
+    success: (res) => {
+      if (res.confirm) {
+        // 更新选择的语言
+        selectedLanguage.value = lang;
+        // 保存到本地存储
+        uni.setStorageSync("language", lang);
+        // 设置应用语言
+        setAppLanguage(activeLanguage.value);
+        close();
+      }
+    },
+  });
+};
+
+// 监听语言变化
+watch(selectedLanguage, (newVal) => {
+  console.log("Language changed to:", newVal);
+});
+
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+</script>
+
+<style lang="scss" scoped>
+.language-popup {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 999;
+}
+
+.popup-content {
+  background-color: #fff;
+  width: 100%;
+  // max-width: 300px;
+  border-radius: 10px 10px 0px 0px;
+  overflow: hidden;
+}
+
+.popup-header {
+  padding: 15px;
+  background-color: #007aff;
+  color: #fff;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  text-align: center;
+}
+
+.close-btn {
+  font-size: 20px;
+  cursor: pointer;
+}
+
+.language-options {
+  padding: 15px;
+}
+
+.language-option {
+  padding: 10px;
+  border-bottom: 1px solid #eee;
+  cursor: pointer;
+  text-align: center;
+}
+
+.language-option:last-child {
+  border-bottom: none;
+}
+
+.language-option.selected {
+  color: #007aff;
+  font-weight: bold;
+}
+</style>

+ 208 - 0
components/local-search.vue

@@ -0,0 +1,208 @@
+<template>
+  <uni-popup
+    class="local-search-popup"
+    ref="localSearchPopRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0 "
+    background-color="#fff"
+  >
+  <view class="popup-content">
+
+    <uni-row class="head-row align-center">
+      <uni-col
+        :span="6"
+        class="head-cancel align-center justify-start"
+        @click="oncancel"
+      >
+        {{ $t("operation.cancel") }}
+      </uni-col>
+      <uni-col :span="12" class="head-title justify-center">
+        {{ title }}
+      </uni-col>
+      <uni-col :span="6" class="head-add align-center justify-end"> </uni-col>
+    </uni-row>
+    <uni-row class="search-row flex-row justify-between">
+      <uni-col :span="24">
+        <uni-easyinput
+          v-model="searchValue"
+          :styles="inputStyles"
+          :placeholderStyle="placeholderStyle"
+          :placeholder="`${$t('operation.PleaseInput')}`"
+          :clearable="false"
+          @input="searchList"
+        >
+        </uni-easyinput>
+      </uni-col>
+    </uni-row>
+    <scroll-view scroll-y="true" class="page-table">
+      <uni-row
+        class="item-row align-center"
+        :class="{ choosed: chooseIds.includes(item.value) }"
+        v-for="(item, index) in dataList"
+        :key="index"
+        @click="onChoose(item)"
+      >
+        <uni-col :span="24" class="flex-row align-center">
+          <text>{{ item.text }}</text>
+        </uni-col>
+      </uni-row>
+    </scroll-view>
+  </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import dayjs from "dayjs";
+import { computed, ref, reactive, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+
+const { t, locale } = useI18n({
+  useScope: "global",
+});
+const chooseIds = ref([]);
+
+const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+// 接收父组件传递的参数
+const props = defineProps({});
+
+const oncancel = () => {
+  // 清空搜索值
+  searchValue.value = "";
+  // 关闭弹窗
+  close();
+};
+
+onMounted(() => {});
+
+// 本地搜索弹窗
+const localSearchPopRef = ref(null);
+// 搜索值
+const searchValue = ref("");
+// 弹窗标题
+const title = ref("");
+// 展示的列表数据的参数名
+const propKey = ref("");
+// 搜索列表数据
+const dataList = ref([]);
+// 原始数据列表
+const dataListOrigin = ref([]);
+
+// 搜索列表
+const searchList = (event) => {
+  console.log("🚀 ~ searchList ~ event:", event)
+  console.log("🚀 ~ searchList ~ searchValue.value:", searchValue.value)
+  if (event) {
+    dataList.value = dataList.value.filter((item) => {
+      return item.text.includes(event);
+    });
+  } else {
+    dataList.value = dataListOrigin.value;
+  }
+};
+// 选择列表项
+const onChoose = (item) => {
+  // 选择列表项时,判断是否已选择,已选择则取消选择,否则添加选择
+  if (chooseIds.value.includes(item.value)) {
+    // 已选择则取消选择
+    chooseIds.value.splice(chooseIds.value.indexOf(item.value), 1);
+  } else {
+    // 未选择则添加选择
+    chooseIds.value = [item.value];
+  }
+  emit("choosed", propKey.value, item);
+  close();
+};
+
+// 打开弹窗
+const open = (props) => {
+  if (props.propKey !== propKey.value) {
+    // 清空已选择的值
+    chooseIds.value = [];
+  }
+  if (props.choosed) {
+    // 已选择的值添加到已选择列表
+    chooseIds.value = [props.choosed];
+  }
+  propKey.value = props.propKey;
+  title.value = props.title;
+  dataListOrigin.value = props.list;
+  dataList.value = props.list;
+  localSearchPopRef.value.open();
+};
+
+// 关闭弹窗
+const close = () => {
+  // 清空搜索值
+  searchValue.value = "";
+  localSearchPopRef.value.close();
+};
+
+const emit = defineEmits(["choosed"]);
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/choose-device.scss";
+
+.local-search-popup {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  border: 1px solid #cacccf;
+  background-color: #fff;
+  padding: 20px;
+}
+
+.popup-content {
+  position: relative;
+  width: 100%;
+  height: calc(100% - 280px);
+  max-height: 500px;
+}
+
+:deep(.uni-popup__wrapper) {
+  padding: 20px;
+  box-sizing: border-box;
+  position: relative;
+}
+
+.head-row {
+  margin-bottom: 10px;
+  font-size: 16px;
+  line-height: 21px;
+  color: #a3a3a3;
+  height: 40px;
+  border-bottom: 1px dashed #cacccf;
+}
+
+.head-title {
+  color: #333333;
+  font-weight: bold;
+}
+
+.head-add {
+  color: #004098;
+}
+
+.search-row {
+  margin-bottom: 10px;
+}
+.page-table {
+  width: 100%;
+  position: relative;
+  padding: 0;
+  height: 500px;
+}
+.choosed {
+  color: #004098;
+}
+</style>

+ 495 - 0
components/maintenance/delay.vue

@@ -0,0 +1,495 @@
+<template>
+  <uni-popup
+    class="delay-popup"
+    ref="delayPopRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0 "
+    background-color="#FFF"
+  >
+    <view class="popup-container">
+      <view class="delay-head">
+        <uni-row class="pop-title flex-row align-center justify-center">
+          <uni-col :span="3" class="align-center justify-end">
+            {{ $t("maintenanceWorkOrder.equipment") }}
+          </uni-col>
+          <uni-col class="title align-center justify-center">
+            {{ maintenance.deviceCode }}-{{ maintenance.name }}
+          </uni-col>
+          <uni-col :span="6" class="align-center justify-start">
+            {{ $t("maintenanceWorkOrder.maintenanceItemConfiguration") }}
+          </uni-col>
+        </uni-row>
+        <uni-icons
+          type="closeempty"
+          color="#666"
+          class="pop-close"
+          @click="oncancel"
+        ></uni-icons>
+      </view>
+      <scroll-view scroll-y="true" class="delay-sections">
+        <uni-forms
+          ref="delayFormRef"
+          labelWidth="140px"
+          :rules="delayRules"
+          :modelValue="maintenanceInfo"
+        >
+          <!-- 基础保养记录 -->
+          <uni-section
+            :title="$t('maintenanceWorkOrder.basicMaintenanceRecords')"
+            type="line"
+          >
+            <!-- 运行里程  保养规则-里程(0启用 1停用)-->
+            <view v-if="maintenanceInfo.mileageRule == 0">
+              <!-- 上次保养里程数(KM) -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.lastMaintenanceMileage')"
+                name="lastRunningKilometers"
+                :required="false"
+              >
+                <uni-easyinput
+                  style="text-align: right"
+                  :inputBorder="false"
+                  :clearable="false"
+                  :disabled="true"
+                  :styles="{ disableColor: '#F3F5F9' }"
+                  type="digit"
+                  v-model="maintenanceInfo.lastRunningKilometers"
+                  placeholder=""
+                />
+              </uni-forms-item>
+              <!-- 推迟公里数((KM) -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.delayedKilometers')"
+                name="delayKilometers"
+                :required="false"
+              >
+                <uni-easyinput
+                  style="text-align: right"
+                  :inputBorder="false"
+                  :clearable="true"
+                  :disabled="false"
+                  :styles="{ disableColor: '#F3F5F9' }"
+                  type="digit"
+                  v-model="maintenanceInfo.delayKilometers"
+                  placeholder=""
+                />
+              </uni-forms-item>
+            </view>
+            <!-- 运行时间 保养规则-运行时间(0启用 1停用)-->
+            <view v-if="maintenanceInfo.runningTimeRule == 0">
+              <!-- 上次保养运行时间(H) -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.lastMaintenanceRunningTime')"
+                name="lastRunningTime"
+                :required="false"
+              >
+                <uni-easyinput
+                  style="text-align: right"
+                  :inputBorder="false"
+                  :clearable="false"
+                  :disabled="true"
+                  :styles="{ disableColor: '#F3F5F9' }"
+                  type="digit"
+                  v-model="maintenanceInfo.lastRunningTime"
+                  placeholder=""
+                />
+              </uni-forms-item>
+              <!-- 推迟时长(H) -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.delayedDuration')"
+                name="delayDuration"
+                :required="false"
+              >
+                <uni-easyinput
+                  style="text-align: right"
+                  :inputBorder="false"
+                  :clearable="true"
+                  :disabled="false"
+                  :styles="{ disableColor: '#F3F5F9' }"
+                  type="digit"
+                  v-model="maintenanceInfo.delayDuration"
+                  placeholder=""
+                />
+              </uni-forms-item>
+            </view>
+            <!-- 自然日 保养规则-自然日期(0启用 1停用)-->
+            <view v-if="maintenanceInfo.naturalDateRule == 0">
+              <!-- 上次保养自然日期 -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.lastMaintenanceNaturalDate')"
+                name="lastNaturalDate"
+                :required="false"
+              >
+                <uni-datetime-picker
+                  type="date"
+                  :border="false"
+                  :placeholder="' '"
+                  :clear-icon="false"
+                  :disabled="true"
+                  :style="{
+                    color: maintenanceInfo.lastNaturalDate ? '#999' : '#999',
+                    'font-size': maintenanceInfo.lastNaturalDate
+                      ? '14px !important'
+                      : 'inherit !important',
+                  }"
+                  v-model="maintenanceInfo.lastNaturalDate"
+                >
+                </uni-datetime-picker>
+              </uni-forms-item>
+              <!-- 推迟自然日期(D) -->
+              <uni-forms-item
+                class="form-item"
+                :label="$t('maintenanceWorkOrder.delayedNaturalDate')"
+                name="delayNaturalDate"
+                :required="false"
+              >
+                <uni-easyinput
+                  style="text-align: right"
+                  :inputBorder="false"
+                  :clearable="true"
+                  :disabled="false"
+                  :styles="{ disableColor: '#F3F5F9' }"
+                  type="digit"
+                  v-model="maintenanceInfo.delayNaturalDate"
+                  placeholder=""
+                />
+              </uni-forms-item>
+            </view>
+            <!-- 推迟原因 -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.delayReason')"
+              name="delayReason"
+              :required="false"
+            >
+              <uni-easyinput
+                type="textarea"
+                :autoHeight="true"
+                :inputBorder="false"
+                :clearable="true"
+                :disabled="false"
+                style="text-align: right"
+                :styles="{ disableColor: '#F3F5F9' }"
+                v-model="maintenanceInfo.delayReason"
+                placeholder=""
+              />
+            </uni-forms-item>
+          </uni-section>
+          <!-- 运行里程规则配置 里程(0启用 1停用)-->
+          <uni-section
+            :title="$t('maintenanceWorkOrder.runningMileageRuleConfig')"
+            type="line"
+            v-if="maintenanceInfo.mileageRule == 0"
+          >
+            <!-- 运行里程周期(KM) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.runningMileageCycle')"
+              name="kilometerCycle"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.nextRunningKilometers"
+                placeholder=""
+              />
+            </uni-forms-item>
+            <!-- 运行里程周期-提前量(KM) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.runningMileageCycleLead')"
+              name="runningMileageCycleAdvance"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.kiloCycleLead"
+                placeholder=""
+              />
+            </uni-forms-item>
+          </uni-section>
+          <!-- 运行时间规则配置 -->
+          <uni-section
+            :title="$t('maintenanceWorkOrder.runningTimeRuleConfig')"
+            type="line"
+            v-if="maintenanceInfo.runningTimeRule == 0"
+          >
+            <!-- 运行时间周期(H) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.runningTimeCycle')"
+              name="timePeriod"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.nextRunningTime"
+                placeholder=""
+              />
+            </uni-forms-item>
+            <!-- 运行时间周期-提前量(H) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.runningTimeCycleLead')"
+              name="timePeriodLead"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.timePeriodLead"
+                placeholder=""
+              />
+            </uni-forms-item>
+          </uni-section>
+          <!-- 自然日规则配置 -->
+          <uni-section
+            :title="$t('maintenanceWorkOrder.naturalDateRuleConfig')"
+            type="line"
+            v-if="maintenanceInfo.naturalDateRule == 0"
+          >
+            <!-- 自然日周期(D) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.naturalDateCycle')"
+              name="naturalDatePeriod"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.nextNaturalDate"
+                placeholder=""
+              />
+            </uni-forms-item>
+            <!-- 自然日周期-提前量(D) -->
+            <uni-forms-item
+              class="form-item"
+              :label="$t('maintenanceWorkOrder.naturalDateCycleLead')"
+              name="naturalDatePeriodLead"
+              :required="true"
+            >
+              <uni-easyinput
+                style="text-align: right"
+                :inputBorder="false"
+                :clearable="false"
+                :disabled="true"
+                :styles="{ disableColor: '#F3F5F9' }"
+                type="digit"
+                v-model="maintenanceInfo.naturalDatePeriodLead"
+                placeholder=""
+              />
+            </uni-forms-item>
+          </uni-section>
+        </uni-forms>
+      </scroll-view>
+      <button
+        class="submit-btn"
+        type="primary"
+        @click="onConfirm(delayFormRef)"
+      >
+        {{ $t("operation.save") }}
+      </button>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import dayjs from "dayjs";
+import { computed, ref, reactive, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { getDeptId } from "@/utils/auth";
+import { getMaterialListData } from "@/api/material";
+import { devicePage } from "@/api/device";
+
+const { t, locale } = useI18n({
+  useScope: "global",
+});
+
+// 接收父组件传递的参数
+const props = defineProps({
+  maintenance: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const oncancel = () => {
+  close();
+};
+const onConfirm = async (formEl) => {
+  if (!formEl) return;
+  await formEl
+    .validate()
+    .then((res) => {
+      console.log("success", res);
+      emit("delay-set", maintenanceInfo.value);
+      oncancel();
+    })
+    .catch((err) => {
+      console.log("err", err);
+    });
+};
+
+onMounted(() => {});
+
+const delayPopRef = ref(null);
+const maintenanceInfo = ref({});
+
+// 打开弹窗
+const open = (info) => {
+  console.log("open info-", info);
+  maintenanceInfo.value = info;
+  delayPopRef.value.open();
+};
+
+// 关闭弹窗
+const close = () => {
+  delayPopRef.value.close();
+};
+const delayFormRef = ref(null);
+const baseRules = ref({
+  delayReason: {
+    rules: [
+      {
+        required: false,
+        errorMessage: `${t("operation.PleaseFillIn")}${t(
+          "maintenanceWorkOrder.delayReason"
+        )}`,
+      },
+    ],
+  },
+});
+
+// 动态计算校验规则
+const delayRules = computed(() => {
+  const rules = JSON.parse(JSON.stringify(baseRules.value));
+
+  // 根据填写内容调整规则
+  if (
+    maintenanceInfo.value.delayKilometers > 0 ||
+    maintenanceInfo.value.delayDuration > 0 ||
+    maintenanceInfo.value.delayNaturalDate > 0
+  ) {
+    // 如果填写了推迟公里数、推迟时长或推迟自然日期,则推迟原因必填
+    rules.delayReason.rules[0].required = true;
+  } else {
+    rules.delayReason.rules[0].required = false;
+  }
+
+  return rules;
+});
+
+const emit = defineEmits(["delay-set"]);
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/work-order-detail.scss";
+
+:deep(.uni-popup__wrapper) {
+  padding: 20px 15px;
+}
+
+.popup-container {
+  width: 100%;
+  height: 450px;
+  box-sizing: border-box;
+  background-color: #fff;
+  position: relative;
+}
+
+.pop-title {
+  width: 100%;
+  font-size: 16px;
+  color: #333333;
+  line-height: 22px;
+  margin-bottom: 20px;
+
+  .title {
+    // min-width: 49.999999992%;
+    width: max-content;
+    max-width: 62.49999999%;
+    font-weight: 700;
+  }
+}
+
+.pop-close {
+  position: absolute;
+  right: -5px;
+  top: -10px;
+}
+
+.delay-sections {
+  position: relative;
+  height: calc(100% - 80px);
+}
+
+:deep(.uni-section) {
+  background: #f3f5f9;
+  // border: 1px solid #999;
+  margin-bottom: 10px;
+}
+
+:deep(.uni-section-content) {
+  background: #f3f5f9;
+  padding: 0 10px 10px;
+}
+:deep(.uni-section-header__content) {
+  font-weight: 600;
+}
+:deep(.uni-section-header__decoration) {
+  // position: absolute;
+  // left: 0;
+  margin-right: 10px !important;
+  margin-left: -10px;
+}
+:deep(uni-text) {
+  font-size: 14px;
+}
+
+:deep(.uni-date-x) {
+  background: #f3f5f9;
+}
+
+.pop-btn {
+  margin-top: 20px;
+  width: 120px;
+  height: 32px;
+  line-height: 32px;
+  font-size: 14px;
+}
+</style>

+ 219 - 0
components/materials/add.vue

@@ -0,0 +1,219 @@
+<template>
+	<uni-popup class="materials-popup" ref="materialAddPopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0 " background-color="#fff">
+		<uni-row class="head-row align-center">
+			<uni-col :span="6" class="head-cancel align-center justify-start" @click="oncancel">
+				{{$t('operation.cancel')}}
+			</uni-col>
+			<uni-col :span="12" class="head-title justify-center">
+				{{$t('workOrder.addMaterial')}}
+			</uni-col>
+			<uni-col :span="6" class="head-add align-center justify-end" @click="onConfirm(materialAddFormRef)">
+				{{$t('operation.confirm')}}
+			</uni-col>
+		</uni-row>
+		<view class="material-add-section">
+			<uni-forms ref="materialAddFormRef" labelWidth="140px" :modelValue="addInfo" :rules="formRules">
+				<!-- 物料主数据 -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.masterData')" name="name" :required="true">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false"
+						:styles="{disableColor:'#fff'}" :placeholder="$t('operation.PleaseSelect')"
+						v-model="addInfo.name" @focus="onMasterDataShow" />
+				</uni-forms-item>
+				<!-- 物料编码 -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.materialCode')" name="code" :required="true">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false" :disabled="true"
+						:styles="{disableColor:'#fff'}" v-model="addInfo.code"
+						:placeholder="''" />
+				</uni-forms-item>
+				<!-- 物料名称 -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.materialName')" name="name" :required="true">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false" :disabled="true"
+						:styles="{disableColor:'#fff'}" v-model="addInfo.name"
+						:placeholder="''" />
+				</uni-forms-item>
+				<!-- 单位 -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.unit')" name="unit" :required="false">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false" :disabled="true"
+						:styles="{disableColor:'#fff'}" v-model="addInfo.unit"
+						:placeholder="''" />
+				</uni-forms-item>
+				<!-- 消耗数量 -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.consumptionQuantity')" name="quantity"
+					:required="true">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false"
+						:styles="{disableColor:'#fff'}" type="digit" v-model="addInfo.quantity"
+						:placeholder="$t('operation.PleaseFillIn')" />
+				</uni-forms-item>
+				<!-- 单价(元) -->
+				<uni-forms-item class="form-item" :label="$t('workOrder.unitPrice')" name="unitPrice" :required="true">
+					<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="false" type="digit"
+						:styles="{disableColor:'#fff'}" v-model="addInfo.unitPrice"
+						:placeholder="$t('operation.PleaseFillIn')" />
+				</uni-forms-item>
+
+				<!-- 备注 -->
+				<uni-forms-item class="form-item" :label="$t('operation.remark')" :required="false" name="remark">
+					<uni-easyinput style="text-align: right" type="textarea" :autoHeight="true" :inputBorder="false"
+						:clearable="false" :styles="{disableColor:'#fff'}" v-model="addInfo.remark"
+						:placeholder="$t('operation.PleaseFillIn')" />
+				</uni-forms-item>
+			</uni-forms>
+		</view>
+
+	</uni-popup>
+	<master-data ref="masterDataRef" @master-data-submit="onMasterDataSet"></master-data>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		watch,
+		onMounted,
+	} from 'vue'
+	import {
+		useI18n
+	} from 'vue-i18n'
+	import {
+		getDeptId,
+	} from "@/utils/auth"
+	import {
+		getMaterialListData
+	} from '@/api/material'
+	import {
+		devicePage,
+	} from '@/api/device';
+	import masterData from './master-data'
+
+	const {
+		t,
+		locale
+	} = useI18n({
+		useScope: 'global'
+	})
+
+	// 接收父组件传递的参数
+	const props = defineProps({})
+
+
+	const addInfo = ref({
+		// code: '',
+		// name: '',
+		// unit: '',
+		// quantity: '',
+		// unitPrice: '',
+		// remark: '',
+
+	})
+	const materialAddFormRef = ref(null)
+	const formRules = ref({
+		name: {
+			rules: [{
+				required: true,
+				errorMessage: t('operation.PleaseFillIn'),
+			}, ]
+		},
+		quantity: {
+			rules: [{
+				required: true,
+				errorMessage: t('operation.PleaseFillIn'),
+			}, ]
+		},
+		unitPrice: {
+			rules: [{
+				required: true,
+				errorMessage: t('operation.PleaseFillIn'),
+			}, ]
+		},
+
+	})
+	const oncancel = () => {
+		addInfo.value = {}
+		close()
+	}
+	const onConfirm = async (formEl) => {
+		if (!formEl) return
+		await formEl.validate().then(res => {
+			console.log('onConfirm res-', res)
+			addInfo.value.chooseKey = `${addInfo.value.id}_${addInfo.value.code}_${addInfo.value.unitPrice}` // 手动拼接chooseKey
+			emit('add-set', addInfo.value)
+			oncancel()
+		}).catch(err => {
+			console.error('onConfirm err-', err)
+		})
+
+
+	}
+	const masterDataRef = ref(null)
+	const onMasterDataShow = () => {
+		masterDataRef.value.open()
+	}
+	const onMasterDataSet = (item) => {
+		console.log('onMasterDataSet-', item)
+		addInfo.value = {
+			...addInfo.value,
+			...item,
+			
+		}
+	}
+
+
+
+	onMounted(() => {})
+
+	const materialAddPopRef = ref(null)
+
+	// 打开弹窗
+	const open = () => {
+		materialAddPopRef.value.open()
+	}
+
+	// 关闭弹窗
+	const close = () => {
+		materialAddPopRef.value.close()
+	}
+
+	const emit = defineEmits(['add-set'])
+	// 提供外部方法
+	const expose = {
+		open,
+	}
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/work-order-detail.scss";
+
+	.materials-popup {
+		width: 100%;
+		height: 100%;
+		min-height: 411px;
+		background-color: #fff;
+		padding: 20px;
+	}
+
+	:deep(.uni-popup__wrapper) {
+		padding: 20px;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+		height: 40px;
+		border-bottom: 1px dashed #CACCCF;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+</style>

+ 301 - 0
components/materials/choose.vue

@@ -0,0 +1,301 @@
+<template>
+	<uni-popup class="materials-popup" ref="materialChoosePopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="z-page-popup" ref="paging" style="top: 200px;"  :default-page-size="20" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="6" class="head-cancel align-center justify-start" @click="oncancel">
+							{{$t('operation.cancel')}}
+						</uni-col>
+						<uni-col :span="12" class="head-title justify-center">
+							{{$t('workOrder.selectMaterial')}}
+						</uni-col>
+						<uni-col :span="6" class="head-add align-center justify-end" @click="onAdd" v-if="!noAdd">
+							<uni-icons type="plusempty" color="#004098"></uni-icons>
+							{{$t('operation.add')}}
+						</uni-col>
+					</uni-row>
+					<uni-row class="search-row flex-row justify-between">
+						<uni-col :span="24">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('workOrder.materialName')}`"
+								@confirm="searchList">
+							</uni-easyinput>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="5" class="table-header flex-row align-center justify-between">
+						<uni-col :span="5" class="flex-row justify-center">
+							{{ $t('workOrder.materialCode')}}
+						</uni-col>
+						<uni-col :span="7" class="flex-row justify-center">
+							{{ $t('workOrder.materialName')}}
+						</uni-col>
+						<uni-col :span="3" class="flex-row justify-center">
+							{{ $t('workOrder.unit')}}
+						</uni-col>
+						<uni-col :span="3" class="flex-row justify-center">
+							{{ $t('workOrder.unitPrice')}}
+						</uni-col>
+						<uni-col :span="3" class="flex-row justify-center">
+							{{ $t('workOrder.inventory')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-end">
+							{{ $t('workOrder.source')}}
+						</uni-col>
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<uni-row :gutter="2" class="item-row align-center"
+					:class="{'choosed': chooseIds.includes(item.chooseKey)}" v-for="(item,index) in dataList"
+					:key="index" @click="onChoose(item)">
+					<uni-col :span="5" class="item-col flex-row justify-start">
+						{{item.materialCode}}
+					</uni-col>
+					<uni-col :span="7" class="item-col flex-row justify-start">
+						{{item.materialName}}
+					</uni-col>
+					<uni-col :span="3" class="item-col flex-row justify-center">
+						{{item.unit}}
+					</uni-col>
+					<uni-col :span="3" class="item-col flex-row justify-center">
+						{{item.unitPrice}}
+					</uni-col>
+					<uni-col :span="3" class="item-col flex-row justify-center">
+						{{item.totalInventoryQuantity}}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-end">
+						{{item.materialSource}}
+					</uni-col>
+				</uni-row>
+			</view>
+		</z-paging>
+	</uni-popup>
+	<materials-count ref="countRef" @count-set="onCountSet"></materials-count>
+	<materials-add ref="addRef" @add-set="onEmit"></materials-add>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		watch,
+		onMounted,
+	} from 'vue'
+	import {
+		useI18n
+	} from 'vue-i18n'
+	import {
+		getDeptId,
+	} from "@/utils/auth"
+	import {
+		getMaterialList
+	} from '@/api/material'
+	import {
+		devicePage,
+	} from '@/api/device';
+	import materialsCount from './count'
+	import materialsAdd from './add'
+	// 接收父组件传递的参数
+	const props = defineProps({
+		deviceId: {
+			type: Number,
+		},
+		deptId: {
+			type: Number,
+			default:getDeptId(),
+		},
+		bomNodeId: {
+			type: String,
+			default: null,
+		},
+		materialItem: {
+			type: Object,
+			default: () => {}
+		},
+		noAdd: {
+			type: Boolean,
+			default: false,
+		},
+	})
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		getMaterialList({
+			pageNo,
+			pageSize,
+			deviceId: props.deviceId,
+			deptId: props.deptId,
+			bomNodeId: props.bomNodeId,
+			materialName: searchValue.value
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			res.data.list.forEach(item => {
+				item.chooseKey =
+					`${item.materialCode}_${item.costCenterId}_${item.factoryId}_${item.unitPrice}_${item.storageLocationId}`
+			})
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+		paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+
+
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.chooseKey)
+	})
+	const onChoose = (item) => {
+
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		// 若不存在则添加
+		const index = chooseList.value.findIndex((v) => v.chooseKey === item.chooseKey)
+		if (index > -1) {
+			// chooseList.value.splice(index, 1)
+		} else {
+			chooseList.value = [item]
+			onCountShow(item)
+		}
+	}
+	const onChooseDel = (item) => {
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		const index = chooseList.value.findIndex((v) => v.chooseKey === item.chooseKey)
+		if (index > -1) {
+			chooseList.value.splice(index, 1)
+		}
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+	const addRef = ref(null)
+	const onAdd = () => {
+		// oncancel()
+		addRef.value.open()
+	}
+	const onEmit = (item) => {
+		// chooseList.value = [item]
+		console.log('onEmit-item', item, bomItem.value)
+		const subItem = Object.assign({}, item)
+		// subItem.bomId = bomItem.value.id
+		subItem.bomNodeId = bomItem.value.bomNodeId
+		// subItem.workOrderId = bomItem.value.workOrderId
+		subItem.workOrderBomOnlyKey = bomItem.value.workOrderBomOnlyKey
+		console.log('onEmit-subItem', subItem)
+		emit('material-submit', subItem)
+		oncancel()
+	}
+	const countRef = ref(null)
+	const onCountShow = (item) => {
+		countRef.value.showCountPop(item)
+	}
+	const onCountSet = (item) => {
+		console.log('onCountSet- item', item)
+		onEmit(item)
+		countRef.value.hideCountPop()
+	}
+
+
+	const {
+		t,
+		locale
+	} = useI18n({
+		useScope: 'global'
+	})
+
+	const bomItem = ref({})
+	const materialChoosePopRef = ref(null)
+	// 打开弹窗
+	const open = (bom) => {
+		console.log('open-bom', bom)
+		bomItem.value = bom
+		materialChoosePopRef.value.open()
+	}
+	// 关闭弹窗
+	const close = () => {
+		materialChoosePopRef.value.close()
+	}
+
+	const emit = defineEmits(['material-submit'])
+	// 提供外部方法
+	const expose = {
+		open,
+	}
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+
+	.z-page-popup {
+		padding: 20px 15px;
+		box-sizing: border-box;
+		background: #FFF;
+		border-radius: 10px 10px 0 0;
+	}
+
+	.page-top {
+		background: #fff;
+		padding: 0;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+
+	.table-header {
+		background: #FFF;
+		border-bottom: 1px solid #CACCCF;
+	}
+
+	.page-table {
+		padding: 0;
+	}
+
+	.item-col {
+		font-size: 11px;
+	}
+</style>

+ 167 - 0
components/materials/count.vue

@@ -0,0 +1,167 @@
+<template>
+	<uni-popup class="count-popup" ref="countPopRef" type="center" :is-mask-click="false"
+		borderRadius="10px 10px 10px 10px" background-color="#fff">
+		<view class="popup-container">
+			<view class="pop-title flex-row align-center justify-center">
+				{{$t('operation.PleaseSet')}}{{$t('workOrder.materialCount')}}
+				<uni-icons type="closeempty" color="#666" class="pop-close" @click="onclose"></uni-icons>
+			</view>
+			<view class="pop-content">
+				<view class="content-cell flex-row align-center justify-between">
+					<view class="cell-title">
+						{{$t('workOrder.materialName')}}:
+					</view>
+					<view>
+						{{materialItem.materialName}}
+					</view>
+				</view>
+				<view class="content-cell flex-row align-center justify-between">
+					<view class="cell-title">
+						{{$t('workOrder.remainingInventory')}}:
+					</view>
+					<view>
+						{{materialItem.totalInventoryQuantity}}
+					</view>
+				</view>
+				<view class="content-cell flex-row align-center justify-between">
+					<view class="cell-title">
+						{{$t('workOrder.materialCount')}}:
+					</view>
+					<view>
+						<uni-easyinput style="text-align: right" :inputBorder="false" :clearable="true" type="digit"
+							:styles="{ disableColor: '#fff' }" v-model="materialItem.quantity"
+							:placeholder="$t('operation.PleaseFillIn')" @change="changeQuantity" />
+					</view>
+				</view>
+			</view>
+			<button type="primary" class="pop-btn" @click="onsubmit">
+				{{$t('operation.confirm')}}
+			</button>
+		</view>
+	</uni-popup>
+</template>
+
+<script setup>
+	import {
+		ref,
+		getCurrentInstance
+	} from 'vue'
+
+	// 引用全局变量$t
+	const {
+		appContext
+	} = getCurrentInstance();
+	const t = appContext.config.globalProperties.$t;
+
+
+	const quantityMin = ref(0)
+	const materialItem = ref({})
+	const countPopRef = ref(null)
+	const showCountPop = (iten) => {
+		materialItem.value = iten
+		materialItem.value.quantity = 1
+		countPopRef.value.open()
+	}
+	const hideCountPop = () => {
+		countPopRef.value.close()
+	}
+	const onclose = () => {
+		materialItem.quantity = null
+		hideCountPop()
+	}
+	const changeQuantity = (e) => {
+		console.log('changeQuantity', e)
+		if (validateQuantity(e)) {
+			materialItem.value.quantity = e
+		}
+
+	}
+	const emit = defineEmits(['count-set', ])
+	const validateQuantity = (quantity) => {
+		if (!quantity) {
+			uni.showToast({
+				title: t("workOrder.materialCountEmpty"),
+				icon: 'none'
+			})
+			return false
+		}
+		if (quantity <= 0) {
+			uni.showToast({
+				title: t("workOrder.materialCountMustGreaterThan0"),
+				icon: 'none'
+			})
+			return false
+		}
+		return true
+	}
+	const onsubmit = () => {
+		console.log('materialItem.value', materialItem.value)
+		if (validateQuantity(materialItem.value.quantity)) {
+			// 提交设置的数量
+			emit('count-set', Object.assign({}, materialItem.value))
+			materialItem.value.quantity = null
+			// hideCountPop()
+		}
+	}
+
+	defineExpose({
+		showCountPop,
+		hideCountPop
+	})
+</script>
+
+
+<style lang="scss" scoped>
+	.popup-container {
+		width: 308px;
+		min-height: 217px;
+		background: #FFFFFF;
+		box-shadow: 0px -2px 10px 0px rgba(0, 0, 0, 0.1);
+		border-radius: 10px;
+		box-sizing: border-box;
+		padding: 20px 30px;
+		position: relative;
+	}
+
+	.pop-title {
+		font-weight: bold;
+		font-size: 16px;
+		color: #333333;
+		line-height: 22px;
+	}
+
+	.pop-close {
+		position: absolute;
+		right: 10px;
+		top: 10px;
+	}
+
+	.content-cell {
+		font-size: 14px;
+		color: #333333;
+		font-weight: 500;
+		min-height: 30px;
+		border-bottom: 1px dashed #CACCCF;
+		text-align: right;
+
+		&:last-child {
+			// border-bottom: none;
+		}
+
+		&:first-child {
+			margin-top: 20px;
+		}
+	}
+
+	.cell-title {
+		min-width: max-content;
+	}
+
+	.pop-btn {
+		margin-top: 20px;
+		width: 120px;
+		height: 32px;
+		line-height: 32px;
+		font-size: 14px;
+	}
+</style>

+ 259 - 0
components/materials/master-data.vue

@@ -0,0 +1,259 @@
+<template>
+	<uni-popup class="materials-popup" ref="masterDataPopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="z-page-popup" ref="paging" style="top: 230px;" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="6" class="head-cancel align-center justify-start">
+							{{$t('operation.cancel')}}
+						</uni-col>
+						<uni-col :span="12" class="head-title justify-center">
+							{{$t('workOrder.masterData')}}
+						</uni-col>
+						<uni-col :span="6" class="head-add align-center justify-end" @click="oncancel">
+							<uni-icons type="closeempty" color="#333"></uni-icons>
+						</uni-col>
+					</uni-row>
+					<uni-row class="search-row flex-row justify-between">
+						<uni-col :span="24">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('workOrder.materialName')}`"
+								@confirm="searchList">
+							</uni-easyinput>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="5" class="table-header flex-row align-center justify-between">
+						<uni-col :span="5" class="flex-row justify-center">
+							{{ $t('workOrder.materialCode')}}
+						</uni-col>
+						<uni-col :span="9" class="flex-row justify-center">
+							{{ $t('workOrder.materialName')}}
+						</uni-col>
+						<uni-col :span="3" class="flex-row justify-center">
+							{{ $t('workOrder.unit')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('workOrder.specification')}}
+						</uni-col>
+						<uni-col :span="3" class="flex-row justify-center">
+							{{ $t('workOrder.status')}}
+						</uni-col>
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<uni-row :gutter="5" class="item-row align-center"
+					:class="{'choosed': chooseIds.includes(item.id)}" v-for="(item,index) in dataList"
+					:key="index" @click="onChoose(item)">
+					<uni-col :span="5" class="item-col flex-row justify-start">
+						{{item.code}}
+					</uni-col>
+					<uni-col :span="9" class="item-col flex-row justify-center">
+						{{item.name}}
+					</uni-col>
+					<uni-col :span="3" class="item-col flex-row justify-center">
+						{{item.unit}}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{item.unitPrice}}
+					</uni-col>
+					<uni-col :span="3" class="item-col flex-row justify-center">
+						{{item.status==0 ? $t('status.enable') : $t('status.disable')}}
+					</uni-col>
+				</uni-row>
+			</view>
+		</z-paging>
+	</uni-popup>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		watch,
+		onMounted,
+	} from 'vue'
+	import {
+		useI18n
+	} from 'vue-i18n'
+	import {
+		getDeptId,
+	} from "@/utils/auth"
+	import {
+		getMaterialListData
+	} from '@/api/material'
+
+	// 接收父组件传递的参数
+	const props = defineProps({
+		deviceId: {
+			type: Number,
+		},
+		materialItem: {
+			type: Object,
+			default: () => {}
+		},
+	})
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		getMaterialListData({
+			pageNo,
+			pageSize,
+			name: searchValue.value
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			// res.data.list.forEach(item => {
+			// 	item.chooseKey =
+			// 		`${item.code}_${item.id}`
+			// })
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+		paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+
+
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.id)
+	})
+	const onChoose = (item) => {
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		// 若不存在则添加
+		const index = chooseList.value.findIndex((v) => v.id === item.id)
+		if (index > -1) {
+			// chooseList.value.splice(index, 1)
+		} else {
+			chooseList.value = [item]
+			onSubmit(item)
+		}
+	}
+	const onChooseDel = (item) => {
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		const index = chooseList.value.findIndex((v) => v.id === item.id)
+		if (index > -1) {
+			chooseList.value.splice(index, 1)
+		}
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+
+	const onSubmit = (item) => {
+		console.log('item', item)
+		emit('master-data-submit', {
+			...item,
+			materialCode: item.code, // 转换物料编码字段
+			materialName: item.name, // 转换物料名称字段
+		})
+		oncancel()
+	}
+
+
+	const {
+		t,
+		locale
+	} = useI18n({
+		useScope: 'global'
+	})
+
+
+
+	onMounted(() => {})
+
+	const masterDataPopRef = ref(null)
+
+	// 打开弹窗
+	const open = () => {
+		masterDataPopRef.value.open()
+	}
+
+	// 关闭弹窗
+	const close = () => {
+		masterDataPopRef.value.close()
+	}
+
+	const emit = defineEmits(['master-data-submit'])
+	// 提供外部方法
+	const expose = {
+		open,
+	}
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+
+	.z-page-popup {
+		padding: 20px 15px;
+		box-sizing: border-box;
+		background: #FFF;
+		border-radius: 10px 10px 0 0;
+	}
+
+	.page-top {
+		background: #fff;
+		padding: 0;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+
+	.table-header {
+		background: #FFF;
+		border-bottom: 1px solid #CACCCF;
+	}
+
+	.page-table {
+		padding: 0;
+	}
+
+	.item-col {
+		font-size: 11px;
+	}
+</style>

+ 91 - 0
components/materials/view.vue

@@ -0,0 +1,91 @@
+<template>
+	<uni-drawer ref="drawerRightRef" mode="right" :mask-click="false" @change="change($event,'drawerRightRef')">
+		<z-paging class="page-nopadding" ref="paging" style="top: 0px;" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top justify-center">
+					{{$t('workOrder.materialDetails')}}
+				</view>
+			</template>
+			<view class="page-list">
+				<view class="item" v-for="(item,index) in dataList" :key="index">
+					<view class="item-content flex-row align-center justify-between bold">
+						<view class="item-title flex-row align-center">
+							<span>{{$t('operationRecordFilling.belongToTeam')}}:</span>
+							<span>{{item.orgName}}</span>
+						</view>
+					</view>
+				</view>
+
+			</view>
+			<!-- 如果需要使用页脚,请使用slot="bottom"slot节点不支持通过v-if或v-show动态显示/隐藏,若需要动态控制,可将v-if添加在其子节点上 -->
+			<template #bottom>
+				<button style="border-radius: 0;" type="primary"
+					@click="closeDrawer('drawerRightRef')">{{$t('operation.back')}}</button>
+			</template>
+		</z-paging>
+	</uni-drawer>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive
+	} from 'vue'
+	import dayjs from 'dayjs'
+	import {
+		getMaterialDetail,
+	} from '@/api/material.js';
+	import {
+		getUserId,
+		getDeptId
+	} from "@/utils/auth.js"
+
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		getMaterialDetail({
+			pageNo,
+			pageSize,
+			workOrderId: workOrderId,
+			bomNodeId: bomNodeId
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+
+
+
+	const drawerRightRef = ref(null)
+	const change = (e, ref) => {
+		console.log(e, ref)
+	}
+	const openDrawer = (ref) => {
+		drawerRightRef.value.open()
+	}
+	const closeDrawer = (ref) => {
+		drawerRightRef.value.close()
+	}
+	defineExpose({
+		openDrawer,
+		closeDrawer
+	})
+</script>
+
+<style lang="scss" scoped>
+	:deep(.uni-drawer__content) {
+		width: 100% !important;
+		min-width: 375px !important;
+	}
+</style>

+ 213 - 0
components/repair/add.vue

@@ -0,0 +1,213 @@
+<template>
+  <uni-popup
+    class="repairs-popup"
+    ref="repairAddPopRef"
+    type="bottom"
+    :is-mask-click="false"
+    borderRadius="10px 10px 0 0 "
+    background-color="#fff"
+  >
+    <uni-row class="head-row align-center">
+      <uni-col
+        :span="6"
+        class="head-cancel align-center justify-start"
+        @click="oncancel"
+      >
+        {{ $t("operation.cancel") }}
+      </uni-col>
+      <uni-col :span="12" class="head-title justify-center">
+        {{ $t("operation.add")
+        }}{{ $t("equipmentMaintenance.maintenanceItems") }}
+      </uni-col>
+      <uni-col
+        :span="6"
+        class="head-add align-center justify-end"
+        @click="onConfirm(repairAddFormRef)"
+      >
+        {{ $t("operation.confirm") }}
+      </uni-col>
+    </uni-row>
+    <view class="repair-add-section">
+      <uni-forms
+        ref="repairAddFormRef"
+        labelWidth="140px"
+        :modelValue="addInfo"
+        :rules="formRules"
+      >
+        <!-- 设备编码 -->
+        <uni-forms-item
+          class="form-item"
+          :label="$t('device.deviceCode')"
+          name="deviceCode"
+          :required="true"
+        >
+          <uni-easyinput
+            style="text-align: right"
+            :inputBorder="false"
+            :clearable="false"
+            :disabled="true"
+            :styles="{ disableColor: '#fff' }"
+            v-model="addInfo.deviceCode"
+            :placeholder="$t('operation.PleaseFillIn')"
+          />
+        </uni-forms-item>
+        <!-- 设备名称 -->
+        <uni-forms-item
+          class="form-item"
+          :label="$t('device.deviceName')"
+          name="deviceName"
+          :required="true"
+        >
+          <uni-easyinput
+            style="text-align: right"
+            :inputBorder="false"
+            :clearable="false"
+            :disabled="true"
+            :styles="{ disableColor: '#fff' }"
+            v-model="addInfo.deviceName"
+            :placeholder="$t('operation.PleaseFillIn')"
+          />
+        </uni-forms-item>
+        <!-- 维修项 -->
+        <uni-forms-item
+          class="form-item"
+          :label="$t('equipmentMaintenance.maintenanceItems')"
+          name="name"
+          :required="false"
+        >
+          <uni-easyinput
+            style="text-align: right"
+            :inputBorder="false"
+            :clearable="false"
+            :disabled="false"
+            :styles="{ disableColor: '#fff' }"
+            v-model="addInfo.name"
+            :placeholder="$t('operation.PleaseFillIn')"
+          />
+        </uni-forms-item>
+      </uni-forms>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup>
+import dayjs from "dayjs";
+import { computed, ref, reactive, watch, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { getDeptId } from "@/utils/auth";
+
+const { t, locale } = useI18n({
+  useScope: "global",
+});
+
+// 接收父组件传递的参数
+const props = defineProps({});
+
+const allBoms = ref([]);
+const addInfo = ref({});
+const repairAddFormRef = ref(null);
+const formRules = ref({
+  name: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t("operation.PleaseFillIn"),
+      },
+    ],
+  },
+});
+const oncancel = () => {
+  addInfo.value = {};
+  close();
+};
+const onConfirm = async (formEl) => {
+  if (!formEl) return;
+  await formEl
+    .validate()
+    .then((res) => {
+      console.log("onConfirm res-", res);
+
+      addInfo.value.chooseKey = `${addInfo.value.deviceCode}_${addInfo.value.deviceName}_${addInfo.value.name}`; // 手动拼接chooseKey
+      // 生成8位随机数字作为bomNodeId
+      addInfo.value.bomNodeId = Math.floor(Math.random() * 100000000); //.toString();
+
+      // 判断addInfo.value.chooseKey是否在allBoms.value中
+      if (allBoms.value.includes(addInfo.value.chooseKey)) {
+        uni.showToast({
+          title:
+            t("equipmentMaintenance.maintenanceItems") + t("operation.repeat"),
+          icon: "none",
+        });
+      } else {
+        emit("add-set", addInfo.value);
+        oncancel();
+      }
+    })
+    .catch((err) => {
+      console.error("onConfirm err-", err);
+    });
+};
+
+onMounted(() => {});
+
+const repairAddPopRef = ref(null);
+
+// 打开弹窗
+const open = (info) => {
+  const { bom, ...device } = info;
+  console.log("repair-open", info, bom);
+  addInfo.value = device;
+  // 遍历bom,返回deviceCode,deviceName,name拼接成的字符串形成新数组赋值allBoms
+  allBoms.value = bom.map(
+    (item) => `${item.deviceCode}_${item.deviceName}_${item.name}`
+  );
+  console.log("allBoms.value-", allBoms.value);
+  repairAddPopRef.value.open();
+};
+
+// 关闭弹窗
+const close = () => {
+  repairAddPopRef.value.close();
+};
+
+const emit = defineEmits(["add-set"]);
+// 提供外部方法
+const expose = {
+  open,
+};
+defineExpose(expose);
+</script>
+
+<style lang="scss" scoped>
+@import "@/style/work-order-detail.scss";
+
+.repairs-popup {
+  width: 100%;
+  height: 100%;
+  min-height: 411px;
+  background-color: #fff;
+  padding: 20px;
+}
+
+:deep(.uni-popup__wrapper) {
+  padding: 20px;
+}
+
+.head-row {
+  margin-bottom: 20px;
+  font-size: 16px;
+  line-height: 21px;
+  color: #a3a3a3;
+  height: 40px;
+  border-bottom: 1px dashed #cacccf;
+}
+
+.head-title {
+  color: #333333;
+  font-weight: bold;
+}
+
+.head-add {
+  color: #004098;
+}
+</style>

+ 179 - 0
components/repair/multiple.vue

@@ -0,0 +1,179 @@
+<template>
+	<uni-popup class="device-popup" ref="deviceChoosePopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="page-nopadding z-page-popup" ref="paging" style="top: 100px;" v-model="dataList"
+			@query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="22" class="head-title justify-center">
+							{{$t('operation.select')}}{{$t('equipmentMaintenance.maintenanceItems')}}
+						</uni-col>
+						<uni-col :span="2" class="head-cancel align-center justify-end" @click="oncancel">
+							<uni-icons type="closeempty" color="#333"></uni-icons>
+						</uni-col>
+					</uni-row>
+					<view class="choice-row flex-row flex-wrap align-center justify-start">
+						<view class="choice-item align-center justify-center" v-for="item in chooseList"
+							@click="onChooseDel(item)">
+							{{item.name}}
+							<uni-icons class="choice-close-icon" type="closeempty" color="#fff"></uni-icons>
+						</view>
+					</view>
+					<uni-row class="search-row flex-rowc justify-between">
+						<uni-col :span="18">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('device.deviceName')}`">
+							</uni-easyinput>
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-end">
+							<button class="mini-btn" type="primary" size="mini" @click="searchList">
+								{{ $t('operation.search')}}</button>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="2" class="table-header flex-row align-center justify-between">
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('device.assetCode')}}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('device.deviceName')}}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('equipmentMaintenance.maintenanceItems')}}
+						</uni-col>
+						<uni-col :span="6" class="flex-row justify-center">
+							{{ $t('operation.createTime')}}
+						</uni-col>
+
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<view class="table-content">
+					<uni-row :gutter="2" class="item-row align-center" :class="{'choosed': chooseIds.includes(item.bomNodeId)}"
+						v-for="(item,index) in dataList" :key="index" @click="onChoose(item)">
+						<uni-col :span="6" class="item-col flex-row justify-start">
+							{{item.deviceCode}}
+						</uni-col>
+						<uni-col :span="6" class="item-col flex-row justify-center">
+							{{item.deviceName}}
+						</uni-col>
+						<uni-col :span="6" class="item-col flex-row justify-center">
+							{{item.name}}
+						</uni-col>
+						<uni-col :span="6" class="item-col flex-row justify-end">
+							{{ item.createTime ? formatDate(item.createTime) : '' }}
+						</uni-col>
+					</uni-row>
+				</view>
+			</view>
+			<template #bottom>
+				<button style="border-radius: 0;" type="primary" @click="onSubmit">{{$t('operation.confirm')}}</button>
+			</template>
+		</z-paging>
+	</uni-popup>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive,
+		computed,
+	} from 'vue'
+	import dayjs from 'dayjs'
+	import {
+		deviceAssociateBomListPage,
+	} from '@/api/repair.js';
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px')
+	const inputStyles = reactive({
+		backgroundColor: '#FFFFFF',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		deviceAssociateBomListPage({
+			pageNo,
+			pageSize,
+			bomFlag: 'w', // 维修时选择 物料 传值 w   保养时选择 物料 传值 b
+			deviceIds: deviceIds.value,
+			name: searchValue.value
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+		paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+	const deviceIds = ref({})
+	const deviceChoosePopRef = ref(null)
+	const open = (ids) => {
+		deviceIds.value = ids
+		deviceChoosePopRef.value.open()
+	}
+	const close = () => {
+		deviceChoosePopRef.value.close()
+	}
+	// 提供外部方法
+	const expose = {
+		open,
+	}
+	defineExpose(expose)
+	const emit = defineEmits(['repair-submit'])
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.bomNodeId)
+	})
+	const onChoose = (item) => {
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		// 若不存在则添加
+		const index = chooseList.value.findIndex((v) => v.bomNodeId === item.bomNodeId)
+		if (index > -1) {
+			// chooseList.value.splice(index, 1)
+		} else {
+			chooseList.value.push(item)
+		}
+	}
+	const onChooseDel = (item) => {
+		// 判断当前点击的设备是否存在于chooseList中,
+		// 若存在,则从chooseList中删除,否则添加到chooseList中
+		const index = chooseList.value.findIndex((v) => v.bomNodeId === item.bomNodeId)
+		if (index > -1) {
+			chooseList.value.splice(index, 1)
+		}
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+	const onSubmit = () => {
+		emit('repair-submit', chooseList.value)
+		oncancel()
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+</style>

+ 530 - 0
components/statistic/front.vue

@@ -0,0 +1,530 @@
+<template>
+  <view>
+    <uni-card>
+      <uni-row class="flex-row flex-wrap">
+        <!--   设备总数   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.deviceCount")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.repairCount")
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.device.total || 0
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.maintain.total || 0
+          }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: end" />
+        <!--   运行记录工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{
+            $t("statistic.front.runCount")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.filled")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.unfilled")
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.pending.filledCount || 0
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.pending.unfilledCount || 0
+          }}</uni-col>
+        </uni-row>
+        <uni-col :span="24">
+          <view class="divider-v" />
+        </uni-col>
+        <!--   保养工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{
+            $t("statistic.front.maintenanceCount")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.execute")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.unexecute")
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.maintenance.finished || 0
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.maintenance.todo || 0
+          }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: start" />
+        <!--   巡检工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{
+            $t("statistic.front.inspectionCount")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.filled")
+          }}</uni-col>
+          <uni-col class="count-label" :span="12">{{
+            $t("statistic.front.unfilled")
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.inspect.finished || 0
+          }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{
+            frontData.inspect.todo || 0
+          }}</uni-col>
+        </uni-row>
+      </uni-row>
+    </uni-card>
+    <uni-card>
+      <uni-row class="flex-row flex-wrap">
+        <!--   MTTR(平均解决时间)   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{
+            $t("statistic.front.mttr")
+          }}</uni-col>
+          <uni-col
+            class="count-value mttr"
+            :span="24"
+            style="margin-top: 6px"
+            >{{ frontData.mttr ? frontData.mttr +'h' : '' }}</uni-col
+
+          >
+        </uni-row>
+        <view class="divider-h" style="align-self: end" />
+        <!--   库存预警物料数量   -->
+
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{
+            $t("statistic.front.stockWarningCount")
+          }}</uni-col>
+          <uni-col
+            class="count-value warning"
+            :span="24"
+            style="margin-top: 6px"
+            >{{ frontData.stockWarning }}</uni-col
+          >
+        </uni-row>
+      </uni-row>
+    </uni-card>
+    <!--   设备状态统计   -->
+
+    <uni-card>
+      <uni-section :title="$t('statistic.front.deviceStatus')" />
+
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+            type="ring"
+            :opts="deviceStatusOpts"
+            :chartData="frontData.deviceStatusChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   设备类别TOP5数量   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.front.deviceTypeCount')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+            type="bar"
+            :opts="deviceTypeOpts"
+            :chartData="frontData.deviceTypeChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   近一周活跃用户数   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.front.weekUserActive')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+            type="column"
+            :opts="weeklyUserActivityOpts"
+            :chartData="frontData.weeklyUserActivityChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   工单数量统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.front.workOrderCount')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+            type="line"
+            :opts="workOrderCountOpts"
+            :chartData="frontData.workOrderCountChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance, reactive, ref } from "vue";
+import dayjs from "dayjs";
+import { getDeptId } from "@/utils/auth";
+import {
+  getDeviceCount,
+  getMaintainCount,
+  getPendingCount,
+  getMaintenanceCount,
+  getInspectCount,
+  getMTTR,
+  getStockWarningCount,
+  getDeviceStatusStatistic,
+  getDeviceTypeCount,
+  getWeeklyUserActivity,
+  getWorkOrderCount,
+} from "@/api/statistic";
+
+const { appContext } = getCurrentInstance();
+const t = appContext.config.globalProperties.$t;
+
+// 初始化环形图 - 设备状态统计
+const deviceStatusOpts = {
+  color: ["#5470c6", "#91cc75", "#fac858", "#ee6666"],
+  padding: [0, 10, 0, 10],
+  title: { name: "" },
+  subtitle: { name: "" },
+  legend: {
+    position: "bottom",
+  },
+  extra: {
+    ring: {
+      ringWidth: 30, // 圆环的宽度
+      activeOpacity: 0.5, // 启用Tooltip点击时,突出部分的透明度
+      activeRadius: 10, // 启用Tooltip点击时,突出部分的宽度(最大值不得超过labelWidth)
+      offsetAngle: -90, // 起始角度偏移度数
+      labelWidth: 10, // 数据标签到饼图外圆连线的长度
+      customRadius: 80, // 自定义半径
+      borderWidth: 3, // 分割线的宽度
+      borderColor: "#FFFFFF", // 分割线的颜色
+    },
+  },
+};
+
+// 初始化条状图 - 设备类别TOP5数量
+const deviceTypeOpts = {
+  padding: [10, 50, 10, 10],
+  title: { name: "" },
+  subtitle: { name: "" },
+  legend: {
+    show: false,
+  },
+  xAxis: {
+    boundaryGap: "justify",
+    disableGrid: false,
+    axisLine: false,
+  },
+  yAxis: {},
+  extra: {
+    bar: {
+      type: "group",
+      width: 30,
+      meterBorde: 1,
+      meterFillColor: "#FFFFFF",
+      activeBgColor: "#000000",
+      activeBgOpacity: 0.08,
+      linearType: "custom",
+      barBorderCircle: true,
+      seriesGap: 2,
+      categoryGap: 2,
+      customColor: ["#7fbdf6", "#188df0"],
+    },
+  },
+};
+// 初始化柱状图 - 近一周活跃用户数
+const weeklyUserActivityOpts = {
+  color: [],
+
+  padding: [20, 10, 10, 10],
+  title: { name: "" },
+  subtitle: { name: "" },
+  legend: {
+    position: "bottom",
+  },
+  extra: {
+    column: {
+      type: "group",
+      width: 30,
+      activeBgColor: "#000000",
+      activeBgOpacity: 0.08,
+      linearType: "custom",
+      seriesGap: 5,
+      linearOpacity: 0.8,
+      barBorderCircle: false,
+      customColor: ["#5978cb", "#e2f2ce", ,], //"#e2f2ce","#91cc75"
+    },
+  },
+};
+// 初始化折线图 - 工单数量统计
+const workOrderCountOpts = {
+  color: ["#5470c6", "#f1d209", "#e14f0f", "#91cc75"],
+  padding: [20, 20, 20, 20],
+  title: { name: "" },
+  subtitle: { name: "" },
+  legend: {
+    position: "bottom",
+  },
+  xAxis: {
+    rotateLabel: true, // 旋转标签
+    rotateAngle: 45, // 旋转角度
+    marginTop: 10, // 标签与轴线的距离
+    fontSize: 12, // 字体大小
+  },
+  yAxis: {
+    data: [
+      { type: "value", position: "left" },
+      { type: "value", position: "right" },
+    ],
+  },
+};
+
+const frontData = reactive({
+  device: { total: 0 },
+  maintain: { total: 0 },
+  pending: { filledCount: 0, unfilledCount: 0 },
+  maintenance: { finished: 0, todo: 0 },
+  inspect: { finished: 0, todo: 0 },
+  mttr: "4.8",
+  stockWarning: "0",
+  deviceStatusChart: {
+    series: [],
+  },
+  deviceTypeChart: {},
+});
+
+const isLoadData = ref(false);
+
+// 加载维修相关统计数据
+const loadData = async () => {
+  console.log("🚀 ~ loadData ~ isLoadData.value:", isLoadData.value);
+  if (isLoadData.value) return; // 已加载数据后不再加载数据
+  isLoadData.value = true;
+
+  const startTime = dayjs().subtract(7, "day").valueOf();
+
+  console.log("🚀 ~ loadData ~ startTime:", startTime);
+  const endTime = dayjs().valueOf();
+
+  console.log("🚀 ~ loadData ~ endTime:", endTime);
+
+  // 设备数
+  const getDeviceCountAsync = getDeviceCount();
+  // 维修工单数
+  const getMaintainCountAsync = getMaintainCount();
+  // 运行记录工单数
+  const getPendingCountAsync = getPendingCount({
+    startTime,
+    endTime,
+    deptId: getDeptId(),
+  });
+  // 保养工单数
+  const getMaintenanceCountAsync = getMaintenanceCount();
+  // 巡检工单数
+  const getInspectCountAsync = getInspectCount({
+    startTime,
+    endTime,
+  });
+  // MTTR(平均解决时间)
+  const getMTTRAsync = getMTTR();
+  // 库存预警物料数量
+  const getStockWarningCountAsync = getStockWarningCount();
+  // 设备状态统计
+  const getDeviceStatusStatisticAsync = getDeviceStatusStatistic();
+  // 设备类别TOP5
+  const getDeviceTypeCountAsync = getDeviceTypeCount();
+  // 近一周活跃用户数
+  const getWeeklyUserActivityAsync = getWeeklyUserActivity();
+  // 工单数量统计
+  const getWorkOrderCountAsync = getWorkOrderCount();
+
+  // 设备数
+  frontData.device = (await getDeviceCountAsync).data;
+  // 维修工单数
+  frontData.maintain = (await getMaintainCountAsync).data;
+  // 运行记录工单数
+  frontData.pending = (await getPendingCountAsync).data?.totalList[0];
+  // 保养工单数
+  frontData.maintenance = (await getMaintenanceCountAsync).data;
+  // 巡检工单数
+  frontData.inspect = (await getInspectCountAsync).data;
+  // MTTR(平均解决时间)
+  frontData.mttr = (await getMTTRAsync).data;
+  // 库存预警物料数量
+  frontData.stockWarning = (await getStockWarningCountAsync).data;
+  // 设备状态统计
+  const deviceStatusChartSeries = (await getDeviceStatusStatisticAsync).data;
+  //设备状态统计数据填充
+  frontData.deviceStatusChart.series = [
+    {
+      data: deviceStatusChartSeries.map((item) => ({
+        name: item.name,
+        value: item.value,
+        labelText: item.name + ": " + item.value,
+        textColor: "#333", // 设置文本颜色
+      })),
+    },
+  ];
+  // 设备类别TOP5
+  const deviceTypeChartData = (await getDeviceTypeCountAsync).data;
+  frontData.deviceTypeChart = {
+    categories: deviceTypeChartData.map((item) => item.category),
+    series: [
+      {
+        name: "",
+        data: deviceTypeChartData.map((item) => item.value),
+        textColor: "#333", // 设置文本颜色
+      },
+    ],
+  };
+  console.log(
+    "🚀 ~ loadData ~ frontData.deviceTypeChart:",
+    frontData.deviceTypeChart
+  );
+  // 近一周活跃用户数
+  const weeklyUserActivity = (await getWeeklyUserActivityAsync).data;
+  console.log("🚀 ~ loadData ~ weeklyUserActivity:", weeklyUserActivity);
+  frontData.weeklyUserActivityChart = {
+    categories: weeklyUserActivity.map((item) => item.department),
+    series: [
+      {
+        name: t("statistic.front.totalUserCount"),
+        data: weeklyUserActivity.map((item) => item.total),
+        textColor: "#333", // 设置文本颜色
+      },
+      {
+        name: t("statistic.front.activeUserCount"),
+        data: weeklyUserActivity.map((item) => item.active),
+        textColor: "#333", // 设置文本颜色
+      },
+    ],
+  };
+  console.log(
+    "🚀 ~ loadData ~  frontData.weeklyUserActivityChart :",
+    frontData.weeklyUserActivityChart
+  );
+  // 工单数量统计 维修与保养一个轴,运行与巡检一个轴
+
+  const workOrderCount = (await getWorkOrderCountAsync).data;
+  frontData.workOrderCountChart = {
+    categories: workOrderCount.xAxis,
+    series: workOrderCount.series.map((item, itemIndex) => ({
+      ...item,
+      index: itemIndex > 1 ? 1 : 0, // 维修与保养一个轴(0),运行与巡检一个轴(1)
+      textColor: "#000", // 设置文本颜色
+    })),
+  };
+  console.log(
+    "🚀 ~ loadData ~ frontData.workOrderCountChart:",
+    frontData.workOrderCountChart
+  );
+};
+
+defineExpose({ loadData });
+</script>
+
+<style scoped lang="scss">
+.charts-box {
+  width: 100%;
+  height: 300px;
+}
+
+:deep(.uni-card) {
+  padding: 0 !important;
+
+  .uni-card__content {
+    padding: 0 !important;
+  }
+
+  .uni-section {
+    margin-top: 8px;
+    padding: 0 15px 0 20px;
+
+    .uni-section-header {
+      padding: 10px 0;
+    }
+
+    .uni-section__content-title {
+      font-size: 16px !important;
+      font-weight: 600;
+    }
+  }
+}
+
+:deep(.uni-section) {
+  background-color: transparent;
+
+  .uni-section-header {
+    padding: 5px 10px;
+  }
+  .line {
+    width: 3px;
+    height: 14px;
+    background-color: #004098;
+  }
+}
+
+.divider {
+  width: 1px;
+  height: 114px;
+  background-color: #cacccf;
+  margin: 0 24px;
+}
+
+.divider-v {
+  width: auto;
+  height: 1.5px;
+  border-bottom: 1.5px #cacccf dashed;
+  margin: 0 20px;
+}
+
+.divider-h {
+  width: 1.5px;
+  height: 50px;
+  border-left: 1.5px #cacccf dashed;
+  margin: 0 4px;
+}
+
+.count-label {
+  color: #666666;
+  font-size: 14px;
+}
+
+.count-value {
+  color: #333333;
+  font-size: 18px;
+  font-weight: 500;
+  &.mttr {
+    color: #20b2aa;
+    font-weight: 700;
+    font-size: 28px;
+    text-align: center;
+  }
+  &.warning {
+    color: #cd5c5c;
+    font-weight: 700;
+    font-size: 28px;
+    text-align: center;
+  }
+}
+
+.mt-5 {
+  margin-top: 5px;
+}
+
+.mt-8 {
+  margin-top: 8px;
+}
+
+.pl-10 {
+  padding-left: 10px;
+}
+</style>

+ 259 - 0
components/statistic/inspection.vue

@@ -0,0 +1,259 @@
+<template>
+  <view>
+    <uni-card>
+      <uni-row class="flex-row flex-wrap" >
+        <!--   昨日工单数量   -->
+        <uni-row class="flex-row flex-wrap align-center" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.dayCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.yesterday.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.yesterday.todo }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: end" />
+        <!--   近一周工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.weeklyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.week.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.week.todo }}</uni-col>
+        </uni-row>
+        <uni-col :span="24">
+          <view class="divider-v" />
+        </uni-col>
+        <!--   近一月工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.monthlyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.month.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.month.todo }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: start" />
+        <!--   总工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.totalCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.total.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.total.todo }}</uni-col>
+        </uni-row>
+      </uni-row>
+    </uni-card>
+    <!--   保养工单状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.inspection.workOrder.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="data.statusChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   今日工单状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.maintenance.dayWorkOrder.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="data.todayStatusChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance, reactive, ref } from "vue";
+import {
+  getInspectionStatusStatistic,
+  getInspectionTodayStatusStatistic,
+  getMonthlyInspectionStatistic,
+  getTotalInspectionStatistic,
+  getWeeklyInspectionStatistic,
+  getYesterdayInspectionStatistic,
+} from "@/api/statistic";
+
+const { appContext } = getCurrentInstance()
+const t = appContext.config.globalProperties.$t
+
+// 初始化环形图
+const opts = {
+  color: ['#0055BB', '#FF5D00'],
+  padding: [0, 10, 0, 10],
+  title: { name: '' },
+  subtitle: { name: '' },
+  legend: {
+    position: 'bottom',
+  },
+  extra: {
+    ring: {
+      ringWidth: 30, // 圆环的宽度
+      activeOpacity: 0.5, // 启用Tooltip点击时,突出部分的透明度
+      activeRadius: 10, // 启用Tooltip点击时,突出部分的宽度(最大值不得超过labelWidth)
+      offsetAngle: -90, // 起始角度偏移度数
+      labelWidth: 10, // 数据标签到饼图外圆连线的长度
+      customRadius: 80, // 自定义半径
+      borderWidth: 3, // 分割线的宽度
+      borderColor: "#FFFFFF" // 分割线的颜色
+    }
+  }
+}
+
+const data = reactive({
+  yesterday: {},
+  week: {},
+  month: {},
+  total: {},
+  statusChart: { series: [] },
+  todayStatusChart: { series: [] },
+})
+
+const isLoadData = ref(false)
+
+// 加载巡检相关统计数据
+const loadData = async () => {
+  if (isLoadData.value) return // 已加载数据后不再加载数据
+  isLoadData.value = true
+
+  const getYesterdayInspectionStatisticAsync = getYesterdayInspectionStatistic()
+  const getWeeklyInspectionStatisticAsync = getWeeklyInspectionStatistic()
+  const getMonthlyInspectionStatisticAsync = getMonthlyInspectionStatistic()
+  const getTotalInspectionStatisticAsync = getTotalInspectionStatistic()
+  const getInspectionStatusStatisticAsync = getInspectionStatusStatistic()
+  const getInspectionTodayStatusStatisticAsync = getInspectionTodayStatusStatistic()
+
+  data.yesterday = (await getYesterdayInspectionStatisticAsync).data
+  data.week = (await getWeeklyInspectionStatisticAsync).data
+  data.month = (await getMonthlyInspectionStatisticAsync).data
+  data.total = (await getTotalInspectionStatisticAsync).data
+
+  const status = (await getInspectionStatusStatisticAsync).data
+  const todayStatus = (await getInspectionTodayStatusStatisticAsync).data
+
+  // 工单状态数据填充
+  data.statusChart.series = [{
+    data: [
+      {
+        name: t('statistic.maintenance.workOrder.status1'),
+        value: status.todo || 0,
+        labelText: t('statistic.maintenance.workOrder.status1') + ': ' + (status.todo || 0)
+      },
+      { name: t('statistic.maintenance.workOrder.status2'),
+        value: status.finished || 0,
+        labelText: t('statistic.maintenance.workOrder.status2') + ': ' + (status.finished || 0)
+      },
+    ],
+  }]
+
+  // 今日工单状态相关数据填充
+  data.todayStatusChart.series = [{
+    data: [
+      {
+        name: t('statistic.maintenance.workOrder.status1'),
+        value: todayStatus.todo || 0,
+        labelText: t('statistic.maintenance.workOrder.status1') + ': ' + (todayStatus.todo || 0)
+      },
+      { name: t('statistic.maintenance.workOrder.status2'),
+        value: todayStatus.finished || 0,
+        labelText: t('statistic.maintenance.workOrder.status2') + ': ' + (todayStatus.finished || 0)
+      },
+    ],
+  }]
+}
+
+defineExpose({ loadData })
+</script>
+
+<style scoped lang="scss">
+.charts-box {
+  width: 100%;
+  height: 300px;
+}
+
+:deep(.uni-card) {
+  padding: 0 !important;
+
+  .uni-card__content {
+    padding: 0 !important;
+  }
+
+  .uni-section {
+    margin-top: 8px;
+    padding: 0 15px 0 20px;
+
+    .uni-section-header {
+      padding: 10px 0;
+    }
+
+    .uni-section__content-title {
+      font-size: 16px !important;
+      font-weight: 600;
+    }
+  }
+}
+
+:deep(.uni-section) {
+  background-color: transparent;
+
+  .uni-section-header {
+    padding: 5px 10px;
+  }
+  .line {
+    width: 3px;
+    height: 14px;
+    background-color: #004098;
+  }
+}
+
+.divider {
+  width: 1px;
+  height: 114px;
+  background-color: #CACCCF;
+  margin: 0 24px;
+}
+
+.divider-v {
+  width: auto;
+  height: 1.5px;
+  border-bottom: 1.5px #CACCCF dashed;
+  margin: 0 20px;
+}
+
+.divider-h {
+  width: 1.5px;
+  height: 50px;
+  border-left: 1.5px #CACCCF dashed;
+  margin: 0 4px;
+}
+
+.count-label {
+  color: #666666;
+  font-size: 14px;
+}
+
+.count-value {
+  color: #333333;
+  font-size: 18px;
+  font-weight: 500;
+}
+
+.mt-5 {
+  margin-top: 5px;
+}
+
+.mt-8 {
+  margin-top: 8px;
+}
+
+.pl-10 {
+  padding-left: 10px;
+}
+</style>

+ 292 - 0
components/statistic/maintenance.vue

@@ -0,0 +1,292 @@
+<template>
+  <view>
+    <uni-card>
+      <uni-row class="flex-row flex-wrap" >
+        <!--   昨日工单数量   -->
+        <uni-row class="flex-row flex-wrap align-center" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.dayCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.yesterday.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.yesterday.todo }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: end" />
+        <!--   近一周工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.weeklyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.week.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.week.todo }}</uni-col>
+        </uni-row>
+        <uni-col :span="24">
+          <view class="divider-v" />
+        </uni-col>
+        <!--   近一月工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.monthlyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.month.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.month.todo }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: start" />
+        <!--   总工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.maintenance.totalCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count1') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.maintenance.count2') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.total.total }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ data.total.todo }}</uni-col>
+        </uni-row>
+      </uni-row>
+    </uni-card>
+    <!--   保养工单状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.maintenance.workOrder.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="data.statusChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   今日工单状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.maintenance.dayWorkOrder.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="data.todayStatusChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   工单类型状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.maintenance.orderType.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts1"
+              :chartData="data.typeChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance, reactive, ref } from "vue";
+import {
+  getMaintenanceStatusStatistic,
+  getMaintenanceTodayStatusStatistic,
+  getMaintenanceTypeStatistic,
+  getMonthlyMaintenanceStatistic,
+  getTotalMaintenanceStatistic,
+  getWeeklyMaintenanceStatistic,
+  getYesterdayMaintenanceStatistic
+} from "@/api/statistic";
+
+const { appContext } = getCurrentInstance()
+const t = appContext.config.globalProperties.$t
+
+// 初始化环形图
+const opts = {
+  color: ['#0055BB', '#FF5D00'],
+  padding: [0, 10, 0, 10],
+  title: { name: '' },
+  subtitle: { name: '' },
+  legend: {
+    position: 'bottom',
+  },
+  extra: {
+    ring: {
+      ringWidth: 30, // 圆环的宽度
+      activeOpacity: 0.5, // 启用Tooltip点击时,突出部分的透明度
+      activeRadius: 10, // 启用Tooltip点击时,突出部分的宽度(最大值不得超过labelWidth)
+      offsetAngle: -90, // 起始角度偏移度数
+      labelWidth: 10, // 数据标签到饼图外圆连线的长度
+      customRadius: 80, // 自定义半径
+      borderWidth: 3, // 分割线的宽度
+      borderColor: "#FFFFFF" // 分割线的颜色
+    }
+  }
+}
+const opts1 = { ...opts, color: ['#5470C6', '#9CDC7E'] }
+
+const data = reactive({
+  yesterday: {},
+  week: {},
+  month: {},
+  total: {},
+  statusChart: { series: [] },
+  todayStatusChart: { series: [] },
+  typeChart: { series: [] },
+})
+
+const isLoadData = ref(false)
+
+// 加载维修相关统计数据
+const loadData = async () => {
+  if (isLoadData.value) return // 已加载数据后不再加载数据
+  isLoadData.value = true
+
+  const getYesterdayMaintenanceStatisticAsync = getYesterdayMaintenanceStatistic()
+  const getWeeklyMaintenanceStatisticAsync = getWeeklyMaintenanceStatistic()
+  const getMonthlyMaintenanceStatisticAsync = getMonthlyMaintenanceStatistic()
+  const getTotalMaintenanceStatisticAsync = getTotalMaintenanceStatistic()
+  const getMaintenanceStatusStatisticAsync = getMaintenanceStatusStatistic()
+  const getMaintenanceTodayStatusStatisticAsync = getMaintenanceTodayStatusStatistic()
+  const getMaintenanceTypeStatisticAsync = getMaintenanceTypeStatistic()
+
+  data.yesterday = (await getYesterdayMaintenanceStatisticAsync).data
+  data.week = (await getWeeklyMaintenanceStatisticAsync).data
+  data.month = (await getMonthlyMaintenanceStatisticAsync).data
+  data.total = (await getTotalMaintenanceStatisticAsync).data
+
+  const status = (await getMaintenanceStatusStatisticAsync).data
+  const todayStatus = (await getMaintenanceTodayStatusStatisticAsync).data
+  const type = (await getMaintenanceTypeStatisticAsync).data
+
+  // 工单状态数据填充
+  data.statusChart.series = [{
+    data: [
+      {
+        name: t('statistic.maintenance.workOrder.status1'),
+        value: status.todo || 0,
+        labelText: t('statistic.maintenance.workOrder.status1') + ': ' + (status.todo || 0)
+      },
+      { name: t('statistic.maintenance.workOrder.status2'),
+        value: status.finished || 0,
+        labelText: t('statistic.maintenance.workOrder.status2') + ': ' + (status.finished || 0)
+      },
+    ],
+  }]
+
+  // 今日工单状态相关数据填充
+  data.todayStatusChart.series = [{
+    data: [
+      {
+        name: t('statistic.maintenance.workOrder.status1'),
+        value: todayStatus.todo || 0,
+        labelText: t('statistic.maintenance.workOrder.status1') + ': ' + (todayStatus.todo || 0)
+      },
+      { name: t('statistic.maintenance.workOrder.status2'),
+        value: todayStatus.finished || 0,
+        labelText: t('statistic.maintenance.workOrder.status2') + ': ' + (todayStatus.finished || 0)
+      },
+    ],
+  }]
+
+  // 工单类型数据填充
+  data.typeChart.series = [{
+    data: [
+      {
+        name: t('statistic.maintenance.orderType.status1'),
+        value: type['临时新建'] ?? 0,
+        labelText: t('statistic.maintenance.orderType.status1') + ': ' + (type['临时新建'] ?? 0)
+      },
+      { name: t('statistic.maintenance.orderType.status2'),
+        value: type['计划生成'] ?? 0,
+        labelText: t('statistic.maintenance.orderType.status2') + ': ' + (type['计划生成'] ?? 0)
+      },
+    ],
+  }]
+}
+
+defineExpose({ loadData })
+</script>
+
+<style scoped lang="scss">
+.charts-box {
+  width: 100%;
+  height: 300px;
+}
+
+:deep(.uni-card) {
+  padding: 0 !important;
+
+  .uni-card__content {
+    padding: 0 !important;
+  }
+
+  .uni-section {
+    margin-top: 8px;
+    padding: 0 15px 0 20px;
+
+    .uni-section-header {
+      padding: 10px 0;
+    }
+
+    .uni-section__content-title {
+      font-size: 16px !important;
+      font-weight: 600;
+    }
+  }
+}
+
+:deep(.uni-section) {
+  background-color: transparent;
+
+  .uni-section-header {
+    padding: 5px 10px;
+  }
+  .line {
+    width: 3px;
+    height: 14px;
+    background-color: #004098;
+  }
+}
+
+.divider {
+  width: 1px;
+  height: 114px;
+  background-color: #CACCCF;
+  margin: 0 24px;
+}
+
+.divider-v {
+  width: auto;
+  height: 1.5px;
+  border-bottom: 1.5px #CACCCF dashed;
+  margin: 0 20px;
+}
+
+.divider-h {
+  width: 1.5px;
+  height: 50px;
+  border-left: 1.5px #CACCCF dashed;
+  margin: 0 4px;
+}
+
+.count-label {
+  color: #666666;
+  font-size: 14px;
+}
+
+.count-value {
+  color: #333333;
+  font-size: 18px;
+  font-weight: 500;
+}
+
+.mt-5 {
+  margin-top: 5px;
+}
+
+.mt-8 {
+  margin-top: 8px;
+}
+
+.pl-10 {
+  padding-left: 10px;
+}
+</style>

+ 259 - 0
components/statistic/rapair.vue

@@ -0,0 +1,259 @@
+<template>
+  <view>
+    <uni-card>
+      <uni-row class="flex-row flex-wrap" >
+        <!--   平均解决时间   -->
+        <uni-row class="flex-row flex-wrap align-center" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">MTTR</uni-col>
+          <uni-col class="count-label" :span="24">{{ $t('statistic.repair.resolutionTime') }}</uni-col>
+          <uni-col class="count-value" :span="24" style="margin-top: 6px">{{ 0 }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: end" />
+        <!--   近一周工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.repair.weeklyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.report') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.workOrder') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.week.failureWeek }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.week.maintainWeek }}</uni-col>
+        </uni-row>
+        <uni-col :span="24">
+          <view class="divider-v" />
+        </uni-col>
+        <!--   近一月工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.repair.monthlyCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.report') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.workOrder') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.month.failureMonth }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.month.maintainMonth }}</uni-col>
+        </uni-row>
+        <view class="divider-h" style="align-self: start" />
+        <!--   总工单数量   -->
+        <uni-row class="flex-row flex-wrap" style="flex: 1; padding: 10px">
+          <uni-col class="count-label" :span="24">{{ $t('statistic.repair.totalCount') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.report') }}</uni-col>
+          <uni-col class="count-label" :span="12">{{ $t('statistic.repair.workOrder') }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.total.failureTotal }}</uni-col>
+          <uni-col class="count-value" :span="12" style="margin-top: 6px">{{ repairData.total.maintainTotal }}</uni-col>
+        </uni-row>
+      </uni-row>
+    </uni-card>
+    <!--   故障上报状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.repair.failure.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="repairData.chart"
+          />
+        </view>
+      </view>
+    </uni-card>
+    <!--   维修工单状态统计   -->
+    <uni-card>
+      <uni-section :title="$t('statistic.repair.workOrder.title')" />
+      <view class="flex-row align-center">
+        <view class="charts-box">
+          <qiun-data-charts
+              type="ring"
+              :opts="opts"
+              :chartData="repairData.orderChart"
+          />
+        </view>
+      </view>
+    </uni-card>
+  </view>
+</template>
+
+<script setup>
+import { getCurrentInstance, reactive, ref } from "vue";
+import {
+  getMonthlyRepairStatistic,
+  getOtherRepairStatistic,
+  getTotalRepairStatistic,
+  getWeeklyRepairStatistic
+} from "@/api/statistic";
+
+const { appContext } = getCurrentInstance()
+const t = appContext.config.globalProperties.$t
+
+// 初始化环形图
+const opts = {
+  color: ['#00DD99', '#FF5500', '#0056BF', '#FAC858'],
+  padding: [0, 10, 0, 10],
+  title: { name: '' },
+  subtitle: { name: '' },
+  legend: {
+    position: 'bottom',
+  },
+  extra: {
+    ring: {
+      ringWidth: 30, // 圆环的宽度
+      activeOpacity: 0.5, // 启用Tooltip点击时,突出部分的透明度
+      activeRadius: 10, // 启用Tooltip点击时,突出部分的宽度(最大值不得超过labelWidth)
+      offsetAngle: -90, // 起始角度偏移度数
+      labelWidth: 10, // 数据标签到饼图外圆连线的长度
+      customRadius: 80, // 自定义半径
+      borderWidth: 3, // 分割线的宽度
+      borderColor: "#FFFFFF" // 分割线的颜色
+    }
+  }
+}
+
+const repairData = reactive({
+  week: {},
+  month: {},
+  total: {},
+  chart: { series: [] },
+  orderChart: { series: [] },
+})
+
+const isLoadData = ref(false)
+
+// 加载维修相关统计数据
+const loadData = async () => {
+  if (isLoadData.value) return // 已加载数据后不再加载数据
+  isLoadData.value = true
+
+  const getWeeklyRepairStatisticAsync = getWeeklyRepairStatistic()
+  const getMonthlyRepairStatisticAsync = getMonthlyRepairStatistic()
+  const getTotalRepairStatisticAsync = getTotalRepairStatistic()
+  const getOtherRepairStatisticAsync = getOtherRepairStatistic()
+
+  repairData.week = (await getWeeklyRepairStatisticAsync).data
+  repairData.month = (await getMonthlyRepairStatisticAsync).data
+  repairData.total = (await getTotalRepairStatisticAsync).data
+  const data = (await getOtherRepairStatisticAsync).data
+
+  // 故障相关数据填充
+  repairData.chart.series = [{
+    data: [
+      {
+        name: t('statistic.repair.failure.reporting'),
+        value: data.failureStatus.reporting || 0,
+        labelText: t('statistic.repair.failure.reporting') + ': ' + (data.failureStatus.reporting || 0)
+      },
+      { name: t('statistic.repair.failure.finished'),
+        value: data.failureStatus.finished || 0,
+        labelText: t('statistic.repair.failure.finished') + ': ' + (data.failureStatus.finished || 0)
+      },
+      {
+        name: t('statistic.repair.failure.trans'),
+        value: data.failureStatus.trans || 0,
+        labelText: t('statistic.repair.failure.trans') + ': ' + (data.failureStatus.trans || 0)
+      },
+      {
+        name: t('statistic.repair.failure.over'),
+        value: 0,
+        labelText: t('statistic.repair.failure.over') + ': ' + 0
+      },
+    ],
+  }]
+
+  // 工单相关数据填充
+  repairData.orderChart.series = [{
+    data: [
+      {
+        name: t('statistic.repair.workOrder.tx'),
+        value: data.maintainStatus.tx || 0,
+        labelText: t('statistic.repair.workOrder.tx') + ': ' + (data.maintainStatus.tx || 0)
+      },
+      { name: t('statistic.repair.workOrder.finished'),
+        value: data.maintainStatus.finished || 0,
+        labelText: t('statistic.repair.workOrder.finished') + ': ' + (data.maintainStatus.finished || 0)
+      },
+    ],
+  }]
+}
+
+defineExpose({ loadData })
+</script>
+
+<style scoped lang="scss">
+.charts-box {
+  width: 100%;
+  height: 300px;
+}
+
+:deep(.uni-card) {
+  padding: 0 !important;
+
+  .uni-card__content {
+    padding: 0 !important;
+  }
+
+  .uni-section {
+    margin-top: 8px;
+    padding: 0 15px 0 20px;
+
+    .uni-section-header {
+      padding: 10px 0;
+    }
+
+    .uni-section__content-title {
+      font-size: 16px !important;
+      font-weight: 600;
+    }
+  }
+}
+
+:deep(.uni-section) {
+  background-color: transparent;
+
+  .uni-section-header {
+    padding: 5px 10px;
+  }
+  .line {
+    width: 3px;
+    height: 14px;
+    background-color: #004098;
+  }
+}
+
+.divider {
+  width: 1px;
+  height: 114px;
+  background-color: #CACCCF;
+  margin: 0 24px;
+}
+
+.divider-v {
+  width: auto;
+  height: 1.5px;
+  border-bottom: 1.5px #CACCCF dashed;
+  margin: 0 20px;
+}
+
+.divider-h {
+  width: 1.5px;
+  height: 50px;
+  border-left: 1.5px #CACCCF dashed;
+  margin: 0 4px;
+}
+
+.count-label {
+  color: #666666;
+  font-size: 14px;
+}
+
+.count-value {
+  color: #333333;
+  font-size: 18px;
+  font-weight: 500;
+}
+
+.mt-5 {
+  margin-top: 5px;
+}
+
+.mt-8 {
+  margin-top: 8px;
+}
+
+.pl-10 {
+  padding-left: 10px;
+}
+</style>

+ 235 - 0
components/supplier/choose.vue

@@ -0,0 +1,235 @@
+<template>
+	<uni-popup class="materials-popup" ref="materialChoosePopRef" type="bottom" :is-mask-click="false"
+		borderRadius="10px 10px 0 0">
+		<z-paging class="z-page-popup" ref="paging" style="top: 200px;" v-model="dataList" @query="queryList">
+			<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
+			<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
+			<template #top>
+				<view class="page-top">
+					<uni-row class="head-row">
+						<uni-col :span="6" class="head-cancel align-center justify-start" @click="oncancel">
+							{{$t('operation.cancel')}}
+						</uni-col>
+						<uni-col :span="12" class="head-title justify-center">
+							{{ $t('ledger.form.supplier') }}
+						</uni-col>
+					</uni-row>
+					<uni-row class="search-row flex-row justify-between">
+						<uni-col :span="24">
+							<uni-easyinput v-model="searchValue" :styles="inputStyles"
+								:placeholderStyle="placeholderStyle"
+								:placeholder="`${$t('operation.PleaseInput')}${$t('ledger.supplier.nameHint')}`"
+								@confirm="searchList">
+							</uni-easyinput>
+						</uni-col>
+					</uni-row>
+					<uni-row :gutter="5" class="table-header flex-row align-center justify-between">
+						<uni-col :span="5" class="flex-row justify-center">
+							{{ $t('ledger.supplier.name') }}
+						</uni-col>
+						<uni-col :span="7" class="flex-row justify-center">
+							{{ $t('ledger.supplier.code')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.supplier.type')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.supplier.status')}}
+						</uni-col>
+						<uni-col :span="4" class="flex-row justify-center">
+							{{ $t('ledger.supplier.time')}}
+						</uni-col>
+					</uni-row>
+				</view>
+			</template>
+			<view class="page-table">
+				<uni-row
+          :gutter="5"
+          class="item-row align-center"
+					:class="{'choosed': selectedItemId === item.id}"
+          v-for="(item,index) in dataList"
+					:key="index"
+          @click="onChoose(item)"
+        >
+					<uni-col :span="5" class="item-col flex-row justify-center">
+						{{ item.name }}
+					</uni-col>
+					<uni-col :span="7" class="item-col flex-row justify-center">
+						{{ item.code }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ getSupplierTypeName(item.classification) }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ getStatusName(item.status) }}
+					</uni-col>
+					<uni-col :span="4" class="item-col flex-row justify-center">
+						{{ item.createTime ? formatDate(item.createTime) : '' }}
+					</uni-col>
+				</uni-row>
+			</view>
+		</z-paging>
+	</uni-popup>
+</template>
+
+<script setup>
+	import dayjs from 'dayjs'
+	import {
+		computed,
+		ref,
+		reactive,
+		onMounted,
+	} from 'vue'
+	import { useI18n } from 'vue-i18n'
+  import { getSupplierList } from "@/api";
+  import { useDataDictStore } from "@/store/modules/dataDict";
+	// 接收父组件传递的参数
+	const props = defineProps({
+    selectedItemId: {
+      type: Number,
+      default: 0,
+    }
+	})
+
+  const getSupplierTypeName = (code) => {
+    for (const item of supplierTypeList.value) {
+      if (item.value === code) {
+        return item.label
+      }
+    }
+  }
+
+  const getStatusName = (status) => {
+    if (status === 1) {
+      return t('ledger.supplier.status1')
+    } else if (status === 2) {
+      return t('ledger.supplier.status2')
+    } else {
+      return t('ledger.supplier.status3')
+    }
+  }
+
+	const searchValue = ref('')
+	const placeholderStyle = ref('color:#ADADAD;font-weight:400;font-size:12px')
+	const inputStyles = reactive({
+		backgroundColor: '#F5F5F5',
+		color: '#797979',
+	})
+	const paging = ref(null)
+	// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
+	const dataList = ref([])
+
+	// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
+	const queryList = (pageNo, pageSize) => {
+		// 此处请求仅为演示,请替换为自己项目中的请求
+		getSupplierList({
+			pageNo,
+			pageSize:20,
+			name: searchValue.value
+		}).then(res => {
+			// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
+			res.data.list.forEach(item => {
+				item.chooseKey = item.id
+			})
+			paging.value.complete(res.data.list);
+		}).catch(res => {
+			// 如果请求失败写paging.value.complete(false);
+			// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
+			// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
+			paging.value.complete(false);
+		})
+	}
+	const searchList = () => {
+		paging.value.reload()
+	}
+	const formatDate = (time) => {
+		return dayjs(time).format('YYYY-MM-DD');
+	}
+	// ----------------------------------------------
+
+
+	// ---------------------------------------------
+	const chooseList = ref([])
+	const chooseIds = computed(() => {
+		// 返回chooseList的id组成新数组
+		return chooseList.value.map(c => c.chooseKey)
+	})
+	const onChoose = (item) => {
+		emit('confirm', item)
+    close()
+	}
+	const oncancel = () => {
+		chooseList.value = []
+		close()
+	}
+
+	const { t } = useI18n({ useScope: 'global' })
+
+  const supplierTypeList = ref([])
+  const { getDataDictList } = useDataDictStore()
+	onMounted (() => {
+    supplierTypeList.value = getDataDictList('supplier_classification')
+  })
+
+	const materialChoosePopRef = ref(null)
+
+	// 打开弹窗
+	const open = () => {
+		materialChoosePopRef.value.open()
+	}
+
+	// 关闭弹窗
+	const close = () => {
+		materialChoosePopRef.value.close()
+	}
+
+  const emit = defineEmits(['confirm'])
+	// 提供外部方法
+	const expose = { open }
+	defineExpose(expose)
+</script>
+
+<style lang="scss" scoped>
+	@import "@/style/choose-device.scss";
+
+	.z-page-popup {
+		padding: 20px 15px;
+		box-sizing: border-box;
+		background: #FFF;
+		border-radius: 10px 10px 0 0;
+	}
+
+	.page-top {
+		background: #fff;
+		padding: 0;
+	}
+
+	.head-row {
+		margin-bottom: 20px;
+		font-size: 16px;
+		line-height: 21px;
+		color: #A3A3A3;
+	}
+
+	.head-title {
+		color: #333333;
+		font-weight: bold;
+	}
+
+	.head-add {
+		color: #004098;
+	}
+
+	.table-header {
+		background: #FFF;
+		border-bottom: 1px solid #CACCCF;
+	}
+
+	.page-table {
+		padding: 0;
+	}
+
+	.item-col {
+		font-size: 11px;
+	}
+</style>

+ 322 - 0
components/tpf-time-range/tpf-time-range.vue

@@ -0,0 +1,322 @@
+<template>
+	<uni-popup ref="popup" type="bottom">
+		<view class="tpf-time-range-section">
+			<view class="tpf-time-range-title-section flex flex-align-center flex-pack-justify">
+				<text class="tpf-time-range-title-txt tpf-time-range-cancel" @tap="closePopup('cancel')">取消</text>
+				<text class="tpf-time-range-title-txt tpf-time-range-title">时间范围选择</text>
+				<text class="tpf-time-range-title-txt tpf-time-range-sure" @tap="closePopup('sure')">确定</text>
+			</view>
+			<view class="tpf-time-range-main flex flex-l flex-align-center flex-pack-justify">
+				<view class="tpf-time-range-item flex flex-v flex-align-center">
+					<text class="tpf-start-time">开始时间</text>
+					<picker-view class="flex-1 tpf-picker-view" :value="startDefaultTimeArr" indicator-style="height: 50px;" @change="startTimeChange">
+						<picker-view-column>
+							<view class="tpf-time-range-picker-item flex flex-align-center flex-pack-center" v-for="(item,index) in createTimeRange.hours" :key="index">{{item}}</view>
+						</picker-view-column>
+						<picker-view-column>
+							<view class="tpf-time-range-picker-item flex flex-align-center flex-pack-center" v-for="(item,index) in createTimeRange.startMinutes" :key="index">{{item}}</view>
+						</picker-view-column>
+					</picker-view>
+				</view>
+				<text class="tpf-time-divide"> - </text>
+				<view class="tpf-time-range-item flex flex-v flex-align-center">
+					<text class="tpf-start-time">结束时间</text>
+					<picker-view class="flex-1 tpf-picker-view" :value="endDefaultTimeArr" indicator-style="height: 50px;" @change="endTimeChange">
+						<picker-view-column>
+							<view class="tpf-time-range-picker-item flex flex-align-center flex-pack-center" v-for="(item,index) in createTimeRange.hours" :key="index">{{item}}</view>
+						</picker-view-column>
+						<picker-view-column>
+							<view class="tpf-time-range-picker-item flex flex-align-center flex-pack-center" v-for="(item,index) in createTimeRange.endMinutes" :key="index">{{item}}</view>
+						</picker-view-column>
+					</picker-view>
+				</view>
+			</view>
+		</view>
+	</uni-popup>
+</template>
+
+<script>
+/**
+* TimeRange 时间范围选择
+* @description 对时间(时、分)区间进行选择,限制选择范围
+* @property {string} startTime 定义开始时间
+* @property {string} startDefaultTime 定义开始默认时间
+* @property {string} endTime 定义结束时间
+* @property {string} endDefaultTime 定义结束默认时间
+* @event {Function()} name 
+*/
+export default{
+	name:"TpfTimeRange",
+	props:{
+		// 开始时间
+		startTime:{
+			type:String,
+			default:"00:00",
+			validator:(value)=>{
+				return /(((2[0-3])|([0-1][0-9])):[0-5][0-9])|24:00/.test(value);
+			}
+		},
+		// 开始默认时间
+		startDefaultTime:{
+			type:String,
+			// #ifdef MP-WEIXIN
+			default:"00:00",
+			// #endif
+			// #ifndef MP-WEIXIN
+			default(){
+				return this.startTime;
+			},
+			// #endif
+			
+			validator:(value)=>{
+				return /(((2[0-3])|([0-1][0-9])):[0-5][0-9])|24:00/.test(value);
+			}
+		},
+		// 结束时间
+		endTime:{
+			type:String,
+			default:"23:59",
+			validator:(value)=>{
+				return /(((2[0-3])|([0-1][0-9])):[0-5][0-9])|24:00/.test(value);
+			}
+		},
+		// 结束默认时间
+		endDefaultTime:{
+			type:String,
+			// #ifdef MP-WEIXIN
+			default:"23:59",
+			// #endif
+			// #ifndef MP-WEIXIN
+			default(){
+				return this.endTime;
+			},
+			// #endif
+			validator:(value)=>{
+				return /(((2[0-3])|([0-1][0-9])):[0-5][0-9])|24:00/.test(value);
+			}
+		}
+	},
+	data(){
+		return {
+			startDefaultTimeArr:[0,0],
+			endDefaultTimeArr:[0,0],
+		}
+	},
+	methods:{
+		startTimeChange(e){
+			this.startDefaultTimeArr = e.detail.value;
+			if(this.compareTwoTimeRange(e.detail.value,this.endDefaultTimeArr)) this.endDefaultTimeArr = e.detail.value;
+		},
+		endTimeChange(e){
+			this.endDefaultTimeArr = e.detail.value;
+			if(this.compareTwoTimeRange(this.startDefaultTimeArr,e.detail.value)) this.startDefaultTimeArr = e.detail.value;
+		},
+		open(){
+			this.$refs.popup.open();
+		},
+		closePopup(action=""){
+			if(this.compareTwoTimeRange(this.startDefaultTimeArr , this.endDefaultTimeArr)){
+				uni.showToast({
+					title:"开始时间不能大于结束时间",
+					icon:'none'
+				});
+				return false;
+			}
+			let startTime = this.createTimeRange.hours[this.startDefaultTimeArr[0]]+":"+this.createTimeRange.startMinutes[this.startDefaultTimeArr[1]];
+			let endTime = this.createTimeRange.hours[this.endDefaultTimeArr[0]]+":"+this.createTimeRange.endMinutes[this.endDefaultTimeArr[1]];
+			if(action != 'sure'){
+				this.$refs.popup.close();
+				return false;
+			}
+			this.$emit('timeRange',[
+				startTime,endTime
+			]);
+			this.$refs.popup.close();
+		},
+		compareTwoTimeRange(arr1=[],arr2=[]){
+			if(arr1[0]>arr2[0] || (arr1[0] == arr2[0] && arr1[1] > arr2[1])) return true;
+			return false;
+		},
+	},
+	beforeCreate(){
+		// 初始化小时
+		let hour = [],minute=[];
+		for(let h=0;h<=24;h++){
+			hour.push(h<10?'0'+h:h+'');
+		}
+		for(let m=0;m<60;m++){
+			minute.push(m<10?'0'+m:m+'');
+		}
+		this.timeRange = {hour,minute};
+	},
+	created() {
+		
+	},
+	computed:{
+		createTimeRange(){
+			let {startTime,startDefaultTime,endTime,endDefaultTime} = this.timeRangeDateChange;
+			let startTimeArr = startTime.split(":"),endTimeArr = endTime.split(":");
+			let hours = this.timeRange.hour.slice(
+				this.timeRange.hour.findIndex(item=>item == startTimeArr[0]),
+				this.timeRange.hour.findIndex(item=>item == endTimeArr[0])+1,
+			);
+
+			let startMinutes = null;
+			if(startTimeArr[0] == endTimeArr[0]){
+				startMinutes = this.timeRange.minute.slice(
+					this.timeRange.minute.findIndex(item=>item == startTimeArr[1]),
+					this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1,
+				);
+			}else{
+				if(this.startDefaultTimeArr[0] == 0){
+					startMinutes = this.timeRange.minute.slice(
+						this.timeRange.minute.findIndex(item=>item == startTimeArr[1])
+					);
+				}
+				else if(this.startDefaultTimeArr[0] == hours.length-1){
+					startMinutes = this.timeRange.minute.slice(
+						0,
+						this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1
+					);
+				}else{
+					startMinutes = this.timeRange.minute;	// 完整数据
+				}
+			}
+			let endMinutes = null;
+			if(startTimeArr[0] == endTimeArr[0]){
+				endMinutes = this.timeRange.minute.slice(
+					this.timeRange.minute.findIndex(item=>item == startTimeArr[1]),
+					this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1,
+				);
+			}
+			else{
+				if(this.endDefaultTimeArr[0] == 0){
+					endMinutes = this.timeRange.minute.slice(
+						this.timeRange.minute.findIndex(item=>item == startTimeArr[1])
+					);
+				}
+				else if(this.endDefaultTimeArr[0] == hours.length-1){
+					endMinutes = this.timeRange.minute.slice(
+						0,
+						this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1
+					);
+				}else{
+					endMinutes = this.timeRange.minute;	// 完整数据
+				}
+			}
+			return {
+				hours,
+				startMinutes,
+				endMinutes,
+			}
+		},
+		// 用于监听属性的变化
+		timeRangeDateChange(){
+			let {startTime,startDefaultTime,endTime,endDefaultTime} = this;
+			startTime = startTime<endTime?startTime:endTime;
+			startDefaultTime = startDefaultTime>=startTime && startDefaultTime<=endTime?startDefaultTime:startTime;
+			endDefaultTime = endDefaultTime>=startTime && endDefaultTime<=endTime && endDefaultTime>=startDefaultTime?endDefaultTime:startDefaultTime;
+			return {
+				startTime,
+				startDefaultTime,
+				endTime,
+				endDefaultTime
+			}
+		}
+	},
+	watch:{
+		timeRangeDateChange:{
+			handler(newVal,oldVal){
+				let {startTime,startDefaultTime,endTime,endDefaultTime} = newVal;
+				let startTimeArr = startTime.split(":"),endTimeArr = endTime.split(":");
+				let startDefaultTimeArr = startDefaultTime.split(":"),endDefaultTimeArr = endDefaultTime.split(":");
+				
+				let hours = this.timeRange.hour.slice(
+					this.timeRange.hour.findIndex(item=>item == startTimeArr[0]),
+					this.timeRange.hour.findIndex(item=>item == endTimeArr[0])+1,
+				);
+				
+				this.$set(this.startDefaultTimeArr,0, hours.includes(startDefaultTimeArr[0])?hours.findIndex(item=>item == startDefaultTimeArr[0]):0);
+				this.$set(this.endDefaultTimeArr,0, hours.includes(endDefaultTimeArr[0])?hours.findIndex(item=>item == endDefaultTimeArr[0]):this.startDefaultTimeArr[0]);
+				
+				let startMinute = null,endMinute = null;
+				if(startTimeArr[0] == endTimeArr[0]){
+					startMinute = endMinute = this.timeRange.minute.slice(
+						this.timeRange.minute.findIndex(item=>item == startTimeArr[1]),
+						this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1,
+					);
+				}
+				else{
+					if(startDefaultTime.split(":")[0] == startTimeArr[0]){
+						startMinute = this.timeRange.minute.slice(
+							this.timeRange.minute.findIndex(item=>item == startTimeArr[1]),
+						);
+					}
+					else if(startDefaultTime.split(":")[0] == endTimeArr[0]){
+						startMinute = this.timeRange.minute.slice(
+							0,
+							this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1,
+						);
+					}else{
+						startMinute = this.timeRange.minute;
+					}
+					if(endDefaultTime.split(":")[0] == startTimeArr[0]){
+						endMinute = this.timeRange.minute.slice(
+							this.timeRange.minute.findIndex(item=>item == startTimeArr[1]),
+						);
+					}else if(endDefaultTime.split(":")[0] == endTimeArr[0]){
+						endMinute = this.timeRange.minute.slice(
+							0,
+							this.timeRange.minute.findIndex(item=>item == endTimeArr[1])+1,
+						);
+					}else{
+						endMinute = this.timeRange.minute;
+					}
+				}
+				this.$set(this.startDefaultTimeArr,1, startMinute.includes(startDefaultTimeArr[1])?startMinute.findIndex(item=>item == startDefaultTimeArr[1]):0);
+				this.$set(this.endDefaultTimeArr,1, endMinute.includes(endDefaultTimeArr[1])?endMinute.findIndex(item=>item == endDefaultTimeArr[1]):this.startDefaultTimeArr[1]);
+			},
+			deep:true,		// 深度监听
+			immediate:true,	// 初始化立即执行
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+.flex{display:flex;}
+.flex-v{flex-direction:column;}
+.flex-wrap{flex-wrap:wrap;}
+.flex-row-wrap{flex-flow:row wrap;}
+.flex-1{flex:1;}
+.flex-align-center{align-items:center;}
+.flex-pack-center{justify-content:center;}
+.flex-pack-justify{justify-content:space-between;}
+.flex-pack-around{justify-content:space-around;}
+.tpf-time-range-section{
+	background-color: #FFF;
+}
+.tpf-time-range-title-section{
+	padding: 20rpx;
+	border-bottom: 1px #f2f2f2 solid;
+}
+.tpf-time-range-title-txt{
+	font-size: 28rpx;
+}
+.tpf-time-range-title{
+	font-size:32rpx;
+}
+.tpf-time-range-main{
+	padding: 0 20rpx 20rpx;
+}
+.tpf-time-range-item{
+	height: 400rpx;
+	width: 300rpx;
+}
+.tpf-start-time{
+	padding: 20rpx 0;
+}
+.tpf-picker-view{
+	width:280rpx;
+}
+</style>

+ 336 - 0
components/upgrade.vue

@@ -0,0 +1,336 @@
+<template>
+  <uni-popup
+    class="upgrade-unipopup"
+    ref="popup"
+    type="center"
+    :animation="false"
+    :mask-click="false"
+    style="z-index: 999"
+  >
+    <view class="upgrade-popup">
+      <image
+        class="header-bg"
+        src="../static/common/upgrade_bg.png"
+        mode="widthFix"
+      ></image>
+      <view class="main">
+        <view class="version"
+          >{{ t("version.newVersion") }}{{ versionName }}</view
+        >
+        <view class="content" v-if="versionDesc">
+          <text class="title">{{ t("version.updateInfo") }}</text>
+          <view class="desc" v-html="versionDesc"></view>
+        </view>
+
+        <!--下载状态-进度条显示 -->
+        <view class="footer" v-if="isStartDownload">
+          <view class="progress-view" @click="handleInstallApp">
+            <view style="height: 100%">
+              <view class="txt">{{ percentText }}</view>
+              <view class="progress" :style="setProStyle"></view>
+            </view>
+          </view>
+        </view>
+        <!-- 强制更新 -->
+        <view class="footer" v-else-if="isForceUpdate">
+          <view class="btn upgrade force" @click="handleUpgrade">{{
+            t("version.updateNow")
+          }}</view>
+        </view>
+        <!-- 可选择更新 -->
+        <view class="footer" v-else>
+          <view class="btn close" @click="handleClose">{{
+            t("version.updateLater")
+          }}</view>
+          <view class="btn upgrade" @click="handleUpgrade">{{
+            t("version.updateNow")
+          }}</view>
+        </view>
+      </view>
+    </view>
+  </uni-popup>
+</template>
+
+<script>
+import { getAppVersion } from "@/api/app.js";
+import { checkVersion, downloadApp, installApp } from "@/utils/upgrade.js";
+// 引用全局变量$t
+import { getCurrentInstance } from "vue";
+
+export default {
+  data() {
+    return {
+      isForceUpdate: true, //是否强制更新
+      versionName: "", //版本名称
+      versionDesc: "", //更新说明
+      downloadUrl: "", //APP下载链接
+      isDownloadFinish: false, //是否下载完成
+      hasProgress: false, //是否能显示进度条
+      currentPercent: 0, //当前下载百分比
+      isStartDownload: false, //是否开始下载
+      fileName: "", //下载后app本地路径名称
+
+      t: null, // 全局翻译函数
+    };
+  },
+  computed: {
+    //设置进度条样式,实时更新进度位置
+    setProStyle() {
+      return {
+        width: (510 * this.currentPercent) / 100 + "rpx", //510:按钮进度条宽度
+      };
+    },
+    //百分比文字
+    percentText() {
+      let percent = this.currentPercent;
+      if (typeof percent !== "number" || isNaN(percent))
+        return this.t("version.downloading"); // 下载中
+      if (percent < 100) return `${this.t("version.download")}${percent}%`;
+      return this.t("version.install"); // 下载完成
+    },
+  },
+
+  onBackPress(options) {
+    // 禁用返回
+    if (options.from == "backbutton") {
+      return true;
+    }
+  },
+  mounted() {
+    //检测版本
+    // #ifdef APP
+    this.appCheckVersion();
+    // #endif
+    const { appContext } = getCurrentInstance();
+    const t = appContext.config.globalProperties.$t;
+    this.t = t;
+  },
+  created() {
+    uni.$on("upgrade-app", this.bindEmit);
+  },
+  beforeDestroy() {
+    uni.$off("upgrade-app", this.bindEmit);
+  },
+  methods: {
+    async appCheckVersion() {
+      console.log("🚀 ~ appCheckVersion ~ appCheckVersion:");
+
+      const { data: remote } = await getAppVersion();
+      console.log("checkVersion-remote", remote);
+      const up = checkVersion({
+        name: remote.appVersion, //最新版本名称
+        code: remote.appVersion, //最新版本号
+        content: "", //更新内容
+        url: remote.url, //下载链接
+        forceUpdate: true, //是否强制升级
+      });
+      console.log("🚀 ~ appCheckVersion ~ up:", up);
+      if (up) {
+        this.open();
+      }
+    },
+    open() {
+      //打开升级弹窗
+      console.log("open upgrade popup");
+      this.$refs.popup.open();
+    },
+    bindEmit(e) {
+      let { name, content, url, forceUpdate } = e;
+      this.isForceUpdate = forceUpdate;
+      this.versionName = name;
+      this.versionDesc = content;
+      this.downloadUrl = url;
+    },
+    //更新
+    handleUpgrade() {
+      if (this.downloadUrl) {
+        this.isStartDownload = true;
+        this.currentPercent = 0; // 初始化进度
+        downloadApp(this.downloadUrl, (current, downloadedSize, totalSize) => {
+          // 始终显示进度条,无需hasProgress判断
+          this.currentPercent = current;
+        })
+          .then((fileName) => {
+            this.isDownloadFinish = true;
+            this.fileName = fileName;
+            this.currentPercent = 100; // 确保最终显示100%
+            if (fileName) {
+              // 下载完成后,自动安装
+              console.log("🚀 ~ handleUpgrade ~ fileName:", fileName);
+              this.handleInstallApp();
+            }
+          })
+          .catch((e) => {
+            console.error("🚀 ~ handleUpgrade ~ e:", e);
+            this.currentPercent = 0; // 失败时重置
+          });
+      } else {
+        uni.showToast({
+          title: this.t("version.downloadLinkNotExist"),
+          icon: "none",
+        });
+      }
+    },
+    //安装app
+    handleInstallApp() {
+      //下载完成才能安装,防止下载过程中点击
+      if (this.isDownloadFinish && this.fileName) {
+        installApp(this.fileName, () => {
+          this.$refs.popup.close();
+          //安装成功,关闭升级弹窗
+          // uni.navigateBack()
+        });
+      }
+    },
+    //关闭返回
+    handleClose() {
+      // uni.navigateBack()
+      this.$refs.popup.close();
+    },
+  },
+};
+</script>
+
+<style>
+page {
+  background: rgba(0, 0, 0, 0.5);
+  /**设置窗口背景半透明*/
+}
+</style>
+<style lang="scss" scoped>
+:deep(.upgrade-unipopup) {
+  z-index: 9999 !important;
+}
+.upgrade-popup {
+  width: 580rpx;
+  height: auto;
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  background: #fff;
+  border-radius: 20rpx;
+  box-sizing: border-box;
+  border: 1px solid #eee;
+}
+
+.header-bg {
+  width: 100%;
+  margin-top: -112rpx;
+}
+
+.main {
+  padding: 10rpx 30rpx 30rpx;
+  box-sizing: border-box;
+
+  .version {
+    font-size: 36rpx;
+    color: #026df7;
+    font-weight: 700;
+    width: 100%;
+    text-align: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    letter-spacing: 1px;
+  }
+
+  .content {
+    margin-top: 60rpx;
+
+    .title {
+      font-size: 28rpx;
+      font-weight: 700;
+      color: #000000;
+    }
+
+    .desc {
+      box-sizing: border-box;
+      margin-top: 20rpx;
+      font-size: 28rpx;
+      color: #6a6a6a;
+      max-height: 40vh;
+      overflow-y: auto;
+    }
+  }
+
+  .footer {
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    flex-shrink: 0;
+    margin-top: 100rpx;
+
+    .btn {
+      width: 246rpx;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      position: relative;
+      z-index: 999;
+      height: 96rpx;
+      box-sizing: border-box;
+      font-size: 32rpx;
+      border-radius: 10rpx;
+      letter-spacing: 2rpx;
+
+      &.force {
+        width: 500rpx;
+      }
+
+      &.close {
+        border: 1px solid #e0e0e0;
+        margin-right: 25rpx;
+        color: #000;
+      }
+
+      &.upgrade {
+        background-color: #026df7;
+        color: white;
+      }
+    }
+
+    .progress-view {
+      width: 510rpx;
+      height: 90rpx;
+      display: flex;
+      position: relative;
+      align-items: center;
+      border-radius: 6rpx;
+      background-color: #dcdcdc;
+      display: flex;
+      justify-content: flex-start;
+      padding: 0px;
+      box-sizing: border-box;
+      border: none;
+      overflow: hidden;
+
+      &.active {
+        background-color: #026df7;
+      }
+
+      .progress {
+        height: 100%;
+        background-color: #026df7;
+        padding: 0px;
+        box-sizing: border-box;
+        border: none;
+        border-top-left-radius: 10rpx;
+        border-bottom-left-radius: 10rpx;
+        transition: width 0.3s ease; // 添加平滑过渡动画
+      }
+
+      .txt {
+        font-size: 28rpx;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        color: #fff;
+      }
+    }
+  }
+}
+</style>

+ 102 - 0
composables/useDingTalkLogin.js

@@ -0,0 +1,102 @@
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+export function useDingTalkLogin() {
+  const { t } = useI18n({ useScope: 'global' })
+  const loggingIn = ref(false)
+  const loginError = ref(null)
+
+  const loginByDingTalk = async () => {
+    if (loggingIn.value) return
+    
+    loggingIn.value = true
+    loginError.value = null
+    
+    try {
+      // 检查钉钉是否安装
+      const providerRes = await checkDingTalkInstalled()
+      if (!providerRes.provider.includes('dingtalk')) {
+        throw new Error(t('login.dingtalkNotInstalled'))
+      }
+      
+      // 调用钉钉登录
+      const loginRes = await uniLogin()
+      console.log('钉钉登录成功获取code:', loginRes)
+      
+      // 获取用户信息
+      const userInfo = await fetchUserInfo(loginRes.code)
+      console.log('获取用户信息成功:', userInfo)
+      
+      // 保存用户信息
+      saveUserInfo(userInfo)
+      
+      return userInfo
+    } catch (error) {
+      console.error('钉钉登录失败:', error)
+      loginError.value = error.message || t('login.failed')
+      showLoginError()
+      return null
+    } finally {
+      loggingIn.value = false
+    }
+  }
+
+  const checkDingTalkInstalled = () => {
+    return new Promise((resolve, reject) => {
+      uni.getProvider({
+        service: 'oauth',
+        success: resolve,
+        fail: reject
+      })
+    })
+  }
+
+  const uniLogin = () => {
+    return new Promise((resolve, reject) => {
+      uni.login({
+        provider: 'dingtalk',
+        success: resolve,
+        fail: reject
+      })
+    })
+  }
+
+  const fetchUserInfo = (code) => {
+    return new Promise((resolve, reject) => {
+      uni.request({
+        url: 'https://your-api.com/api/dingtalk/login',
+        method: 'POST',
+        data: { code },
+        success: (res) => {
+          if (res.data.code === 200) {
+            resolve(res.data.data)
+          } else {
+            reject(new Error(res.data.message || '获取用户信息失败'))
+          }
+        },
+        fail: (err) => {
+          reject(err)
+        }
+      })
+    })
+  }
+
+  const saveUserInfo = (userInfo) => {
+    uni.setStorageSync('userInfo', userInfo)
+    uni.setStorageSync('isLoggedIn', true)
+  }
+
+  const showLoginError = () => {
+    uni.showToast({
+      title: loginError.value,
+      icon: 'none',
+      duration: 3000
+    })
+  }
+
+  return {
+    loggingIn,
+    loginError,
+    loginByDingTalk
+  }
+}    

+ 14 - 0
config/env.dev.js

@@ -0,0 +1,14 @@
+// 开发环境配置
+export default {
+	// apiUrl: 'http://192.168.1.63:8888',  
+	apiUrl: 'https://iot.deepoil.cc',
+	// apiUrl: 'https://aims.deepoil.cc', //正式
+	apiUrlSuffix: '/admin-api',
+	// 其他开发环境配置...  
+	corpId: 'dingbe7f9a7e8cffa2bd35c2f4657eb6378f', //钉钉微应用 企业的CorpID - 正式环境
+	clientId: 'dingmr9ez0ecgbmscfeb', //钉钉微应用的Client ID - 正式环境(原企业内部应用的 AppKey。原第三方企业应用的 SuiteKey。)
+	AgentId: '3687646006', //钉钉微应用的AgentID - 正式环境
+	MiniAppId: '5000000006298501', //钉钉微应用的MiniAppID - 正式环境 
+	AppKey: 'dingmr9ez0ecgbmscfeb', //钉钉微应用的AppKey - 正式环境
+	AppSecret: 'VhG_zMdTvIBwA_0Ef8FJ0foH3VYYo5T-kw0ukX_PBA8Ah1xl7AjDw5RVYCU0DTpe', //钉钉微应用的AppSecret - 正式环境
+};

+ 14 - 0
config/env.prod.js

@@ -0,0 +1,14 @@
+// 生产环境配置
+export default {
+	// apiUrl: 'http://192.168.1.63:8888',  
+	// apiUrl: 'https://iot.deepoil.cc',	//测试
+	apiUrl: 'https://aims.deepoil.cc', //正式
+	apiUrlSuffix: '/admin-api',
+	// 其他开发环境配置...  
+	corpId: 'dingbe7f9a7e8cffa2bd35c2f4657eb6378f', //钉钉微应用 企业的CorpID - 正式环境
+	clientId: 'dingmr9ez0ecgbmscfeb', //钉钉微应用的Client ID - 正式环境(原企业内部应用的 AppKey。原第三方企业应用的 SuiteKey。)
+	AgentId: '3687646006', //钉钉微应用的AgentID - 正式环境
+	MiniAppId: '5000000006298501', //钉钉微应用的MiniAppID - 正式环境
+	AppKey: 'dingmr9ez0ecgbmscfeb', //钉钉微应用的AppKey - 正式环境
+	AppSecret: 'VhG_zMdTvIBwA_0Ef8FJ0foH3VYYo5T-kw0ukX_PBA8Ah1xl7AjDw5RVYCU0DTpe', //钉钉微应用的AppSecret - 正式环境
+};

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 485 - 0
locale/en.json

@@ -0,0 +1,485 @@
+{
+	"locale.auto": "System",
+	"locale.en": "English",
+	"locale.zh-hans": "Simplified Chinese",
+	"locale.zh-hant": "Traditional Chinese",
+	"locale.ru": "Russian",
+	"locale.ja": "Japanese",
+	"index.detail": "Details",
+	"index.language": "Language",
+	"index.languageChange": "Select Language",
+	"index.language-info": "Language Information",
+	"index.system-language": "System Language",
+	"index.application-language": "Application Language",
+	"index.language-change-confirm": "Applying this setting will restart the App",
+	"api.message": "Prompt",
+	"app.appName": "DEEP OIL",
+	"app.home": "Home",
+	"app.user": "Mine",
+	"version.newVersion": "New version found",
+	"version.updateInfo": "Update content",
+	"version.update": "Version update",
+	"version.forceUpdate": "Force update",
+	"version.downloading": "Downloading...",
+	"version.download": "Downloading",
+	"version.install": "Install now",
+	"version.updateSuccess": "Update successful",
+	"version.updateFail": "Update failed, please try again later",
+	"version.updateLater": "Remind me later",
+	"version.updateNow": "Update now",
+	"version.downloadLinkNotExist": "Download link does not exist",
+	"operation.cancel": "Cancel",
+	"operation.back": "Back",
+	"operation.confirm": "Confirm",
+	"operation.confirm1": "Confirm",
+	"operation.search": "Search",
+	"operation.view": "View",
+	"operation.edit": "Edit",
+	"operation.fill": "Fill in",
+	"operation.submit": "Submit",
+	"operation.save": "Save",
+	"operation.add": "Add",
+	"operation.select": "Select",
+	"operation.delete": "Delete",
+	"operation.please": "Please",
+	"operation.PleaseFillIn": "Please fill in",
+	"operation.PleaseSelect": "Please select",
+	"operation.PleaseInput": "Please input",
+	"operation.PleaseSet": "Please set",
+	"operation.remark": "Remark",
+	"operation.createTime": "Creation time",
+	"operation.updateTime": "Update time",
+	"operation.fillTime": "Filling time",
+	"operation.yes": "Yes",
+	"operation.no": "No",
+	"operation.success": "Operation successful",
+	"operation.fail": "Operation failed, please try again",
+	"operation.repeat": "Repeat",
+	"operation.deleteConfirm": "Are you sure to delete the selected data?",
+	"operation.dispatchUser": "Assign responsible person",
+	"operation.reject": "Reject",
+	"operation.approval": "Approve",
+	"operation.searchText": "Please enter query conditions",
+	"operation.allItem": "All items",
+	"operation.networkError": "Network error, please check network connection and try again",
+	"operation.saving": "Saving...",
+	"general.picture": "Picture",
+	"general.timeNotBeLater": "Start time cannot be later than end time",
+	"general.timeNotBeEarlier": "End time cannot be earlier than start time",
+	"general.startTimeNotBeEarlier": "Maintenance start time cannot be earlier than fault time",
+	"general.endTimeNotBeEarlier": "Maintenance end time cannot be earlier than fault time",
+	"general.submitSuccess": "Filling completed",
+	"common.searchHint": "Please enter query conditions",
+	"login.welcome": "Hello, welcome to log in",
+	"login.login": "Log in",
+	"login.loginWithDingTalk": "Log in with DingTalk",
+	"login.languageChange": "Change language",
+	"login.enterUsername": "Please enter username",
+	"login.enterPhoneNumber": "Please enter phone number",
+	"login.invalidPhoneFormat": "Invalid phone number format",
+	"login.enterPassword": "Please enter password",
+	"login.passwordRule": "Password must be at least 8 characters, including uppercase and lowercase letters, numbers and special characters\nSpecial characters include:!@#$%^&",
+	"login.dingTalkError": "DingTalk login failed",
+	"login.logoutConfirm": "Are you sure to log out?",
+	"login.h5DingTalk": "H5 does not support DingTalk login",
+	"home.todo": "To-do",
+	"home.remind": "Reminder",
+	"home.unmaintained": "Overdue for maintenance",
+	"home.uninspected": "Overdue for inspection",
+	"home.unrecorded": "Overdue for recording",
+	"home.operationRecordFilling": "Operation record filling",
+	"home.fillDailyOperationRecord": "Fill in daily operation records",
+	"home.maintenanceWorkOrder": "Maintenance work order",
+	"home.receiveMaintenanceWorkOrderAndSubmit": "Receive maintenance work orders and submit",
+	"home.equipmentMaintenance": "Equipment maintenance",
+	"home.fillMaintenanceWorkOrder": "Fill in maintenance work orders",
+	"home.inspectionWorkOrder": "Inspection work order",
+	"home.receiveInspectionWorkOrderAndSubmit": "Receive inspection work orders and submit",
+	"home.faultReporting": "Fault reporting",
+	"home.fillAndReportFaultWorkOrder": "Fill in and report fault work orders",
+	"home.inventoryQuery": "Inventory query",
+	"home.clickToQueryInventoryData": "Click to query inventory data",
+	"home.equipmentLedger": "Equipment ledger",
+	"home.viewEquipmentLedger": "View equipment ledger",
+	"home.equipmentStatusChange": "Equipment status change",
+	"home.deviceUser": "Equipment responsible person",
+	"home.deviceUserTip": "Adjust equipment responsible person",
+	"home.adjustEquipmentStatus": "Adjust equipment status",
+	"home.realTimeEquipmentDataMonitoring": "Real-time equipment data monitoring",
+	"home.viewRealTimeEquipmentData": "View real-time equipment data",
+	"home.statisticalAnalysis": "Statistical analysis",
+	"home.equipmentDataStatisticalAnalysis": "Equipment data statistical analysis",
+	"user.username": "Username",
+	"user.phone": "Phone number",
+	"user.phoneHint": "Please enter correct phone number",
+	"user.avatar": "User avatar",
+	"user.securityCenter": "Security center",
+	"user.modifyPhoneAndPassword": "Modify phone and password",
+	"user.aboutUs": "About us",
+	"user.currentVersion": "Current version",
+	"user.logout": "Log out",
+	"user.userInfo": "User information",
+	"user.updatePassword": "Update password",
+	"user.oldPassword": "Old password",
+	"user.oldPasswordHint": "Please enter old password",
+	"user.password": "New password",
+	"user.passwordHint": "Please enter new password",
+	"user.confirmPassword": "Confirm password",
+	"user.confirmPasswordHint": "Please enter confirm password",
+	"user.passwordError1": "New password is the same as old password",
+	"user.passwordError2": "New password does not match confirm password",
+	"operationRecordFilling.responsiblePerson": "Responsible person",
+	"operationRecordFilling.workOrderName": "Work order name",
+	"operationRecordFilling.belongToTeam": "Belonging team",
+	"operationRecordFilling.totalRunningTime": "Total running time",
+	"operationRecordFilling.plcNotice": "The following values are from PLC, please modify if inconsistent",
+	"operationRecordFilling.workOrderDevice": "Work order equipment",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Filled content cannot be greater than",
+	"workOrder.addDevice": "Add equipment",
+	"workOrder.addMaterial": "Add material",
+	"workOrder.selectMaterial": "Select material",
+	"workOrder.materialDetails": "Material details",
+	"workOrder.materialCode": "Material code",
+	"workOrder.materialName": "Material name",
+	"workOrder.materialCount": "Material quantity",
+	"workOrder.masterData": "Material master data",
+	"workOrder.planCode": "Plan code",
+	"workOrder.unit": "Unit",
+	"workOrder.unitPrice": "Unit price",
+	"workOrder.inventory": "Inventory",
+	"workOrder.remainingInventory": "Remaining inventory",
+	"workOrder.inventoryShortage": "Insufficient inventory",
+	"workOrder.source": "Source",
+	"workOrder.auditStatus": "Audit status",
+	"workOrder.isSolved": "Whether solved",
+	"workOrder.isHelp": "Whether to assist",
+	"workOrder.Needassistance": "Need assistance",
+	"workOrder.workOrderSource": "Work order source",
+	"workOrder.searchPlaceholder": "Enter work order number",
+	"workOrder.workOrderStatus": "Work order status",
+	"workOrder.workOrdertype": "Work order type",
+	"workOrder.workOrderNumber": "Work order number",
+	"workOrder.workOrderName": "Work order name",
+	"workOrder.executed": "Executed",
+	"workOrder.pending": "Pending execution",
+	"workOrder.responsiblePerson": "Responsible person",
+	"workOrder.viewDetails": "View details",
+	"workOrder.inventoryType": "Inventory type",
+	"workOrder.consumptionQuantity": "Consumption quantity",
+	"workOrder.yuan": "Yuan",
+	"workOrder.specification": "Specification model",
+	"workOrder.status": "Status",
+	"workOrder.materialCountEmpty": "Material quantity cannot be empty",
+	"workOrder.materialCountMustGreaterThan0": "Material quantity must be greater than 0",
+	"workOrder.executionTime": "Execution time",
+	"status.enable": "Enabled",
+	"status.disable": "Disabled",
+	"status.unaudited": "Unaudited",
+	"status.audited": "Audited",
+	"status.unfinished": "Unfinished",
+	"status.finished": "Finished",
+	"status.unsubmitted": "Unsubmitted",
+	"status.submitted": "Submitted",
+	"status.unprocessed": "Unprocessed",
+	"status.processed": "Processed",
+	"status.unsolved": "Unsolved",
+	"status.solved": "Solved",
+	"status.unfilled": "Unfilled",
+	"status.filled": "Filled",
+	"status.tobeFilled": "To be filled",
+	"device.selectDevice": "Select equipment",
+	"device.assetCode": "Asset code",
+	"device.deviceCode": "Equipment code",
+	"device.deviceName": "Equipment name",
+	"device.department": "Department",
+	"maintenanceWorkOrder.title": "Maintenance work order",
+	"maintenanceWorkOrder.totalWorkOrders": "Total work orders",
+	"maintenanceWorkOrder.createButton": "Create maintenance work order",
+	"maintenanceWorkOrder.status": "Maintenance status",
+	"maintenanceWorkOrder.temporaryCreation": "Temporary creation",
+	"maintenanceWorkOrder.planGenerator": "Plan generated",
+	"maintenanceWorkOrder.actualMaintenanceStartTime": "Actual maintenance start time",
+	"maintenanceWorkOrder.actualEndTime": "Actual maintenance end time",
+	"maintenanceWorkOrder.timeToMaintenance": "Time to maintenance",
+	"maintenanceWorkOrder.maintenanceButton": "Go to maintenance",
+	"maintenanceWorkOrder.isPostponed": "Whether postponed",
+	"maintenanceWorkOrder.createMaintenanceWorkOrder": "Create maintenance work order",
+	"maintenanceWorkOrder.editMaintenanceWorkOrder": "Fill in maintenance work order",
+	"maintenanceWorkOrder.viewMaintenanceWorkOrder": "View maintenance work order",
+	"maintenanceWorkOrder.maintenanceType": "Maintenance type",
+	"maintenanceWorkOrder.maintenanceTypeIn": "Internal",
+	"maintenanceWorkOrder.maintenanceTypeOut": "Outsourced",
+	"maintenanceWorkOrder.maintenanceCost": "Maintenance cost",
+	"maintenanceWorkOrder.otherCost": "Other costs",
+	"maintenanceWorkOrder.accumulatedRunningTime": "Accumulated running time",
+	"maintenanceWorkOrder.accumulatedRunningMileage": "Accumulated running mileage",
+	"maintenanceWorkOrder.maintenanceItems": "Maintenance items",
+	"maintenanceWorkOrder.materialSelected": "Whether materials have been selected",
+	"maintenanceWorkOrder.extendMaintenance": "Extended maintenance",
+	"maintenanceWorkOrder.timeNotBeEarlier": "Actual maintenance end time cannot be earlier than actual maintenance start time",
+	"maintenanceWorkOrder.bomEmpty": "Please add at least one equipment maintenance detail",
+	"maintenanceWorkOrder.materialEmpty": "Please add at least one material",
+	"maintenanceWorkOrder.materialUnselected": "Materials not selected",
+	"maintenanceWorkOrder.equipment": "Equipment",
+	"maintenanceWorkOrder.maintenanceItemConfiguration": "Maintenance item configuration",
+	"maintenanceWorkOrder.noMaintenanceItems": "The selected equipment has no maintenance items, please reselect",
+	"maintenanceWorkOrder.unselectedMaintenanceItems": "There are maintenance items without added materials",
+	"maintenanceWorkOrder.basicMaintenanceRecords": "Basic maintenance records",
+	"maintenanceWorkOrder.lastMaintenanceMileage": "Last maintenance mileage (KM)",
+	"maintenanceWorkOrder.delayedKilometers": "Delayed kilometers (KM)",
+	"maintenanceWorkOrder.lastMaintenanceRunningTime": "Last maintenance running time (H)",
+	"maintenanceWorkOrder.delayedDuration": "Delayed duration (H)",
+	"maintenanceWorkOrder.lastMaintenanceNaturalDate": "Last maintenance natural date",
+	"maintenanceWorkOrder.delayedNaturalDate": "Delayed natural date (D)",
+	"equipmentMaintenance.maintenanceStartTime": "Maintenance start time",
+	"equipmentMaintenance.maintenanceEndTime": "Maintenance end time",
+	"equipmentMaintenance.isStop": "Whether to stop",
+	"equipmentMaintenance.maintenanceType": "Maintenance type",
+	"equipmentMaintenance.description": "Maintenance description",
+	"equipmentMaintenance.cost": "Maintenance cost",
+	"equipmentMaintenance.maintenanceItems": "Maintenance items",
+	"fault.createButton": "Create fault work order",
+	"fault.createWorkOrder": "Create new fault work order",
+	"fault.editWorkOrder": "Edit fault work order",
+	"fault.viewWorkOrder": "View fault details",
+	"fault.faultTotal": "Total fault reports",
+	"fault.faultCode": "Fault code",
+	"fault.faultName": "Fault name",
+	"fault.faultTime": "Fault time",
+	"fault.faultResolutionTime": "Fault resolution time",
+	"fault.faultImpact": "Fault impact",
+	"fault.faultSystem": "Fault system",
+	"fault.description": "Fault description",
+	"fault.solution": "Solution",
+	"fault.timeNotBeEarlier": "Fault resolution time cannot be earlier than fault time",
+	"inspection.title": "Inspection work order",
+	"inspection.totalWorkOrders": "Total inspections",
+	"inspection.editWorkOrder": "Fill in inspection work order",
+	"inspection.viewWorkOrder": "View inspection work order details",
+	"inspection.clickView": "Click to view",
+	"inspection.proj": "Inspection project",
+	"inspection.projItem": "Inspection item",
+	"inspection.standard": "Inspection standard",
+	"inspection.standardFile": "Attachment:",
+	"inspection.isAbnormal": "Whether abnormal",
+	"inspection.abnormalDesc": "Abnormal description",
+	"inspection.normal": "Normal",
+	"inspection.abnormal": "Abnormal",
+	"inspection.last": "Previous",
+	"inspection.next": "Next",
+	"inspection.finish": "Complete submission",
+	"inspection.equipmentNum": "Number of equipment to be inspected",
+	"inspection.misNum": "Number of missed equipment",
+	"inspection.abnormalNum": "Number of abnormal equipment",
+	"inspection.pendingInspectionItems": "Pending inspection items to be filled",
+	"inspection.normalInspectionItems": "Normal inspection items",
+	"inspection.abnormalInspectionItems": "Abnormal inspection items",
+	"inventory.title": "Inventory query",
+	"inventory.searchHint": "Please enter material name",
+	"inventory.deviceName": "Equipment name:",
+	"inventory.materialName": "Material name:",
+	"inventory.costCenter": "Storage location:",
+	"inventory.quantity": "Inventory:",
+	"inventory.search.title": "Filter conditions",
+	"inventory.search.factory": "Factory",
+	"inventory.search.storageLocation": "Inventory location",
+	"inventory.search.costCenter": "Cost center",
+	"inventory.search.materialCode": "Material code",
+	"inventory.search.materialCodeHint": "Please enter material code",
+	"inventory.search.materialName": "Material name",
+	"inventory.search.materialNameHint": "Please enter material name",
+	"inventory.search.storageTime": "Storage time",
+	"inventory.search.storageTimeHint": "Please select time",
+	"inventory.search.reset": "Reset",
+	"message.title": "Message management",
+	"message.tab1": "Pending tasks",
+	"message.tab2": "Task approval",
+	"message.tab3": "System messages",
+	"message.id": "ID",
+	"message.processId": "Work order number: ",
+	"message.startUser": "Reporter: ",
+	"message.deviceName": "Equipment name: ",
+	"message.desc": "Maintenance description: ",
+	"message.faultTime": "Report time: ",
+	"message.statusName": "Latest progress: ",
+	"message.dispatchUser": "Responsible person",
+	"message.repairType": "Maintenance type",
+	"message.reason": "Approval opinion",
+	"message.reason1": "Cancellation reason",
+	"message.cancelHint": "After cancellation, the approval process will automatically end",
+	"message.form.errorHint1": "Maintenance responsible person cannot be empty",
+	"message.form.errorHint2": "Maintenance type cannot be empty",
+	"message.form.errorHint3": "New approver cannot be empty",
+	"message.form.errorHint4": "Approval opinion cannot be empty",
+	"message.form.errorHint5": "Recipient cannot be empty",
+	"message.form.errorHint6": "Co-signer cannot be empty",
+	"message.form.errorHint7": "Cancellation reason cannot be empty",
+	"message.form.user3": "New approver",
+	"message.form.user4": "Recipient",
+	"message.form.user5": "Co-signer",
+	"message.form.beforeSign": "Add co-signer before",
+	"message.form.afterSign": "Add co-signer after",
+	"approval.fault.name": "Fault name:",
+	"approval.fault.system": "Fault system:",
+	"approval.fault.ifDeal": "Whether solved:",
+	"approval.fault.status": "Status:",
+	"approval.fault.ifStop": "Whether to stop:",
+	"approval.fault.failureTime": "Fault time:",
+	"approval.fault.dealTime": "Resolution time:",
+	"approval.fault.needHelp": "Whether need assistance:",
+	"approval.fault.failureInfluence": "Fault impact:",
+	"approval.fault.solution": "Solution:",
+	"approval.fault.description": "Fault description:",
+	"approval.fault.remark": "Remark:",
+	"approval.fault.pic": "Picture:",
+	"approval.maintain.type": "Maintenance type:",
+	"approval.maintain.startTime": "Maintenance start time:",
+	"approval.maintain.endTime": "Maintenance end time:",
+	"approval.maintain.person": "Responsible person:",
+	"approval.maintain.maintainFee": "Maintenance cost:",
+	"approval.maintain.desc": "Maintenance description:",
+	"statusChange.title": "Equipment status change",
+	"statusChange.insert": "+ New  ",
+	"statusChange.searchHint": "Please enter asset code",
+	"statusChange.deviceName": "Equipment name",
+	"statusChange.deviceCode": "Equipment code",
+	"statusChange.beforeLeader": "Responsible person before adjustment",
+	"statusChange.afterLeader": "Responsible person after adjustment",
+	"statusChange.reason": "Adjustment reason",
+	"statusChange.createUser": "Adjuster",
+	"statusChange.assetCode": "Asset code",
+	"statusChange.dept": "Department",
+	"statusChange.createDate": "Creation time",
+	"statusChange.beforeStatus": "Status before change",
+	"statusChange.afterStatus": "Status after change",
+	"statusChange.history": "History",
+	"realTimeData.type": "Type:",
+	"realTimeData.detail.title": "Equipment real-time data details",
+	"realTimeData.detail.assetCode": "Asset code:",
+	"realTimeData.detail.isOnline": "Whether online:",
+	"realTimeData.detail.deviceType": "Equipment category:",
+	"realTimeData.detail.lastUpdateTime": "Last data:",
+	"realTimeData.detail.chartTitle": "Data trend",
+	"ledger.title": "Equipment ledger",
+	"ledger.deviceStatus": "Equipment status: ",
+	"ledger.form.title": "Create new ledger",
+	"ledger.form.basicInfo": "Basic information:",
+	"ledger.form.deviceCode": "Equipment code",
+	"ledger.form.deviceCodeError": "Equipment code cannot be empty",
+	"ledger.form.deviceName": "Equipment name",
+	"ledger.form.deviceNameError": "Equipment name cannot be empty",
+	"ledger.form.brand": "Brand",
+	"ledger.form.brandError": "Brand cannot be empty",
+	"ledger.form.dept": "Department",
+	"ledger.form.deptError": "Department cannot be empty",
+	"ledger.form.deviceType": "Equipment category",
+	"ledger.form.deviceTypeError": "Equipment category cannot be empty",
+	"ledger.form.deviceStatus": "Equipment status",
+	"ledger.form.deviceStatusError": "Equipment status is empty",
+	"ledger.form.assetProperty": "Asset nature",
+	"ledger.form.assetPropertyError": "Asset nature cannot be empty",
+	"ledger.form.model": "Specification model",
+	"ledger.form.image": "Picture",
+	"ledger.form.remark": "Remark",
+	"ledger.form.produceInfo": "Manufacturing information:",
+	"ledger.form.manufacturer": "Manufacturer",
+	"ledger.form.manufacturerError": "Manufacturer cannot be empty",
+	"ledger.form.manDate": "Production date",
+	"ledger.form.manDateError": "Production date cannot be empty",
+	"ledger.form.supplier": "Supplier",
+	"ledger.form.expires": "Warranty expiration",
+	"ledger.form.nameplate": "Nameplate information",
+	"ledger.form.financeInfo": "Financial information:",
+	"ledger.form.plPrice": "Purchase price",
+	"ledger.form.plDate": "Purchase date",
+	"ledger.form.plYear": "Depreciation period",
+	"ledger.form.plStartDate": "Depreciation start date",
+	"ledger.form.plMonth": "Number of depreciation months",
+	"ledger.form.plAmounted": "Accumulated depreciation amount",
+	"ledger.form.remainAmount": "Remaining amount",
+	"ledger.supplier.name": "Supplier name",
+	"ledger.supplier.nameHint": "Please enter supplier name",
+	"ledger.supplier.code": "Supplier code",
+	"ledger.supplier.type": "Supplier category",
+	"ledger.supplier.status": "Supplier status",
+	"ledger.supplier.time": "Creation time",
+	"ledger.supplier.status1": "Draft",
+	"ledger.supplier.status2": "Active",
+	"ledger.supplier.status3": "Closed",
+	"ledger.brand.hint": "Please select brand first",
+	"ledger.brand.id": "Dictionary code",
+	"ledger.brand.label": "Dictionary label",
+	"ledger.brand.labelHint": "Please enter brand name",
+	"ledger.brand.status": "Status",
+	"ledger.brand.status0": "Normal",
+	"ledger.brand.status1": "Closed",
+	"ledger.model.name": "Model name",
+	"ledger.model.standard": "Compliant standard",
+	"ledger.detail.title": "Equipment ledger details",
+	"ledger.detail.section": "Equipment information details:",
+	"ledger.detail.user": "Responsible person",
+	"ledger.detail.assetType": "Asset category",
+	"statistic.title": "Statistical analysis",
+	"statistic.tab0": "Homepage",
+	"statistic.tab1": "Maintenance statistics",
+	"statistic.tab2": "Upkeep statistics",
+	"statistic.tab3": "Inspection statistics",
+	"statistic.front.deviceCount": "Number of equipment",
+	"statistic.front.repairCount": "Maintenance work orders",
+	"statistic.front.runCount": "Number of operation record work orders",
+	"statistic.front.maintenanceCount": "Number of upkeep work orders",
+	"statistic.front.inspectionCount": "Number of inspection work orders",
+	"statistic.front.filled": "Filled",
+	"statistic.front.unfilled": "Unfilled",
+	"statistic.front.execute": "Executed",
+	"statistic.front.unexecute": "Unexecuted",
+	"statistic.front.mttr": "MTTR (Mean Time To Repair)",
+	"statistic.front.stockWarningCount": "Number of materials with inventory warning",
+	"statistic.front.deviceStatus": "Equipment status statistics",
+	"statistic.front.deviceTypeCount": "Top 5 equipment categories by quantity",
+	"statistic.front.weekUserActive": "User activity in the past week",
+	"statistic.front.totalUserCount": "Total users",
+	"statistic.front.activeUserCount": "Active users",
+	"statistic.front.workOrderCount": "Work order quantity status",
+	"statistic.repair.resolutionTime": "Average resolution time",
+	"statistic.repair.weeklyCount": "Number of work orders in the past week",
+	"statistic.repair.monthlyCount": "Number of work orders in the past month",
+	"statistic.repair.totalCount": "Total number of work orders",
+	"statistic.repair.report": "Fault report",
+	"statistic.repair.workOrder": "Maintenance work order",
+	"statistic.repair.failure.title": "Fault report status statistics",
+	"statistic.repair.failure.reporting": "Reporting",
+	"statistic.repair.failure.finished": "Processed",
+	"statistic.repair.failure.trans": "Converted to work order",
+	"statistic.repair.failure.over": "Work order processed",
+	"statistic.repair.workOrder.title": "Maintenance work order status statistics",
+	"statistic.repair.workOrder.tx": "Pending filling",
+	"statistic.repair.workOrder.finished": "Completed",
+	"statistic.repair.title": "Maintenance task statistical analysis",
+	"statistic.repair.completionRate": "Maintenance completion rate",
+	"statistic.repair.repairedCount": "Repaired",
+	"statistic.repair.pendingRepairCount": "Pending repair",
+	"statistic.maintenance.dayCount": "Number of work orders yesterday",
+	"statistic.maintenance.count1": "Total quantity",
+	"statistic.maintenance.count2": "Uncompleted",
+	"statistic.maintenance.weeklyCount": "Number of work orders in the past week",
+	"statistic.maintenance.monthlyCount": "Number of work orders in the past month",
+	"statistic.maintenance.totalCount": "Number of work orders",
+	"statistic.maintenance.workOrder.title": "Upkeep work order status statistics",
+	"statistic.maintenance.workOrder.status1": "Pending execution",
+	"statistic.maintenance.workOrder.status2": "Executed",
+	"statistic.maintenance.dayWorkOrder.title": "Today's work order status statistics",
+	"statistic.maintenance.orderType.title": "Work order type statistics",
+	"statistic.maintenance.orderType.status1": "Temporary creation",
+	"statistic.maintenance.orderType.status2": "Plan generated",
+	"statistic.inspection.workOrder.title": "Inspection work order status statistics",
+	"overtime.title": "Pending work orders",
+	"overtime.item.type": "Work order type",
+	"overtime.item.title": "Work order name",
+	"overtime.item.status": "Status",
+	"overtime.item.time": "Time",
+	"overtime.type1": "Operation record",
+	"overtime.type2": "Maintenance work order",
+	"overtime.type3": "Upkeep work order",
+	"overtime.type4": "Inspection work order"
+}

+ 12 - 0
locale/index.js

@@ -0,0 +1,12 @@
+ import en from './en.json'
+ import zhHans from './zh-Hans.json'
+ // import zhHant from './zh-Hant.json'
+ import ja from './ja.json'
+ import ru from './ru.json'
+ export default {
+ 	en,
+ 	'zh-Hans': zhHans,
+ 	// 'zh-Hant': zhHant,
+ 	ja,
+ 	ru
+ }

+ 485 - 0
locale/ja.json

@@ -0,0 +1,485 @@
+{
+	"locale.auto": "システム",
+	"locale.en": "英語",
+	"locale.zh-hans": "簡体字中国語",
+	"locale.zh-hant": "繁体字中国語",
+	"locale.ru": "ロシア語",
+	"locale.ja": "日本語",
+	"index.detail": "詳細",
+	"index.language": "言語",
+	"index.languageChange": "言語を選択",
+	"index.language-info": "言語情報",
+	"index.system-language": "システム言語",
+	"index.application-language": "アプリケーション言語",
+	"index.language-change-confirm": "この設定を適用するとアプリが再起動します",
+	"api.message": "ヒント",
+	"app.appName": "DEEP OIL",
+	"app.home": "ホーム",
+	"app.user": "マイページ",
+	"version.newVersion": "新しいバージョンが見つかりました",
+	"version.updateInfo": "更新内容",
+	"version.update": "バージョン更新",
+	"version.forceUpdate": "強制更新",
+	"version.downloading": "ダウンロード中...",
+	"version.download": "ダウンロード中",
+	"version.install": "すぐにインストール",
+	"version.updateSuccess": "更新成功",
+	"version.updateFail": "更新に失敗しました。後で再試行してください",
+	"version.updateLater": "後で再通知",
+	"version.updateNow": "すぐに更新",
+	"version.downloadLinkNotExist": "ダウンロードリンクが存在しません",
+	"operation.cancel": "キャンセル",
+	"operation.back": "戻る",
+	"operation.confirm": "確認",
+	"operation.confirm1": "確認",
+	"operation.search": "検索",
+	"operation.view": "表示",
+	"operation.edit": "編集",
+	"operation.fill": "記入",
+	"operation.submit": "送信",
+	"operation.save": "保存",
+	"operation.add": "追加",
+	"operation.select": "選択",
+	"operation.delete": "削除",
+	"operation.please": "お願いします",
+	"operation.PleaseFillIn": "記入してください",
+	"operation.PleaseSelect": "選択してください",
+	"operation.PleaseInput": "入力してください",
+	"operation.PleaseSet": "設定してください",
+	"operation.remark": "備考",
+	"operation.createTime": "作成時間",
+	"operation.updateTime": "更新時間",
+	"operation.fillTime": "記入時間",
+	"operation.yes": "はい",
+	"operation.no": "いいえ",
+	"operation.success": "操作成功",
+	"operation.fail": "操作に失敗しました。再試行してください",
+	"operation.repeat": "繰り返し",
+	"operation.deleteConfirm": "選択したデータを削除してもよろしいですか?",
+	"operation.dispatchUser": "担当者を割り当て",
+	"operation.reject": "拒否",
+	"operation.approval": "承認",
+	"operation.searchText": "検索条件を入力してください",
+	"operation.allItem": "すべての項目",
+	"operation.networkError": "ネットワークエラーが発生しました。ネットワーク接続を確認して再試行してください",
+	"operation.saving": "保存中...",
+	"general.picture": "画像",
+	"general.timeNotBeLater": "開始時間は終了時間より後にすることはできません",
+	"general.timeNotBeEarlier": "終了時間は開始時間より前にすることはできません",
+	"general.startTimeNotBeEarlier": "修理開始時間は故障時間より前にすることはできません",
+	"general.endTimeNotBeEarlier": "修理終了時間は故障時間より前にすることはできません",
+	"general.submitSuccess": "記入完了",
+	"common.searchHint": "検索条件を入力してください",
+	"login.welcome": "こんにちは、ログインへようこそ",
+	"login.login": "ログイン",
+	"login.loginWithDingTalk": "钉钉でログイン",
+	"login.languageChange": "言語を変更",
+	"login.enterUsername": "ユーザー名を入力してください",
+	"login.enterPhoneNumber": "携帯電話番号を入力してください",
+	"login.invalidPhoneFormat": "携帯電話番号の形式が正しくありません",
+	"login.enterPassword": "パスワードを入力してください",
+	"login.passwordRule": "パスワードは少なくとも8文字で、大文字と小文字、数字、特殊文字を含む必要があります\n特殊文字には以下が含まれます:!@#$%^&",
+	"login.dingTalkError": "钉钉のログインに失敗しました",
+	"login.logoutConfirm": "ログアウトしてもよろしいですか?",
+	"login.h5DingTalk": "H5は钉钉ログインをサポートしていません",
+	"home.todo": "未処理",
+	"home.remind": "リマインダー",
+	"home.unmaintained": "保養期限切れ",
+	"home.uninspected": "検査期限切れ",
+	"home.unrecorded": "記録期限切れ",
+	"home.operationRecordFilling": "運行記録入力",
+	"home.fillDailyOperationRecord": "毎日の運行記録を入力",
+	"home.maintenanceWorkOrder": "保養作業指示書",
+	"home.receiveMaintenanceWorkOrderAndSubmit": "保養作業指示書を受け取り、提出",
+	"home.equipmentMaintenance": "機器修理",
+	"home.fillMaintenanceWorkOrder": "修理作業指示書を入力",
+	"home.inspectionWorkOrder": "検査作業指示書",
+	"home.receiveInspectionWorkOrderAndSubmit": "検査作業指示書を受け取り、提出",
+	"home.faultReporting": "故障報告",
+	"home.fillAndReportFaultWorkOrder": "故障作業指示書を入力して報告",
+	"home.inventoryQuery": "在庫照会",
+	"home.clickToQueryInventoryData": "クリックして在庫データを照会",
+	"home.equipmentLedger": "機器台帳",
+	"home.viewEquipmentLedger": "機器台帳を表示",
+	"home.equipmentStatusChange": "機器状態変更",
+	"home.deviceUser": "機器担当者",
+	"home.deviceUserTip": "機器担当者を調整",
+	"home.adjustEquipmentStatus": "機器状態を調整",
+	"home.realTimeEquipmentDataMonitoring": "機器リアルタイムデータ監視",
+	"home.viewRealTimeEquipmentData": "機器リアルタイムデータを表示",
+	"home.statisticalAnalysis": "統計分析",
+	"home.equipmentDataStatisticalAnalysis": "機器データ統計分析",
+	"user.username": "ユーザー名",
+	"user.phone": "電話番号",
+	"user.phoneHint": "正しい電話番号を入力してください",
+	"user.avatar": "ユーザーアバター",
+	"user.securityCenter": "セキュリティセンター",
+	"user.modifyPhoneAndPassword": "電話とパスワードを変更",
+	"user.aboutUs": "会社概要",
+	"user.currentVersion": "現在のバージョン",
+	"user.logout": "ログアウト",
+	"user.userInfo": "ユーザー情報",
+	"user.updatePassword": "パスワードを更新",
+	"user.oldPassword": "旧パスワード",
+	"user.oldPasswordHint": "旧パスワードを入力してください",
+	"user.password": "新パスワード",
+	"user.passwordHint": "新パスワードを入力してください",
+	"user.confirmPassword": "パスワード確認",
+	"user.confirmPasswordHint": "確認用パスワードを入力してください",
+	"user.passwordError1": "新しいパスワードは旧パスワードと同じです",
+	"user.passwordError2": "新しいパスワードが確認用パスワードと一致しません",
+	"operationRecordFilling.responsiblePerson": "担当者",
+	"operationRecordFilling.workOrderName": "作業指示書名",
+	"operationRecordFilling.belongToTeam": "所属チーム",
+	"operationRecordFilling.totalRunningTime": "累計運行時間",
+	"operationRecordFilling.plcNotice": "以下の数値はPLCから取得したものです。不一致がある場合は修正してください",
+	"operationRecordFilling.workOrderDevice": "作業指示書機器",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "入力内容は次の値を超えることができません",
+	"workOrder.addDevice": "機器を追加",
+	"workOrder.addMaterial": "資材を追加",
+	"workOrder.selectMaterial": "資材を選択",
+	"workOrder.materialDetails": "資材詳細",
+	"workOrder.materialCode": "資材コード",
+	"workOrder.materialName": "資材名",
+	"workOrder.materialCount": "資材数量",
+	"workOrder.masterData": "資材マスターデータ",
+	"workOrder.planCode": "計画コード",
+	"workOrder.unit": "単位",
+	"workOrder.unitPrice": "単価",
+	"workOrder.inventory": "在庫",
+	"workOrder.remainingInventory": "残り在庫",
+	"workOrder.inventoryShortage": "在庫不足",
+	"workOrder.source": "出所",
+	"workOrder.auditStatus": "審査状態",
+	"workOrder.isSolved": "解決済みかどうか",
+	"workOrder.isHelp": "協力するかどうか",
+	"workOrder.Needassistance": "協力が必要",
+	"workOrder.workOrderSource": "作業指示書ソース",
+	"workOrder.searchPlaceholder": "作業指示書番号を入力",
+	"workOrder.workOrderStatus": "作業指示書状態",
+	"workOrder.workOrdertype": "作業指示書タイプ",
+	"workOrder.workOrderNumber": "作業指示書番号",
+	"workOrder.workOrderName": "作業指示書名",
+	"workOrder.executed": "実行済み",
+	"workOrder.pending": "実行待ち",
+	"workOrder.responsiblePerson": "担当者",
+	"workOrder.viewDetails": "詳細を表示",
+	"workOrder.inventoryType": "在庫タイプ",
+	"workOrder.consumptionQuantity": "消費数量",
+	"workOrder.yuan": "元",
+	"workOrder.specification": "仕様モデル",
+	"workOrder.status": "状態",
+	"workOrder.materialCountEmpty": "資材数量を入力してください",
+	"workOrder.materialCountMustGreaterThan0": "資材数量は0より大きくなければなりません",
+	"workOrder.executionTime": "実行時間",
+	"status.enable": "有効",
+	"status.disable": "無効",
+	"status.unaudited": "未審査",
+	"status.audited": "審査済み",
+	"status.unfinished": "未完成",
+	"status.finished": "完成済み",
+	"status.unsubmitted": "未提出",
+	"status.submitted": "提出済み",
+	"status.unprocessed": "未処理",
+	"status.processed": "処理済み",
+	"status.unsolved": "未解決",
+	"status.solved": "解決済み",
+	"status.unfilled": "未記入",
+	"status.filled": "記入済み",
+	"status.tobeFilled": "記入待ち",
+	"device.selectDevice": "機器を選択",
+	"device.assetCode": "資産コード",
+	"device.deviceCode": "機器コード",
+	"device.deviceName": "機器名",
+	"device.department": "部署",
+	"maintenanceWorkOrder.title": "保養作業指示書",
+	"maintenanceWorkOrder.totalWorkOrders": "総作業指示書数",
+	"maintenanceWorkOrder.createButton": "保養作業指示書を作成",
+	"maintenanceWorkOrder.status": "保養状態",
+	"maintenanceWorkOrder.temporaryCreation": "臨時作成",
+	"maintenanceWorkOrder.planGenerator": "計画生成",
+	"maintenanceWorkOrder.actualMaintenanceStartTime": "実際の保養開始時間",
+	"maintenanceWorkOrder.actualEndTime": "実際の保養終了時間",
+	"maintenanceWorkOrder.timeToMaintenance": "保養までの時間",
+	"maintenanceWorkOrder.maintenanceButton": "保養に行く",
+	"maintenanceWorkOrder.isPostponed": "延期するかどうか",
+	"maintenanceWorkOrder.createMaintenanceWorkOrder": "保養作業指示書を作成",
+	"maintenanceWorkOrder.editMaintenanceWorkOrder": "保養作業指示書を記入",
+	"maintenanceWorkOrder.viewMaintenanceWorkOrder": "保養作業指示書を表示",
+	"maintenanceWorkOrder.maintenanceType": "保養タイプ",
+	"maintenanceWorkOrder.maintenanceTypeIn": "内部",
+	"maintenanceWorkOrder.maintenanceTypeOut": "外部委託",
+	"maintenanceWorkOrder.maintenanceCost": "保養費用",
+	"maintenanceWorkOrder.otherCost": "その他の費用",
+	"maintenanceWorkOrder.accumulatedRunningTime": "累計運行時間",
+	"maintenanceWorkOrder.accumulatedRunningMileage": "累計運行距離",
+	"maintenanceWorkOrder.maintenanceItems": "保養項目",
+	"maintenanceWorkOrder.materialSelected": "資材が選択されているかどうか",
+	"maintenanceWorkOrder.extendMaintenance": "延長保養",
+	"maintenanceWorkOrder.timeNotBeEarlier": "実際の保養終了時間は実際の保養開始時間より前にすることはできません",
+	"maintenanceWorkOrder.bomEmpty": "少なくとも1つの機器保養明細を追加してください",
+	"maintenanceWorkOrder.materialEmpty": "少なくとも1つの資材を追加してください",
+	"maintenanceWorkOrder.materialUnselected": "資材が選択されていません",
+	"maintenanceWorkOrder.equipment": "機器",
+	"maintenanceWorkOrder.maintenanceItemConfiguration": "保養項目設定",
+	"maintenanceWorkOrder.noMaintenanceItems": "選択した機器に保養項目がありません。再選択してください",
+	"maintenanceWorkOrder.unselectedMaintenanceItems": "資材が追加されていない保養項目が存在します",
+	"maintenanceWorkOrder.basicMaintenanceRecords": "基本保養記録",
+	"maintenanceWorkOrder.lastMaintenanceMileage": "前回保養里程(KM)",
+	"maintenanceWorkOrder.delayedKilometers": "延期キロメートル(KM)",
+	"maintenanceWorkOrder.lastMaintenanceRunningTime": "前回保養運行時間(H)",
+	"maintenanceWorkOrder.delayedDuration": "延期期間(H)",
+	"maintenanceWorkOrder.lastMaintenanceNaturalDate": "前回保養日付",
+	"maintenanceWorkOrder.delayedNaturalDate": "延期日数(D)",
+	"equipmentMaintenance.maintenanceStartTime": "修理開始時間",
+	"equipmentMaintenance.maintenanceEndTime": "修理終了時間",
+	"equipmentMaintenance.isStop": "停止するかどうか",
+	"equipmentMaintenance.maintenanceType": "修理タイプ",
+	"equipmentMaintenance.description": "修理説明",
+	"equipmentMaintenance.cost": "修理費用",
+	"equipmentMaintenance.maintenanceItems": "修理項目",
+	"fault.createButton": "故障作業指示書を作成",
+	"fault.createWorkOrder": "新しい故障作業指示書を作成",
+	"fault.editWorkOrder": "故障作業指示書を編集",
+	"fault.viewWorkOrder": "故障詳細を表示",
+	"fault.faultTotal": "総故障報告数",
+	"fault.faultCode": "故障コード",
+	"fault.faultName": "故障名",
+	"fault.faultTime": "故障時間",
+	"fault.faultResolutionTime": "故障解決時間",
+	"fault.faultImpact": "故障影響",
+	"fault.faultSystem": "故障システム",
+	"fault.description": "故障説明",
+	"fault.solution": "解決方法",
+	"fault.timeNotBeEarlier": "故障解決時間は故障時間より前にすることはできません",
+	"inspection.title": "検査作業指示書",
+	"inspection.totalWorkOrders": "総検査数",
+	"inspection.editWorkOrder": "検査作業指示書を記入",
+	"inspection.viewWorkOrder": "検査作業指示書詳細を表示",
+	"inspection.clickView": "クリックして表示",
+	"inspection.proj": "検査プロジェクト",
+	"inspection.projItem": "検査項目",
+	"inspection.standard": "検査基準",
+	"inspection.standardFile": "添付ファイル:",
+	"inspection.isAbnormal": "異常かどうか",
+	"inspection.abnormalDesc": "異常説明",
+	"inspection.normal": "正常",
+	"inspection.abnormal": "異常",
+	"inspection.last": "前へ",
+	"inspection.next": "次へ",
+	"inspection.finish": "送信完了",
+	"inspection.equipmentNum": "検査対象機器数",
+	"inspection.misNum": "未検査機器数",
+	"inspection.abnormalNum": "異常機器数",
+	"inspection.pendingInspectionItems": "記入待ちの検査項目",
+	"inspection.normalInspectionItems": "正常な検査項目",
+	"inspection.abnormalInspectionItems": "異常な検査項目",
+	"inventory.title": "在庫照会",
+	"inventory.searchHint": "資材名を入力してください",
+	"inventory.deviceName": "機器名:",
+	"inventory.materialName": "資材名:",
+	"inventory.costCenter": "倉庫ロケーション:",
+	"inventory.quantity": "在庫:",
+	"inventory.search.title": "フィルタ条件",
+	"inventory.search.factory": "工場",
+	"inventory.search.storageLocation": "在庫場所",
+	"inventory.search.costCenter": "コストセンター",
+	"inventory.search.materialCode": "資材コード",
+	"inventory.search.materialCodeHint": "資材コードを入力してください",
+	"inventory.search.materialName": "資材名",
+	"inventory.search.materialNameHint": "資材名を入力してください",
+	"inventory.search.storageTime": "入庫時間",
+	"inventory.search.storageTimeHint": "時間を選択してください",
+	"inventory.search.reset": "リセット",
+	"message.title": "メッセージ管理",
+	"message.tab1": "未処理タスク",
+	"message.tab2": "タスク承認",
+	"message.tab3": "システムメッセージ",
+	"message.id": "ID",
+	"message.processId": "作業指示書番号:",
+	"message.startUser": "報告者:",
+	"message.deviceName": "機器名:",
+	"message.desc": "修理説明:",
+	"message.faultTime": "報告時間:",
+	"message.statusName": "最新進捗:",
+	"message.dispatchUser": "担当者",
+	"message.repairType": "修理タイプ",
+	"message.reason": "承認意見",
+	"message.reason1": "キャンセル理由",
+	"message.cancelHint": "キャンセル後、承認プロセスは自動的に終了します",
+	"message.form.errorHint1": "修理担当者を入力してください",
+	"message.form.errorHint2": "修理タイプを選択してください",
+	"message.form.errorHint3": "新しい承認者を入力してください",
+	"message.form.errorHint4": "承認意見を入力してください",
+	"message.form.errorHint5": "受信者を入力してください",
+	"message.form.errorHint6": "共通署名者を入力してください",
+	"message.form.errorHint7": "キャンセル理由を入力してください",
+	"message.form.user3": "新しい承認者",
+	"message.form.user4": "受信者",
+	"message.form.user5": "共通署名者",
+	"message.form.beforeSign": "前に共通署名を追加",
+	"message.form.afterSign": "後に共通署名を追加",
+	"approval.fault.name": "故障名:",
+	"approval.fault.system": "故障システム:",
+	"approval.fault.ifDeal": "解決済みかどうか:",
+	"approval.fault.status": "状態:",
+	"approval.fault.ifStop": "停止するかどうか:",
+	"approval.fault.failureTime": "故障時間:",
+	"approval.fault.dealTime": "解決時間:",
+	"approval.fault.needHelp": "協力が必要かどうか:",
+	"approval.fault.failureInfluence": "故障影響:",
+	"approval.fault.solution": "解決方法:",
+	"approval.fault.description": "故障説明:",
+	"approval.fault.remark": "備考:",
+	"approval.fault.pic": "画像:",
+	"approval.maintain.type": "修理タイプ:",
+	"approval.maintain.startTime": "修理開始時間:",
+	"approval.maintain.endTime": "修理終了時間:",
+	"approval.maintain.person": "担当者:",
+	"approval.maintain.maintainFee": "修理費用:",
+	"approval.maintain.desc": "修理説明:",
+	"statusChange.title": "機器状態変更",
+	"statusChange.insert": "+ 新規作成  ",
+	"statusChange.searchHint": "資産コードを入力してください",
+	"statusChange.deviceName": "機器名",
+	"statusChange.deviceCode": "機器コード",
+	"statusChange.beforeLeader": "調整前の担当者",
+	"statusChange.afterLeader": "調整後の担当者",
+	"statusChange.reason": "調整理由",
+	"statusChange.createUser": "調整者",
+	"statusChange.assetCode": "資産コード",
+	"statusChange.dept": "部署",
+	"statusChange.createDate": "作成時間",
+	"statusChange.beforeStatus": "変更前の状態",
+	"statusChange.afterStatus": "変更後の状態",
+	"statusChange.history": "履歴",
+	"realTimeData.type": "タイプ:",
+	"realTimeData.detail.title": "機器リアルタイムデータ詳細",
+	"realTimeData.detail.assetCode": "資産コード:",
+	"realTimeData.detail.isOnline": "オンラインかどうか:",
+	"realTimeData.detail.deviceType": "機器カテゴリ:",
+	"realTimeData.detail.lastUpdateTime": "最終データ:",
+	"realTimeData.detail.chartTitle": "データ傾向",
+	"ledger.title": "機器台帳",
+	"ledger.deviceStatus": "機器状態:",
+	"ledger.form.title": "新しい台帳を作成",
+	"ledger.form.basicInfo": "基本情報:",
+	"ledger.form.deviceCode": "機器コード",
+	"ledger.form.deviceCodeError": "機器コードを入力してください",
+	"ledger.form.deviceName": "機器名",
+	"ledger.form.deviceNameError": "機器名を入力してください",
+	"ledger.form.brand": "ブランド",
+	"ledger.form.brandError": "ブランドを入力してください",
+	"ledger.form.dept": "部署",
+	"ledger.form.deptError": "部署を入力してください",
+	"ledger.form.deviceType": "機器カテゴリ",
+	"ledger.form.deviceTypeError": "機器カテゴリを入力してください",
+	"ledger.form.deviceStatus": "機器状態",
+	"ledger.form.deviceStatusError": "機器状態が空です",
+	"ledger.form.assetProperty": "資産性質",
+	"ledger.form.assetPropertyError": "資産性質を入力してください",
+	"ledger.form.model": "仕様モデル",
+	"ledger.form.image": "画像",
+	"ledger.form.remark": "備考",
+	"ledger.form.produceInfo": "製造情報:",
+	"ledger.form.manufacturer": "製造業者",
+	"ledger.form.manufacturerError": "製造業者を入力してください",
+	"ledger.form.manDate": "生産日",
+	"ledger.form.manDateError": "生産日を入力してください",
+	"ledger.form.supplier": "供給業者",
+	"ledger.form.expires": "保証期限",
+	"ledger.form.nameplate": "銘板情報",
+	"ledger.form.financeInfo": "財務情報:",
+	"ledger.form.plPrice": "購入価格",
+	"ledger.form.plDate": "購入日",
+	"ledger.form.plYear": "減価償却期間",
+	"ledger.form.plStartDate": "減価償却開始日",
+	"ledger.form.plMonth": "減価償却月数",
+	"ledger.form.plAmounted": "累計減価償却額",
+	"ledger.form.remainAmount": "残額",
+	"ledger.supplier.name": "取引先名",
+	"ledger.supplier.nameHint": "供給業者名を入力してください",
+	"ledger.supplier.code": "取引先コード",
+	"ledger.supplier.type": "取引先カテゴリ",
+	"ledger.supplier.status": "取引先状態",
+	"ledger.supplier.time": "作成時間",
+	"ledger.supplier.status1": "ドラフト",
+	"ledger.supplier.status2": "活動中",
+	"ledger.supplier.status3": "閉鎖",
+	"ledger.brand.hint": "先にブランドを選択してください",
+	"ledger.brand.id": "辞書コード",
+	"ledger.brand.label": "辞書ラベル",
+	"ledger.brand.labelHint": "ブランド名を入力してください",
+	"ledger.brand.status": "状態",
+	"ledger.brand.status0": "正常",
+	"ledger.brand.status1": "閉鎖",
+	"ledger.model.name": "モデル名",
+	"ledger.model.standard": "適合基準",
+	"ledger.detail.title": "機器台帳詳細",
+	"ledger.detail.section": "機器情報詳細:",
+	"ledger.detail.user": "担当者",
+	"ledger.detail.assetType": "資産カテゴリ",
+	"statistic.title": "統計分析",
+	"statistic.tab0": "ホームページ",
+	"statistic.tab1": "修理統計",
+	"statistic.tab2": "保養統計",
+	"statistic.tab3": "検査統計",
+	"statistic.front.deviceCount": "機器数",
+	"statistic.front.repairCount": "修理作業指示書",
+	"statistic.front.runCount": "運行記録作業指示書数",
+	"statistic.front.maintenanceCount": "保養作業指示書数",
+	"statistic.front.inspectionCount": "検査作業指示書数",
+	"statistic.front.filled": "記入済み",
+	"statistic.front.unfilled": "未記入",
+	"statistic.front.execute": "実行済み",
+	"statistic.front.unexecute": "未実行",
+	"statistic.front.mttr": "MTTR(平均修復時間)",
+	"statistic.front.stockWarningCount": "在庫警告資材数",
+	"statistic.front.deviceStatus": "機器状態統計",
+	"statistic.front.deviceTypeCount": "数量トップ5の機器カテゴリ",
+	"statistic.front.weekUserActive": "過去一週間のユーザー活動状況",
+	"statistic.front.totalUserCount": "総ユーザー数",
+	"statistic.front.activeUserCount": "活動ユーザー数",
+	"statistic.front.workOrderCount": "作業指示書数量状況",
+	"statistic.repair.resolutionTime": "平均解決時間",
+	"statistic.repair.weeklyCount": "過去一週間の作業指示書数",
+	"statistic.repair.monthlyCount": "過去一か月の作業指示書数",
+	"statistic.repair.totalCount": "総作業指示書数",
+	"statistic.repair.report": "故障報告",
+	"statistic.repair.workOrder": "修理作業指示書",
+	"statistic.repair.failure.title": "故障報告状態統計",
+	"statistic.repair.failure.reporting": "報告中",
+	"statistic.repair.failure.finished": "処理済み",
+	"statistic.repair.failure.trans": "作業指示書に変換",
+	"statistic.repair.failure.over": "作業指示書処理済み",
+	"statistic.repair.workOrder.title": "修理作業指示書状態統計",
+	"statistic.repair.workOrder.tx": "記入待ち",
+	"statistic.repair.workOrder.finished": "完了済み",
+	"statistic.repair.title": "修理タスク統計分析",
+	"statistic.repair.completionRate": "修理完了率",
+	"statistic.repair.repairedCount": "修理済み",
+	"statistic.repair.pendingRepairCount": "修理待ち",
+	"statistic.maintenance.dayCount": "昨日の作業指示書数",
+	"statistic.maintenance.count1": "総数量",
+	"statistic.maintenance.count2": "未完成",
+	"statistic.maintenance.weeklyCount": "過去一週間の作業指示書数",
+	"statistic.maintenance.monthlyCount": "過去一か月の作業指示書数",
+	"statistic.maintenance.totalCount": "作業指示書数",
+	"statistic.maintenance.workOrder.title": "保養作業指示書状態統計",
+	"statistic.maintenance.workOrder.status1": "実行待ち",
+	"statistic.maintenance.workOrder.status2": "実行済み",
+	"statistic.maintenance.dayWorkOrder.title": "今日の作業指示書状態統計",
+	"statistic.maintenance.orderType.title": "作業指示書タイプ統計",
+	"statistic.maintenance.orderType.status1": "臨時作成",
+	"statistic.maintenance.orderType.status2": "計画生成",
+	"statistic.inspection.workOrder.title": "検査作業指示書状態統計",
+	"overtime.title": "処理待ち作業指示書",
+	"overtime.item.type": "作業指示書タイプ",
+	"overtime.item.title": "作業指示書名",
+	"overtime.item.status": "状態",
+	"overtime.item.time": "時間",
+	"overtime.type1": "運行記録",
+	"overtime.type2": "修理作業指示書",
+	"overtime.type3": "保養作業指示書",
+	"overtime.type4": "検査作業指示書"
+}

+ 485 - 0
locale/ru.json

@@ -0,0 +1,485 @@
+{
+	"locale.auto": "Система",
+	"locale.en": "Английский",
+	"locale.zh-hans": "Простой китайский",
+	"locale.zh-hant": "Традиционный китайский",
+	"locale.ru": "Русский",
+	"locale.ja": "Японский",
+	"index.detail": "Детали",
+	"index.language": "Язык",
+	"index.languageChange": "Выберите язык",
+	"index.language-info": "Языковая информация",
+	"index.system-language": "Системный язык",
+	"index.application-language": "Язык приложения",
+	"index.language-change-confirm": "Применение этого параметра перезапустит приложение",
+	"api.message": "Подсказка",
+	"app.appName": "DEEP OIL",
+	"app.home": "Главная",
+	"app.user": "Мой",
+	"version.newVersion": "Найдена новая версия",
+	"version.updateInfo": "Содержимое обновления",
+	"version.update": "Обновление версии",
+	"version.forceUpdate": "Принудительное обновление",
+	"version.downloading": "Загрузка...",
+	"version.download": "Загрузка",
+	"version.install": "Установить сейчас",
+	"version.updateSuccess": "Обновление успешно",
+	"version.updateFail": "Обновление не удалось, попробуйте позже",
+	"version.updateLater": "Напомнить позже",
+	"version.updateNow": "Обновить сейчас",
+	"version.downloadLinkNotExist": "Ссылка для скачивания не существует",
+	"operation.cancel": "Отмена",
+	"operation.back": "Назад",
+	"operation.confirm": "Подтвердить",
+	"operation.confirm1": "Подтвердить",
+	"operation.search": "Поиск",
+	"operation.view": "Просмотр",
+	"operation.edit": "Редактировать",
+	"operation.fill": "Заполнить",
+	"operation.submit": "Отправить",
+	"operation.save": "Сохранить",
+	"operation.add": "Добавить",
+	"operation.select": "Выбрать",
+	"operation.delete": "Удалить",
+	"operation.please": "Пожалуйста",
+	"operation.PleaseFillIn": "Пожалуйста, заполните",
+	"operation.PleaseSelect": "Пожалуйста, выберите",
+	"operation.PleaseInput": "Пожалуйста, введите",
+	"operation.PleaseSet": "Пожалуйста, настройте",
+	"operation.remark": "Примечание",
+	"operation.createTime": "Время создания",
+	"operation.updateTime": "Время обновления",
+	"operation.fillTime": "Время заполнения",
+	"operation.yes": "Да",
+	"operation.no": "Нет",
+	"operation.success": "Операция успешна",
+	"operation.fail": "Операция не удалась, попробуйте снова",
+	"operation.repeat": "Повтор",
+	"operation.deleteConfirm": "Вы уверены, что хотите удалить выбранные данные?",
+	"operation.dispatchUser": "Назначить ответственного",
+	"operation.reject": "Отклонить",
+	"operation.approval": "Одобрить",
+	"operation.searchText": "Пожалуйста, введите условия поиска",
+	"operation.allItem": "Все элементы",
+	"operation.networkError": "Сетевая ошибка, проверьте сетевое соединение и попробуйте снова",
+	"operation.saving": "Сохранение...",
+	"general.picture": "Изображение",
+	"general.timeNotBeLater": "Время начала не может быть позже времени окончания",
+	"general.timeNotBeEarlier": "Время окончания не может быть раньше времени начала",
+	"general.startTimeNotBeEarlier": "Время начала ремонта не может быть раньше времени поломки",
+	"general.endTimeNotBeEarlier": "Время окончания ремонта не может быть раньше времени поломки",
+	"general.submitSuccess": "Заполнение завершено",
+	"common.searchHint": "Пожалуйста, введите условия поиска",
+	"login.welcome": "Здравствуйте, добро пожаловать в систему",
+	"login.login": "Войти",
+	"login.loginWithDingTalk": "Войти через DingTalk",
+	"login.languageChange": "Сменить язык",
+	"login.enterUsername": "Пожалуйста, введите имя пользователя",
+	"login.enterPhoneNumber": "Пожалуйста, введите номер телефона",
+	"login.invalidPhoneFormat": "Неверный формат номера телефона",
+	"login.enterPassword": "Пожалуйста, введите пароль",
+	"login.passwordRule": "Пароль должен содержать не менее 8 символов, включая прописные и строчные буквы, цифры и специальные символы\nСпециальные символы:!@#$%^&",
+	"login.dingTalkError": "Ошибка входа через DingTalk",
+	"login.logoutConfirm": "Вы уверены, что хотите выйти?",
+	"login.h5DingTalk": "H5 не поддерживает вход через DingTalk",
+	"home.todo": "Задачи",
+	"home.remind": "Напоминание",
+	"home.unmaintained": "Просроченное обслуживание",
+	"home.uninspected": "Просроченная инспекция",
+	"home.unrecorded": "Просроченная запись",
+	"home.operationRecordFilling": "Заполнение операционных записей",
+	"home.fillDailyOperationRecord": "Заполнение ежедневных операционных записей",
+	"home.maintenanceWorkOrder": "Рабочий заказ на обслуживание",
+	"home.receiveMaintenanceWorkOrderAndSubmit": "Получить рабочий заказ на обслуживание и отправить",
+	"home.equipmentMaintenance": "Ремонт оборудования",
+	"home.fillMaintenanceWorkOrder": "Заполнение рабочего заказа на ремонт",
+	"home.inspectionWorkOrder": "Рабочий заказ на инспекцию",
+	"home.receiveInspectionWorkOrderAndSubmit": "Получить рабочий заказ на инспекцию и отправить",
+	"home.faultReporting": "Сообщение о неисправности",
+	"home.fillAndReportFaultWorkOrder": "Заполнение и отправка рабочего заказа на неисправность",
+	"home.inventoryQuery": "Запрос инвентаря",
+	"home.clickToQueryInventoryData": "Нажмите, чтобы запросить данные инвентаря",
+	"home.equipmentLedger": "Книга учета оборудования",
+	"home.viewEquipmentLedger": "Просмотр книги учета оборудования",
+	"home.equipmentStatusChange": "Изменение статуса оборудования",
+	"home.deviceUser": "Ответственный за оборудование",
+	"home.deviceUserTip": "Изменить ответственного за оборудование",
+	"home.adjustEquipmentStatus": "Изменить статус оборудования",
+	"home.realTimeEquipmentDataMonitoring": "Мониторинг реальных данных оборудования",
+	"home.viewRealTimeEquipmentData": "Просмотр реальных данных оборудования",
+	"home.statisticalAnalysis": "Статистический анализ",
+	"home.equipmentDataStatisticalAnalysis": "Статистический анализ данных оборудования",
+	"user.username": "Имя пользователя",
+	"user.phone": "Номер телефона",
+	"user.phoneHint": "Пожалуйста, введите правильный номер телефона",
+	"user.avatar": "Аватар пользователя",
+	"user.securityCenter": "Безопасность",
+	"user.modifyPhoneAndPassword": "Изменить телефон и пароль",
+	"user.aboutUs": "О нас",
+	"user.currentVersion": "Текущая версия",
+	"user.logout": "Выйти",
+	"user.userInfo": "Информация о пользователе",
+	"user.updatePassword": "Обновить пароль",
+	"user.oldPassword": "Старый пароль",
+	"user.oldPasswordHint": "Пожалуйста, введите старый пароль",
+	"user.password": "Новый пароль",
+	"user.passwordHint": "Пожалуйста, введите новый пароль",
+	"user.confirmPassword": "Подтвердить пароль",
+	"user.confirmPasswordHint": "Пожалуйста, введите подтверждение пароля",
+	"user.passwordError1": "Новый пароль совпадает со старым",
+	"user.passwordError2": "Новый пароль не совпадает с подтверждением",
+	"operationRecordFilling.responsiblePerson": "Ответственный",
+	"operationRecordFilling.workOrderName": "Название рабочего заказа",
+	"operationRecordFilling.belongToTeam": "Принадлежащая команда",
+	"operationRecordFilling.totalRunningTime": "Общее время работы",
+	"operationRecordFilling.plcNotice": "Следующие значения получены из PLC, при несоответствии исправьте",
+	"operationRecordFilling.workOrderDevice": "Оборудование рабочего заказа",
+	"operationRecordFilling.fillContentCannotGreaterThanThreshold": "Заполненное содержимое не может превышать",
+	"workOrder.addDevice": "Добавить оборудование",
+	"workOrder.addMaterial": "Добавить материал",
+	"workOrder.selectMaterial": "Выбрать материал",
+	"workOrder.materialDetails": "Детали материала",
+	"workOrder.materialCode": "Код материала",
+	"workOrder.materialName": "Название материала",
+	"workOrder.materialCount": "Количество материала",
+	"workOrder.masterData": "Мастер-данные материалов",
+	"workOrder.planCode": "Плановый код",
+	"workOrder.unit": "Единица",
+	"workOrder.unitPrice": "Цена за единицу",
+	"workOrder.inventory": "Инвентарь",
+	"workOrder.remainingInventory": "Оставшийся инвентарь",
+	"workOrder.inventoryShortage": "Недостаток инвентаря",
+	"workOrder.source": "Источник",
+	"workOrder.auditStatus": "Статус аудита",
+	"workOrder.isSolved": "Решено ли",
+	"workOrder.isHelp": "Нужна ли помощь",
+	"workOrder.Needassistance": "Нужна помощь",
+	"workOrder.workOrderSource": "Источник рабочего заказа",
+	"workOrder.searchPlaceholder": "Введите номер рабочего заказа",
+	"workOrder.workOrderStatus": "Статус рабочего заказа",
+	"workOrder.workOrdertype": "Тип рабочего заказа",
+	"workOrder.workOrderNumber": "Номер рабочего заказа",
+	"workOrder.workOrderName": "Название рабочего заказа",
+	"workOrder.executed": "Исполнен",
+	"workOrder.pending": "В ожидании исполнения",
+	"workOrder.responsiblePerson": "Ответственный",
+	"workOrder.viewDetails": "Просмотреть детали",
+	"workOrder.inventoryType": "Тип инвентаря",
+	"workOrder.consumptionQuantity": "Количество потребления",
+	"workOrder.yuan": "Юань",
+	"workOrder.specification": "Спецификация",
+	"workOrder.status": "Статус",
+	"workOrder.materialCountEmpty": "Количество материала не может быть пустым",
+	"workOrder.materialCountMustGreaterThan0": "Количество материала должно быть больше 0",
+	"workOrder.executionTime": "Время исполнения",
+	"status.enable": "Включен",
+	"status.disable": "Отключен",
+	"status.unaudited": "Неаудитированный",
+	"status.audited": "Аудитированный",
+	"status.unfinished": "Незавершенный",
+	"status.finished": "Завершенный",
+	"status.unsubmitted": "Неотправленный",
+	"status.submitted": "Отправленный",
+	"status.unprocessed": "Нобработанный",
+	"status.processed": "Обработанный",
+	"status.unsolved": "Нерешенный",
+	"status.solved": "Решенный",
+	"status.unfilled": "Незаполненный",
+	"status.filled": "Заполненный",
+	"status.tobeFilled": "К заполнению",
+	"device.selectDevice": "Выбрать оборудование",
+	"device.assetCode": "Код актива",
+	"device.deviceCode": "Код оборудования",
+	"device.deviceName": "Название оборудования",
+	"device.department": "Отдел",
+	"maintenanceWorkOrder.title": "Рабочий заказ на обслуживание",
+	"maintenanceWorkOrder.totalWorkOrders": "Всего рабочих заказов",
+	"maintenanceWorkOrder.createButton": "Создать рабочий заказ на обслуживание",
+	"maintenanceWorkOrder.status": "Статус обслуживания",
+	"maintenanceWorkOrder.temporaryCreation": "Временное создание",
+	"maintenanceWorkOrder.planGenerator": "Плановое создание",
+	"maintenanceWorkOrder.actualMaintenanceStartTime": "Фактическое время начала обслуживания",
+	"maintenanceWorkOrder.actualEndTime": "Фактическое время окончания обслуживания",
+	"maintenanceWorkOrder.timeToMaintenance": "До обслуживания",
+	"maintenanceWorkOrder.maintenanceButton": "Перейти к обслуживанию",
+	"maintenanceWorkOrder.isPostponed": "Отложено ли",
+	"maintenanceWorkOrder.createMaintenanceWorkOrder": "Создать рабочий заказ на обслуживание",
+	"maintenanceWorkOrder.editMaintenanceWorkOrder": "Заполнить рабочий заказ на обслуживание",
+	"maintenanceWorkOrder.viewMaintenanceWorkOrder": "Просмотреть рабочий заказ на обслуживание",
+	"maintenanceWorkOrder.maintenanceType": "Тип обслуживания",
+	"maintenanceWorkOrder.maintenanceTypeIn": "Внутренний",
+	"maintenanceWorkOrder.maintenanceTypeOut": "Контрактный",
+	"maintenanceWorkOrder.maintenanceCost": "Стоимость обслуживания",
+	"maintenanceWorkOrder.otherCost": "Другие расходы",
+	"maintenanceWorkOrder.accumulatedRunningTime": "Суммарное время работы",
+	"maintenanceWorkOrder.accumulatedRunningMileage": "Суммарный пробег",
+	"maintenanceWorkOrder.maintenanceItems": "Пункты обслуживания",
+	"maintenanceWorkOrder.materialSelected": "Выбраны ли материалы",
+	"maintenanceWorkOrder.extendMaintenance": "Продление обслуживания",
+	"maintenanceWorkOrder.timeNotBeEarlier": "Фактическое время окончания обслуживания не может быть раньше фактического времени начала",
+	"maintenanceWorkOrder.bomEmpty": "Добавьте хотя бы один пункт обслуживания оборудования",
+	"maintenanceWorkOrder.materialEmpty": "Добавьте хотя бы один материал",
+	"maintenanceWorkOrder.materialUnselected": "Материалы не выбраны",
+	"maintenanceWorkOrder.equipment": "Оборудование",
+	"maintenanceWorkOrder.maintenanceItemConfiguration": "Конфигурация пунктов обслуживания",
+	"maintenanceWorkOrder.noMaintenanceItems": "Выбранное оборудование не имеет пунктов обслуживания, выберите другое",
+	"maintenanceWorkOrder.unselectedMaintenanceItems": "Имеются пункты обслуживания без добавленных материалов",
+	"maintenanceWorkOrder.basicMaintenanceRecords": "Базовые записи обслуживания",
+	"maintenanceWorkOrder.lastMaintenanceMileage": "Пробег при последнем обслуживании (км)",
+	"maintenanceWorkOrder.delayedKilometers": "Продолжительность задержки (км)",
+	"maintenanceWorkOrder.lastMaintenanceRunningTime": "Время работы при последнем обслуживании (ч)",
+	"maintenanceWorkOrder.delayedDuration": "Продолжительность задержки (ч)",
+	"maintenanceWorkOrder.lastMaintenanceNaturalDate": "Дата последнего обслуживания",
+	"maintenanceWorkOrder.delayedNaturalDate": "Продолжительность задержки (дней)",
+	"equipmentMaintenance.maintenanceStartTime": "Время начала ремонта",
+	"equipmentMaintenance.maintenanceEndTime": "Время окончания ремонта",
+	"equipmentMaintenance.isStop": "Остановлено ли",
+	"equipmentMaintenance.maintenanceType": "Тип ремонта",
+	"equipmentMaintenance.description": "Описание ремонта",
+	"equipmentMaintenance.cost": "Стоимость ремонта",
+	"equipmentMaintenance.maintenanceItems": "Пункты ремонта",
+	"fault.createButton": "Создать рабочий заказ на неисправность",
+	"fault.createWorkOrder": "Создать новый рабочий заказ на неисправность",
+	"fault.editWorkOrder": "Редактировать рабочий заказ на неисправность",
+	"fault.viewWorkOrder": "Просмотреть детали неисправности",
+	"fault.faultTotal": "Общее количество сообщений о неисправностях",
+	"fault.faultCode": "Код неисправности",
+	"fault.faultName": "Название неисправности",
+	"fault.faultTime": "Время неисправности",
+	"fault.faultResolutionTime": "Время решения неисправности",
+	"fault.faultImpact": "Воздействие неисправности",
+	"fault.faultSystem": "Система с неисправностью",
+	"fault.description": "Описание неисправности",
+	"fault.solution": "Решение",
+	"fault.timeNotBeEarlier": "Время решения неисправности не может быть раньше времени ее возникновения",
+	"inspection.title": "Рабочий заказ на инспекцию",
+	"inspection.totalWorkOrders": "Общее количество инспекций",
+	"inspection.editWorkOrder": "Заполнить рабочий заказ на инспекцию",
+	"inspection.viewWorkOrder": "Просмотреть детали рабочего заказа на инспекцию",
+	"inspection.clickView": "Нажмите для просмотра",
+	"inspection.proj": "Инспекционный проект",
+	"inspection.projItem": "Пункт инспекции",
+	"inspection.standard": "Инспекционный стандарт",
+	"inspection.standardFile": "Вложение:",
+	"inspection.isAbnormal": "Является ли аномалией",
+	"inspection.abnormalDesc": "Описание аномалии",
+	"inspection.normal": "Нормальный",
+	"inspection.abnormal": "Аномальный",
+	"inspection.last": "Предыдущий",
+	"inspection.next": "Следующий",
+	"inspection.finish": "Завершить отправку",
+	"inspection.equipmentNum": "Количество оборудования для инспекции",
+	"inspection.misNum": "Количество пропущенного оборудования",
+	"inspection.abnormalNum": "Количество оборудования с аномалиями",
+	"inspection.pendingInspectionItems": "Пункты инспекции, подлежащие заполнению",
+	"inspection.normalInspectionItems": "Нормальные пункты инспекции",
+	"inspection.abnormalInspectionItems": "Аномальные пункты инспекции",
+	"inventory.title": "Запрос инвентаря",
+	"inventory.searchHint": "Пожалуйста, введите название материала",
+	"inventory.deviceName": "Название оборудования:",
+	"inventory.materialName": "Название материала:",
+	"inventory.costCenter": "Место хранения:",
+	"inventory.quantity": "Инвентарь:",
+	"inventory.search.title": "Фильтры",
+	"inventory.search.factory": "Завод",
+	"inventory.search.storageLocation": "Место хранения",
+	"inventory.search.costCenter": "Центр затрат",
+	"inventory.search.materialCode": "Код материала",
+	"inventory.search.materialCodeHint": "Пожалуйста, введите код материала",
+	"inventory.search.materialName": "Название материала",
+	"inventory.search.materialNameHint": "Пожалуйста, введите название материала",
+	"inventory.search.storageTime": "Время поступления",
+	"inventory.search.storageTimeHint": "Пожалуйста, выберите время",
+	"inventory.search.reset": "Сбросить",
+	"message.title": "Управление сообщениями",
+	"message.tab1": "Предстоящие задачи",
+	"message.tab2": "Одобрение задач",
+	"message.tab3": "Системные сообщения",
+	"message.id": "Идентификатор",
+	"message.processId": "Номер рабочего заказа: ",
+	"message.startUser": "Сообщитель: ",
+	"message.deviceName": "Название оборудования: ",
+	"message.desc": "Описание ремонта: ",
+	"message.faultTime": "Время сообщения: ",
+	"message.statusName": "Последний прогресс: ",
+	"message.dispatchUser": "Ответственный",
+	"message.repairType": "Тип ремонта",
+	"message.reason": "Мнение по одобрению",
+	"message.reason1": "Причина отмены",
+	"message.cancelHint": "После отмены процесс одобрения автоматически завершится",
+	"message.form.errorHint1": "Ответственный за ремонт не может быть пустым",
+	"message.form.errorHint2": "Тип ремонта не может быть пустым",
+	"message.form.errorHint3": "Новый одобряющий не может быть пустым",
+	"message.form.errorHint4": "Мнение по одобрению не может быть пустым",
+	"message.form.errorHint5": "Получатель не может быть пустым",
+	"message.form.errorHint6": "Совместный подписант не может быть пустым",
+	"message.form.errorHint7": "Причина отмены не может быть пустой",
+	"message.form.user3": "Новый одобряющий",
+	"message.form.user4": "Получатель",
+	"message.form.user5": "Совместный подписант",
+	"message.form.beforeSign": "Добавить совместный подписант перед",
+	"message.form.afterSign": "Добавить совместный подписант после",
+	"approval.fault.name": "Название неисправности:",
+	"approval.fault.system": "Система с неисправностью:",
+	"approval.fault.ifDeal": "Решена ли:",
+	"approval.fault.status": "Статус:",
+	"approval.fault.ifStop": "Остановлено ли:",
+	"approval.fault.failureTime": "Время неисправности:",
+	"approval.fault.dealTime": "Время решения:",
+	"approval.fault.needHelp": "Нужна ли помощь:",
+	"approval.fault.failureInfluence": "Воздействие неисправности:",
+	"approval.fault.solution": "Решение:",
+	"approval.fault.description": "Описание неисправности:",
+	"approval.fault.remark": "Примечание:",
+	"approval.fault.pic": "Изображение:",
+	"approval.maintain.type": "Тип ремонта:",
+	"approval.maintain.startTime": "Время начала ремонта:",
+	"approval.maintain.endTime": "Время окончания ремонта:",
+	"approval.maintain.person": "Ответственный:",
+	"approval.maintain.maintainFee": "Стоимость ремонта:",
+	"approval.maintain.desc": "Описание ремонта:",
+	"statusChange.title": "Изменение статуса оборудования",
+	"statusChange.insert": "+ Новое создание  ",
+	"statusChange.searchHint": "Пожалуйста, введите код актива",
+	"statusChange.deviceName": "Название оборудования",
+	"statusChange.deviceCode": "Код оборудования",
+	"statusChange.beforeLeader": "Ответственный до изменения",
+	"statusChange.afterLeader": "Ответственный после изменения",
+	"statusChange.reason": "Причина изменения",
+	"statusChange.createUser": "Изменивший",
+	"statusChange.assetCode": "Код актива",
+	"statusChange.dept": "Отдел",
+	"statusChange.createDate": "Время создания",
+	"statusChange.beforeStatus": "Статус до изменения",
+	"statusChange.afterStatus": "Статус после изменения",
+	"statusChange.history": "История",
+	"realTimeData.type": "Тип:",
+	"realTimeData.detail.title": "Детали реальных данных оборудования",
+	"realTimeData.detail.assetCode": "Код актива:",
+	"realTimeData.detail.isOnline": "Онлайн ли:",
+	"realTimeData.detail.deviceType": "Категория оборудования:",
+	"realTimeData.detail.lastUpdateTime": "Последние данные:",
+	"realTimeData.detail.chartTitle": "Тенденция данных",
+	"ledger.title": "Книга учета оборудования",
+	"ledger.deviceStatus": "Статус оборудования: ",
+	"ledger.form.title": "Создать новую книгу учета",
+	"ledger.form.basicInfo": "Базовая информация:",
+	"ledger.form.deviceCode": "Код оборудования",
+	"ledger.form.deviceCodeError": "Код оборудования не может быть пустым",
+	"ledger.form.deviceName": "Название оборудования",
+	"ledger.form.deviceNameError": "Название оборудования не может быть пустым",
+	"ledger.form.brand": "Бренд",
+	"ledger.form.brandError": "Бренд не может быть пустым",
+	"ledger.form.dept": "Отдел",
+	"ledger.form.deptError": "Отдел не может быть пустым",
+	"ledger.form.deviceType": "Категория оборудования",
+	"ledger.form.deviceTypeError": "Категория оборудования не может быть пустой",
+	"ledger.form.deviceStatus": "Статус оборудования",
+	"ledger.form.deviceStatusError": "Статус оборудования пустой",
+	"ledger.form.assetProperty": "Свойство актива",
+	"ledger.form.assetPropertyError": "Свойство актива не может быть пустым",
+	"ledger.form.model": "Спецификация",
+	"ledger.form.image": "Изображение",
+	"ledger.form.remark": "Примечание",
+	"ledger.form.produceInfo": "Производственная информация:",
+	"ledger.form.manufacturer": "Производитель",
+	"ledger.form.manufacturerError": "Производитель не может быть пустым",
+	"ledger.form.manDate": "Дата производства",
+	"ledger.form.manDateError": "Дата производства не может быть пустой",
+	"ledger.form.supplier": "Поставщик",
+	"ledger.form.expires": "Окончание гарантии",
+	"ledger.form.nameplate": "Данные таблички",
+	"ledger.form.financeInfo": "Финансовая информация:",
+	"ledger.form.plPrice": "Закупочная цена",
+	"ledger.form.plDate": "Дата покупки",
+	"ledger.form.plYear": "Срок амортизации",
+	"ledger.form.plStartDate": "Дата начала амортизации",
+	"ledger.form.plMonth": "Количество месяцев амортизации",
+	"ledger.form.plAmounted": "Сумма амортизации",
+	"ledger.form.remainAmount": "Оставшаяся сумма",
+	"ledger.supplier.name": "Название поставщика",
+	"ledger.supplier.nameHint": "Пожалуйста, введите название поставщика",
+	"ledger.supplier.code": "Код поставщика",
+	"ledger.supplier.type": "Категория поставщика",
+	"ledger.supplier.status": "Статус поставщика",
+	"ledger.supplier.time": "Время создания",
+	"ledger.supplier.status1": "Черновик",
+	"ledger.supplier.status2": "Активен",
+	"ledger.supplier.status3": "Закрыт",
+	"ledger.brand.hint": "Сначала выберите бренд",
+	"ledger.brand.id": "Код словаря",
+	"ledger.brand.label": "Метка словаря",
+	"ledger.brand.labelHint": "Пожалуйста, введите название бренда",
+	"ledger.brand.status": "Статус",
+	"ledger.brand.status0": "Нормальный",
+	"ledger.brand.status1": "Закрыт",
+	"ledger.model.name": "Название модели",
+	"ledger.model.standard": "Соответствующий стандарт",
+	"ledger.detail.title": "Детали книги учета оборудования",
+	"ledger.detail.section": "Детали информации об оборудовании:",
+	"ledger.detail.user": "Ответственный",
+	"ledger.detail.assetType": "Категория актива",
+	"statistic.title": "Статистический анализ",
+	"statistic.tab0": "Главная страница",
+	"statistic.tab1": "Ремонтная статистика",
+	"statistic.tab2": "Статистика обслуживания",
+	"statistic.tab3": "Статистика инспекции",
+	"statistic.front.deviceCount": "Количество оборудования",
+	"statistic.front.repairCount": "Ремонтные рабочие заказы",
+	"statistic.front.runCount": "Количество рабочих заказов на операционные записи",
+	"statistic.front.maintenanceCount": "Количество рабочих заказов на обслуживание",
+	"statistic.front.inspectionCount": "Количество рабочих заказов на инспекцию",
+	"statistic.front.filled": "Заполнено",
+	"statistic.front.unfilled": "Не заполнено",
+	"statistic.front.execute": "Исполнено",
+	"statistic.front.unexecute": "Не исполнено",
+	"statistic.front.mttr": "MTTR (среднее время восстановления)",
+	"statistic.front.stockWarningCount": "Количество материалов с инвентарным предупреждением",
+	"statistic.front.deviceStatus": "Статистика статусов оборудования",
+	"statistic.front.deviceTypeCount": "Топ 5 категорий оборудования по количеству",
+	"statistic.front.weekUserActive": "Активность пользователей за последнюю неделю",
+	"statistic.front.totalUserCount": "Общее количество пользователей",
+	"statistic.front.activeUserCount": "Количество активных пользователей",
+	"statistic.front.workOrderCount": "Ситуация с количеством рабочих заказов",
+	"statistic.repair.resolutionTime": "Среднее время решения",
+	"statistic.repair.weeklyCount": "Количество рабочих заказов за последнюю неделю",
+	"statistic.repair.monthlyCount": "Количество рабочих заказов за последний месяц",
+	"statistic.repair.totalCount": "Общее количество рабочих заказов",
+	"statistic.repair.report": "Сообщение о неисправности",
+	"statistic.repair.workOrder": "Ремонтный рабочий заказ",
+	"statistic.repair.failure.title": "Статистика статусов сообщений о неисправностях",
+	"statistic.repair.failure.reporting": "Сообщается",
+	"statistic.repair.failure.finished": "Обработано",
+	"statistic.repair.failure.trans": "Преобразован в рабочий заказ",
+	"statistic.repair.failure.over": "Рабочий заказ обработан",
+	"statistic.repair.workOrder.title": "Статистика статусов ремонтных рабочих заказов",
+	"statistic.repair.workOrder.tx": "К заполнению",
+	"statistic.repair.workOrder.finished": "Завершено",
+	"statistic.repair.title": "Статический анализ ремонтных задач",
+	"statistic.repair.completionRate": "Процент завершения ремонта",
+	"statistic.repair.repairedCount": "Ремонтированно",
+	"statistic.repair.pendingRepairCount": "В ожидании ремонта",
+	"statistic.maintenance.dayCount": "Количество рабочих заказов за вчера",
+	"statistic.maintenance.count1": "Общее количество",
+	"statistic.maintenance.count2": "Незавершенные",
+	"statistic.maintenance.weeklyCount": "Количество рабочих заказов за последнюю неделю",
+	"statistic.maintenance.monthlyCount": "Количество рабочих заказов за последний месяц",
+	"statistic.maintenance.totalCount": "Количество рабочих заказов",
+	"statistic.maintenance.workOrder.title": "Статистика статусов рабочих заказов на обслуживание",
+	"statistic.maintenance.workOrder.status1": "В ожидании исполнения",
+	"statistic.maintenance.workOrder.status2": "Исполнено",
+	"statistic.maintenance.dayWorkOrder.title": "Статистика статусов рабочих заказов за сегодня",
+	"statistic.maintenance.orderType.title": "Статистика типов рабочих заказов",
+	"statistic.maintenance.orderType.status1": "Временное создание",
+	"statistic.maintenance.orderType.status2": "Плановое создание",
+	"statistic.inspection.workOrder.title": "Статистика статусов рабочих заказов на инспекцию",
+	"overtime.title": "Рабочие заказы, подлежащие обработке",
+	"overtime.item.type": "Тип рабочего заказа",
+	"overtime.item.title": "Название рабочего заказа",
+	"overtime.item.status": "Статус",
+	"overtime.item.time": "Время",
+	"overtime.type1": "Операционная запись",
+	"overtime.type2": "Ремонтный рабочий заказ",
+	"overtime.type3": "Рабочий заказ на обслуживание",
+	"overtime.type4": "Рабочий заказ на инспекцию"
+}

+ 36 - 0
locale/uni-app.ja.json

@@ -0,0 +1,36 @@
+{
+  "common": {
+    "uni.app.quit": "もう一度押すと、アプリケーションが終了します",
+    "uni.async.error": "サーバーへの接続がタイムアウトしました。画面をクリックして再試行してください",
+    "uni.showActionSheet.cancel": "キャンセル",
+    "uni.showToast.unpaired": "使用するには、showToastとhideToastをペアにする必要があることに注意してください",
+    "uni.showLoading.unpaired": "使用するには、showLoadingとhideLoadingをペアにする必要があることに注意してください",
+    "uni.showModal.cancel": "キャンセル",
+    "uni.showModal.confirm": "OK",
+    "uni.chooseImage.cancel": "キャンセル",
+    "uni.chooseImage.sourceType.album": "アルバムから選択",
+    "uni.chooseImage.sourceType.camera": "カメラ",
+    "uni.chooseVideo.cancel": "キャンセル",
+    "uni.chooseVideo.sourceType.album": "アルバムから選択",
+    "uni.chooseVideo.sourceType.camera": "カメラ",
+    "uni.previewImage.cancel": "キャンセル",
+    "uni.previewImage.button.save": "画像を保存",
+    "uni.previewImage.save.success": "画像をアルバムに正常に保存します",
+    "uni.previewImage.save.fail": "画像をアルバムに保存できませんでした",
+    "uni.setClipboardData.success": "コンテンツがコピーされました",
+    "uni.scanCode.title": "スキャンコード",
+    "uni.scanCode.album": "アルバム",
+    "uni.scanCode.fail": "認識に失敗しました",
+    "uni.scanCode.flash.on": "タッチして点灯",
+    "uni.scanCode.flash.off": "タップして閉じる",
+    "uni.startSoterAuthentication.authContent": "指紋認識...",
+    "uni.picker.done": "完了",
+    "uni.picker.cancel": "キャンセル",
+    "uni.video.danmu": "「弾幕」",
+    "uni.video.volume": "ボリューム",
+    "uni.button.feedback.title": "質問のフィードバック",
+    "uni.button.feedback.send": "送信"
+  },
+  "ios": {},
+  "android": {}
+}

+ 694 - 0
locale/zh-Hans.json

@@ -0,0 +1,694 @@
+{
+  "locale.auto": "系统",
+  "locale.en": "English",
+  "locale.zh-hans": "简体中文",
+  "locale.zh-hant": "繁体中文",
+  "locale.ru": "俄语",
+  "locale.ja": "日语",
+  "index.detail": "详情",
+  "index.language": "语言",
+  "index.languageChange": "选择语言",
+  "index.language-info": "语言信息",
+  "index.system-language": "系统语言",
+  "index.application-language": "应用语言",
+  "index.language-change-confirm": "应用此设置将重启App",
+  "api.message": "提示",
+  // --------------------------------------- app ----------------------------------------
+  "app.appName": "DEEP OIL",
+  "app.home": "首页",
+  "app.user": "我的",
+  // --------------------------------------- 版本升级 ----------------------------------------
+  "version.newVersion": "发现新版本",
+  "version.updateInfo": "更新内容",
+  "version.update": "版本升级",
+  "version.forceUpdate": "强制更新",
+  "version.downloading": "下载中...",
+  "version.download": "下载中",
+  "version.install": "立即安装",
+  "version.updateSuccess": "更新成功",
+  "version.updateFail": "更新失败,请稍后重试",
+  "version.updateLater": "稍后再说",
+  "version.updateNow": "立即更新",
+  "version.downloadLinkNotExist": "下载链接不存在",
+
+  // --------------------------------------- 通用 ----------------------------------------
+  "operation.cancel": "取消",
+  "operation.back": "返回",
+  "operation.confirm": "确定",
+  "operation.confirm1": "确认",
+  "operation.search": "搜索",
+  "operation.view": "查看",
+  "operation.edit": "编辑",
+  "operation.fill": "填写",
+  "operation.submit": "提交",
+  "operation.save": "保存",
+  "operation.add": "新增",
+  "operation.update": "更新",
+  "operation.select": "选择",
+  "operation.delete": "删除",
+  "operation.please": "请",
+  "operation.PleaseFillIn": "请填写",
+  "operation.PleaseSelect": "请选择",
+  "operation.PleaseInput": "请输入",
+  "operation.PleaseSet": "请设置",
+  "operation.remark": "备注",
+  "operation.createTime": "创建时间",
+  "operation.updateTime": "更新时间",
+  "operation.fillTime": "填写时间",
+  "operation.yes": "是",
+  "operation.no": "否",
+  "operation.success": "操作成功",
+  "operation.fail": "操作失败,请重试",
+  "operation.repeat": "重复",
+  "operation.deleteConfirm": "是否删除当前所选数据?",
+  "operation.dispatchUser": "分配责任人",
+  "operation.reject": "拒绝",
+  "operation.approval": "通过",
+  "operation.searchText": "请输入查询条件",
+  "operation.allItem": "所有项",
+  "operation.networkError": "网络异常,请检查网络连接后重试",
+  "operation.saving": "保存中...",
+  "operation.saveSuccess": "保存成功",
+  "operation.ignore": "忽略",
+  "operation.ignoreReason": "忽略理由",
+  "operation.formValidateFailed": "表单验证失败",
+  "operation.download": "下载",
+  "operation.downloadStart": "下载已开始",
+  "operation.downloadSuccess": "下载成功",
+  "operation.downloadFail": "下载失败",
+  "operation.fileUrlEmpty": "文件地址为空,无法下载",
+  "operation.fileDownloadError": "文件下载出错,请重试",
+  "operation.openFileSuccess": "文件打开成功",
+  "operation.downloading": "正在下载...",
+  "operation.uploadFail": "上传失败,请重试",
+
+  // --------------------------------------- 通用提示----------------------------------------
+  "general.picture": "图片",
+  "general.timeNotBeLater": "开始时间不能晚于结束时间",
+  "general.timeNotBeEarlier": "结束时间不能早于开始时间",
+  "general.startTimeNotBeEarlier": "维修开始时间不能早于故障时间",
+  "general.endTimeNotBeEarlier": "维修结束时间不能早于故障时间",
+  "general.submitSuccess": "填写完成",
+  "common.searchHint": "请输入查询条件",
+  "common.noData": "无数据",
+  "common.loading": "加载中...",
+
+  // --------------------------------------- 登录 ----------------------------------------
+  "login.welcome": "您好,欢迎登录",
+  "login.login": "登录",
+  "login.loginWithDingTalk": "用钉钉登录",
+  "login.languageChange": "更换语言",
+  "login.enterUsername": "请输入用户名",
+  "login.enterPhoneNumber": "请输入手机号",
+  "login.invalidPhoneFormat": "手机号格式不正确",
+  "login.enterPassword": "请输入密码",
+  "login.passwordRule": "密码至少八位字符,且包含大小写字母、数字和特殊字符\n特殊字符包含:!@#$%^&",
+  "login.dingTalkError": "钉钉登录失败",
+  "login.logoutConfirm": "确定要退出登录吗",
+  "login.h5DingTalk": "H5不支持钉钉登录",
+  "login.dingTalkJsapiMissing": "钉钉JSAPI加载失败",
+  // --------------------------------------- 首页 ----------------------------------------
+  "home.todo": "待办",
+  "home.remind": "提醒",
+  "home.unmaintained": "超时未保养",
+  "home.uninspected": "超时未巡检",
+  "home.unrecorded": "超时未记录",
+  "home.operationRecordFilling": "运行记录填报",
+  "home.fillDailyOperationRecord": "填报每日运行记录",
+  "home.maintenanceWorkOrder": "保养工单",
+  "home.receiveMaintenanceWorkOrderAndSubmit": "接收保养工单并提报",
+  "home.equipmentMaintenance": "设备维修",
+  "home.fillMaintenanceWorkOrder": "填报维修工单",
+  "home.inspectionWorkOrder": "巡检工单",
+  "home.receiveInspectionWorkOrderAndSubmit": "接收巡检工单并提报",
+  "home.faultReporting": "故障上报",
+  "home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
+  "home.dailyReportRuiDu": "瑞都日报",
+  "home.dailyReportRuiDuTip": "填写日报",
+  "home.inventoryQuery": "库存查询",
+  "home.clickToQueryInventoryData": "点击查询库存数据",
+  "home.equipmentLedger": "设备台账",
+  "home.viewEquipmentLedger": "查看设备台账",
+  "home.equipmentStatusChange": "设备状态变更",
+  "home.deviceUser": "设备责任人",
+  "home.deviceUserTip": "调整设备责任人",
+  "home.adjustEquipmentStatus": "调整设备状态",
+  "home.realTimeEquipmentDataMonitoring": "设备实时数据监控",
+  "home.viewRealTimeEquipmentData": "查看设备实时数据",
+  "home.statisticalAnalysis": "统计分析",
+  "home.equipmentDataStatisticalAnalysis": "设备数据统计分析",
+  // --------------------------------------- 个人中心 ----------------------------------------
+  "user.username": "用户名",
+  "user.phone": "手机号码",
+  "user.phoneHint": "请输入正确的手机号码",
+  "user.avatar": "用户头像",
+  "user.securityCenter": "安全中心",
+  "user.modifyPhoneAndPassword": "修改手机和密码",
+  "user.aboutUs": "关于我们",
+  "user.currentVersion": "当前版本",
+  "user.logout": "退出",
+  "user.userInfo": "用户信息",
+  "user.updatePassword": "修改密码",
+  "user.oldPassword": "旧密码",
+  "user.oldPasswordHint": "请输入旧密码",
+  "user.password": "新密码",
+  "user.passwordHint": "请输入新密码",
+  "user.confirmPassword": "确认密码",
+  "user.confirmPasswordHint": "请输入确认密码",
+  "user.passwordError1": "新密码与旧密码一致",
+  "user.passwordError2": "新密码与确认密码不一致",
+  // --------------------------------------- 运行记录 ----------------------------------------
+  "operationRecordFilling.responsiblePerson": "负责人",
+  "operationRecordFilling.workOrderName": "工单名称",
+  "operationRecordFilling.belongToTeam": "所属队伍",
+  "operationRecordFilling.totalRunningTime": "累计运行时间",
+  "operationRecordFilling.plcNotice": "以下数值取自PLC,如有不符请修改",
+  "operationRecordFilling.workOrderDevice": "工单设备",
+  "operationRecordFilling.fillContentCannotGreaterThanThreshold": "填写内容不能大于",
+  "operationRecordFilling.fillContentCannotLessThanThreshold": "填写内容不能小于",
+  "operationRecordFilling.totalDeviceCount": "应填设备数",
+  "operationRecordFilling.filledDeviceCount": "已填设备数",
+  "operationRecordFilling.unfilledDeviceCount": "未填设备数",
+  // --------------------------------------- 状态相关 ----------------------------------------
+  "status.enable": "启用",
+  "status.disable": "停用",
+  "status.unaudited": "未审核",
+  "status.audited": "已审核",
+  "status.unfinished": "未完成",
+  "status.finished": "已完成",
+  "status.unsubmitted": "未提交",
+  "status.submitted": "已提交",
+  "status.unprocessed": "未处理",
+  "status.processed": "已处理",
+  "status.unsolved": "未解决",
+  "status.solved": "已解决",
+  "status.unfilled": "未填写",
+  "status.filled": "已填写",
+  "status.tobeFilled": "待填写",
+
+  // --------------------------------------- 设备通用 ----------------------------------------
+  "device.selectDevice": "选择设备",
+  "device.assetCode": "资产编码",
+  "device.deviceCode": "设备编码",
+  "device.deviceName": "设备名称",
+  "device.department": "所在部门",
+  // --------------------------------------- 工单通用 ----------------------------------------
+  "workOrder.workOrderInfo": "工单信息",
+  "workOrder.repairWorkOrderList": "维修项列表",
+  "workOrder.maintenanceWorkOrderList": "保养项列表",
+  "workOrder.materialList": "物料列表",
+  "workOrder.allMaterial": "所有物料",
+  "workOrder.currentMaterial": "当前物料",
+  "workOrder.currentMaterialList": "当前物料列表",
+  "workOrder.isConsumptionMaterial": "是否消耗物料",
+  "workOrder.show": "显示",
+  "workOrder.total": "共",
+  "workOrder.item": "项",
+  "workOrder.serialNumber": "序号",
+
+  "workOrder.addDevice": "新增设备",
+  "workOrder.addMaterial": "新增物料",
+  "workOrder.selectMaterial": "选择物料",
+  "workOrder.materialDetails": "物料详情",
+  "workOrder.materialCode": "物料编码",
+  "workOrder.materialName": "物料名称",
+  "workOrder.materialCount": "物料数量",
+  "workOrder.masterData": "物料主数据",
+  "workOrder.planCode": "计划编码",
+  "workOrder.unit": "单位",
+  "workOrder.unitPrice": "单价",
+  "workOrder.unitPriceCNY": "单价(CNY/元)",
+  "workOrder.inventory": "库存",
+  "workOrder.remainingInventory": "剩余库存",
+  "workOrder.inventoryShortage": "库存不足",
+  "workOrder.source": "来源",
+  "workOrder.auditStatus": "审核状态",
+  "workOrder.isSolved": "是否解决",
+  "workOrder.isHelp": "是否协助",
+  "workOrder.Needassistance": "需要协助",
+  "workOrder.workOrderSource": "工单来源",
+  "workOrder.searchPlaceholder": "输入工单编号",
+  "workOrder.workOrderStatus": "工单状态",
+  "workOrder.workOrdertype": "工单类型",
+  "workOrder.workOrderNumber": "工单编号",
+  "workOrder.workOrderName": "工单名称",
+  "workOrder.executed": "已执行",
+  "workOrder.pending": "待执行",
+  "workOrder.responsiblePerson": "负责人",
+  "workOrder.viewDetails": "查看详情",
+  "workOrder.inventoryType": "库存类型",
+  "workOrder.manualAdd": "手动添加",
+  "workOrder.consumptionQuantity": "消耗数量",
+  "workOrder.inventoryQuantity": "库存数量",
+  "workOrder.yuan": "元",
+  "workOrder.specification": "规格型号",
+  "workOrder.status": "状态",
+  "workOrder.materialCountEmpty": "物料数量不能为空",
+  "workOrder.materialCountMustGreaterThan0": "物料数量必须大于0",
+  "workOrder.executionTime": "执行时间",
+  "workOrder.approver": "审批人",
+  "workOrder.isDeleteMaterial": "是否删除当前物料",
+  "workOrder.isDeleteBom": "是否删除当前维修项",
+  "workOrder.materialInvalid": "未填写有效物料",
+  "workOrder.materialRequired": "(必填)",
+  "workOrder.invalid": "无效",
+
+  // ------------------------------------------------保养工单---------------------------------------------
+
+  "maintenanceWorkOrder.title": "保养工单",
+  "maintenanceWorkOrder.totalWorkOrders": "工单总数",
+  "maintenanceWorkOrder.createButton": "创建保养工单",
+  "maintenanceWorkOrder.status": "保养状态",
+  "maintenanceWorkOrder.temporaryCreation": "临时新建",
+  "maintenanceWorkOrder.planGenerator": "计划生成",
+  "maintenanceWorkOrder.actualMaintenanceStartTime": "实际保养开始时间",
+  "maintenanceWorkOrder.actualEndTime": "实际保养结束时间",
+  "maintenanceWorkOrder.timeToMaintenance": "距离保养",
+  "maintenanceWorkOrder.maintenanceButton": "去保养",
+  "maintenanceWorkOrder.isPostponed": "是否延期",
+  "maintenanceWorkOrder.createMaintenanceWorkOrder": "新建保养工单",
+  "maintenanceWorkOrder.editMaintenanceWorkOrder": "填报保养工单",
+  "maintenanceWorkOrder.viewMaintenanceWorkOrder": "查看保养工单",
+  "maintenanceWorkOrder.maintenanceType": "保养类型",
+  "maintenanceWorkOrder.maintenanceTypeIn": "内部",
+  "maintenanceWorkOrder.maintenanceTypeOut": "委外",
+  "maintenanceWorkOrder.maintenanceCost": "保养费用",
+  "maintenanceWorkOrder.otherCost": "其他费用",
+  "maintenanceWorkOrder.accumulatedRunningTime": "累计运行时间",
+  "maintenanceWorkOrder.accumulatedRunningMileage": "累计运行里数",
+  "maintenanceWorkOrder.maintenanceItems": "保养项",
+  "maintenanceWorkOrder.materialSelected": "是否已选物料",
+  "maintenanceWorkOrder.extendMaintenance": "延保",
+  "maintenanceWorkOrder.timeNotBeEarlier": "实际保养结束时间不能早于实际保养开始时间",
+  "maintenanceWorkOrder.bomEmpty": "请至少添加一条设备保养明细",
+  "maintenanceWorkOrder.materialEmpty": "请至少添加一条物料",
+  "maintenanceWorkOrder.materialUnselected": "未选择物料",
+  "maintenanceWorkOrder.equipment": "设备",
+  "maintenanceWorkOrder.maintenanceItemConfiguration": "保养项配置",
+  "maintenanceWorkOrder.noMaintenanceItems": "当前所选的设备没有保养项,请重新选择",
+  "maintenanceWorkOrder.unselectedMaintenanceItems": "存在未添加物料的保养项",
+  "maintenanceWorkOrder.noMaterialMaintenanceReason": "无物料保养原因",
+  "maintenanceWorkOrder.unFinishedMaintenanceItems": "存在未完成的保养项",
+  "maintenanceWorkOrder.maintained": "已保养",
+  "maintenanceWorkOrder.notMaintained": "未保养",
+  "maintenanceWorkOrder.maintenanceInProgress": "保养中",
+  "maintenanceWorkOrder.delayed": "延时",
+  "maintenanceWorkOrder.completed": "完成",
+  "maintenanceWorkOrder.invalidMaterial": "存在无效物料",
+  "maintenanceWorkOrder.maintenanceStatusCompleted": "已标记为完成",
+  "maintenanceWorkOrder.notConsumeMaterial": "当前保养项已经设置了不消耗物料",
+
+  // 基础保养记录模块
+  "maintenanceWorkOrder.basicMaintenanceRecords": "基础保养记录",
+  "maintenanceWorkOrder.lastMaintenanceMileage": "上次保养里程数(KM)",
+  "maintenanceWorkOrder.delayedKilometers": "推迟公里数(KM)",
+  "maintenanceWorkOrder.lastMaintenanceRunningTime": "上次保养运行时间(H)",
+  "maintenanceWorkOrder.delayedDuration": "推迟时长(H)",
+  "maintenanceWorkOrder.lastMaintenanceNaturalDate": "上次保养自然日期",
+  "maintenanceWorkOrder.delayedNaturalDate": "推迟自然日期(D)",
+  // 运行规则配置模块(里程、时间区分)
+  "maintenanceWorkOrder.runningMileageRuleConfig": "运行里程规则配置",
+  "maintenanceWorkOrder.runningMileageCycle": "运行里程周期(KM)",
+  "maintenanceWorkOrder.runningMileageCycleLead": "运行里程周期-提前量(KM)",
+  "maintenanceWorkOrder.runningTimeRuleConfig": "运行时间规则配置",
+  "maintenanceWorkOrder.runningTimeCycle": "运行时间周期(H)",
+  "maintenanceWorkOrder.runningTimeCycleMustGreaterThan0": "运行时间周期(H)必须大于0",
+  "maintenanceWorkOrder.runningTimeCycleLead": "运行时间周期-提前量(H)",
+  // 自然日规则配置模块
+  "maintenanceWorkOrder.naturalDateRuleConfig": "自然日规则配置",
+  "maintenanceWorkOrder.naturalDateCycle": "自然日周期(D)",
+  "maintenanceWorkOrder.naturalDateCycleLead": "自然日周期-提前量(D)",
+  "maintenanceWorkOrder.delayReason": "推迟原因",
+
+  // ---------------------------------维修工单-------------------------------
+
+  "equipmentMaintenance.title": "设备维修",
+  "equipmentMaintenance.searchHint": "请输入设备名称",
+  "equipmentMaintenance.averageResolutionTime": "平均解决时间",
+  "equipmentMaintenance.createButton": "创建维修工单",
+  "equipmentMaintenance.createWorkOrder": "新建维修工单",
+  "equipmentMaintenance.editWorkOrder": "填报维修工单",
+  "equipmentMaintenance.viewWorkOrder": "查看维修工单",
+  "equipmentMaintenance.repairCode": "维修编码",
+  "equipmentMaintenance.maintenanceStartTime": "维修开始时间",
+  "equipmentMaintenance.maintenanceEndTime": "维修结束时间",
+  "equipmentMaintenance.isStop": "是否停机",
+  "equipmentMaintenance.maintenanceType": "维修类型",
+  "equipmentMaintenance.description": "维修描述",
+  "equipmentMaintenance.cost": "维修费用",
+  "equipmentMaintenance.maintenanceItems": "维修项",
+  "equipmentMaintenance.applicant": "申请人",
+  "equipmentMaintenance.classify": "维修类别",
+  "equipmentMaintenance.runningKilometersPerHour": "运行公里/小时",
+  "equipmentMaintenance.specificationModel": "规格型号",
+  "equipmentMaintenance.enableDate": "启用日期",
+  "equipmentMaintenance.supplier": "供应商",
+  "equipmentMaintenance.projectManager": "项目经理",
+  "equipmentMaintenance.maintenanceLocation": "维修地点",
+  "equipmentMaintenance.outsourceRelatedAttachments": "委外相关附件",
+  "equipmentMaintenance.maintenanceItem": "维修项目",
+  "equipmentMaintenance.repaired": "已维修",
+  "equipmentMaintenance.notRepaired": "未维修",
+  "equipmentMaintenance.noMaintenanceItems": "当前设备不存在维修项",
+  "equipmentMaintenance.noConsumeMaterialReason": "不消耗物料原因",
+  "equipmentMaintenance.unFinishedMaintenanceItems": "存在未完成的维修项",
+  "equipmentMaintenance.notConsumeMaterial": "当前维修项已经设置了不消耗物料",
+
+  // ---------------------------------------故障工单----------------------------------------
+  "fault.createButton": "创建故障工单",
+  "fault.createWorkOrder": "新建故障工单",
+  "fault.editWorkOrder": "编辑故障工单",
+  "fault.viewWorkOrder": "查看故障详情",
+  "fault.faultTotal": "故障上报总数",
+  "fault.faultCode": "故障编码",
+  "fault.faultName": "故障名称",
+  "fault.faultTime": "故障时间",
+  "fault.faultResolutionTime": "故障解决时间",
+  "fault.faultImpact": "故障影响",
+  "fault.faultSystem": "故障系统",
+  "fault.description": "故障描述",
+  "fault.solution": "解决办法",
+  "fault.timeNotBeEarlier": "故障解决时间不得早于故障时间",
+
+  // ---------------------------------------巡检工单----------------------------------------
+  "inspection.title": "巡检工单",
+  "inspection.totalWorkOrders": "巡检总数",
+  "inspection.editWorkOrder": "巡检工单填写",
+  "inspection.viewWorkOrder": "巡检工单详情",
+  "inspection.clickView": "点击查看",
+  "inspection.proj": "巡检项目",
+  "inspection.projItem": "巡检项",
+  "inspection.standard": "巡检标准",
+  "inspection.standardFile": "附件:",
+  "inspection.isAbnormal": "是否异常",
+  "inspection.abnormalDesc": "异常描述",
+  "inspection.normal": "正常",
+  "inspection.abnormal": "异常",
+  "inspection.last": "上一步",
+  "inspection.next": "下一步",
+  "inspection.finish": "完成提交",
+  "inspection.equipmentNum": "应检设备数",
+  "inspection.misNum": "漏检设备数",
+  "inspection.abnormalNum": "异常设备数",
+  "inspection.pendingInspectionItems": "待填写巡检项",
+  "inspection.normalInspectionItems": "正常巡检项",
+  "inspection.abnormalInspectionItems": "异常巡检项",
+  // --------------------------------------- 瑞都日报 ----------------------------------------
+  "ruiDu.indexTitle": "日报",
+  "ruiDu.detailTitle": "日报详情",
+  "ruiDu.editTitle": "日报填报",
+  "ruiDu.shiftLeader": "带班干部",
+  "ruiDu.reportName": "日报名称",
+  "ruiDu.project": "项目",
+  "ruiDu.task": "任务",
+  "ruiDu.taskInfo": "任务信息",
+  "ruiDu.reportInfo": "日报信息",
+  "ruiDu.firstParty": "甲方",
+  "ruiDu.contractNo": "合同号",
+  "ruiDu.wellNo": "井号",
+  "ruiDu.constructionTeam": "施工队伍",
+  "ruiDu.constructionLocation": "施工地点",
+  "ruiDu.process": "工艺",
+  "ruiDu.relocationDate": "搬迁日期",
+  "ruiDu.commencementDate": "开工日期",
+  "ruiDu.completionDate": "完工日期",
+  "ruiDu.constructionCycle": "施工周期(D)",
+  "ruiDu.equipmentConfig": "设备配置",
+  "ruiDu.timeNode": "时间节点",
+  "ruiDu.constructionStatus": "施工状态",
+  "ruiDu.constructionEquipment": "施工设备",
+  "ruiDu.unselectedEquipment": "未选择设备",
+  "ruiDu.optionalEquipment": "可选设备",
+  "ruiDu.selectedEquipment": "已选设备",
+  "ruiDu.allEquipmentConstructed": "所有设备都已施工",
+  "ruiDu.constructionProcess": "施工工艺",
+  "ruiDu.dailyProductionDynamic": "当日生产动态",
+  "ruiDu.nextWorkPlan": "下步工作计划",
+  "ruiDu.externalRentalEquipment": "外租设备",
+  "ruiDu.faultSituation": "故障情况",
+  "ruiDu.faultDowntimeH": "故障误工(H)",
+  "ruiDu.attachment": "附件",
+  "ruiDu.selectFile": "选择文件",
+  "ruiDu.fileSizeLimit": "文件大小不能超过50M",
+  "ruiDu.cumulativeConstructionLayer": "累计施工-层",
+  "ruiDu.dailyPumpTruckTrips": "当日泵车台次",
+  "ruiDu.dailyInstrumentMixingSandTrips": "当日仪表/混砂",
+  
+  
+
+
+  // --------------------------------------- 库存查询 ----------------------------------------
+  "inventory.title": "库存查询",
+  "inventory.searchHint": "请输入物料名称",
+  "inventory.deviceName": "设备名称:",
+  "inventory.materialName": "物料名称:",
+  "inventory.costCenter": "库位:",
+  "inventory.quantity": "库存:",
+  "inventory.search.title": "筛选条件",
+  "inventory.search.factory": "工厂",
+  "inventory.search.storageLocation": "库存地点",
+  "inventory.search.costCenter": "成本中心",
+  "inventory.search.materialCode": "物料编码",
+  "inventory.search.materialCodeHint": "请输入物料编码",
+  "inventory.search.materialName": "物料名称",
+  "inventory.search.materialNameHint": "请输入物料名称",
+  "inventory.search.storageTime": "入库时间",
+  "inventory.search.storageTimeHint": "请选择时间",
+  "inventory.search.reset": "重置",
+
+  // --------------------------------------- 消息管理 ----------------------------------------
+  "message.title": "消息管理",
+  "message.tab1": "待办任务",
+  "message.tab2": "任务审批",
+  "message.tab3": "系统消息",
+  "message.id": "编号",
+  "message.processId": "工单编号: ",
+  "message.startUser": "报修人: ",
+  "message.deviceName": "设备名称: ",
+  "message.desc": "维修描述: ",
+  "message.faultTime": "报修时间: ",
+  "message.statusName": "最新进展: ",
+  "message.dispatchUser": "负责人",
+  "message.repairType": "维修类型",
+  "message.reason": "审批意见",
+  "message.reason1": "取消理由",
+  "message.cancelHint": "取消后,该审批流程将自动结束",
+  "message.form.errorHint1": "维修负责人不能为空",
+  "message.form.errorHint2": "维修类型不能为空",
+  "message.form.errorHint3": "新审批人不能为空",
+  "message.form.errorHint4": "审批意见不能为空",
+  "message.form.errorHint5": "接收人不能为空",
+  "message.form.errorHint6": "加签处理人不能为空",
+  "message.form.errorHint7": "取消理由不能为空",
+  "message.form.user3": "新审批人",
+  "message.form.user4": "接收人",
+  "message.form.user5": "加签处理人",
+  "message.form.beforeSign": "向前加签",
+  "message.form.afterSign": "向后加签",
+
+  // --------------------------------------- 审批详情 ----------------------------------------
+  "approval.fault.name": "故障名称:",
+  "approval.fault.system": "故障系统:",
+  "approval.fault.ifDeal": "是否解决:",
+  "approval.fault.status": "状态:",
+  "approval.fault.ifStop": "是否停机:",
+  "approval.fault.failureTime": "故障时间:",
+  "approval.fault.dealTime": "解决时间:",
+  "approval.fault.needHelp": "是否需要协助:",
+  "approval.fault.failureInfluence": "故障影响:",
+  "approval.fault.solution": "解决办法:",
+  "approval.fault.description": "故障描述:",
+  "approval.fault.remark": "备注:",
+  "approval.fault.pic": "图片:",
+  "approval.maintain.type": "维修类型:",
+  "approval.maintain.startTime": "维修开始时间:",
+  "approval.maintain.endTime": "维修结束时间:",
+  "approval.maintain.person": "负责人:",
+  "approval.maintain.maintainFee": "维修费用:",
+  "approval.maintain.desc": "维修描述:",
+
+  // --------------------------------------- 状态变更 ----------------------------------------
+  "statusChange.title": "设备状态变更",
+  "statusChange.insert": "+ 新建  ",
+  "statusChange.searchHint": "请输入资产编码",
+  "statusChange.deviceName": "设备名称",
+  "statusChange.deviceCode": "设备编码",
+  "statusChange.beforeLeader": "调整前责任人",
+  "statusChange.afterLeader": "调整后责任人",
+  "statusChange.reason": "调整原因",
+  "statusChange.createUser": "调整人",
+  "statusChange.assetCode": "资产编码",
+  "statusChange.dept": "所在部门",
+  "statusChange.createDate": "创建时间",
+  "statusChange.beforeStatus": "变更前状态",
+  "statusChange.afterStatus": "变更后状态",
+  "statusChange.history": "历史",
+  "statusChange.history.title": "设备状态调整记录",
+  "statusChange.form.title": "新增设备变更",
+  "statusChange.form.device": "设备",
+  "statusChange.form.deviceHint": "请选择设备",
+  "statusChange.form.deviceError": "设备不能为空",
+  "statusChange.form.selectDevice": "选择设备",
+  "statusChange.form.status": "设备状态",
+  "statusChange.form.statusHint": "请选择设备状态",
+  "statusChange.form.statusError": "设备状态不能为空",
+  "statusChange.form.statusTitle": "变更状态",
+  "statusChange.form.reason": "调整原因",
+  "statusChange.form.reasonHint": "请输入调整原因",
+  "statusChange.form.reasonError": "调整原因不能为空",
+  "statusChange.form.searchHint": "搜索设备名称/资产编码",
+  "statusChange.form.success": "提交成功",
+
+  // --------------------------------------- 设备责任人 ----------------------------------------
+  "deviceUser.title": "设备责任人",
+  "deviceUser.user": "责任人",
+  "deviceUser.history.title": "责任人调整记录",
+  "deviceUser.form.title": "新增设备责任人",
+  "deviceUser.form.user": "责任人",
+  "deviceUser.form.userPlaceholder": "责任人名称",
+  "deviceUser.form.userHint": "请选择责任人",
+  "deviceUser.form.selectUser": "选择责任人",
+
+  // --------------------------------------- 实时数据 ----------------------------------------
+  "realTimeData.title": "设备实时数据监控",
+  "realTimeData.status.online": "在线",
+  "realTimeData.status.offline": "离线",
+  "realTimeData.number": "编号:",
+  "realTimeData.type": "类型:",
+
+  // --------------------------------------- 实时数据详情 ----------------------------------------
+  "realTimeData.detail.title": "设备实时数据详情",
+  "realTimeData.detail.assetCode": "资产编码:",
+  "realTimeData.detail.isOnline": "是否在线:",
+  "realTimeData.detail.deviceType": "设备类别:",
+  "realTimeData.detail.lastUpdateTime": "最后数据:",
+  "realTimeData.detail.chartTitle": "数据趋势",
+
+  // --------------------------------------- 台账 ----------------------------------------
+  "ledger.title": "设备台账",
+  "ledger.deviceStatus": "设备状态: ",
+  "ledger.form.title": "新建台账",
+  "ledger.form.basicInfo": "基本信息:",
+  "ledger.form.deviceCode": "设备编码",
+  "ledger.form.deviceCodeError": "设备编码不能为空",
+  "ledger.form.deviceName": "设备名称",
+  "ledger.form.deviceNameError": "设备名称不能为空",
+  "ledger.form.brand": "品牌",
+  "ledger.form.brandError": "品牌不能为空",
+  "ledger.form.dept": "所在部门",
+  "ledger.form.deptError": "所在部门不能为空",
+  "ledger.form.deviceType": "设备类别",
+  "ledger.form.deviceTypeError": "设备类别不能为空",
+  "ledger.form.deviceStatus": "设备状态",
+  "ledger.form.deviceStatusError": "设备状态为空",
+  "ledger.form.assetProperty": "资产性质",
+  "ledger.form.assetPropertyError": "资产性质不能为空",
+  "ledger.form.model": "规格型号",
+  "ledger.form.image": "图片",
+  "ledger.form.remark": "备注",
+  "ledger.form.produceInfo": "制造信息:",
+  "ledger.form.manufacturer": "制造商",
+  "ledger.form.manufacturerError": "制造商不能为空",
+  "ledger.form.manDate": "生产日期",
+  "ledger.form.manDateError": "生产日期不能为空",
+  "ledger.form.supplier": "供应商",
+  "ledger.form.expires": "质保到期",
+  "ledger.form.nameplate": "铭牌信息",
+  "ledger.form.financeInfo": "财务信息:",
+  "ledger.form.plPrice": "采购价格",
+  "ledger.form.plDate": "采购日期",
+  "ledger.form.plYear": "折旧年限",
+  "ledger.form.plStartDate": "折旧开始日期",
+  "ledger.form.plMonth": "已提折旧月数",
+  "ledger.form.plAmounted": "已提折旧金额",
+  "ledger.form.remainAmount": "剩余金额",
+  "ledger.supplier.name": "客商名称",
+  "ledger.supplier.nameHint": "请输入供应商名称",
+  "ledger.supplier.code": "客商编号",
+  "ledger.supplier.type": "客商分类",
+  "ledger.supplier.status": "客商状态",
+  "ledger.supplier.time": "创建时间",
+  "ledger.supplier.status1": "草稿",
+  "ledger.supplier.status2": "活动",
+  "ledger.supplier.status3": "关闭",
+  "ledger.brand.hint": "请先选择品牌",
+  "ledger.brand.id": "字典编码",
+  "ledger.brand.label": "字典标签",
+  "ledger.brand.labelHint": "请输入品牌名称",
+  "ledger.brand.status": "状态",
+  "ledger.brand.status0": "正常",
+  "ledger.brand.status1": "关闭",
+  "ledger.model.name": "型号名称",
+  "ledger.model.standard": "符合标准",
+  "ledger.detail.title": "设备台账详情",
+  "ledger.detail.section": "设备信息详情:",
+  "ledger.detail.user": "责任人",
+  "ledger.detail.assetType": "资产类别",
+
+  // --------------------------------------- 统计分析 ----------------------------------------
+  "statistic.title": "统计分析",
+  "statistic.tab0": "首页",
+  "statistic.tab1": "维修统计",
+  "statistic.tab2": "保养统计",
+  "statistic.tab3": "巡检统计",
+  "statistic.front.deviceCount": "设备数",
+  "statistic.front.repairCount": "维修工单",
+  "statistic.front.runCount": "运行记录工单数量",
+  "statistic.front.maintenanceCount": "保养工单数量",
+  "statistic.front.inspectionCount": "巡检工单数量",
+  "statistic.front.filled": "已填写",
+  "statistic.front.unfilled": "未填写",
+  "statistic.front.execute": "已执行",
+  "statistic.front.unexecute": "未执行",
+  "statistic.front.mttr": "MTTR(平均解决时间)",
+  "statistic.front.stockWarningCount": "库存预警物料数量",
+  "statistic.front.deviceStatus": "设备状态统计",
+  "statistic.front.deviceTypeCount": "设备类别TOP5数量",
+  "statistic.front.weekUserActive": "近一周用户活跃度",
+  "statistic.front.totalUserCount": "总人数",
+  "statistic.front.activeUserCount": "活跃人数",
+  "statistic.front.workOrderCount": "工单数量情况",
+  "statistic.repair.resolutionTime": "平均解决时间",
+  "statistic.repair.weeklyCount": "近一周工单数量",
+  "statistic.repair.monthlyCount": "近一月工单数量",
+  "statistic.repair.totalCount": "工单总数量",
+  "statistic.repair.report": "故障上报",
+  "statistic.repair.workOrder": "维修工单",
+  "statistic.repair.failure.title": "故障上报状态统计",
+  "statistic.repair.failure.reporting": "上报中",
+  "statistic.repair.failure.finished": "处理完成",
+  "statistic.repair.failure.trans": "转工单",
+  "statistic.repair.failure.over": "工单处理完成",
+  "statistic.repair.workOrder.title": "维修工单状态统计",
+  "statistic.repair.workOrder.tx": "待填写",
+  "statistic.repair.workOrder.finished": "已完成",
+  "statistic.repair.title": "维修任务统计分析",
+  "statistic.repair.completionRate": "维修完成率",
+  "statistic.repair.repairedCount": "已维修",
+  "statistic.repair.pendingRepairCount": "待维修",
+  "statistic.maintenance.dayCount": "昨日工单数量",
+  "statistic.maintenance.count1": "总数量",
+  "statistic.maintenance.count2": "未完成",
+  "statistic.maintenance.weeklyCount": "近一周工单数量",
+  "statistic.maintenance.monthlyCount": "近一月工单数量",
+  "statistic.maintenance.totalCount": "工单数量",
+  "statistic.maintenance.workOrder.title": "保养工单状态统计",
+  "statistic.maintenance.workOrder.status1": "待执行",
+  "statistic.maintenance.workOrder.status2": "已执行",
+  "statistic.maintenance.dayWorkOrder.title": "今日工单状态统计",
+  "statistic.maintenance.orderType.title": "工单类型统计",
+  "statistic.maintenance.orderType.status1": "临时新建",
+  "statistic.maintenance.orderType.status2": "计划生成",
+  "statistic.inspection.workOrder.title": "巡检工单状态统计",
+
+  // --------------------------------------- 超时工单 ----------------------------------------
+  "overtime.title": "待处理工单",
+  "overtime.item.type": "工单类型",
+  "overtime.item.title": "工单名称",
+  "overtime.item.status": "状态",
+  "overtime.item.time": "时间",
+  "overtime.type1": "运行记录",
+  "overtime.type2": "维修工单",
+  "overtime.type3": "保养工单",
+  "overtime.type4": "巡检工单"
+}

+ 491 - 0
locale/zh-Hant.json

@@ -0,0 +1,491 @@
+{
+	"locale.auto": "系统",
+	"locale.en": "English",
+	"locale.zh-hans": "简体中文",
+	"locale.zh-hant": "繁体中文",
+	"locale.ru": "俄语",
+	"locale.ja": "日语",
+	"index.detail": "详情",
+	"index.language": "语言",
+	"index.languageChange": "选择语言",
+	"index.language-info": "语言信息",
+	"index.system-language": "系统语言",
+	"index.application-language": "应用语言",
+	"index.language-change-confirm": "应用此设置将重启App",
+	"api.message": "提示",
+	// -----------------------------------------------
+	"app.appName": "DEEP OIL",
+	"app.home": "首页",
+	"app.user": "我的",
+	// ------------------通用-----------------------------
+	"operation.cancel": "取消",
+	"operation.back": "返回",
+	"operation.confirm": "确定",
+	"operation.confirm1": "确认",
+	"operation.search": "搜索",
+	"operation.view": "查看",
+	"operation.edit": "编辑",
+	"operation.fill": "填写",
+	"operation.submit": "提交",
+	"operation.save": "保存",
+	"operation.add": "新增",
+	"operation.select": "选择",
+	"operation.delete": "删除",
+	"operation.please": "请",
+	"operation.PleaseFillIn": "请填写",
+	"operation.PleaseSelect": "请选择",
+	"operation.PleaseInput": "请输入",
+	"operation.PleaseSet": "请设置",
+	"operation.remark": "备注",
+	"operation.createTime": "创建时间",
+	"operation.updateTime": "更新时间",
+	"operation.yes": "是",
+	"operation.no": "否",
+	"operation.success": "操作成功",
+	"operation.fail": "操作失败,请重试",
+	"operation.repeat": "重复",
+	"operation.deleteConfirm": "是否删除当前所选数据?",
+	"operation.dispatchUser": "分配责任人",
+	"operation.reject": "拒绝",
+	"operation.approval": "通过",
+
+	// --------------------
+	"general.picture": "图片",
+	"general.timeNotBeLater": "开始时间不能晚于结束时间",
+	"general.timeNotBeEarlier": "结束时间不能早于开始时间",
+
+
+
+
+	// -----------------------------------------------
+	"login.welcome": "您好,欢迎登录",
+	"login.login": "登录",
+	"login.loginWithDingTalk": "用钉钉登录",
+	"login.languageChange": "更换语言",
+	"login.enterUsername": "请输入用户名",
+	"login.enterPhoneNumber": "请输入手机号",
+	"login.invalidPhoneFormat": "手机号格式不正确",
+	"login.enterPassword": "请输入密码",
+	"login.passwordRule": "密码至少八位字符,且包含大小写字母、数字和特殊字符\n特殊字符包含:!@#$%^&",
+	"login.dingTalkError": "钉钉登录失败",
+	"login.logoutConfirm": "确定要退出登录吗",
+	
+	"home.todo": "待办",
+	"home.remind": "提醒",
+	"home.unmaintained": "超时未保养",
+	"home.uninspected": "超时未巡检",
+	"home.unrecorded": "超时未记录",
+	"home.operationRecordFilling": "运行记录填报",
+	"home.fillDailyOperationRecord": "填报每日运行记录",
+	"home.maintenanceWorkOrder": "保养工单",
+	"home.receiveMaintenanceWorkOrderAndSubmit": "接收保养工单并提报",
+	"home.equipmentMaintenance": "设备维修",
+	"home.fillMaintenanceWorkOrder": "填报维修工单",
+	"home.inspectionWorkOrder": "巡检工单",
+	"home.receiveInspectionWorkOrderAndSubmit": "接收巡检工单并提报",
+	"home.faultReporting": "故障上报",
+	"home.fillAndReportFaultWorkOrder": "故障工单的填报及上报故障问题",
+	"home.inventoryQuery": "库存查询",
+	"home.clickToQueryInventoryData": "点击查询库存数据",
+	"home.equipmentLedger": "设备台账",
+	"home.viewEquipmentLedger": "查看设备台账",
+	"home.equipmentStatusChange": "设备状态变更",
+	"home.adjustEquipmentStatus": "调整设备状态",
+	"home.realTimeEquipmentDataMonitoring": "设备实时数据监控",
+	"home.viewRealTimeEquipmentData": "查看设备实时数据",
+	"home.statisticalAnalysis": "统计分析",
+	"home.equipmentDataStatisticalAnalysis": "设备数据统计分析",
+	// ----------------------个人中心-------------------------
+	"user.username": "用户名",
+	"user.phone": "手机号码",
+	"user.phoneHint": "请输入正确的手机号码",
+	"user.avatar": "用户头像",
+	"user.securityCenter": "安全中心",
+	"user.modifyPhoneAndPassword": "修改手机和密码",
+	"user.aboutUs": "关于我们",
+	"user.currentVersion": "当前版本",
+	"user.logout": "退出",
+	"user.userInfo": "用户信息",
+	"user.updatePassword": "修改密码",
+	"user.oldPassword": "旧密码",
+	"user.oldPasswordHint": "请输入旧密码",
+	"user.password": "新密码",
+	"user.passwordHint": "请输入新密码",
+	"user.confirmPassword": "确认密码",
+	"user.confirmPasswordHint": "请输入确认密码",
+	"user.passwordError1": "新密码与旧密码一致",
+	"user.passwordError2": "新密码与确认密码不一致",
+
+	// ---------------------运行记录--------------------------
+	"operationRecordFilling.responsiblePerson": "负责人",
+	"operationRecordFilling.workOrderName": "工单名称",
+	"operationRecordFilling.belongToTeam": "所属队伍",
+	"operationRecordFilling.totalRunningTime": "累计运行时间",
+	// ----------------------------------------------------
+	"workOrder.addDevice": "新增设备",
+	"workOrder.addMaterial": "新增物料",
+	"workOrder.selectMaterial": "选择物料",
+	"workOrder.materialDetails": "物料详情",
+	"workOrder.materialCode": "物料编码",
+	"workOrder.materialName": "物料名称",
+	"workOrder.materialCount": "物料数量",
+	"workOrder.masterData": "物料主数据",
+	"workOrder.planCode": "计划编码",
+	"workOrder.unit": "单位",
+	"workOrder.unitPrice": "单价",
+	"workOrder.inventory": "库存",
+	"workOrder.remainingInventory": "剩余库存",
+	"workOrder.inventoryShortage": "库存不足",
+	"workOrder.source": "来源",
+	"workOrder.auditStatus": "审核状态",
+	"workOrder.isSolved": "是否解决",
+	"workOrder.isHelp": "是否协助",
+	"workOrder.Needassistance": "需要协助",
+	"workOrder.workOrderSource": "工单来源",
+	"workOrder.searchPlaceholder": "输入工单编号",
+	"workOrder.workOrderStatus": "工单状态",
+	"workOrder.workOrdertype": "工单类型",
+	"workOrder.workOrderNumber": "工单编号",
+	"workOrder.workOrderName": "工单名称",
+	"workOrder.executed": "已执行",
+	"workOrder.pending": "待执行",
+	"workOrder.responsiblePerson": "负责人",
+	"workOrder.viewDetails": "查看详情",
+	"workOrder.inventoryType": "库存类型",
+	"workOrder.consumptionQuantity": "消耗数量",
+	"workOrder.yuan": "元",
+	"workOrder.specification": "规格型号",
+	"workOrder.status": "状态",
+	// -------------------状态相关---------------------------
+	"status.enable": "启用",
+	"status.disable": "停用",
+	"status.unaudited": "未审核",
+	"status.audited": "已审核",
+	"status.unfinished": "未完成",
+	"status.finished": "已完成",
+	"status.unsubmitted": "未提交",
+	"status.submitted": "已提交",
+	"status.unprocessed": "未处理",
+	"status.processed": "已处理",
+	"status.unsolved": "未解决",
+	"status.solved": "已解决",
+	"status.unfilled": "未填写",
+	"status.filled": "已填写",
+	"status.tobeFilled": "待填写",
+
+
+
+
+
+	// ---------------------选择设备-------------------------------
+	"device.selectDevice": "选择设备",
+	"device.assetCode": "资产编码",
+	"device.deviceCode": "设备编码",
+	"device.deviceName": "设备名称",
+	"device.department": "所在部门",
+
+
+	// -------------------保养工单----------------------------
+
+	"maintenanceWorkOrder.title": "保养工单",
+	"maintenanceWorkOrder.totalWorkOrders": "工单总数",
+	"maintenanceWorkOrder.createButton": "创建保养工单",
+	"maintenanceWorkOrder.status": "保养状态",
+	"maintenanceWorkOrder.temporaryCreation": "临时新建",
+	"maintenanceWorkOrder.planGenerator": "计划生成",
+	"maintenanceWorkOrder.actualMaintenanceStartTime": "实际保养开始时间",
+	"maintenanceWorkOrder.actualEndTime": "实际保养结束时间",
+	"maintenanceWorkOrder.timeToMaintenance": "距离保养",
+	"maintenanceWorkOrder.maintenanceButton": "去保养",
+	"maintenanceWorkOrder.isPostponed": "是否延期",
+	"maintenanceWorkOrder.createMaintenanceWorkOrder": "新建保养工单",
+	"maintenanceWorkOrder.editMaintenanceWorkOrder": "填报保养工单",
+	"maintenanceWorkOrder.viewMaintenanceWorkOrder": "查看保养工单",
+	"maintenanceWorkOrder.maintenanceType": "保养类型",
+	"maintenanceWorkOrder.maintenanceTypeIn": "内部",
+	"maintenanceWorkOrder.maintenanceTypeOut": "委外",
+	"maintenanceWorkOrder.maintenanceCost": "保养费用",
+	"maintenanceWorkOrder.otherCost": "其他费用",
+	"maintenanceWorkOrder.accumulatedRunningTime": "累计运行时间",
+	"maintenanceWorkOrder.accumulatedRunningMileage": "累计运行里数",
+	"maintenanceWorkOrder.maintenanceItems": "保养项",
+	"maintenanceWorkOrder.materialSelected": "是否已选物料",
+	"maintenanceWorkOrder.extendMaintenance": "延保",
+	"maintenanceWorkOrder.timeNotBeEarlier": "实际保养结束时间不能早于实际保养开始时间",
+	"maintenanceWorkOrder.bomEmpty": "请至少添加一条设备保养明细",
+	"maintenanceWorkOrder.materialEmpty": "请至少添加一条物料",
+	"maintenanceWorkOrder.materialUnselected": "未选择物料",
+	"maintenanceWorkOrder.equipment": "设备",
+	"maintenanceWorkOrder.maintenanceItemConfiguration": "保养项配置",
+	"maintenanceWorkOrder.noMaintenanceItems":"当前所选的设备没有保养项,请重新选择",
+	// 基础保养记录模块
+	"maintenanceWorkOrder.basicMaintenanceRecords": "基础保养记录",
+	"maintenanceWorkOrder.lastMaintenanceMileage": "上次保养里程数(KM)",
+	"maintenanceWorkOrder.delayedKilometers": "推迟公里数(KM)",
+	"maintenanceWorkOrder.lastMaintenanceRunningTime": "上次保养运行时间(H)",
+	"maintenanceWorkOrder.delayedDuration": "推迟时长(H)",
+	"maintenanceWorkOrder.lastMaintenanceNaturalDate": "上次保养自然日期",
+	"maintenanceWorkOrder.delayedNaturalDate": "推迟自然日期(D)",
+	// 运行规则配置模块(里程、时间区分)
+	"maintenanceWorkOrder.runningMileageRuleConfig": "运行里程规则配置",
+	"maintenanceWorkOrder.runningMileageCycle": "运行里程周期(KM)",
+	"maintenanceWorkOrder.runningMileageCycleLead": "运行里程周期-提前量(KM)",
+	"maintenanceWorkOrder.runningTimeRuleConfig": "运行时间规则配置",
+	"maintenanceWorkOrder.runningTimeCycle": "运行时间周期(H)",
+	"maintenanceWorkOrder.runningTimeCycleLead": "运行时间周期-提前量(H)",
+	// 自然日规则配置模块
+	"maintenanceWorkOrder.naturalDateRuleConfig": "自然日规则配置",
+	"maintenanceWorkOrder.naturalDateCycle": "自然日周期(D)",
+	"maintenanceWorkOrder.naturalDateCycleLead": "自然日周期-提前量(D)",
+
+	// -------------------设备维修-------------------
+
+	"equipmentMaintenance.title": "设备维修",
+	"equipmentMaintenance.searchHint": "请输入设备名称",
+	"equipmentMaintenance.averageResolutionTime": "平均解决时间",
+	"equipmentMaintenance.createButton": "创建维修工单",
+	"equipmentMaintenance.createWorkOrder": "新建维修工单",
+	"equipmentMaintenance.editWorkOrder": "填报维修工单",
+	"equipmentMaintenance.viewWorkOrder": "查看维修工单",
+	"equipmentMaintenance.repairCode": "维修编码",
+	"equipmentMaintenance.maintenanceStartTime": "维修开始时间",
+	"equipmentMaintenance.maintenanceEndTime": "维修结束时间",
+	"equipmentMaintenance.isStop": "是否停机",
+	"equipmentMaintenance.maintenanceType": "维修类型",
+	"equipmentMaintenance.description": "维修描述",
+	"equipmentMaintenance.cost": "维修费用",
+	"equipmentMaintenance.maintenanceItems": "维修项",
+	
+
+	// -------------------故障------------------------
+	"fault.createButton": "创建故障工单",
+	"fault.createWorkOrder": "新建故障工单",
+	"fault.editWorkOrder": "编辑故障工单",
+	"fault.viewWorkOrder": "查看故障详情",
+	"fault.faultTotal": "故障上报总数",
+	"fault.faultCode": "故障编码",
+	"fault.faultName": "故障名称",
+	"fault.faultTime": "故障时间",
+	"fault.faultResolutionTime": "故障解决时间",
+	"fault.faultImpact": "故障影响",
+	"fault.faultSystem": "故障系统",
+	"fault.description": "故障描述",
+	"fault.solution": "解决办法",
+	"fault.timeNotBeEarlier": "故障解决时间不得早于故障时间",
+	
+
+	// ------------------巡检工单----------------------
+	"inspection.title": "巡检工单",
+	"inspection.totalWorkOrders": "巡检总数",
+	"inspection.editWorkOrder": "巡检工单填写",
+	"inspection.viewWorkOrder": "巡检工单详情",
+	"inspection.clickView": "点击查看",
+	"inspection.proj": "巡检项目",
+	"inspection.projItem": "巡检项",
+	"inspection.standard": "巡检标准",
+	"inspection.standardFile": "附件:",
+	"inspection.isAbnormal": "是否异常",
+	"inspection.abnormalDesc": "异常描述",
+	"inspection.normal": "正常",
+	"inspection.abnormal": "异常",
+	"inspection.last": "上一步",
+	"inspection.next": "下一步",
+	"inspection.finish": "完成提交",
+
+
+
+
+	// ------------------- 库存查询 -------------------
+	"inventory.title": "库存查询",
+	"inventory.searchHint": "请输入设备",
+	"inventory.deviceName": "设备名称:",
+	"inventory.costCenter": "库位:",
+	"inventory.quantity": "库存:",
+	"inventory.search.title": "筛选条件",
+	"inventory.search.factory": "工厂",
+	"inventory.search.storageLocation": "库存地点",
+	"inventory.search.costCenter": "成本中心",
+	"inventory.search.materialCode": "物料编码",
+	"inventory.search.materialCodeHint": "请输入物料编码",
+	"inventory.search.materialName": "物料名称",
+	"inventory.search.materialNameHint": "请输入物料名称",
+	"inventory.search.storageTime": "入库时间",
+	"inventory.search.storageTimeHint": "请选择时间",
+	"inventory.search.reset": "重置",
+
+	// ------------------- 消息管理 -------------------
+	"message.title": "消息管理",
+	"message.tab1": "待办任务",
+	"message.tab2": "任务审批",
+	"message.tab3": "系统消息",
+	"message.id": "编号",
+	"message.processId": "工单编号: ",
+	"message.startUser": "报修人: ",
+	"message.deviceName": "设备名称: ",
+	"message.desc": "维修描述: ",
+	"message.faultTime": "报修时间: ",
+	"message.statusName": "最新进展: ",
+	"message.dispatchUser": "负责人",
+	"message.repairType": "维修类型",
+	"message.reason": "审批意见",
+	"message.reason1": "取消理由",
+	"message.cancelHint": "取消后,该审批流程将自动结束",
+	"message.form.errorHint1": "维修负责人不能为空",
+	"message.form.errorHint2": "维修类型不能为空",
+	"message.form.errorHint3": "新审批人不能为空",
+	"message.form.errorHint4": "审批意见不能为空",
+	"message.form.errorHint5": "接收人不能为空",
+	"message.form.errorHint6": "加签处理人不能为空",
+	"message.form.errorHint7": "取消理由不能为空",
+	"message.form.user3": "新审批人",
+	"message.form.user4": "接收人",
+	"message.form.user5": "加签处理人",
+	"message.form.beforeSign": "向前加签",
+	"message.form.afterSign": "向后加签",
+
+	// ------------------- 状态变更 -------------------
+	"statusChange.title": "设备状态变更",
+	"statusChange.insert": "+ 新建  ",
+	"statusChange.searchHint": "请输入资产编码",
+	"statusChange.deviceName": "设备名称",
+	"statusChange.deviceCode": "设备编码",
+	"statusChange.beforeLeader": "调整前责任人",
+	"statusChange.afterLeader": "调整后责任人",
+	"statusChange.reason": "调整原因",
+	"statusChange.createUser": "调整人",
+	"statusChange.assetCode": "资产编码",
+	"statusChange.dept": "所在部门",
+	"statusChange.createDate": "创建时间",
+	"statusChange.beforeStatus": "变更前状态",
+	"statusChange.afterStatus": "变更后状态",
+	"statusChange.history": "历史",
+	"statusChange.history.title": "设备状态调整记录",
+	"statusChange.form.title": "新增设备变更",
+	"statusChange.form.device": "设备",
+	"statusChange.form.deviceHint": "请选择设备",
+	"statusChange.form.deviceError": "设备不能为空",
+	"statusChange.form.selectDevice": "选择设备",
+	"statusChange.form.status": "设备状态",
+	"statusChange.form.statusHint": "请选择设备状态",
+	"statusChange.form.statusError": "设备状态不能为空",
+	"statusChange.form.statusTitle": "变更状态",
+	"statusChange.form.reason": "调整原因",
+	"statusChange.form.reasonHint": "请输入调整原因",
+	"statusChange.form.reasonError": "调整原因不能为空",
+	"statusChange.form.searchHint": "搜索设备名称/资产编码",
+	"statusChange.form.success": "提交成功",
+
+	// ------------------- 状态变更 -------------------
+	"realTimeData.title": "设备实时数据监控",
+	"realTimeData.status.online": "在线",
+	"realTimeData.status.offline": "离线",
+	"realTimeData.number": "编号:",
+	"realTimeData.type": "类型:",
+
+	// ------------------- 状态变更详情 -------------------
+	"realTimeData.detail.title": "设备实时数据详情",
+	"realTimeData.detail.assetCode": "资产编码:",
+	"realTimeData.detail.isOnline": "是否在线:",
+	"realTimeData.detail.deviceType": "设备类别:",
+	"realTimeData.detail.lastUpdateTime": "最后数据:",
+	"realTimeData.detail.chartTitle": "数据趋势",
+
+	// ------------------- 台账 -------------------
+	"ledger.title": "设备台账",
+	"ledger.deviceStatus": "设备状态: ",
+	"ledger.form.title": "新建台账",
+	"ledger.form.basicInfo": "基本信息:",
+	"ledger.form.deviceCode": "设备编码",
+	"ledger.form.deviceCodeError": "设备编码不能为空",
+	"ledger.form.deviceName": "设备名称",
+	"ledger.form.deviceNameError": "设备名称不能为空",
+	"ledger.form.brand": "品牌",
+	"ledger.form.brandError": "品牌不能为空",
+	"ledger.form.dept": "所在部门",
+	"ledger.form.deptError": "所在部门不能为空",
+	"ledger.form.deviceType": "设备类别",
+	"ledger.form.deviceTypeError": "设备类别不能为空",
+	"ledger.form.deviceStatus": "设备状态",
+	"ledger.form.deviceStatusError": "设备状态为空",
+	"ledger.form.assetProperty": "资产性质",
+	"ledger.form.assetPropertyError": "资产性质不能为空",
+	"ledger.form.model": "规格型号",
+	"ledger.form.image": "图片",
+	"ledger.form.remark": "备注",
+	"ledger.form.produceInfo": "制造信息:",
+	"ledger.form.manufacturer": "制造商",
+	"ledger.form.manufacturerError": "制造商不能为空",
+	"ledger.form.manDate": "生产日期",
+	"ledger.form.manDateError": "生产日期不能为空",
+	"ledger.form.supplier": "供应商",
+	"ledger.form.expires": "质保到期",
+	"ledger.form.nameplate": "铭牌信息",
+	"ledger.form.financeInfo": "财务信息:",
+	"ledger.form.plPrice": "采购价格",
+	"ledger.form.plDate": "采购日期",
+	"ledger.form.plYear": "折旧年限",
+	"ledger.form.plStartDate": "折旧开始日期",
+	"ledger.form.plMonth": "已提折旧月数",
+	"ledger.form.plAmounted": "已提折旧金额",
+	"ledger.form.remainAmount": "剩余金额",
+	"ledger.supplier.name": "客商名称",
+	"ledger.supplier.nameHint": "请输入供应商名称",
+	"ledger.supplier.code": "客商编号",
+	"ledger.supplier.type": "客商分类",
+	"ledger.supplier.status": "客商状态",
+	"ledger.supplier.time": "创建时间",
+	"ledger.supplier.status1": "草稿",
+	"ledger.supplier.status2": "活动",
+	"ledger.supplier.status3": "关闭",
+	"ledger.brand.hint": "请先选择品牌",
+	"ledger.brand.id": "字典编码",
+	"ledger.brand.label": "字典标签",
+	"ledger.brand.labelHint": "请输入品牌名称",
+	"ledger.brand.status": "状态",
+	"ledger.brand.status0": "正常",
+	"ledger.brand.status1": "关闭",
+	"ledger.model.name": "型号名称",
+	"ledger.model.standard": "符合标准",
+	"ledger.detail.title": "设备台账详情",
+	"ledger.detail.section": "设备信息详情:",
+	"ledger.detail.user": "责任人",
+	"ledger.detail.assetType": "资产类别",
+
+	// ------------------- 统计分析 -------------------
+	"statistic.title": "统计分析",
+	"statistic.tab1": "维修统计",
+	"statistic.tab2": "保养统计",
+	"statistic.tab3": "巡检统计",
+	"statistic.repair.resolutionTime": "平均解决时间",
+	"statistic.repair.weeklyCount": "近一周工单数量",
+	"statistic.repair.monthlyCount": "近一月工单数量",
+	"statistic.repair.totalCount": "工单总数量",
+	"statistic.repair.report": "故障上报",
+	"statistic.repair.workOrder": "维修工单",
+	"statistic.repair.failure.title": "故障上报状态统计",
+	"statistic.repair.failure.reporting": "上报中",
+	"statistic.repair.failure.finished": "处理完成",
+	"statistic.repair.failure.trans": "转工单",
+	"statistic.repair.failure.over": "工单处理完成",
+	"statistic.repair.workOrder.title": "维修工单状态统计",
+	"statistic.repair.workOrder.tx": "待填写",
+	"statistic.repair.workOrder.finished": "已完成",
+	"statistic.repair.title": "维修任务统计分析",
+	"statistic.repair.completionRate": "维修完成率",
+	"statistic.repair.repairedCount": "已维修",
+	"statistic.repair.pendingRepairCount": "待维修",
+	"statistic.maintenance.dayCount": "昨日工单数量",
+	"statistic.maintenance.count1": "总数量",
+	"statistic.maintenance.count2": "未完成",
+	"statistic.maintenance.weeklyCount": "近一周工单数量",
+	"statistic.maintenance.monthlyCount": "近一月工单数量",
+	"statistic.maintenance.totalCount": "工单数量",
+	"statistic.maintenance.workOrder.title": "保养工单状态统计",
+	"statistic.maintenance.workOrder.status1": "待执行",
+	"statistic.maintenance.workOrder.status2": "已执行",
+	"statistic.maintenance.dayWorkOrder.title": "今日工单状态统计",
+	"statistic.maintenance.orderType.title": "工单类型统计",
+	"statistic.maintenance.orderType.status1": "临时新建",
+	"statistic.maintenance.orderType.status2": "计划生成",
+	"statistic.inspection.workOrder.title": "巡检工单状态统计"
+}

+ 31 - 0
main.js

@@ -0,0 +1,31 @@
+import App from './App'
+import './permission'
+import languages from './locale/index'
+import {
+	setupPinia
+} from './store';
+import {
+	createSSRApp
+} from 'vue'
+
+import {
+	createI18n
+} from 'vue-i18n'
+let i18nConfig = {
+	locale: uni.getLocale(),
+	messages: languages
+}
+
+
+const i18n = createI18n(i18nConfig)
+export function createApp() {
+	const app = createSSRApp(App)
+	setupPinia(app)
+	app.use(i18n)
+	// 把t函数添加到全局属性
+	app.config.globalProperties.$t = i18n.global.t
+
+	return {
+		app,
+	}
+}

+ 127 - 0
manifest.json

@@ -0,0 +1,127 @@
+{
+    "name" : "DeepOil",
+    "appid" : "__UNI__6E4BC49",
+    "description" : "",
+    "versionName" : "1.2.4",
+    "versionCode" : 10204,
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+		"webpack": {
+		  "externals": {
+			"dingtalk-jsapi": "dingtalk-jsapi" // 告诉 webpack 不打包该依赖
+		  }
+		},
+        /* 模块配置 */
+        "modules" : {
+            "VideoPlayer" : {
+                "enabled" : true,
+                "mode" : "default",
+                "orientation" : "auto",
+                "background" : "#000000",
+                "controls" : true,
+                "autoPlay" : false,
+                "loop" : false,
+                "showFullscreenBtn" : true,
+                "showPlayBtn" : true,
+                "showCenterPlayBtn" : true,
+                "showProgress" : true,
+                "objectFit" : "contain"
+            },
+            "SQLite" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* sdk */
+            "sdkConfigs" : {
+                "oauth" : {
+                    "dingtalk" : {
+                        "appid" : "dingcrhejkptu0mcsw3r",
+                        "universalLinks" : "http://1.94.244.160:70/"
+                    }
+                }
+            },
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    // "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
+                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
+                ],
+                "schemes" : "deepoil"
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false
+            },
+            /* SDK配置 */
+            "sdkConfigs__UNI__6E4BC49\t" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "3",
+    "h5" : {
+        "router" : {
+            "base" : "./"
+        }
+    }
+}

+ 1116 - 0
package-lock.json

@@ -0,0 +1,1116 @@
+{
+	"name": "DeepOil",
+	"lockfileVersion": 2,
+	"requires": true,
+	"packages": {
+		"": {
+			"dependencies": {
+				"crypto-js": "^4.2.0",
+				"dayjs": "^1.11.7",
+				"dingtalk-jsapi": "^3.2.0",
+				"lodash": "^4.17.21",
+				"lodash-es": "^4.17.21",
+				"luch-request": "^3.0.8",
+				"pinia": "^2.0.33",
+				"pinia-plugin-persist-uni": "^1.2.0",
+				"qs": "^6.14.0"
+			}
+		},
+		"node_modules/@babel/helper-string-parser": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+			"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+			"peer": true,
+			"engines": {
+				"node": ">=6.9.0"
+			}
+		},
+		"node_modules/@babel/helper-validator-identifier": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+			"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+			"peer": true,
+			"engines": {
+				"node": ">=6.9.0"
+			}
+		},
+		"node_modules/@babel/parser": {
+			"version": "7.27.2",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+			"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+			"peer": true,
+			"dependencies": {
+				"@babel/types": "^7.27.1"
+			},
+			"bin": {
+				"parser": "bin/babel-parser.js"
+			},
+			"engines": {
+				"node": ">=6.0.0"
+			}
+		},
+		"node_modules/@babel/types": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+			"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+			"peer": true,
+			"dependencies": {
+				"@babel/helper-string-parser": "^7.27.1",
+				"@babel/helper-validator-identifier": "^7.27.1"
+			},
+			"engines": {
+				"node": ">=6.9.0"
+			}
+		},
+		"node_modules/@dcloudio/types": {
+			"version": "2.6.12",
+			"resolved": "https://registry.npmjs.org/@dcloudio/types/-/types-2.6.12.tgz",
+			"integrity": "sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ=="
+		},
+		"node_modules/@jridgewell/sourcemap-codec": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+			"peer": true
+		},
+		"node_modules/@vue/compiler-core": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
+			"integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+			"peer": true,
+			"dependencies": {
+				"@babel/parser": "^7.27.2",
+				"@vue/shared": "3.5.14",
+				"entities": "^4.5.0",
+				"estree-walker": "^2.0.2",
+				"source-map-js": "^1.2.1"
+			}
+		},
+		"node_modules/@vue/compiler-dom": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
+			"integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+			"peer": true,
+			"dependencies": {
+				"@vue/compiler-core": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"node_modules/@vue/compiler-sfc": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
+			"integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+			"peer": true,
+			"dependencies": {
+				"@babel/parser": "^7.27.2",
+				"@vue/compiler-core": "3.5.14",
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/compiler-ssr": "3.5.14",
+				"@vue/shared": "3.5.14",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.30.17",
+				"postcss": "^8.5.3",
+				"source-map-js": "^1.2.1"
+			}
+		},
+		"node_modules/@vue/compiler-ssr": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
+			"integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+			"peer": true,
+			"dependencies": {
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"node_modules/@vue/devtools-api": {
+			"version": "6.6.4",
+			"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+			"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+		},
+		"node_modules/@vue/reactivity": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.14.tgz",
+			"integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+			"peer": true,
+			"dependencies": {
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"node_modules/@vue/runtime-core": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
+			"integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+			"peer": true,
+			"dependencies": {
+				"@vue/reactivity": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"node_modules/@vue/runtime-dom": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
+			"integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+			"peer": true,
+			"dependencies": {
+				"@vue/reactivity": "3.5.14",
+				"@vue/runtime-core": "3.5.14",
+				"@vue/shared": "3.5.14",
+				"csstype": "^3.1.3"
+			}
+		},
+		"node_modules/@vue/server-renderer": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
+			"integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+			"peer": true,
+			"dependencies": {
+				"@vue/compiler-ssr": "3.5.14",
+				"@vue/shared": "3.5.14"
+			},
+			"peerDependencies": {
+				"vue": "3.5.14"
+			}
+		},
+		"node_modules/@vue/shared": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.14.tgz",
+			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==",
+			"peer": true
+		},
+		"node_modules/call-bind-apply-helpers": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+			"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+			"dependencies": {
+				"es-errors": "^1.3.0",
+				"function-bind": "^1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/call-bound": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+			"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+			"dependencies": {
+				"call-bind-apply-helpers": "^1.0.2",
+				"get-intrinsic": "^1.3.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/crypto-js": {
+			"version": "4.2.0",
+			"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+			"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+			"license": "MIT"
+		},
+		"node_modules/csstype": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+			"peer": true
+		},
+		"node_modules/dayjs": {
+			"version": "1.11.13",
+			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+			"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+		},
+		"node_modules/dingtalk-jsapi": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/dingtalk-jsapi/-/dingtalk-jsapi-3.2.0.tgz",
+			"integrity": "sha512-PZhTM1T9ERdaOzjFbyokegVmf/vUVvlg0BKcMiP1bB0e3vBflroPpbW2SjrZEKuvFxFtuFDpvYpk2EhbbKH/6Q==",
+			"dependencies": {
+				"promise-polyfill": "^7.1.0"
+			}
+		},
+		"node_modules/dunder-proto": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+			"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+			"dependencies": {
+				"call-bind-apply-helpers": "^1.0.1",
+				"es-errors": "^1.3.0",
+				"gopd": "^1.2.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/entities": {
+			"version": "4.5.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"peer": true,
+			"engines": {
+				"node": ">=0.12"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/entities?sponsor=1"
+			}
+		},
+		"node_modules/es-define-property": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+			"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/es-errors": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+			"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/es-object-atoms": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+			"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+			"dependencies": {
+				"es-errors": "^1.3.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+			"peer": true
+		},
+		"node_modules/function-bind": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+			"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/get-intrinsic": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+			"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+			"dependencies": {
+				"call-bind-apply-helpers": "^1.0.2",
+				"es-define-property": "^1.0.1",
+				"es-errors": "^1.3.0",
+				"es-object-atoms": "^1.1.1",
+				"function-bind": "^1.1.2",
+				"get-proto": "^1.0.1",
+				"gopd": "^1.2.0",
+				"has-symbols": "^1.1.0",
+				"hasown": "^2.0.2",
+				"math-intrinsics": "^1.1.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/get-proto": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+			"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+			"dependencies": {
+				"dunder-proto": "^1.0.1",
+				"es-object-atoms": "^1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/gopd": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+			"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/has-symbols": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+			"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/hasown": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+			"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+			"dependencies": {
+				"function-bind": "^1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/lodash": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+		},
+		"node_modules/lodash-es": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+			"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+		},
+		"node_modules/luch-request": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/luch-request/-/luch-request-3.1.1.tgz",
+			"integrity": "sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==",
+			"dependencies": {
+				"@dcloudio/types": "^2.0.16"
+			}
+		},
+		"node_modules/magic-string": {
+			"version": "0.30.17",
+			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+			"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+			"peer": true,
+			"dependencies": {
+				"@jridgewell/sourcemap-codec": "^1.5.0"
+			}
+		},
+		"node_modules/math-intrinsics": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+			"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+			"engines": {
+				"node": ">= 0.4"
+			}
+		},
+		"node_modules/nanoid": {
+			"version": "3.3.11",
+			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+			"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"peer": true,
+			"bin": {
+				"nanoid": "bin/nanoid.cjs"
+			},
+			"engines": {
+				"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+			}
+		},
+		"node_modules/object-inspect": {
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/picocolors": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+			"peer": true
+		},
+		"node_modules/pinia": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+			"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+			"dependencies": {
+				"@vue/devtools-api": "^6.6.3",
+				"vue-demi": "^0.14.10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/posva"
+			},
+			"peerDependencies": {
+				"typescript": ">=4.4.4",
+				"vue": "^2.7.0 || ^3.5.11"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/pinia-plugin-persist-uni": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/pinia-plugin-persist-uni/-/pinia-plugin-persist-uni-1.3.1.tgz",
+			"integrity": "sha512-E7HEj6Hy3ZZKXCDG7yKWQ7Dg99Ibtei2qssY6w8wlqVBl3X54xSydiju8kO7IYPHw8heYAPjPBAF8TGCTdvRGQ==",
+			"dependencies": {
+				"vue-demi": "^0.12.1"
+			},
+			"peerDependencies": {
+				"@vue/composition-api": "^1.0.0",
+				"pinia": "^2.0.0",
+				"vue": "^2.0.0 || >=3.0.0"
+			},
+			"peerDependenciesMeta": {
+				"@vue/composition-api": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/pinia-plugin-persist-uni/node_modules/vue-demi": {
+			"version": "0.12.5",
+			"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
+			"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
+			"hasInstallScript": true,
+			"bin": {
+				"vue-demi-fix": "bin/vue-demi-fix.js",
+				"vue-demi-switch": "bin/vue-demi-switch.js"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antfu"
+			},
+			"peerDependencies": {
+				"@vue/composition-api": "^1.0.0-rc.1",
+				"vue": "^3.0.0-0 || ^2.6.0"
+			},
+			"peerDependenciesMeta": {
+				"@vue/composition-api": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/postcss": {
+			"version": "8.5.3",
+			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+			"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+			"funding": [
+				{
+					"type": "opencollective",
+					"url": "https://opencollective.com/postcss/"
+				},
+				{
+					"type": "tidelift",
+					"url": "https://tidelift.com/funding/github/npm/postcss"
+				},
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/ai"
+				}
+			],
+			"peer": true,
+			"dependencies": {
+				"nanoid": "^3.3.8",
+				"picocolors": "^1.1.1",
+				"source-map-js": "^1.2.1"
+			},
+			"engines": {
+				"node": "^10 || ^12 || >=14"
+			}
+		},
+		"node_modules/promise-polyfill": {
+			"version": "7.1.2",
+			"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-7.1.2.tgz",
+			"integrity": "sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ=="
+		},
+		"node_modules/qs": {
+			"version": "6.14.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+			"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+			"dependencies": {
+				"side-channel": "^1.1.0"
+			},
+			"engines": {
+				"node": ">=0.6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/side-channel": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+			"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+			"dependencies": {
+				"es-errors": "^1.3.0",
+				"object-inspect": "^1.13.3",
+				"side-channel-list": "^1.0.0",
+				"side-channel-map": "^1.0.1",
+				"side-channel-weakmap": "^1.0.2"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/side-channel-list": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+			"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+			"dependencies": {
+				"es-errors": "^1.3.0",
+				"object-inspect": "^1.13.3"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/side-channel-map": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+			"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+			"dependencies": {
+				"call-bound": "^1.0.2",
+				"es-errors": "^1.3.0",
+				"get-intrinsic": "^1.2.5",
+				"object-inspect": "^1.13.3"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/side-channel-weakmap": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+			"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+			"dependencies": {
+				"call-bound": "^1.0.2",
+				"es-errors": "^1.3.0",
+				"get-intrinsic": "^1.2.5",
+				"object-inspect": "^1.13.3",
+				"side-channel-map": "^1.0.1"
+			},
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/source-map-js": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+			"peer": true,
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/vue": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.14.tgz",
+			"integrity": "sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==",
+			"peer": true,
+			"dependencies": {
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/compiler-sfc": "3.5.14",
+				"@vue/runtime-dom": "3.5.14",
+				"@vue/server-renderer": "3.5.14",
+				"@vue/shared": "3.5.14"
+			},
+			"peerDependencies": {
+				"typescript": "*"
+			},
+			"peerDependenciesMeta": {
+				"typescript": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/vue-demi": {
+			"version": "0.14.10",
+			"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+			"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+			"hasInstallScript": true,
+			"bin": {
+				"vue-demi-fix": "bin/vue-demi-fix.js",
+				"vue-demi-switch": "bin/vue-demi-switch.js"
+			},
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/antfu"
+			},
+			"peerDependencies": {
+				"@vue/composition-api": "^1.0.0-rc.1",
+				"vue": "^3.0.0-0 || ^2.6.0"
+			},
+			"peerDependenciesMeta": {
+				"@vue/composition-api": {
+					"optional": true
+				}
+			}
+		}
+	},
+	"dependencies": {
+		"@babel/helper-string-parser": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+			"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+			"peer": true
+		},
+		"@babel/helper-validator-identifier": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+			"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+			"peer": true
+		},
+		"@babel/parser": {
+			"version": "7.27.2",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
+			"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+			"peer": true,
+			"requires": {
+				"@babel/types": "^7.27.1"
+			}
+		},
+		"@babel/types": {
+			"version": "7.27.1",
+			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+			"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+			"peer": true,
+			"requires": {
+				"@babel/helper-string-parser": "^7.27.1",
+				"@babel/helper-validator-identifier": "^7.27.1"
+			}
+		},
+		"@dcloudio/types": {
+			"version": "2.6.12",
+			"resolved": "https://registry.npmjs.org/@dcloudio/types/-/types-2.6.12.tgz",
+			"integrity": "sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ=="
+		},
+		"@jridgewell/sourcemap-codec": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+			"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+			"peer": true
+		},
+		"@vue/compiler-core": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.14.tgz",
+			"integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==",
+			"peer": true,
+			"requires": {
+				"@babel/parser": "^7.27.2",
+				"@vue/shared": "3.5.14",
+				"entities": "^4.5.0",
+				"estree-walker": "^2.0.2",
+				"source-map-js": "^1.2.1"
+			}
+		},
+		"@vue/compiler-dom": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz",
+			"integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==",
+			"peer": true,
+			"requires": {
+				"@vue/compiler-core": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"@vue/compiler-sfc": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz",
+			"integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==",
+			"peer": true,
+			"requires": {
+				"@babel/parser": "^7.27.2",
+				"@vue/compiler-core": "3.5.14",
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/compiler-ssr": "3.5.14",
+				"@vue/shared": "3.5.14",
+				"estree-walker": "^2.0.2",
+				"magic-string": "^0.30.17",
+				"postcss": "^8.5.3",
+				"source-map-js": "^1.2.1"
+			}
+		},
+		"@vue/compiler-ssr": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz",
+			"integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==",
+			"peer": true,
+			"requires": {
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"@vue/devtools-api": {
+			"version": "6.6.4",
+			"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+			"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+		},
+		"@vue/reactivity": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.14.tgz",
+			"integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==",
+			"peer": true,
+			"requires": {
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"@vue/runtime-core": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.14.tgz",
+			"integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==",
+			"peer": true,
+			"requires": {
+				"@vue/reactivity": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"@vue/runtime-dom": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz",
+			"integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==",
+			"peer": true,
+			"requires": {
+				"@vue/reactivity": "3.5.14",
+				"@vue/runtime-core": "3.5.14",
+				"@vue/shared": "3.5.14",
+				"csstype": "^3.1.3"
+			}
+		},
+		"@vue/server-renderer": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.14.tgz",
+			"integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==",
+			"peer": true,
+			"requires": {
+				"@vue/compiler-ssr": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"@vue/shared": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.14.tgz",
+			"integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==",
+			"peer": true
+		},
+		"call-bind-apply-helpers": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+			"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+			"requires": {
+				"es-errors": "^1.3.0",
+				"function-bind": "^1.1.2"
+			}
+		},
+		"call-bound": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+			"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+			"requires": {
+				"call-bind-apply-helpers": "^1.0.2",
+				"get-intrinsic": "^1.3.0"
+			}
+		},
+		"crypto-js": {
+			"version": "4.2.0",
+			"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+			"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
+		},
+		"csstype": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+			"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+			"peer": true
+		},
+		"dayjs": {
+			"version": "1.11.13",
+			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+			"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+		},
+		"dingtalk-jsapi": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmjs.org/dingtalk-jsapi/-/dingtalk-jsapi-3.2.0.tgz",
+			"integrity": "sha512-PZhTM1T9ERdaOzjFbyokegVmf/vUVvlg0BKcMiP1bB0e3vBflroPpbW2SjrZEKuvFxFtuFDpvYpk2EhbbKH/6Q==",
+			"requires": {
+				"promise-polyfill": "^7.1.0"
+			}
+		},
+		"dunder-proto": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+			"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+			"requires": {
+				"call-bind-apply-helpers": "^1.0.1",
+				"es-errors": "^1.3.0",
+				"gopd": "^1.2.0"
+			}
+		},
+		"entities": {
+			"version": "4.5.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"peer": true
+		},
+		"es-define-property": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+			"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+		},
+		"es-errors": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+			"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+		},
+		"es-object-atoms": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+			"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+			"requires": {
+				"es-errors": "^1.3.0"
+			}
+		},
+		"estree-walker": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+			"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+			"peer": true
+		},
+		"function-bind": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+			"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+		},
+		"get-intrinsic": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+			"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+			"requires": {
+				"call-bind-apply-helpers": "^1.0.2",
+				"es-define-property": "^1.0.1",
+				"es-errors": "^1.3.0",
+				"es-object-atoms": "^1.1.1",
+				"function-bind": "^1.1.2",
+				"get-proto": "^1.0.1",
+				"gopd": "^1.2.0",
+				"has-symbols": "^1.1.0",
+				"hasown": "^2.0.2",
+				"math-intrinsics": "^1.1.0"
+			}
+		},
+		"get-proto": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+			"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+			"requires": {
+				"dunder-proto": "^1.0.1",
+				"es-object-atoms": "^1.0.0"
+			}
+		},
+		"gopd": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+			"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+		},
+		"has-symbols": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+			"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
+		},
+		"hasown": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+			"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+			"requires": {
+				"function-bind": "^1.1.2"
+			}
+		},
+		"lodash": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+		},
+		"lodash-es": {
+			"version": "4.17.21",
+			"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+			"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+		},
+		"luch-request": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/luch-request/-/luch-request-3.1.1.tgz",
+			"integrity": "sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==",
+			"requires": {
+				"@dcloudio/types": "^2.0.16"
+			}
+		},
+		"magic-string": {
+			"version": "0.30.17",
+			"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+			"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+			"peer": true,
+			"requires": {
+				"@jridgewell/sourcemap-codec": "^1.5.0"
+			}
+		},
+		"math-intrinsics": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+			"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+		},
+		"nanoid": {
+			"version": "3.3.11",
+			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+			"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+			"peer": true
+		},
+		"object-inspect": {
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
+		},
+		"picocolors": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+			"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+			"peer": true
+		},
+		"pinia": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+			"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+			"requires": {
+				"@vue/devtools-api": "^6.6.3",
+				"vue-demi": "^0.14.10"
+			}
+		},
+		"pinia-plugin-persist-uni": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/pinia-plugin-persist-uni/-/pinia-plugin-persist-uni-1.3.1.tgz",
+			"integrity": "sha512-E7HEj6Hy3ZZKXCDG7yKWQ7Dg99Ibtei2qssY6w8wlqVBl3X54xSydiju8kO7IYPHw8heYAPjPBAF8TGCTdvRGQ==",
+			"requires": {
+				"vue-demi": "^0.12.1"
+			},
+			"dependencies": {
+				"vue-demi": {
+					"version": "0.12.5",
+					"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
+					"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
+					"requires": {}
+				}
+			}
+		},
+		"postcss": {
+			"version": "8.5.3",
+			"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+			"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+			"peer": true,
+			"requires": {
+				"nanoid": "^3.3.8",
+				"picocolors": "^1.1.1",
+				"source-map-js": "^1.2.1"
+			}
+		},
+		"promise-polyfill": {
+			"version": "7.1.2",
+			"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-7.1.2.tgz",
+			"integrity": "sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ=="
+		},
+		"qs": {
+			"version": "6.14.0",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+			"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+			"requires": {
+				"side-channel": "^1.1.0"
+			}
+		},
+		"side-channel": {
+			"version": "1.1.0",
+			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+			"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+			"requires": {
+				"es-errors": "^1.3.0",
+				"object-inspect": "^1.13.3",
+				"side-channel-list": "^1.0.0",
+				"side-channel-map": "^1.0.1",
+				"side-channel-weakmap": "^1.0.2"
+			}
+		},
+		"side-channel-list": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+			"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+			"requires": {
+				"es-errors": "^1.3.0",
+				"object-inspect": "^1.13.3"
+			}
+		},
+		"side-channel-map": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+			"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+			"requires": {
+				"call-bound": "^1.0.2",
+				"es-errors": "^1.3.0",
+				"get-intrinsic": "^1.2.5",
+				"object-inspect": "^1.13.3"
+			}
+		},
+		"side-channel-weakmap": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+			"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+			"requires": {
+				"call-bound": "^1.0.2",
+				"es-errors": "^1.3.0",
+				"get-intrinsic": "^1.2.5",
+				"object-inspect": "^1.13.3",
+				"side-channel-map": "^1.0.1"
+			}
+		},
+		"source-map-js": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+			"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+			"peer": true
+		},
+		"vue": {
+			"version": "3.5.14",
+			"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.14.tgz",
+			"integrity": "sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==",
+			"peer": true,
+			"requires": {
+				"@vue/compiler-dom": "3.5.14",
+				"@vue/compiler-sfc": "3.5.14",
+				"@vue/runtime-dom": "3.5.14",
+				"@vue/server-renderer": "3.5.14",
+				"@vue/shared": "3.5.14"
+			}
+		},
+		"vue-demi": {
+			"version": "0.14.10",
+			"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+			"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+			"requires": {}
+		}
+	}
+}

+ 13 - 0
package.json

@@ -0,0 +1,13 @@
+{
+	"dependencies": {
+		"crypto-js": "^4.2.0",
+		"dayjs": "^1.11.7",
+		"dingtalk-jsapi": "^3.2.0",
+		"lodash": "^4.17.21",
+		"lodash-es": "^4.17.21",
+		"luch-request": "^3.0.8",
+		"pinia": "^2.0.33",
+		"pinia-plugin-persist-uni": "^1.2.0",
+		"qs": "^6.14.0"
+	}
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff